Skip to content

Commit 398e93d

Browse files
committed
Enable Windows linker discovery
* Make Platform Registry initialization async * Change the function signature of additionalPlatformExecutableSearchPaths to allow passing of a filesystem for discovery. Plugins will need to be updated to match new signatures * Add the visual studio install directory to platform search paths for executables. * Enable Windows linker discovery to get link.exe type and version * Add Windows platform plugin extension to get install directory * Add test for discovery of windows linker * Paths in the clang ouput have double slashes so this needs to be handled.
1 parent baa4634 commit 398e93d

File tree

8 files changed

+172
-104
lines changed

8 files changed

+172
-104
lines changed

Sources/SWBCore/Core.swift

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ public final class Core: Sendable {
9898

9999
await core.initializeSpecRegistry()
100100

101+
await core.initializePlatformRegistry()
102+
101103
await core.initializeToolchainRegistry()
102104

103105
// Force loading SDKs.
@@ -301,32 +303,10 @@ public final class Core: Sendable {
301303
@_spi(Testing) public var toolchainPaths: [(Path, strict: Bool)]
302304

303305
/// The platform registry.
304-
public lazy var platformRegistry: PlatformRegistry = {
305-
// FIXME: We should support building the platforms (with symlinks) locally (for `inferiorProductsPath`).
306-
307-
// Search the default location first (unless directed not to), then search any extra locations we've been passed.
308-
var searchPaths: [Path]
309-
let fs = localFS
310-
if let onlySearchAdditionalPlatformPaths = getEnvironmentVariable("XCODE_ONLY_EXTRA_PLATFORM_FOLDERS"), onlySearchAdditionalPlatformPaths.boolValue {
311-
searchPaths = []
312-
}
313-
else {
314-
let platformsDir = self.developerPath.join("Platforms")
315-
searchPaths = [platformsDir]
316-
if hostOperatingSystem == .windows {
317-
for dir in (try? localFS.listdir(platformsDir)) ?? [] {
318-
searchPaths.append(platformsDir.join(dir))
319-
}
320-
}
321-
}
322-
if let additionalPlatformSearchPaths = getEnvironmentVariable("XCODE_EXTRA_PLATFORM_FOLDERS") {
323-
for searchPath in additionalPlatformSearchPaths.split(separator: ":") {
324-
searchPaths.append(Path(searchPath))
325-
}
326-
}
327-
searchPaths += UserDefaults.additionalPlatformSearchPaths
328-
return PlatformRegistry(delegate: self.registryDelegate, searchPaths: searchPaths, hostOperatingSystem: hostOperatingSystem, fs: fs)
329-
}()
306+
let _platformRegistry: UnsafeDelayedInitializationSendableWrapper<PlatformRegistry> = .init()
307+
public var platformRegistry: PlatformRegistry {
308+
_platformRegistry.value
309+
}
330310

331311
@PluginExtensionSystemActor public var loadedPluginPaths: [Path] {
332312
pluginManager.pluginsByIdentifier.values.map(\.path)
@@ -388,6 +368,30 @@ public final class Core: Sendable {
388368

389369
private var _specRegistry: SpecRegistry?
390370

371+
private func initializePlatformRegistry() async {
372+
var searchPaths: [Path]
373+
let fs = localFS
374+
if let onlySearchAdditionalPlatformPaths = getEnvironmentVariable("XCODE_ONLY_EXTRA_PLATFORM_FOLDERS"), onlySearchAdditionalPlatformPaths.boolValue {
375+
searchPaths = []
376+
} else {
377+
let platformsDir = self.developerPath.join("Platforms")
378+
searchPaths = [platformsDir]
379+
if hostOperatingSystem == .windows {
380+
for dir in (try? fs.listdir(platformsDir)) ?? [] {
381+
searchPaths.append(platformsDir.join(dir))
382+
}
383+
}
384+
}
385+
if let additionalPlatformSearchPaths = getEnvironmentVariable("XCODE_EXTRA_PLATFORM_FOLDERS") {
386+
for searchPath in additionalPlatformSearchPaths.split(separator: Path.pathEnvironmentSeparator) {
387+
searchPaths.append(Path(searchPath))
388+
}
389+
}
390+
searchPaths += UserDefaults.additionalPlatformSearchPaths
391+
_platformRegistry.initialize(to: await PlatformRegistry(delegate: self.registryDelegate, searchPaths: searchPaths, hostOperatingSystem: hostOperatingSystem, fs: fs))
392+
}
393+
394+
391395
private func initializeToolchainRegistry() async {
392396
self.toolchainRegistry = await ToolchainRegistry(delegate: self.registryDelegate, searchPaths: self.toolchainPaths, fs: localFS, hostOperatingSystem: hostOperatingSystem)
393397
}

Sources/SWBCore/Extensions/PlatformInfoExtension.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public protocol PlatformInfoExtension: Sendable {
3030

3131
func additionalKnownTestLibraryPathSuffixes() -> [Path]
3232

33-
func additionalPlatformExecutableSearchPaths(platformName: String, platformPath: Path) -> [Path]
33+
func additionalPlatformExecutableSearchPaths(platformName: String, platformPath: Path, fs: any FSProxy) async -> [Path]
3434

3535
func additionalToolchainExecutableSearchPaths(toolchainIdentifier: String, toolchainPath: Path) -> [Path]
3636

@@ -54,7 +54,7 @@ extension PlatformInfoExtension {
5454
[]
5555
}
5656

57-
public func additionalPlatformExecutableSearchPaths(platformName: String, platformPath: Path) -> [Path] {
57+
public func additionalPlatformExecutableSearchPaths(platformName: String, platformPath: Path, fs: any FSProxy) async -> [Path] {
5858
[]
5959
}
6060

Sources/SWBCore/PlatformRegistry.swift

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -319,34 +319,34 @@ public final class PlatformRegistry {
319319
})
320320
}
321321

322-
@_spi(Testing) public init(delegate: any PlatformRegistryDelegate, searchPaths: [Path], hostOperatingSystem: OperatingSystem, fs: any FSProxy) {
322+
@_spi(Testing) public init(delegate: any PlatformRegistryDelegate, searchPaths: [Path], hostOperatingSystem: OperatingSystem, fs: any FSProxy) async {
323323
self.delegate = delegate
324324

325325
for path in searchPaths {
326-
registerPlatformsInDirectory(path, fs)
326+
await registerPlatformsInDirectory(path, fs)
327327
}
328328

329329
do {
330330
if hostOperatingSystem.createFallbackSystemToolchain {
331-
try registerFallbackSystemPlatform(operatingSystem: hostOperatingSystem, fs: fs)
331+
try await registerFallbackSystemPlatform(operatingSystem: hostOperatingSystem, fs: fs)
332332
}
333333
} catch {
334334
delegate.error(error)
335335
}
336336

337-
@preconcurrency @PluginExtensionSystemActor func platformInfoExtensions() -> [any PlatformInfoExtensionPoint.ExtensionProtocol] {
338-
delegate.pluginManager.extensions(of: PlatformInfoExtensionPoint.self)
337+
@preconcurrency @PluginExtensionSystemActor func platformInfoExtensions() async -> [any PlatformInfoExtensionPoint.ExtensionProtocol] {
338+
return await delegate.pluginManager.extensions(of: PlatformInfoExtensionPoint.self)
339339
}
340340

341-
for platformExtension in platformInfoExtensions() {
341+
for platformExtension in await platformInfoExtensions() {
342342
for (path, data) in platformExtension.additionalPlatforms() {
343-
registerPlatform(path, .plDict(data), fs)
343+
await registerPlatform(path, .plDict(data), fs)
344344
}
345345
}
346346
}
347347

348-
private func registerFallbackSystemPlatform(operatingSystem: OperatingSystem, fs: any FSProxy) throws {
349-
try registerPlatform(Path("/"), .plDict(fallbackSystemPlatformSettings(operatingSystem: operatingSystem)), fs)
348+
private func registerFallbackSystemPlatform(operatingSystem: OperatingSystem, fs: any FSProxy) async throws {
349+
try await registerPlatform(Path("/"), .plDict(fallbackSystemPlatformSettings(operatingSystem: operatingSystem)), fs)
350350
}
351351

352352
private func fallbackSystemPlatformSettings(operatingSystem: OperatingSystem) throws -> [String: PropertyListItem] {
@@ -413,7 +413,7 @@ public final class PlatformRegistry {
413413
}
414414

415415
/// Register all platforms in the given directory.
416-
private func registerPlatformsInDirectory(_ path: Path, _ fs: any FSProxy) {
416+
private func registerPlatformsInDirectory(_ path: Path, _ fs: any FSProxy) async {
417417
for item in (try? localFS.listdir(path))?.sorted(by: <) ?? [] {
418418
let itemPath = path.join(item)
419419

@@ -431,14 +431,14 @@ public final class PlatformRegistry {
431431
// Silently skip loading the platform if it does not have an Info.plist at all. (We will still error below if it has an Info.plist which is malformed.)
432432
continue
433433
}
434-
registerPlatform(itemPath, infoPlist, fs)
434+
await registerPlatform(itemPath, infoPlist, fs)
435435
} catch let err {
436436
delegate.error(itemPath, "unable to load platform: 'Info.plist' was malformed: \(err)")
437437
}
438438
}
439439
}
440440

441-
private func registerPlatform(_ path: Path, _ data: PropertyListItem, _ fs: any FSProxy) {
441+
private func registerPlatform(_ path: Path, _ data: PropertyListItem, _ fs: any FSProxy) async {
442442
// The data should always be a dictionary.
443443
guard case .plDict(var items) = data else {
444444
delegate.error(path, "unexpected platform data")
@@ -613,7 +613,7 @@ public final class PlatformRegistry {
613613
delegate.pluginManager.extensions(of: PlatformInfoExtensionPoint.self)
614614
}
615615

616-
for platformExtension in platformInfoExtensions() {
616+
for platformExtension in await platformInfoExtensions() {
617617
if let value = platformExtension.preferredArchValue(for: name) {
618618
preferredArchValue = value
619619
}
@@ -623,8 +623,8 @@ public final class PlatformRegistry {
623623
path.join("usr").join("bin"),
624624
]
625625

626-
for platformExtension in platformInfoExtensions() {
627-
executableSearchPaths.append(contentsOf: platformExtension.additionalPlatformExecutableSearchPaths(platformName: name, platformPath: path))
626+
for platformExtension in await platformInfoExtensions() {
627+
await executableSearchPaths.append(contentsOf: platformExtension.additionalPlatformExecutableSearchPaths(platformName: name, platformPath: path, fs: localFS))
628628
}
629629

630630
executableSearchPaths.append(contentsOf: [

Sources/SWBCore/Specs/Tools/LinkerTools.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,15 +1309,14 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
13091309
//
13101310
// Note: On Linux you cannot invoke the llvm linker by the direct name for determining the version,
13111311
// you need to use ld.<ALTERNATE_LINKER>
1312-
var linkerPath = Path("ld")
1312+
var linkerPath = producer.hostOperatingSystem == .windows ? Path("link") : Path("ld")
13131313
if alternateLinker != "" && alternateLinker != "ld" {
13141314
linkerPath = Path(producer.hostOperatingSystem.imageFormat.executableName(basename: "ld.\(alternateLinker)"))
13151315
}
1316-
// Create the cache key. This is just the path to the linker we would invoke if we were invoking the linker directly.
1317-
guard let toolPath = producer.executableSearchPaths.lookup(linkerPath) else {
1316+
guard let toolPath = producer.executableSearchPaths.findExecutable(operatingSystem: producer.hostOperatingSystem, basename: linkerPath.str) else {
13181317
return nil
13191318
}
1320-
1319+
// Create the cache key. This is just the path to the linker we would invoke if we were invoking the linker directly.
13211320
return await discoveredLinkerToolsInfo(producer, delegate, at: toolPath)
13221321
}
13231322
}
@@ -1638,10 +1637,19 @@ public func discoveredLinkerToolsInfo(_ producer: any CommandProducer, _ delegat
16381637
#/GNU gold \(GNU Binutils.*\) (?<version>[\d.]+)/#, // Ubuntu "GNU gold (GNU Binutils for Ubuntu 2.38) 1.16", Debian "GNU gold (GNU Binutils for Debian 2.40) 1.16"
16391638
#/GNU gold \(version .*\) (?<version>[\d.]+)/#, // Fedora "GNU gold (version 2.40-14.fc39) 1.16", RHEL "GNU gold (version 2.35.2-54.el9) 1.16", Amazon "GNU gold (version 2.29.1-31.amzn2.0.1) 1.14"
16401639
]
1640+
16411641
if let match = try goLD.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
16421642
return DiscoveredLdLinkerToolSpecInfo(linker: .gold, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
16431643
}
16441644

1645+
// link.exe has no option to simply dump the version, running, the program will no arguments or an invalid one will dump a header that contains the version.
1646+
let linkExe = [
1647+
#/Microsoft \(R\) Incremental Linker Version (?<version>[\d.]+)/#
1648+
]
1649+
if let match = try linkExe.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
1650+
return DiscoveredLdLinkerToolSpecInfo(linker: .linkExe, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
1651+
}
1652+
16451653
struct LDVersionDetails: Decodable {
16461654
let version: Version
16471655
let architectures: Set<String>

Sources/SWBWindowsPlatform/Plugin.swift

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Foundation
1717
@PluginExtensionSystemActor public func initializePlugin(_ manager: PluginManager) {
1818
manager.register(WindowsPlatformSpecsExtension(), type: SpecificationsExtensionPoint.self)
1919
manager.register(WindowsEnvironmentExtension(), type: EnvironmentExtensionPoint.self)
20+
manager.register(WindowsPlatformExtension(), type: PlatformInfoExtensionPoint.self)
2021
}
2122

2223
struct WindowsPlatformSpecsExtension: SpecificationsExtension {
@@ -25,22 +26,42 @@ struct WindowsPlatformSpecsExtension: SpecificationsExtension {
2526
}
2627
}
2728

29+
private func findLatestInstallDirectory(fs: any FSProxy) async throws -> Path? {
30+
if try ProcessInfo.processInfo.hostOperatingSystem() == .windows {
31+
let installations = try await VSInstallation.findInstallations(fs: fs)
32+
.sorted(by: { $0.installationVersion > $1.installationVersion })
33+
if let latest = installations.first {
34+
let msvcDir = latest.installationPath.join("VC").join("Tools").join("MSVC")
35+
let versions = try fs.listdir(msvcDir).map { try Version($0) }.sorted { $0 > $1 }
36+
if let latestVersion = versions.first {
37+
let dir = msvcDir.join(latestVersion.description).str
38+
return Path(dir)
39+
}
40+
}
41+
}
42+
return nil
43+
}
44+
2845
struct WindowsEnvironmentExtension: EnvironmentExtension {
2946
func additionalEnvironmentVariables(fs: any FSProxy) async throws -> [String: String] {
30-
if try ProcessInfo.processInfo.hostOperatingSystem() == .windows {
31-
// Add the environment variable for the MSVC toolset for Swift and Clang to find it
32-
let vcToolsInstallDir = "VCToolsInstallDir"
33-
let installations = try await VSInstallation.findInstallations(fs: fs)
34-
.sorted(by: { $0.installationVersion > $1.installationVersion })
35-
if let latest = installations.first {
36-
let msvcDir = latest.installationPath.join("VC").join("Tools").join("MSVC")
37-
let versions = try fs.listdir(msvcDir).map { try Version($0) }.sorted { $0 > $1 }
38-
if let latestVersion = versions.first {
39-
let dir = msvcDir.join(latestVersion.description).str
40-
return [vcToolsInstallDir: dir]
41-
}
42-
}
47+
// Add the environment variable for the MSVC toolset for Swift and Clang to find it
48+
let vcToolsInstallDir = "VCToolsInstallDir"
49+
guard let dir = try? await findLatestInstallDirectory(fs: fs) else {
50+
return [:]
51+
}
52+
return [vcToolsInstallDir: dir.str]
53+
}
54+
}
55+
56+
struct WindowsPlatformExtension: PlatformInfoExtension {
57+
public func additionalPlatformExecutableSearchPaths(platformName: String, platformPath: Path, fs: any FSProxy) async -> [Path] {
58+
guard let dir = try? await findLatestInstallDirectory(fs: fs) else {
59+
return []
60+
}
61+
if Architecture.hostStringValue == "aarch64" {
62+
return [dir.join("bin/Hostarm64/arm64")]
63+
} else {
64+
return [dir.join("bin/Hostx64/x64")]
4365
}
44-
return [:]
4566
}
4667
}

0 commit comments

Comments
 (0)