Skip to content

Commit 0f47cce

Browse files
kcieplakjakepetroules
authored andcommitted
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 a6458b8 commit 0f47cce

File tree

8 files changed

+162
-96
lines changed

8 files changed

+162
-96
lines changed

Sources/SWBCore/Core.swift

Lines changed: 25 additions & 21 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.
@@ -315,27 +317,10 @@ public final class Core: Sendable {
315317
@_spi(Testing) public var toolchainPaths: [(Path, strict: Bool)]
316318

317319
/// The platform registry.
318-
public lazy var platformRegistry: PlatformRegistry = {
319-
// FIXME: We should support building the platforms (with symlinks) locally (for `inferiorProductsPath`).
320-
321-
// Search the default location first (unless directed not to), then search any extra locations we've been passed.
322-
var searchPaths: [Path]
323-
let fs = localFS
324-
if let onlySearchAdditionalPlatformPaths = getEnvironmentVariable("XCODE_ONLY_EXTRA_PLATFORM_FOLDERS"), onlySearchAdditionalPlatformPaths.boolValue {
325-
searchPaths = []
326-
}
327-
else {
328-
let platformsDir = self.developerPath.join("Platforms")
329-
searchPaths = [platformsDir]
330-
}
331-
if let additionalPlatformSearchPaths = getEnvironmentVariable("XCODE_EXTRA_PLATFORM_FOLDERS") {
332-
for searchPath in additionalPlatformSearchPaths.split(separator: ":") {
333-
searchPaths.append(Path(searchPath))
334-
}
335-
}
336-
searchPaths += UserDefaults.additionalPlatformSearchPaths
337-
return PlatformRegistry(delegate: self.registryDelegate, searchPaths: searchPaths, hostOperatingSystem: hostOperatingSystem, fs: fs)
338-
}()
320+
let _platformRegistry: UnsafeDelayedInitializationSendableWrapper<PlatformRegistry> = .init()
321+
public var platformRegistry: PlatformRegistry {
322+
_platformRegistry.value
323+
}
339324

340325
@PluginExtensionSystemActor public var loadedPluginPaths: [Path] {
341326
pluginManager.pluginsByIdentifier.values.map(\.path)
@@ -397,6 +382,25 @@ public final class Core: Sendable {
397382

398383
private var _specRegistry: SpecRegistry?
399384

385+
private func initializePlatformRegistry() async {
386+
var searchPaths: [Path]
387+
let fs = localFS
388+
if let onlySearchAdditionalPlatformPaths = getEnvironmentVariable("XCODE_ONLY_EXTRA_PLATFORM_FOLDERS"), onlySearchAdditionalPlatformPaths.boolValue {
389+
searchPaths = []
390+
} else {
391+
let platformsDir = self.developerPath.join("Platforms")
392+
searchPaths = [platformsDir]
393+
}
394+
if let additionalPlatformSearchPaths = getEnvironmentVariable("XCODE_EXTRA_PLATFORM_FOLDERS") {
395+
for searchPath in additionalPlatformSearchPaths.split(separator: Path.pathEnvironmentSeparator) {
396+
searchPaths.append(Path(searchPath))
397+
}
398+
}
399+
searchPaths += UserDefaults.additionalPlatformSearchPaths
400+
_platformRegistry.initialize(to: await PlatformRegistry(delegate: self.registryDelegate, searchPaths: searchPaths, hostOperatingSystem: hostOperatingSystem, fs: fs))
401+
}
402+
403+
400404
private func initializeToolchainRegistry() async {
401405
self.toolchainRegistry = await ToolchainRegistry(delegate: self.registryDelegate, searchPaths: self.toolchainPaths, fs: localFS, hostOperatingSystem: hostOperatingSystem)
402406
}

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

@@ -56,7 +56,7 @@ extension PlatformInfoExtension {
5656
[]
5757
}
5858

59-
public func additionalPlatformExecutableSearchPaths(platformName: String, platformPath: Path) -> [Path] {
59+
public func additionalPlatformExecutableSearchPaths(platformName: String, platformPath: Path, fs: any FSProxy) async -> [Path] {
6060
[]
6161
}
6262

Sources/SWBCore/PlatformRegistry.swift

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -324,23 +324,23 @@ public final class PlatformRegistry {
324324
})
325325
}
326326

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

330330
for path in searchPaths {
331-
registerPlatformsInDirectory(path, fs)
331+
await registerPlatformsInDirectory(path, fs)
332332
}
333333

334334
do {
335335
if hostOperatingSystem.createFallbackSystemToolchain {
336-
try registerFallbackSystemPlatform(operatingSystem: hostOperatingSystem, fs: fs)
336+
try await registerFallbackSystemPlatform(operatingSystem: hostOperatingSystem, fs: fs)
337337
}
338338
} catch {
339339
delegate.error(error)
340340
}
341341

342-
@preconcurrency @PluginExtensionSystemActor func platformInfoExtensions() -> [any PlatformInfoExtensionPoint.ExtensionProtocol] {
343-
delegate.pluginManager.extensions(of: PlatformInfoExtensionPoint.self)
342+
@preconcurrency @PluginExtensionSystemActor func platformInfoExtensions() async -> [any PlatformInfoExtensionPoint.ExtensionProtocol] {
343+
return await delegate.pluginManager.extensions(of: PlatformInfoExtensionPoint.self)
344344
}
345345

346346
struct Context: PlatformInfoExtensionAdditionalPlatformsContext {
@@ -352,16 +352,17 @@ public final class PlatformRegistry {
352352
for platformExtension in platformInfoExtensions() {
353353
do {
354354
for (path, data) in try platformExtension.additionalPlatforms(context: Context(hostOperatingSystem: hostOperatingSystem, developerPath: delegate.developerPath, fs: fs)) {
355-
registerPlatform(path, .plDict(data), fs)
355+
await registerPlatform(path, .plDict(data), fs)
356356
}
357357
} catch {
358358
delegate.error(error)
359+
359360
}
360361
}
361362
}
362363

363-
private func registerFallbackSystemPlatform(operatingSystem: OperatingSystem, fs: any FSProxy) throws {
364-
try registerPlatform(Path("/"), .plDict(fallbackSystemPlatformSettings(operatingSystem: operatingSystem)), fs)
364+
private func registerFallbackSystemPlatform(operatingSystem: OperatingSystem, fs: any FSProxy) async throws {
365+
try await registerPlatform(Path("/"), .plDict(fallbackSystemPlatformSettings(operatingSystem: operatingSystem)), fs)
365366
}
366367

367368
private func fallbackSystemPlatformSettings(operatingSystem: OperatingSystem) throws -> [String: PropertyListItem] {
@@ -428,7 +429,7 @@ public final class PlatformRegistry {
428429
}
429430

430431
/// Register all platforms in the given directory.
431-
private func registerPlatformsInDirectory(_ path: Path, _ fs: any FSProxy) {
432+
private func registerPlatformsInDirectory(_ path: Path, _ fs: any FSProxy) async {
432433
for item in (try? localFS.listdir(path))?.sorted(by: <) ?? [] {
433434
let itemPath = path.join(item)
434435

@@ -446,14 +447,14 @@ public final class PlatformRegistry {
446447
// 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.)
447448
continue
448449
}
449-
registerPlatform(itemPath, infoPlist, fs)
450+
await registerPlatform(itemPath, infoPlist, fs)
450451
} catch let err {
451452
delegate.error(itemPath, "unable to load platform: 'Info.plist' was malformed: \(err)")
452453
}
453454
}
454455
}
455456

456-
private func registerPlatform(_ path: Path, _ data: PropertyListItem, _ fs: any FSProxy) {
457+
private func registerPlatform(_ path: Path, _ data: PropertyListItem, _ fs: any FSProxy) async {
457458
// The data should always be a dictionary.
458459
guard case .plDict(let items) = data else {
459460
delegate.error(path, "unexpected platform data")
@@ -617,7 +618,7 @@ public final class PlatformRegistry {
617618
delegate.pluginManager.extensions(of: PlatformInfoExtensionPoint.self)
618619
}
619620

620-
for platformExtension in platformInfoExtensions() {
621+
for platformExtension in await platformInfoExtensions() {
621622
if let value = platformExtension.preferredArchValue(for: name) {
622623
preferredArchValue = value
623624
}
@@ -632,9 +633,10 @@ public final class PlatformRegistry {
632633
]
633634

634635
for platformExtension in platformInfoExtensions() {
635-
executableSearchPaths.append(contentsOf: platformExtension.additionalPlatformExecutableSearchPaths(platformName: name, platformPath: path))
636+
await executableSearchPaths.append(contentsOf: platformExtension.additionalPlatformExecutableSearchPaths(platformName: name, platformPath: path))
636637

637638
platformExtension.adjustPlatformSDKSearchPaths(platformName: name, platformPath: path, sdkSearchPaths: &sdkSearchPaths)
639+
638640
}
639641

640642
executableSearchPaths.append(contentsOf: [

Sources/SWBCore/SpecImplementations/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: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,29 @@ struct WindowsPlatformSpecsExtension: SpecificationsExtension {
4444

4545
@_spi(Testing) public func additionalEnvironmentVariables(context: any EnvironmentExtensionAdditionalEnvironmentVariablesContext) async throws -> [String: String] {
4646
if context.hostOperatingSystem == .windows {
47-
// Add the environment variable for the MSVC toolset for Swift and Clang to find it
4847
let vcToolsInstallDir = "VCToolsInstallDir"
49-
let installations = try await plugin.cachedVSInstallations()
50-
.sorted(by: { $0.installationVersion > $1.installationVersion })
51-
if let latest = installations.first {
52-
let msvcDir = latest.installationPath.join("VC").join("Tools").join("MSVC")
53-
if context.fs.exists(msvcDir) {
54-
let versions = try context.fs.listdir(msvcDir).map { try Version($0) }.sorted { $0 > $1 }
55-
if let latestVersion = versions.first {
56-
let dir = msvcDir.join(latestVersion.description).str
57-
return [vcToolsInstallDir: dir]
58-
}
59-
}
48+
guard let dir = try? await findLatestInstallDirectory(fs: context.fs) else {
49+
return [:]
6050
}
51+
return [vcToolsInstallDir: dir.str]
6152
}
62-
return [:]
6353
}
54+
55+
@_spi(Testing)public func findLatestInstallDirectory(fs: any FSProxy) async throws -> Path? {
56+
let plugin: WindowsPlugin
57+
let installations = try await plugin.cachedVSInstallations()
58+
.sorted(by: { $0.installationVersion > $1.installationVersion })
59+
if let latest = installations.first {
60+
let msvcDir = latest.installationPath.join("VC").join("Tools").join("MSVC")
61+
if fs.exists(msvcDir) {
62+
let versions = try fs.listdir(msvcDir).map { try Version($0) }.sorted { $0 > $1 }
63+
if let latestVersion = versions.first {
64+
return msvcDir.join(latestVersion.description)
65+
}
66+
}
67+
}
68+
return nil
69+
}
6470
}
6571

6672
struct WindowsPlatformExtension: PlatformInfoExtension {
@@ -107,6 +113,17 @@ struct WindowsPlatformExtension: PlatformInfoExtension {
107113
sdkSearchPaths = []
108114
}
109115
}
116+
117+
public func additionalPlatformExecutableSearchPaths(platformName: String, platformPath: Path, fs: any FSProxy) async -> [Path] {
118+
guard let dir = try? await findLatestInstallDirectory(fs: fs) else {
119+
return []
120+
}
121+
if Architecture.hostStringValue == "aarch64" {
122+
return [dir.join("bin/Hostarm64/arm64")]
123+
} else {
124+
return [dir.join("bin/Hostx64/x64")]
125+
}
126+
}
110127
}
111128

112129
struct WindowsSDKRegistryExtension: SDKRegistryExtension {

0 commit comments

Comments
 (0)