Skip to content

Commit 2e6eb49

Browse files
committed
Add support for looking up spec resources in a toolchain install
1 parent 719b686 commit 2e6eb49

File tree

18 files changed

+65
-37
lines changed

18 files changed

+65
-37
lines changed

Package.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func swiftSettings(languageMode: SwiftLanguageMode) -> [SwiftSetting] {
4545

4646
.swiftLanguageMode(.v5),
4747

48-
.define("USE_STATIC_PLUGIN_INITIALIZATION")
48+
.define("USE_STATIC_PLUGIN_INITIALIZATION"),
4949
]
5050
case .v6:
5151
return [
@@ -55,7 +55,7 @@ func swiftSettings(languageMode: SwiftLanguageMode) -> [SwiftSetting] {
5555

5656
.swiftLanguageMode(.v6),
5757

58-
.define("USE_STATIC_PLUGIN_INITIALIZATION")
58+
.define("USE_STATIC_PLUGIN_INITIALIZATION"),
5959
]
6060
default:
6161
fatalError("unexpected language mode")

Sources/SWBAndroidPlatform/Plugin.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import Foundation
2424
}
2525

2626
struct AndroidPlatformSpecsExtension: SpecificationsExtension {
27-
func specificationFiles() -> Bundle? {
28-
.module
27+
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? {
28+
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBAndroidPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)
2929
}
3030

3131
func specificationDomains() -> [String : [String]] {

Sources/SWBApplePlatform/Plugin.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ struct ApplePlatformSpecsExtension: SpecificationsExtension {
9898
]
9999
}
100100

101-
func specificationFiles() -> Bundle? {
102-
.module
101+
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? {
102+
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBApplePlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)
103103
}
104104

105105
func specificationDomains() -> [String : [String]] {

Sources/SWBBuildService/BuildService.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ package class BuildService: Service, @unchecked Sendable {
157157
/// Get a shared core instance.
158158
///
159159
/// We use an explicit cache so that we can minimize the number of cores we load while still keeping a flexible public interface that doesn't require all clients to provide all possible required parameters for core initialization (which is useful for testing and debug purposes).
160-
func sharedCore(developerPath: Path?, inferiorProducts: Path? = nil, environment: [String: String] = [:]) async -> (Core?, [Diagnostic]) {
160+
func sharedCore(developerPath: Path?, resourceSearchPaths: [Path] = [], inferiorProducts: Path? = nil, environment: [String: String] = [:]) async -> (Core?, [Diagnostic]) {
161161
let key = CoreCacheKey(developerPath: developerPath, inferiorProducts: inferiorProducts, environment: environment)
162162
return await sharedCoreCacheLock.withLock {
163163
if let existing = sharedCoreCache[key] {
@@ -191,7 +191,7 @@ package class BuildService: Service, @unchecked Sendable {
191191
}
192192
}
193193
let delegate = Delegate()
194-
let (core, diagnostics) = await (Core.getInitializedCore(delegate, pluginManager: pluginManager, developerPath: developerPath, inferiorProductsPath: inferiorProducts, environment: environment, buildServiceModTime: buildServiceModTime, connectionMode: connectionMode), delegate.diagnostics)
194+
let (core, diagnostics) = await (Core.getInitializedCore(delegate, pluginManager: pluginManager, developerPath: developerPath, resourceSearchPaths: resourceSearchPaths, inferiorProductsPath: inferiorProducts, environment: environment, buildServiceModTime: buildServiceModTime, connectionMode: connectionMode), delegate.diagnostics)
195195
delegate.freeze()
196196
sharedCoreCache[key] = (core, diagnostics)
197197
return (core, diagnostics)

Sources/SWBBuildService/Messages.swift

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ private struct CreateSessionHandler: MessageHandler {
124124
let service = request.buildService
125125
let (core, diagnostics) = await service.sharedCore(
126126
developerPath: message.effectiveDeveloperPath,
127+
resourceSearchPaths: message.resourceSearchPaths ?? [],
127128
inferiorProducts: message.inferiorProductsPath,
128129
environment: message.environment ?? [:]
129130
)

Sources/SWBCore/Core.swift

+8-4
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public final class Core: Sendable {
4040
/// Get a configured instance of the core.
4141
///
4242
/// - returns: An initialized Core instance on which all discovery and loading will have been completed. If there are errors during that process, they will be logged to `stderr` and no instance will be returned. Otherwise, the initialized object is returned.
43-
public static func getInitializedCore(_ delegate: any CoreDelegate, pluginManager: PluginManager, developerPath: Path? = nil, inferiorProductsPath: Path? = nil, extraPluginRegistration: @PluginExtensionSystemActor (_ pluginPaths: [Path]) -> Void = { _ in }, additionalContentPaths: [Path] = [], environment: [String:String] = [:], buildServiceModTime: Date, connectionMode: ServiceHostConnectionMode) async -> Core? {
43+
public static func getInitializedCore(_ delegate: any CoreDelegate, pluginManager: PluginManager, developerPath: Path? = nil, resourceSearchPaths: [Path] = [], inferiorProductsPath: Path? = nil, extraPluginRegistration: @PluginExtensionSystemActor (_ pluginPaths: [Path]) -> Void = { _ in }, additionalContentPaths: [Path] = [], environment: [String:String] = [:], buildServiceModTime: Date, connectionMode: ServiceHostConnectionMode) async -> Core? {
4444
// Enable macro expression interning during loading.
4545
return await MacroNamespace.withExpressionInterningEnabled {
4646
let hostOperatingSystem: OperatingSystem
@@ -73,7 +73,7 @@ public final class Core: Sendable {
7373

7474
let core: Core
7575
do {
76-
core = try await Core(delegate: delegate, hostOperatingSystem: hostOperatingSystem, pluginManager: pluginManager, developerPath: resolvedDeveloperPath, inferiorProductsPath: inferiorProductsPath, additionalContentPaths: additionalContentPaths, environment: environment, buildServiceModTime: buildServiceModTime, connectionMode: connectionMode)
76+
core = try await Core(delegate: delegate, hostOperatingSystem: hostOperatingSystem, pluginManager: pluginManager, developerPath: resolvedDeveloperPath, resourceSearchPaths: resourceSearchPaths, inferiorProductsPath: inferiorProductsPath, additionalContentPaths: additionalContentPaths, environment: environment, buildServiceModTime: buildServiceModTime, connectionMode: connectionMode)
7777
} catch {
7878
delegate.error("\(error)")
7979
return nil
@@ -147,6 +147,9 @@ public final class Core: Sendable {
147147
/// The path to the "Developer" directory.
148148
public let developerPath: Path
149149

150+
/// Additional search paths to be used when looking up resource bundles.
151+
public let resourceSearchPaths: [Path]
152+
150153
/// The path to the inferior Xcode build directory, if used.
151154
public let inferiorProductsPath: Path?
152155

@@ -177,11 +180,12 @@ public final class Core: Sendable {
177180

178181
public let connectionMode: ServiceHostConnectionMode
179182

180-
@_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 {
183+
@_spi(Testing) public init(delegate: any CoreDelegate, hostOperatingSystem: OperatingSystem, pluginManager: PluginManager, developerPath: String, resourceSearchPaths: [Path], inferiorProductsPath: Path?, additionalContentPaths: [Path], environment: [String:String], buildServiceModTime: Date, connectionMode: ServiceHostConnectionMode) async throws {
181184
self.delegate = delegate
182185
self.hostOperatingSystem = hostOperatingSystem
183186
self.pluginManager = pluginManager
184187
self.developerPath = Path(developerPath)
188+
self.resourceSearchPaths = resourceSearchPaths
185189
self.inferiorProductsPath = inferiorProductsPath
186190
self.additionalContentPaths = additionalContentPaths
187191
self.buildServiceModTime = buildServiceModTime
@@ -402,7 +406,7 @@ public final class Core: Sendable {
402406

403407
// Find all plugin provided specs.
404408
for ext in await self.pluginManager.extensions(of: SpecificationsExtensionPoint.self) {
405-
if let bundle = ext.specificationFiles() {
409+
if let bundle = ext.specificationFiles(resourceSearchPaths: resourceSearchPaths) {
406410
for url in bundle.urls(forResourcesWithExtension: "xcspec", subdirectory: nil) ?? [] {
407411
do {
408412
try searchPaths.append(((url as URL).filePath, ""))

Sources/SWBCore/Extensions/SpecificationsExtension.swift

+15-4
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,34 @@ public struct SpecificationsExtensionPoint: ExtensionPoint {
5050

5151
public protocol SpecificationsExtension: Sendable {
5252
/// Returns the bundle containing the `.xcspec` files.
53-
func specificationFiles() -> Bundle?
53+
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle?
5454
func specificationDomains() -> [String: [String]]
5555
func specificationTypes() -> [any SpecType.Type]
5656
func specificationClasses() -> [any SpecIdentifierType.Type]
5757
func specificationClassesClassic() -> [any SpecClassType.Type]
5858
func specificationImplementations() -> [any SpecImplementationType.Type]
5959

6060
/// Returns the search paths for two use cases: finding the sole remaining `.xcbuildrules` file, and finding executable scripts next to `.xcspec` files.
61-
func specificationSearchPaths() -> [URL]
61+
func specificationSearchPaths(resourceSearchPaths: [Path]) -> [URL]
6262
}
6363

6464
extension SpecificationsExtension {
65-
public func specificationFiles() -> Bundle? { nil }
65+
public func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? { nil }
6666
public func specificationDomains() -> [String: [String]] { [:] }
6767
public func specificationTypes() -> [any SpecType.Type] { [] }
6868
public func specificationClasses() -> [any SpecIdentifierType.Type] { [] }
6969
public func specificationClassesClassic() -> [any SpecClassType.Type] { [] }
7070
public func specificationImplementations() -> [any SpecImplementationType.Type] { [] }
71-
public func specificationSearchPaths() -> [URL] { [] }
71+
public func specificationSearchPaths(resourceSearchPaths: [Path]) -> [URL] { [] }
72+
73+
public func findResourceBundle(nameWhenInstalledInToolchain: String, resourceSearchPaths: [Path], defaultBundle: @autoclosure () -> Bundle?) -> Bundle? {
74+
for searchPath in resourceSearchPaths {
75+
for bundleBasename in ["\(nameWhenInstalledInToolchain).bundle", "\(nameWhenInstalledInToolchain).resources"] {
76+
if let bundle = Bundle(path: searchPath.join(bundleBasename).str) {
77+
return bundle
78+
}
79+
}
80+
}
81+
return defaultBundle()
82+
}
7283
}

Sources/SWBCore/Settings/Settings.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ fileprivate struct PreOverridesSettings {
348348

349349
@preconcurrency @PluginExtensionSystemActor func searchPaths() -> [Path] {
350350
core.pluginManager.extensions(of: SpecificationsExtensionPoint.self).flatMap { ext in
351-
ext.specificationSearchPaths().compactMap { try? $0.filePath }
351+
ext.specificationSearchPaths(resourceSearchPaths: core.resourceSearchPaths).compactMap { try? $0.filePath }
352352
}.sorted()
353353
}
354354

@@ -999,7 +999,7 @@ extension WorkspaceContext {
999999

10001000
@preconcurrency @PluginExtensionSystemActor func searchPaths() -> [Path] {
10011001
core.pluginManager.extensions(of: SpecificationsExtensionPoint.self).flatMap { ext in
1002-
ext.specificationSearchPaths().compactMap { try? $0.filePath }
1002+
ext.specificationSearchPaths(resourceSearchPaths: core.resourceSearchPaths).compactMap { try? $0.filePath }
10031003
}.sorted()
10041004
}
10051005

Sources/SWBCore/SpecImplementations/RegisterSpecs.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public struct BuiltinSpecsExtension: SpecificationsExtension {
143143
]
144144
}
145145

146-
public func specificationFiles() -> Bundle? {
147-
.module
146+
public func specificationFiles(resourceSearchPaths: [SWBUtil.Path]) -> Bundle? {
147+
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBCore", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)
148148
}
149149
}

Sources/SWBGenericUnixPlatform/Plugin.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import Foundation
1919
}
2020

2121
struct GenericUnixPlatformSpecsExtension: SpecificationsExtension {
22-
func specificationFiles() -> Bundle? {
23-
.module
22+
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? {
23+
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBGenericUnixPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)
2424
}
2525

2626
func specificationDomains() -> [String: [String]] {

Sources/SWBProtocol/Message.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ public struct CreateSessionRequest: RequestMessage, Equatable, SerializableCodab
423423

424424
public let name: String
425425
public let developerPath: Path?
426+
public let resourceSearchPaths: [Path]?
426427
public let appPath: Path?
427428
public let cachePath: Path?
428429
public let inferiorProductsPath: Path?
@@ -432,9 +433,14 @@ public struct CreateSessionRequest: RequestMessage, Equatable, SerializableCodab
432433
self.init(name: name, developerPath: developerPath, cachePath: cachePath, inferiorProductsPath: inferiorProductsPath, environment: nil)
433434
}
434435

435-
public init(name: String, developerPath: Path?, cachePath: Path?, inferiorProductsPath: Path?, environment: [String:String]?) {
436+
public init(name: String, developerPath: Path?, cachePath: Path?, inferiorProductsPath: Path?, environment: [String:String]?) { // ABI Compatibility
437+
self.init(name: name, developerPath: developerPath, resourceSearchPaths: [], cachePath: cachePath, inferiorProductsPath: inferiorProductsPath, environment: environment)
438+
}
439+
440+
public init(name: String, developerPath: Path?, resourceSearchPaths: [Path], cachePath: Path?, inferiorProductsPath: Path?, environment: [String:String]?) {
436441
self.name = name
437442
self.developerPath = developerPath
443+
self.resourceSearchPaths = resourceSearchPaths
438444
self.appPath = developerPath?.dirname.dirname
439445
self.cachePath = cachePath
440446
self.inferiorProductsPath = inferiorProductsPath
@@ -446,6 +452,7 @@ public struct CreateSessionRequest: RequestMessage, Equatable, SerializableCodab
446452
self.name = try deserializer.deserialize()
447453
self.appPath = try deserializer.deserialize()
448454
self.developerPath = count >= 5 ? try deserializer.deserialize() : appPath?.join("Contents").join("Developer")
455+
self.resourceSearchPaths = []
449456
self.cachePath = try deserializer.deserialize()
450457
self.inferiorProductsPath = try deserializer.deserialize()
451458
self.environment = nil

Sources/SWBQNXPlatform/Plugin.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import Foundation
2424
}
2525

2626
struct QNXPlatformSpecsExtension: SpecificationsExtension {
27-
func specificationFiles() -> Bundle? {
28-
.module
27+
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? {
28+
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBQNXPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)
2929
}
3030
}
3131

Sources/SWBTestSupport/CoreTestSupport.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ extension Core {
4040
developerPath = "/"
4141
}
4242
let delegate = TestingCoreDelegate()
43-
return await (try Core(delegate: delegate, hostOperatingSystem: hostOperatingSystem, pluginManager: PluginManager(skipLoadingPluginIdentifiers: []), developerPath: developerPath, inferiorProductsPath: nil, additionalContentPaths: [], environment: [:], buildServiceModTime: Date(), connectionMode: .inProcess), delegate.diagnostics)
43+
return await (try Core(delegate: delegate, hostOperatingSystem: hostOperatingSystem, pluginManager: PluginManager(skipLoadingPluginIdentifiers: []), developerPath: developerPath, resourceSearchPaths: [], inferiorProductsPath: nil, additionalContentPaths: [], environment: [:], buildServiceModTime: Date(), connectionMode: .inProcess), delegate.diagnostics)
4444
}
4545

4646
/// Get an initialized Core suitable for testing.

Sources/SWBUniversalPlatform/Plugin.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ struct UniversalPlatformSpecsExtension: SpecificationsExtension {
3535
]
3636
}
3737

38-
func specificationFiles() -> Bundle? {
39-
.module
38+
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? {
39+
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBUniversalPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)
4040
}
4141

4242
// Allow locating the sole remaining `.xcbuildrules` file.
43-
func specificationSearchPaths() -> [URL] {
44-
Bundle.module.resourceURL.map { [$0] } ?? []
43+
func specificationSearchPaths(resourceSearchPaths: [Path]) -> [URL] {
44+
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBUniversalPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)?.resourceURL.map { [$0] } ?? []
4545
}
4646
}

Sources/SWBWebAssemblyPlatform/Plugin.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import Foundation
2222
}
2323

2424
struct WebAssemblyPlatformSpecsExtension: SpecificationsExtension {
25-
func specificationFiles() -> Bundle? {
26-
.module
25+
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? {
26+
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBWebAssemblyPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)
2727
}
2828
}
2929

Sources/SWBWindowsPlatform/Plugin.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import Foundation
2020
}
2121

2222
struct WindowsPlatformSpecsExtension: SpecificationsExtension {
23-
func specificationFiles() -> Bundle? {
24-
.module
23+
func specificationFiles(resourceSearchPaths: [Path]) -> Bundle? {
24+
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBWindowsPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)
2525
}
2626
}
2727

Sources/SwiftBuild/SWBBuildService.swift

+7-2
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ public final class SWBBuildService: Sendable {
194194
await createSession(name: name, developerPath: developerPath, cachePath: cachePath, inferiorProductsPath: inferiorProductsPath, environment: nil)
195195
}
196196

197+
// ABI compatibility
198+
public func createSession(name: String, developerPath: String? = nil, cachePath: String?, inferiorProductsPath: String?, environment: [String:String]?) async -> (Result<SWBBuildServiceSession, any Error>, [SwiftBuildMessage.DiagnosticInfo]) {
199+
await createSession(name: name, resourceSearchPaths: [], cachePath: cachePath, inferiorProductsPath: inferiorProductsPath, environment: environment)
200+
}
201+
197202
/// Create a new service session.
198203
///
199204
/// - Parameters:
@@ -202,9 +207,9 @@ public final class SWBBuildService: Sendable {
202207
/// - inferiorProductsPath: If provided, the path to where inferior Xcode build data is located.
203208
/// - environment: If provided, a set of environment variables that are relevant to the build session's context
204209
/// - returns: The new session.
205-
public func createSession(name: String, developerPath: String? = nil, cachePath: String?, inferiorProductsPath: String?, environment: [String:String]?) async -> (Result<SWBBuildServiceSession, any Error>, [SwiftBuildMessage.DiagnosticInfo]) {
210+
public func createSession(name: String, developerPath: String? = nil, resourceSearchPaths: [String], cachePath: String?, inferiorProductsPath: String?, environment: [String:String]?) async -> (Result<SWBBuildServiceSession, any Error>, [SwiftBuildMessage.DiagnosticInfo]) {
206211
do {
207-
let response = try await send(request: CreateSessionRequest(name: name, developerPath: developerPath.map(Path.init), cachePath: cachePath.map(Path.init), inferiorProductsPath: inferiorProductsPath.map(Path.init), environment: environment))
212+
let response = try await send(request: CreateSessionRequest(name: name, developerPath: developerPath.map(Path.init), resourceSearchPaths: resourceSearchPaths.map(Path.init), cachePath: cachePath.map(Path.init), inferiorProductsPath: inferiorProductsPath.map(Path.init), environment: environment))
208213
let diagnostics = response.diagnostics.map { SwiftBuildMessage.DiagnosticInfo(.init($0, .global)) }
209214
if let sessionID = response.sessionID {
210215
return (.success(SWBBuildServiceSession(name: name, uid: sessionID, service: self)), diagnostics)

Tests/SWBTaskConstructionTests/TaskConstructionTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -2305,7 +2305,7 @@ fileprivate struct TaskConstructionTests: CoreBasedTests {
23052305

23062306
@PluginExtensionSystemActor func pluginSearchPaths() -> [Path] {
23072307
core.pluginManager.extensions(of: SpecificationsExtensionPoint.self).flatMap { ext in
2308-
ext.specificationSearchPaths().compactMap { try? $0.filePath }
2308+
ext.specificationSearchPaths(resourceSearchPaths: core.resourceSearchPaths).compactMap { try? $0.filePath }
23092309
}.sorted()
23102310
}
23112311

0 commit comments

Comments
 (0)