Skip to content

Commit f8e55fc

Browse files
committed
Enable Windows linker discovery
* Enable Windows linker discovery to get linker type and version * Make Platform Registry initalization 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. * Add Windows platform plugin extention to get install directory * Add new internal build setting _LINKER_EXEC for testing purposes. * Change the search paths of a Platform to a StackedSearchPath, as done with the Toolchain Registry * Add test for discovery of windows linker
1 parent 64c662c commit f8e55fc

File tree

12 files changed

+155
-75
lines changed

12 files changed

+155
-75
lines changed

Sources/SWBCore/Core.swift

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,10 @@ public final class Core: Sendable {
100100

101101
await core.initializeToolchainRegistry()
102102

103+
await core.initializePlatformRegistry()
104+
103105
// Force loading SDKs.
104106
let sdkRegistry = core.sdkRegistry
105-
106107
for `extension` in await pluginManager.extensions(of: SDKRegistryExtensionPoint.self) {
107108
await sdkRegistry.registerSDKs(extension: `extension`, platformRegistry: core.platformRegistry)
108109
}
@@ -178,6 +179,7 @@ public final class Core: Sendable {
178179
public let connectionMode: ServiceHostConnectionMode
179180

180181
@_spi(Testing) public init(delegate: any CoreDelegate, hostOperatingSystem: OperatingSystem, pluginManager: PluginManager, developerPath: String, inferiorProductsPath: Path?, additionalContentPaths: [Path], environment: [String:String], buildServiceModTime: Date, connectionMode: ServiceHostConnectionMode) async throws {
182+
181183
self.delegate = delegate
182184
self.hostOperatingSystem = hostOperatingSystem
183185
self.pluginManager = pluginManager
@@ -301,31 +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-
if let onlySearchAdditionalPlatformPaths = getEnvironmentVariable("XCODE_ONLY_EXTRA_PLATFORM_FOLDERS"), onlySearchAdditionalPlatformPaths.boolValue {
310-
searchPaths = []
311-
}
312-
else {
313-
let platformsDir = self.developerPath.join("Platforms")
314-
searchPaths = [platformsDir]
315-
if hostOperatingSystem == .windows {
316-
for dir in (try? localFS.listdir(platformsDir)) ?? [] {
317-
searchPaths.append(platformsDir.join(dir))
318-
}
319-
}
320-
}
321-
if let additionalPlatformSearchPaths = getEnvironmentVariable("XCODE_EXTRA_PLATFORM_FOLDERS") {
322-
for searchPath in additionalPlatformSearchPaths.split(separator: ":") {
323-
searchPaths.append(Path(searchPath))
324-
}
325-
}
326-
searchPaths += UserDefaults.additionalPlatformSearchPaths
327-
return PlatformRegistry(delegate: self.registryDelegate, searchPaths: searchPaths, hostOperatingSystem: hostOperatingSystem)
328-
}()
306+
let _platformRegistry: UnsafeDelayedInitializationSendableWrapper<PlatformRegistry> = .init()
307+
public var platformRegistry: PlatformRegistry {
308+
_platformRegistry.value
309+
}
329310

330311
@PluginExtensionSystemActor public var loadedPluginPaths: [Path] {
331312
pluginManager.pluginsByIdentifier.values.map(\.path)
@@ -423,6 +404,29 @@ public final class Core: Sendable {
423404
_specRegistry = await SpecRegistry(self.pluginManager, self.registryDelegate, searchPaths, domainInclusions, [:])
424405
}
425406

407+
private func initializePlatformRegistry() async {
408+
var searchPaths: [Path]
409+
let fs = localFS
410+
if let onlySearchAdditionalPlatformPaths = getEnvironmentVariable("XCODE_ONLY_EXTRA_PLATFORM_FOLDERS"), onlySearchAdditionalPlatformPaths.boolValue {
411+
searchPaths = []
412+
} else {
413+
let platformsDir = self.developerPath.join("Platforms")
414+
searchPaths = [platformsDir]
415+
if hostOperatingSystem == .windows {
416+
for dir in (try? fs.listdir(platformsDir)) ?? [] {
417+
searchPaths.append(platformsDir.join(dir))
418+
}
419+
}
420+
}
421+
if let additionalPlatformSearchPaths = getEnvironmentVariable("XCODE_EXTRA_PLATFORM_FOLDERS") {
422+
for searchPath in additionalPlatformSearchPaths.split(separator: ":") {
423+
searchPaths.append(Path(searchPath))
424+
}
425+
}
426+
searchPaths += UserDefaults.additionalPlatformSearchPaths
427+
_platformRegistry.initialize(to: await PlatformRegistry(delegate: self.registryDelegate, searchPaths: searchPaths, hostOperatingSystem: hostOperatingSystem, fs: fs))
428+
}
429+
426430
/// Force all specs to be loaded.
427431
@_spi(Testing) public func loadAllSpecs() {
428432
// Load all platform domain specs first, as they provide the canonical definitions of build settings.

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: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,9 @@ public final class Platform: Sendable {
180180
@_spi(Testing) public var sdks: [SDK] = []
181181

182182
/// The list of executable search paths in the platform.
183-
@_spi(Testing) public var executableSearchPaths: [Path]
183+
@_spi(Testing) public var executableSearchPaths: StackedSearchPath
184184

185-
init(_ name: String, _ displayName: String, _ familyName: String, _ familyDisplayName: String?, _ identifier: String, _ devicePlatformName: String?, _ simulatorPlatformName: String?, _ path: Path, _ version: String?, _ productBuildVersion: String?, _ defaultSettings: [String: PropertyListItem], _ additionalInfoPlistEntries: [String: PropertyListItem], _ isDeploymentPlatform: Bool, _ specRegistryProvider: any SpecRegistryProvider, preferredArchValue: String?, executableSearchPaths: [Path]) {
185+
init(_ name: String, _ displayName: String, _ familyName: String, _ familyDisplayName: String?, _ identifier: String, _ devicePlatformName: String?, _ simulatorPlatformName: String?, _ path: Path, _ version: String?, _ productBuildVersion: String?, _ defaultSettings: [String: PropertyListItem], _ additionalInfoPlistEntries: [String: PropertyListItem], _ isDeploymentPlatform: Bool, _ specRegistryProvider: any SpecRegistryProvider, preferredArchValue: String?, executableSearchPaths: [Path], fs: any FSProxy) {
186186
self.name = name
187187
self.displayName = displayName
188188
self.familyName = familyName
@@ -198,7 +198,7 @@ public final class Platform: Sendable {
198198
self.isDeploymentPlatform = isDeploymentPlatform
199199
self.specRegistryProvider = specRegistryProvider
200200
self.preferredArch = preferredArchValue
201-
self.executableSearchPaths = executableSearchPaths
201+
self.executableSearchPaths = StackedSearchPath(paths: executableSearchPaths, fs: fs)
202202
self.sdkCanonicalName = name
203203
}
204204

@@ -319,34 +319,34 @@ public final class PlatformRegistry {
319319
})
320320
}
321321

322-
@_spi(Testing) public init(delegate: any PlatformRegistryDelegate, searchPaths: [Path], hostOperatingSystem: OperatingSystem) {
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)
326+
await registerPlatformsInDirectory(path, fs)
327327
}
328328

329329
do {
330330
if hostOperatingSystem.createFallbackSystemToolchain {
331-
try registerFallbackSystemPlatform(operatingSystem: hostOperatingSystem)
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))
343+
await registerPlatform(path, .plDict(data), fs)
344344
}
345345
}
346346
}
347347

348-
private func registerFallbackSystemPlatform(operatingSystem: OperatingSystem) throws {
349-
try registerPlatform(Path("/"), .plDict(fallbackSystemPlatformSettings(operatingSystem: operatingSystem)))
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,10 +413,10 @@ public final class PlatformRegistry {
413413
}
414414

415415
/// Register all platforms in the given directory.
416-
private func registerPlatformsInDirectory(_ path: Path) {
417-
for item in (try? localFS.listdir(path))?.sorted(by: <) ?? [] {
418-
let itemPath = path.join(item)
419416

417+
private func registerPlatformsInDirectory(_ path: Path, _ fs: any FSProxy) async {
418+
for item in (try? fs.listdir(path))?.sorted(by: <) ?? [] {
419+
let itemPath = path.join(item)
420420
// Check if this is a platform we should load
421421
guard itemPath.fileSuffix == ".platform" else { continue }
422422

@@ -432,14 +432,15 @@ public final class PlatformRegistry {
432432
continue
433433
}
434434

435-
registerPlatform(itemPath, infoPlist)
435+
await registerPlatform(itemPath, infoPlist, fs)
436436
} catch let err {
437437
delegate.error(itemPath, "unable to load platform: 'Info.plist' was malformed: \(err)")
438438
}
439439
}
440440
}
441441

442-
private func registerPlatform(_ path: Path, _ data: PropertyListItem) {
442+
443+
private func registerPlatform(_ path: Path, _ data: PropertyListItem, _ fs: any FSProxy) async {
443444
// The data should always be a dictionary.
444445
guard case .plDict(var items) = data else {
445446
delegate.error(path, "unexpected platform data")
@@ -614,7 +615,7 @@ public final class PlatformRegistry {
614615
delegate.pluginManager.extensions(of: PlatformInfoExtensionPoint.self)
615616
}
616617

617-
for platformExtension in platformInfoExtensions() {
618+
for platformExtension in await platformInfoExtensions() {
618619
if let value = platformExtension.preferredArchValue(for: name) {
619620
preferredArchValue = value
620621
}
@@ -624,8 +625,8 @@ public final class PlatformRegistry {
624625
path.join("usr").join("bin"),
625626
]
626627

627-
for platformExtension in platformInfoExtensions() {
628-
executableSearchPaths.append(contentsOf: platformExtension.additionalPlatformExecutableSearchPaths(platformName: name, platformPath: path))
628+
for platformExtension in await platformInfoExtensions() {
629+
await executableSearchPaths.append(contentsOf: platformExtension.additionalPlatformExecutableSearchPaths(platformName: name, platformPath: path, fs: localFS))
629630
}
630631

631632
executableSearchPaths.append(contentsOf: [
@@ -635,7 +636,7 @@ public final class PlatformRegistry {
635636
])
636637

637638
// FIXME: Need to parse other fields. It would also be nice to diagnose unused keys like we do for Spec data (and we might want to just use the spec parser here).
638-
let platform = Platform(name, displayName, familyName, familyDisplayName, identifier, devicePlatformName, simulatorPlatformName, path, version, productBuildVersion, defaultSettings, additionalInfoPlistEntries, isDeploymentPlatform, delegate, preferredArchValue: preferredArchValue, executableSearchPaths: executableSearchPaths)
639+
let platform = Platform(name, displayName, familyName, familyDisplayName, identifier, devicePlatformName, simulatorPlatformName, path, version, productBuildVersion, defaultSettings, additionalInfoPlistEntries, isDeploymentPlatform, delegate, preferredArchValue: preferredArchValue, executableSearchPaths: executableSearchPaths, fs: fs)
639640
if let duplicatePlatform = platformsByIdentifier[identifier] {
640641
delegate.error(path, "platform '\(identifier)' already registered from \(duplicatePlatform.path.str)")
641642
return

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,7 @@ public final class BuiltinMacros {
811811
public static let LIBTOOL_DEPENDENCY_INFO_FILE = BuiltinMacros.declarePathMacro("LIBTOOL_DEPENDENCY_INFO_FILE")
812812
public static let LINKER = BuiltinMacros.declareStringMacro("LINKER")
813813
public static let ALTERNATE_LINKER = BuiltinMacros.declareStringMacro("ALTERNATE_LINKER")
814+
public static let _LINKER_EXEC = BuiltinMacros.declarePathMacro("_LINKER_EXEC")
814815
public static let LINK_OBJC_RUNTIME = BuiltinMacros.declareBooleanMacro("LINK_OBJC_RUNTIME")
815816
public static let LINK_WITH_STANDARD_LIBRARIES = BuiltinMacros.declareBooleanMacro("LINK_WITH_STANDARD_LIBRARIES")
816817
public static let LIPO = BuiltinMacros.declareStringMacro("LIPO")
@@ -1357,6 +1358,7 @@ public final class BuiltinMacros {
13571358
ALL_SETTINGS,
13581359
ALTERNATE_GROUP,
13591360
ALTERNATE_LINKER,
1361+
_LINKER_EXEC,
13601362
ALTERNATE_MODE,
13611363
ALTERNATE_OWNER,
13621364
ALTERNATE_PERMISSIONS_FILES,

Sources/SWBCore/Settings/Settings.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,7 @@ extension WorkspaceContext {
10151015
}
10161016

10171017
// Add the platform search paths.
1018-
for path in platform?.executableSearchPaths ?? [] {
1018+
for path in platform?.executableSearchPaths.paths ?? [] {
10191019
paths.append(path)
10201020
}
10211021

Sources/SWBCore/Specs/Tools/LinkerTools.swift

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
401401

402402
// FIXME: Honor LD_QUITE_LINKER_ARGUMENTS_FOR_COMPILER_DRIVER == NO ?
403403

404-
let optionContext = await discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate)
404+
let optionContext: (any DiscoveredCommandLineToolSpecInfo)? = await discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate)
405405

406406
// Gather additional linker arguments from the used tools.
407407
//
@@ -571,7 +571,6 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
571571
if !usesLDClassic, supportsSDKImportsFeature, !sdkImportsInfoFile.isEmpty, cbc.scope.evaluate(BuiltinMacros.ENABLE_SDK_IMPORTS), cbc.producer.isApplePlatform {
572572
commandLine.insert(contentsOf: ["-Xlinker", "-sdk_imports", "-Xlinker", sdkImportsInfoFile.str, "-Xlinker", "-sdk_imports_each_object"], at: commandLine.count - 2) // This preserves the assumption that the last argument is the linker output which a few tests make.
573573
outputs.append(delegate.createNode(sdkImportsInfoFile))
574-
575574
await cbc.producer.processSDKImportsSpec.createTasks(CommandBuildContext(producer: cbc.producer, scope: cbc.scope, inputs: []), delegate, ldSDKImportsPath: sdkImportsInfoFile)
576575
}
577576

@@ -1239,11 +1238,18 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
12391238

12401239
override public func discoveredCommandLineToolSpecInfo(_ producer: any CommandProducer, _ scope: MacroEvaluationScope, _ delegate: any CoreClientTargetDiagnosticProducingDelegate) async -> (any DiscoveredCommandLineToolSpecInfo)? {
12411240
let alternateLinker = scope.evaluate(BuiltinMacros.ALTERNATE_LINKER)
1241+
var linker = if alternateLinker != "" { Path(alternateLinker) } else { producer.hostOperatingSystem == .windows ? Path("link.exe") : Path("ld") }
12421242

1243-
let linkerPath = if alternateLinker != "" { Path(alternateLinker) } else { Path("ld") }
1243+
// ALTERNATE_LINKER is used in specs i.e. -fuseld=$ALTERNATE_LINKER, so it cannot be used to force the linker location.
1244+
// _LINKER_EXEC can be used to force the linker location, for usage in tests with empty pseudo filesystems.
1245+
let linkerExec = scope.evaluate(BuiltinMacros._LINKER_EXEC)
1246+
if !linkerExec.isEmpty{
1247+
linker = linkerExec
1248+
}
12441249

12451250
// Create the cache key. This is just the path to the ld linker we would invoke if we were invoking the linker directly.
1246-
guard let toolPath = producer.executableSearchPaths.lookup(linkerPath) else {
1251+
// Note: If the linker is an absolute path 'findExectable' will simply return the path to execute.
1252+
guard let toolPath = producer.executableSearchPaths.findExecutable(operatingSystem: producer.hostOperatingSystem, basename: linker.withoutSuffix) else {
12471253
return nil
12481254
}
12491255

@@ -1547,7 +1553,7 @@ public final class LibtoolLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @u
15471553
public func discoveredLinkerToolsInfo(_ producer: any CommandProducer, _ delegate: any CoreClientTargetDiagnosticProducingDelegate, at toolPath: Path) async -> (any DiscoveredCommandLineToolSpecInfo)? {
15481554
do {
15491555
do {
1550-
let commandLine = [toolPath.str, "-version_details"]
1556+
let commandLine = [toolPath.str, producer.hostOperatingSystem == .windows ? "/VERSION" : "-version_details"]
15511557
return try await producer.discoveredCommandLineToolSpecInfo(delegate, nil, commandLine, { executionResult in
15521558
let gnuLD = [
15531559
#/GNU ld version (?<version>[\d.]+)-.*/#,
@@ -1556,14 +1562,20 @@ public func discoveredLinkerToolsInfo(_ producer: any CommandProducer, _ delegat
15561562
if let match = try gnuLD.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
15571563
return DiscoveredLdLinkerToolSpecInfo(linker: .gnuld, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
15581564
}
1559-
15601565
let goLD = [
15611566
#/GNU gold version (?<version>[\d.]+)-.*/#,
15621567
#/GNU gold \(GNU Binutils.*\) (?<version>[\d.]+)/#,
15631568
]
15641569
if let match = try goLD.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
15651570
return DiscoveredLdLinkerToolSpecInfo(linker: .gold, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
15661571
}
1572+
// link.exe has no option to simply dump the version, but if you pass an invalid argument, the program will dump a header that contains the version.
1573+
let linkExe = [
1574+
#/Microsoft \(R\) Incremental Linker Version (?<version>[\d.]+)/#
1575+
]
1576+
if let match = try linkExe.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
1577+
return DiscoveredLdLinkerToolSpecInfo(linker: .linkExe, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
1578+
}
15671579

15681580
struct LDVersionDetails: Decodable {
15691581
let version: Version
@@ -1597,7 +1609,6 @@ public func discoveredLinkerToolsInfo(_ producer: any CommandProducer, _ delegat
15971609
if let match = try lld.compactMap({ try $0.firstMatch(in: String(decoding: executionResult.stdout, as: UTF8.self)) }).first {
15981610
return DiscoveredLdLinkerToolSpecInfo(linker: .lld, toolPath: toolPath, toolVersion: try Version(String(match.output.version)), architectures: Set())
15991611
}
1600-
16011612
throw e
16021613
})
16031614
})

0 commit comments

Comments
 (0)