Skip to content

Commit 677320c

Browse files
committed
Move fallback developer directory determination into the plugin system
This moves some Windows and macOS specific code out of core.
1 parent 6217a62 commit 677320c

File tree

11 files changed

+114
-70
lines changed

11 files changed

+114
-70
lines changed

Sources/SWBApplePlatform/Plugin.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Foundation
1818
import SWBTaskConstruction
1919

2020
@PluginExtensionSystemActor public func initializePlugin(_ manager: PluginManager) {
21+
manager.register(AppleDeveloperDirectoryExtension(), type: DeveloperDirectoryExtensionPoint.self)
2122
manager.register(ApplePlatformSpecsExtension(), type: SpecificationsExtensionPoint.self)
2223
manager.register(ActoolInputFileGroupingStrategyExtension(), type: InputFileGroupingStrategyExtensionPoint.self)
2324
manager.register(ImageScaleFactorsInputFileGroupingStrategyExtension(), type: InputFileGroupingStrategyExtensionPoint.self)
@@ -29,6 +30,12 @@ import SWBTaskConstruction
2930
manager.register(AppleSettingsBuilderExtension(), type: SettingsBuilderExtensionPoint.self)
3031
}
3132

33+
struct AppleDeveloperDirectoryExtension: DeveloperDirectoryExtension {
34+
func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? {
35+
try await hostOperatingSystem == .macOS ? Xcode.getActiveDeveloperDirectoryPath() : nil
36+
}
37+
}
38+
3239
struct TaskProducersExtension: TaskProducerExtension {
3340

3441
func createPreSetupTaskProducers(_ context: TaskProducerContext) -> [any TaskProducer] {

Sources/SWBBuildService/BuildServiceEntryPoint.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ extension BuildService {
110110
pluginManager.registerExtensionPoint(EnvironmentExtensionPoint())
111111
pluginManager.registerExtensionPoint(InputFileGroupingStrategyExtensionPoint())
112112
pluginManager.registerExtensionPoint(TaskProducerExtensionPoint())
113+
pluginManager.registerExtensionPoint(DeveloperDirectoryExtensionPoint())
113114
pluginManager.registerExtensionPoint(DiagnosticToolingExtensionPoint())
114115
pluginManager.registerExtensionPoint(SDKVariantInfoExtensionPoint())
115116
pluginManager.registerExtensionPoint(FeatureAvailabilityExtensionPoint())

Sources/SWBCore/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ add_library(SWBCore
5959
DiagnosticSupport.swift
6060
EnvironmentBindings.swift
6161
ExecutableTask.swift
62+
Extensions/DeveloperDirectoryExtension.swift
6263
Extensions/DiagnosticToolingExtension.swift
6364
Extensions/EnvironmentExtension.swift
6465
Extensions/FeatureAvailabilityExtension.swift
@@ -71,7 +72,6 @@ add_library(SWBCore
7172
Extensions/ToolchainRegistryExtension.swift
7273
FileSystemSignatureBasedCache.swift
7374
FileToBuild.swift
74-
KnownFolders.swift
7575
LibclangVendored/ArrayExtensions.swift
7676
LibclangVendored/CStringArray.swift
7777
LibclangVendored/Libclang.swift

Sources/SWBCore/Core.swift

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,30 @@ public final class Core: Sendable {
5151
return nil
5252
}
5353

54+
#if USE_STATIC_PLUGIN_INITIALIZATION
55+
// In a package context, plugins are statically linked into the build system.
56+
// Load specs from service plugins if requested since we don't have a Service in certain tests
57+
// Here we don't have access to `core.pluginPaths` like we do in the call below,
58+
// but it doesn't matter because it will return an empty array when USE_STATIC_PLUGIN_INITIALIZATION is defined.
59+
await extraPluginRegistration([])
60+
#endif
61+
5462
let resolvedDeveloperPath: String
5563
do {
5664
if let resolved = developerPath?.nilIfEmpty?.str {
5765
resolvedDeveloperPath = resolved
58-
} else if hostOperatingSystem == .macOS {
59-
resolvedDeveloperPath = try await Xcode.getActiveDeveloperDirectoryPath().str
60-
} else if hostOperatingSystem == .windows {
61-
guard let userProgramFiles = URL.userProgramFiles, let swiftPath = try? userProgramFiles.appending(component: "Swift").filePath else {
62-
delegate.error("Could not determine path to user program files")
66+
} else {
67+
let values = try await Set(pluginManager.extensions(of: DeveloperDirectoryExtensionPoint.self).asyncMap { try await $0.fallbackDeveloperDirectory(hostOperatingSystem: hostOperatingSystem) }).compactMap { $0 }
68+
switch values.count {
69+
case 0:
70+
delegate.error("Could not determine path to developer directory because no extensions provided a fallback value")
71+
return nil
72+
case 1:
73+
resolvedDeveloperPath = values[0].str
74+
default:
75+
delegate.error("Could not determine path to developer directory because multiple extensions provided conflicting fallback values: \(values.sorted().map { $0.str }.joined(separator: ", "))")
6376
return nil
6477
}
65-
resolvedDeveloperPath = swiftPath.str
66-
} else {
67-
resolvedDeveloperPath = "/"
6878
}
6979
} catch {
7080
delegate.error("Could not determine path to developer directory: \(error)")
@@ -93,8 +103,11 @@ public final class Core: Sendable {
93103
}
94104
}
95105

106+
#if !USE_STATIC_PLUGIN_INITIALIZATION
107+
// In a package context, plugins are statically linked into the build system.
96108
// Load specs from service plugins if requested since we don't have a Service in certain tests
97109
await extraPluginRegistration(core.pluginPaths)
110+
#endif
98111

99112
await core.initializeSpecRegistry()
100113

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
public import SWBUtil
14+
15+
public struct DeveloperDirectoryExtensionPoint: ExtensionPoint {
16+
public typealias ExtensionProtocol = DeveloperDirectoryExtension
17+
18+
public static let name = "DeveloperDirectoryExtensionPoint"
19+
20+
public init() {}
21+
}
22+
23+
public protocol DeveloperDirectoryExtension: Sendable {
24+
func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path?
25+
}

Sources/SWBCore/KnownFolders.swift

Lines changed: 0 additions & 60 deletions
This file was deleted.

Sources/SWBGenericUnixPlatform/Plugin.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,24 @@ import SWBCore
1515
import Foundation
1616

1717
@PluginExtensionSystemActor public func initializePlugin(_ manager: PluginManager) {
18+
manager.register(GenericUnixDeveloperDirectoryExtension(), type: DeveloperDirectoryExtensionPoint.self)
1819
manager.register(GenericUnixPlatformSpecsExtension(), type: SpecificationsExtensionPoint.self)
1920
manager.register(GenericUnixPlatformInfoExtension(), type: PlatformInfoExtensionPoint.self)
2021
manager.register(GenericUnixSDKRegistryExtension(), type: SDKRegistryExtensionPoint.self)
2122
manager.register(GenericUnixToolchainRegistryExtension(), type: ToolchainRegistryExtensionPoint.self)
2223
}
2324

25+
struct GenericUnixDeveloperDirectoryExtension: DeveloperDirectoryExtension {
26+
func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? {
27+
if hostOperatingSystem == .windows || hostOperatingSystem == .macOS {
28+
// Handled by the Windows and Apple plugins
29+
return nil
30+
}
31+
32+
return .root
33+
}
34+
}
35+
2436
struct GenericUnixPlatformSpecsExtension: SpecificationsExtension {
2537
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? {
2638
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBGenericUnixPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)

Sources/SWBTestSupport/CoreTestSupport.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ extension Core {
8989
pluginManager.registerExtensionPoint(EnvironmentExtensionPoint())
9090
pluginManager.registerExtensionPoint(InputFileGroupingStrategyExtensionPoint())
9191
pluginManager.registerExtensionPoint(TaskProducerExtensionPoint())
92+
pluginManager.registerExtensionPoint(DeveloperDirectoryExtensionPoint())
9293
pluginManager.registerExtensionPoint(DiagnosticToolingExtensionPoint())
9394
pluginManager.registerExtensionPoint(SDKVariantInfoExtensionPoint())
9495
pluginManager.registerExtensionPoint(FeatureAvailabilityExtensionPoint())

Sources/SWBWindowsPlatform/Plugin.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Foundation
1616

1717
@PluginExtensionSystemActor public func initializePlugin(_ manager: PluginManager) {
1818
let plugin = WindowsPlugin()
19+
manager.register(WindowsDeveloperDirectoryExtension(), type: DeveloperDirectoryExtensionPoint.self)
1920
manager.register(WindowsPlatformSpecsExtension(), type: SpecificationsExtensionPoint.self)
2021
manager.register(WindowsEnvironmentExtension(plugin: plugin), type: EnvironmentExtensionPoint.self)
2122
manager.register(WindowsPlatformExtension(plugin: plugin), type: PlatformInfoExtensionPoint.self)
@@ -51,6 +52,18 @@ public final class WindowsPlugin: Sendable {
5152
}
5253
}
5354

55+
struct WindowsDeveloperDirectoryExtension: DeveloperDirectoryExtension {
56+
func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? {
57+
guard hostOperatingSystem == .windows else {
58+
return nil
59+
}
60+
guard let userProgramFiles = URL.userProgramFiles, let swiftPath = try? userProgramFiles.appending(component: "Swift").filePath else {
61+
throw StubError.error("Could not determine path to user program files")
62+
}
63+
return swiftPath
64+
}
65+
}
66+
5467
struct WindowsPlatformSpecsExtension: SpecificationsExtension {
5568
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? {
5669
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBWindowsPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)

Tests/SWBCoreTests/CoreTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,15 @@ import Foundation
378378
func toolchainPathsCount() async throws -> Int {
379379
let delegate = Delegate()
380380
let pluginManager = await PluginManager(skipLoadingPluginIdentifiers: [])
381+
await pluginManager.registerExtensionPoint(DeveloperDirectoryExtensionPoint())
381382
await pluginManager.registerExtensionPoint(SpecificationsExtensionPoint())
382383
await pluginManager.registerExtensionPoint(ToolchainRegistryExtensionPoint())
383384
await pluginManager.register(BuiltinSpecsExtension(), type: SpecificationsExtensionPoint.self)
385+
struct MockDeveloperDirectoryExtensionPoint: DeveloperDirectoryExtension {
386+
func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? {
387+
.root
388+
}
389+
}
384390
struct MockToolchainExtension: ToolchainRegistryExtension {
385391
func additionalToolchains(context: any ToolchainRegistryExtensionAdditionalToolchainsContext) async throws -> [Toolchain] {
386392
guard context.toolchainRegistry.lookup(ToolchainRegistry.defaultToolchainIdentifier) == nil else {
@@ -389,6 +395,7 @@ import Foundation
389395
return [Toolchain(identifier: ToolchainRegistry.defaultToolchainIdentifier, displayName: "Mock", version: Version(), aliases: ["default"], path: .root, frameworkPaths: [], libraryPaths: [], defaultSettings: [:], overrideSettings: [:], defaultSettingsWhenPrimary: [:], executableSearchPaths: [], testingLibraryPlatformNames: [], fs: context.fs)]
390396
}
391397
}
398+
await pluginManager.register(MockDeveloperDirectoryExtensionPoint(), type: DeveloperDirectoryExtensionPoint.self)
392399
await pluginManager.register(MockToolchainExtension(), type: ToolchainRegistryExtensionPoint.self)
393400
let core = await Core.getInitializedCore(delegate, pluginManager: pluginManager, inferiorProductsPath: Path.root.join("invalid"), environment: [:], buildServiceModTime: Date(), connectionMode: .inProcess)
394401
for diagnostic in delegate.diagnostics {
@@ -416,9 +423,15 @@ import Foundation
416423
func testExternalToolchainPath(environmentOverrides: [String:String], expecting expectedPathStrings: [String], _ originalToolchainCount: Int) async throws {
417424
let delegate = Delegate()
418425
let pluginManager = await PluginManager(skipLoadingPluginIdentifiers: [])
426+
await pluginManager.registerExtensionPoint(DeveloperDirectoryExtensionPoint())
419427
await pluginManager.registerExtensionPoint(SpecificationsExtensionPoint())
420428
await pluginManager.registerExtensionPoint(ToolchainRegistryExtensionPoint())
421429
await pluginManager.register(BuiltinSpecsExtension(), type: SpecificationsExtensionPoint.self)
430+
struct MockDeveloperDirectoryExtensionPoint: DeveloperDirectoryExtension {
431+
func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? {
432+
.root
433+
}
434+
}
422435
struct MockToolchainExtension: ToolchainRegistryExtension {
423436
func additionalToolchains(context: any ToolchainRegistryExtensionAdditionalToolchainsContext) async throws -> [Toolchain] {
424437
guard context.toolchainRegistry.lookup(ToolchainRegistry.defaultToolchainIdentifier) == nil else {
@@ -427,6 +440,7 @@ import Foundation
427440
return [Toolchain(identifier: ToolchainRegistry.defaultToolchainIdentifier, displayName: "Mock", version: Version(), aliases: ["default"], path: .root, frameworkPaths: [], libraryPaths: [], defaultSettings: [:], overrideSettings: [:], defaultSettingsWhenPrimary: [:], executableSearchPaths: [], testingLibraryPlatformNames: [], fs: context.fs)]
428441
}
429442
}
443+
await pluginManager.register(MockDeveloperDirectoryExtensionPoint(), type: DeveloperDirectoryExtensionPoint.self)
430444
await pluginManager.register(MockToolchainExtension(), type: ToolchainRegistryExtensionPoint.self)
431445
let core = await Core.getInitializedCore(delegate, pluginManager: pluginManager, inferiorProductsPath: Path.root.join("invalid"), environment: environmentOverrides, buildServiceModTime: Date(), connectionMode: .inProcess)
432446
for diagnostic in delegate.diagnostics {

Tests/SWBCoreTests/ToolchainRegistryTests.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,15 @@ import SWBUtil
7070
}
7171

7272
let pluginManager = await PluginManager(skipLoadingPluginIdentifiers: [])
73+
await pluginManager.registerExtensionPoint(DeveloperDirectoryExtensionPoint())
7374
await pluginManager.registerExtensionPoint(SpecificationsExtensionPoint())
7475
await pluginManager.registerExtensionPoint(ToolchainRegistryExtensionPoint())
7576
await pluginManager.register(BuiltinSpecsExtension(), type: SpecificationsExtensionPoint.self)
77+
struct MockDeveloperDirectoryExtensionPoint: DeveloperDirectoryExtension {
78+
func fallbackDeveloperDirectory(hostOperatingSystem: OperatingSystem) async throws -> Path? {
79+
.root
80+
}
81+
}
7682
struct MockToolchainExtension: ToolchainRegistryExtension {
7783
func additionalToolchains(context: any ToolchainRegistryExtensionAdditionalToolchainsContext) async throws -> [Toolchain] {
7884
guard context.toolchainRegistry.lookup(ToolchainRegistry.defaultToolchainIdentifier) == nil else {
@@ -81,8 +87,20 @@ import SWBUtil
8187
return [Toolchain(identifier: ToolchainRegistry.defaultToolchainIdentifier, displayName: "Mock", version: Version(), aliases: ["default"], path: .root, frameworkPaths: [], libraryPaths: [], defaultSettings: [:], overrideSettings: [:], defaultSettingsWhenPrimary: [:], executableSearchPaths: [], testingLibraryPlatformNames: [], fs: context.fs)]
8288
}
8389
}
90+
await pluginManager.register(MockDeveloperDirectoryExtensionPoint(), type: DeveloperDirectoryExtensionPoint.self)
8491
await pluginManager.register(MockToolchainExtension(), type: ToolchainRegistryExtensionPoint.self)
85-
let core = try #require(await Core.getInitializedCore(TestingCoreDelegate(), pluginManager: pluginManager, inferiorProductsPath: Path.root.join("invalid"), environment: [:], buildServiceModTime: Date(), connectionMode: .inProcess))
92+
let coreDelegate = TestingCoreDelegate()
93+
let core = await Core.getInitializedCore(coreDelegate, pluginManager: pluginManager, inferiorProductsPath: Path.root.join("invalid"), environment: [:], buildServiceModTime: Date(), connectionMode: .inProcess)
94+
guard let core else {
95+
let errors = coreDelegate.diagnostics.filter { $0.behavior == .error }
96+
for error in errors {
97+
Issue.record(Comment(rawValue: error.formatLocalizedDescription(.debugWithoutBehavior)))
98+
}
99+
if errors.isEmpty {
100+
Issue.record("Failed to initialize core but no errors were provided")
101+
}
102+
return
103+
}
86104
let delegate = TestDataDelegate(pluginManager: core.pluginManager)
87105
let registry = await ToolchainRegistry(delegate: delegate, searchPaths: [(tmpDirPath, strict: strict)], fs: fs, hostOperatingSystem: core.hostOperatingSystem)
88106
try perform(registry, delegate.warnings, delegate.errors)

0 commit comments

Comments
 (0)