diff --git a/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigure.md b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigure.md index 6ca3f523881..82554186134 100644 --- a/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigure.md +++ b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigure.md @@ -21,7 +21,7 @@ sdk configure [--package-path=] [--include-search-path=...] [--library-search-path=...] [--toolset-path=...] [--reset] - [--show-configuration] [--version] + [--show-configuration] [target-triple...] [--version] [--help] ``` diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift index cdcdfd781f4..d04216cc651 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift @@ -53,7 +53,7 @@ public enum SwiftSDKError: Swift.Error { case unserializableMetadata /// No configuration values are available for this Swift SDK and target triple. - case swiftSDKNotFound(artifactID: String, hostTriple: Triple, targetTriple: Triple) + case swiftSDKNotFound(artifactID: String, hostTriple: Triple, targetTriple: Triple?) /// A Swift SDK bundle with this name is already installed, can't install a new bundle with the same name. case swiftSDKBundleAlreadyInstalled(bundleName: String) @@ -108,10 +108,16 @@ extension SwiftSDKError: CustomStringConvertible { properties required for initialization """ case .swiftSDKNotFound(let artifactID, let hostTriple, let targetTriple): - return """ - Swift SDK with ID `\(artifactID)`, host triple \(hostTriple), and target triple \(targetTriple) is not \ - currently installed. - """ + if let targetTriple { + return """ + Swift SDK with ID `\(artifactID)`, host triple \(hostTriple), and target triple \(targetTriple) is not \ + currently installed. + """ + } else { + return """ + Swift SDK with ID `\(artifactID)` is not currently installed. + """ + } case .swiftSDKBundleAlreadyInstalled(let bundleName): return """ Swift SDK bundle with name `\(bundleName)` is already installed. Can't install a new bundle \ @@ -259,14 +265,17 @@ public struct SwiftSDK: Equatable { /// deserialization. public private(set) var toolset: Toolset - public struct PathsConfiguration: Equatable { + /// The paths associated with a Swift SDK. The Path type can be a `String` + /// to encapsulate the arguments for the `SwiftSDKConfigurationStore.configure` + /// function, or can be a fully-realized `AbsolutePath` when deserialized from a configuration. + public struct PathsConfiguration: Equatable { public init( - sdkRootPath: Basics.AbsolutePath?, - swiftResourcesPath: Basics.AbsolutePath? = nil, - swiftStaticResourcesPath: Basics.AbsolutePath? = nil, - includeSearchPaths: [Basics.AbsolutePath]? = nil, - librarySearchPaths: [Basics.AbsolutePath]? = nil, - toolsetPaths: [Basics.AbsolutePath]? = nil + sdkRootPath: Path? = nil, + swiftResourcesPath: Path? = nil, + swiftStaticResourcesPath: Path? = nil, + includeSearchPaths: [Path]? = nil, + librarySearchPaths: [Path]? = nil, + toolsetPaths: [Path]? = nil ) { self.sdkRootPath = sdkRootPath self.swiftResourcesPath = swiftResourcesPath @@ -277,22 +286,22 @@ public struct SwiftSDK: Equatable { } /// Root directory path of the SDK used to compile for the target triple. - public var sdkRootPath: Basics.AbsolutePath? + public var sdkRootPath: Path? /// Path containing Swift resources for dynamic linking. - public var swiftResourcesPath: Basics.AbsolutePath? + public var swiftResourcesPath: Path? /// Path containing Swift resources for static linking. - public var swiftStaticResourcesPath: Basics.AbsolutePath? + public var swiftStaticResourcesPath: Path? /// Array of paths containing headers. - public var includeSearchPaths: [Basics.AbsolutePath]? + public var includeSearchPaths: [Path]? /// Array of paths containing libraries. - public var librarySearchPaths: [Basics.AbsolutePath]? + public var librarySearchPaths: [Path]? /// Array of paths containing toolset files. - public var toolsetPaths: [Basics.AbsolutePath]? + public var toolsetPaths: [Path]? /// Initialize paths configuration from values deserialized using v3 schema. /// - Parameters: @@ -301,92 +310,53 @@ public struct SwiftSDK: Equatable { fileprivate init( _ properties: SerializedDestinationV3.TripleProperties, swiftSDKDirectory: Basics.AbsolutePath? = nil - ) throws { - if let swiftSDKDirectory { - self.init( - sdkRootPath: try AbsolutePath(validating: properties.sdkRootPath, relativeTo: swiftSDKDirectory), - swiftResourcesPath: try properties.swiftResourcesPath.map { - try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) - }, - swiftStaticResourcesPath: try properties.swiftStaticResourcesPath.map { - try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) - }, - includeSearchPaths: try properties.includeSearchPaths?.map { - try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) - }, - librarySearchPaths: try properties.librarySearchPaths?.map { - try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) - }, - toolsetPaths: try properties.toolsetPaths?.map { - try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) - } - ) - } else { - self.init( - sdkRootPath: try AbsolutePath(validating: properties.sdkRootPath), - swiftResourcesPath: try properties.swiftResourcesPath.map { - try AbsolutePath(validating: $0) - }, - swiftStaticResourcesPath: try properties.swiftStaticResourcesPath.map { - try AbsolutePath(validating: $0) - }, - includeSearchPaths: try properties.includeSearchPaths?.map { - try AbsolutePath(validating: $0) - }, - librarySearchPaths: try properties.librarySearchPaths?.map { - try AbsolutePath(validating: $0) - }, - toolsetPaths: try properties.toolsetPaths?.map { - try AbsolutePath(validating: $0) - } - ) - } + ) throws where Path == Basics.AbsolutePath { + self.init( + sdkRootPath: try AbsolutePath(validating: properties.sdkRootPath, relativeTo: swiftSDKDirectory), + swiftResourcesPath: try properties.swiftResourcesPath.map { + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) + }, + swiftStaticResourcesPath: try properties.swiftStaticResourcesPath.map { + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) + }, + includeSearchPaths: try properties.includeSearchPaths?.map { + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) + }, + librarySearchPaths: try properties.librarySearchPaths?.map { + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) + }, + toolsetPaths: try properties.toolsetPaths?.map { + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) + } + ) } /// Initialize paths configuration from values deserialized using v4 schema. /// - Parameters: /// - properties: properties of a Swift SDK for the given triple. /// - swiftSDKDirectory: directory used for converting relative paths in `properties` to absolute paths. - fileprivate init(_ properties: SwiftSDKMetadataV4.TripleProperties, swiftSDKDirectory: Basics.AbsolutePath? = nil) throws { - if let swiftSDKDirectory { - self.init( - sdkRootPath: try AbsolutePath(validating: properties.sdkRootPath, relativeTo: swiftSDKDirectory), - swiftResourcesPath: try properties.swiftResourcesPath.map { - try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) - }, - swiftStaticResourcesPath: try properties.swiftStaticResourcesPath.map { - try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) - }, - includeSearchPaths: try properties.includeSearchPaths?.map { - try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) - }, - librarySearchPaths: try properties.librarySearchPaths?.map { - try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) - }, - toolsetPaths: try properties.toolsetPaths?.map { - try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) - } - ) - } else { - self.init( - sdkRootPath: try AbsolutePath(validating: properties.sdkRootPath), - swiftResourcesPath: try properties.swiftResourcesPath.map { - try AbsolutePath(validating: $0) - }, - swiftStaticResourcesPath: try properties.swiftStaticResourcesPath.map { - try AbsolutePath(validating: $0) - }, - includeSearchPaths: try properties.includeSearchPaths?.map { - try AbsolutePath(validating: $0) - }, - librarySearchPaths: try properties.librarySearchPaths?.map { - try AbsolutePath(validating: $0) - }, - toolsetPaths: try properties.toolsetPaths?.map { - try AbsolutePath(validating: $0) - } - ) - } + fileprivate init( + _ properties: SwiftSDKMetadataV4.TripleProperties, + swiftSDKDirectory: Basics.AbsolutePath? = nil + ) throws where Path == Basics.AbsolutePath { + self.init( + sdkRootPath: try AbsolutePath(validating: properties.sdkRootPath, relativeTo: swiftSDKDirectory), + swiftResourcesPath: try properties.swiftResourcesPath.map { + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) + }, + swiftStaticResourcesPath: try properties.swiftStaticResourcesPath.map { + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) + }, + includeSearchPaths: try properties.includeSearchPaths?.map { + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) + }, + librarySearchPaths: try properties.librarySearchPaths?.map { + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) + }, + toolsetPaths: try properties.toolsetPaths?.map { + try AbsolutePath(validating: $0, relativeTo: swiftSDKDirectory) + } + ) } public mutating func merge(with newConfiguration: Self) { @@ -414,10 +384,48 @@ public struct SwiftSDK: Equatable { self.toolsetPaths = toolsetPaths } } + + mutating func merge( + with newConfiguration: PathsConfiguration, + relativeTo basePath: Path? + ) throws -> [String] where Path == Basics.AbsolutePath { + var updatedProperties: [String] = [] + if let sdkRootPath = newConfiguration.sdkRootPath { + self.sdkRootPath = try AbsolutePath(validating: sdkRootPath, relativeTo: basePath) + updatedProperties.append("sdkRootPath") + } + + if let swiftResourcesPath = newConfiguration.swiftResourcesPath { + self.swiftResourcesPath = try AbsolutePath(validating: swiftResourcesPath, relativeTo: basePath) + updatedProperties.append("swiftResourcesPath") + } + + if let swiftStaticResourcesPath = newConfiguration.swiftStaticResourcesPath { + self.swiftResourcesPath = try AbsolutePath(validating: swiftStaticResourcesPath, relativeTo: basePath) + updatedProperties.append("swiftStaticResourcesPath") + } + + if let includeSearchPaths = newConfiguration.includeSearchPaths, !includeSearchPaths.isEmpty { + self.includeSearchPaths = try includeSearchPaths.map { try AbsolutePath(validating: $0, relativeTo: basePath) } + updatedProperties.append("includeSearchPath") + } + + if let librarySearchPaths = newConfiguration.librarySearchPaths, !librarySearchPaths.isEmpty { + self.librarySearchPaths = try librarySearchPaths.map { try AbsolutePath(validating: $0, relativeTo: basePath) } + updatedProperties.append("librarySearchPath") + } + + if let toolsetPaths = newConfiguration.toolsetPaths, !toolsetPaths.isEmpty { + self.toolsetPaths = try toolsetPaths.map { try AbsolutePath(validating: $0, relativeTo: basePath) } + updatedProperties.append("toolsetPath") + } + + return updatedProperties + } } /// Configuration of file system paths used by this Swift SDK when building. - public var pathsConfiguration: PathsConfiguration + public var pathsConfiguration: PathsConfiguration /// Creates a Swift SDK with the specified properties. @available(*, deprecated, message: "use `init(targetTriple:sdkRootDir:toolset:)` instead") @@ -464,7 +472,7 @@ public struct SwiftSDK: Equatable { hostTriple: Triple? = nil, targetTriple: Triple? = nil, toolset: Toolset, - pathsConfiguration: PathsConfiguration, + pathsConfiguration: PathsConfiguration, supportsTesting: Bool ) { let xctestSupport: XCTestSupport @@ -489,7 +497,7 @@ public struct SwiftSDK: Equatable { hostTriple: Triple? = nil, targetTriple: Triple? = nil, toolset: Toolset, - pathsConfiguration: PathsConfiguration, + pathsConfiguration: PathsConfiguration, xctestSupport: XCTestSupport = .supported ) { self.hostTriple = hostTriple @@ -1201,7 +1209,7 @@ extension Optional where Wrapped == [Basics.AbsolutePath] { } } -extension SwiftSDK.PathsConfiguration: CustomStringConvertible { +extension SwiftSDK.PathsConfiguration: CustomStringConvertible where Path == Basics.AbsolutePath { public var description: String { """ sdkRootPath: \(sdkRootPath.configurationString) @@ -1213,3 +1221,13 @@ extension SwiftSDK.PathsConfiguration: CustomStringConvertible { """ } } + +extension Basics.AbsolutePath { + fileprivate init(validating string: String, relativeTo basePath: Basics.AbsolutePath?) throws { + if let basePath { + try self.init(validating: string, relativeTo: basePath) + } else { + try self.init(validating: string) + } + } +} diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift index 39bc3d22a43..5b506feba78 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift @@ -70,7 +70,7 @@ public final class SwiftSDKBundleStore { let fileSystem: any FileSystem /// Observability scope used for logging. - private let observabilityScope: ObservabilityScope + let observabilityScope: ObservabilityScope /// Closure invoked for output produced by this store during its operation. private let outputHandler: (Output) -> Void diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift index 105fae09b6c..6f446551233 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift @@ -85,6 +85,22 @@ public final class SwiftSDKConfigurationStore { try encoder.encode(path: configurationPath, fileSystem: fileSystem, properties) } + private func swiftSDKs(for id: String) throws -> [SwiftSDK] { + for bundle in try self.swiftSDKBundleStore.allValidBundles { + for (artifactID, variants) in bundle.artifacts { + guard artifactID == id else { + continue + } + + for variant in variants { + return variant.swiftSDKs + } + } + } + + return [] + } + public func readConfiguration( sdkID: String, targetTriple: Triple @@ -141,4 +157,100 @@ public final class SwiftSDKConfigurationStore { try fileSystem.removeFileTree(configurationPath) return true } + + /// Configures the specified Swift SDK and identified target triple with the configuration parameter. + /// - Parameters: + /// - sdkID: ID of the Swift SDK to operate on. + /// - tripleString: run-time triple for which the properties should be configured, or nil to configure all triples for the Swift SDK + /// - showConfiguration: if true, simply print the current configuration for the target triple(s) + /// - resetConfiguration: if true, reset the configuration for the target triple(s) + /// - config: the configuration parameters to set for for the target triple(s) + /// - Returns: `true` if custom configuration was successful, `false` if no configuration was performed. + package func configure( + sdkID: String, + targetTriple: String?, + showConfiguration: Bool, + resetConfiguration: Bool, + config: SwiftSDK.PathsConfiguration + ) throws -> Bool { + let targetTriples: [Triple] + if let targetTriple = targetTriple { + targetTriples = try [Triple(targetTriple)] + } else { + // when `targetTriple` is unspecified, configure every triple for the SDK + let validBundles = try self.swiftSDKs(for: sdkID) + targetTriples = validBundles.compactMap(\.targetTriple) + if targetTriples.isEmpty { + throw SwiftSDKError.swiftSDKNotFound( + artifactID: sdkID, + hostTriple: hostTriple, + targetTriple: nil + ) + } + } + + for targetTriple in targetTriples { + guard let swiftSDK = try self.readConfiguration( + sdkID: sdkID, + targetTriple: targetTriple + ) else { + throw SwiftSDKError.swiftSDKNotFound( + artifactID: sdkID, + hostTriple: hostTriple, + targetTriple: targetTriple + ) + } + + if showConfiguration { + print(swiftSDK.pathsConfiguration) + continue + } + + if resetConfiguration { + if try !self.resetConfiguration(sdkID: sdkID, targetTriple: targetTriple) { + swiftSDKBundleStore.observabilityScope.emit( + warning: "No configuration for Swift SDK `\(sdkID)`" + ) + } else { + swiftSDKBundleStore.observabilityScope.emit( + info: """ + All configuration properties of Swift SDK `\(sdkID)` for target triple \ + `\(targetTriple)` were successfully reset. + """ + ) + } + } else { + var configuration = swiftSDK.pathsConfiguration + let updatedProperties = try configuration.merge(with: config, relativeTo: fileSystem.currentWorkingDirectory) + + guard !updatedProperties.isEmpty else { + swiftSDKBundleStore.observabilityScope.emit( + error: """ + No properties of Swift SDK `\(sdkID)` for target triple `\(targetTriple)` were updated \ + since none were specified. Pass `--help` flag to see the list of all available properties. + """ + ) + return false + } + + var swiftSDK = swiftSDK + swiftSDK.pathsConfiguration = configuration + swiftSDK.targetTriple = targetTriple + try self.updateConfiguration(sdkID: sdkID, swiftSDK: swiftSDK) + + swiftSDKBundleStore.observabilityScope.emit( + info: """ + These properties of Swift SDK `\(sdkID)` for target triple \ + `\(targetTriple)` were successfully updated: \(updatedProperties.joined(separator: ", ")). + """ + ) + } + + if swiftSDKBundleStore.observabilityScope.errorsReported { + return false + } + } + + return true + } } diff --git a/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift b/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift index 63503962043..1c6f9512e93 100644 --- a/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift @@ -90,7 +90,7 @@ struct ConfigureSwiftSDK: AsyncParsableCommand { var sdkID: String @Argument(help: "The target triple of the Swift SDK to configure.") - var targetTriple: String + var targetTriple: String? /// The file system used by default by this command. private var fileSystem: FileSystem { localFileSystem } @@ -132,101 +132,21 @@ struct ConfigureSwiftSDK: AsyncParsableCommand { hostTimeTriple: triple, swiftSDKBundleStore: bundleStore ) - let targetTriple = try Triple(self.targetTriple) - - guard let swiftSDK = try configurationStore.readConfiguration( + let config = SwiftSDK.PathsConfiguration( + sdkRootPath: self.sdkRootPath, + swiftResourcesPath: self.swiftResourcesPath, + swiftStaticResourcesPath: self.swiftStaticResourcesPath, + includeSearchPaths: self.includeSearchPath, + librarySearchPaths: self.librarySearchPath, + toolsetPaths: self.toolsetPath + ) + if try !configurationStore.configure( sdkID: sdkID, - targetTriple: targetTriple - ) else { - throw SwiftSDKError.swiftSDKNotFound( - artifactID: sdkID, - hostTriple: triple, - targetTriple: targetTriple - ) - } - - if self.shouldShowConfiguration { - print(swiftSDK.pathsConfiguration) - return - } - - var configuration = swiftSDK.pathsConfiguration - if self.shouldReset { - if try !configurationStore.resetConfiguration(sdkID: sdkID, targetTriple: targetTriple) { - observabilityScope.emit( - warning: "No configuration for Swift SDK `\(sdkID)`" - ) - } else { - observabilityScope.emit( - info: """ - All configuration properties of Swift SDK `\(sdkID)` for target triple \ - `\(targetTriple)` were successfully reset. - """ - ) - } - } else { - var updatedProperties = [String]() - - let currentWorkingDirectory: AbsolutePath? = fileSystem.currentWorkingDirectory - - if let sdkRootPath { - configuration.sdkRootPath = try AbsolutePath(validating: sdkRootPath, relativeTo: currentWorkingDirectory) - updatedProperties.append(CodingKeys.sdkRootPath.stringValue) - } - - if let swiftResourcesPath { - configuration.swiftResourcesPath = - try AbsolutePath(validating: swiftResourcesPath, relativeTo: currentWorkingDirectory) - updatedProperties.append(CodingKeys.swiftResourcesPath.stringValue) - } - - if let swiftStaticResourcesPath { - configuration.swiftResourcesPath = - try AbsolutePath(validating: swiftStaticResourcesPath, relativeTo: currentWorkingDirectory) - updatedProperties.append(CodingKeys.swiftStaticResourcesPath.stringValue) - } - - if !includeSearchPath.isEmpty { - configuration.includeSearchPaths = - try includeSearchPath.map { try AbsolutePath(validating: $0, relativeTo: currentWorkingDirectory) } - updatedProperties.append(CodingKeys.includeSearchPath.stringValue) - } - - if !librarySearchPath.isEmpty { - configuration.librarySearchPaths = - try librarySearchPath.map { try AbsolutePath(validating: $0, relativeTo: currentWorkingDirectory) } - updatedProperties.append(CodingKeys.librarySearchPath.stringValue) - } - - if !toolsetPath.isEmpty { - configuration.toolsetPaths = - try toolsetPath.map { try AbsolutePath(validating: $0, relativeTo: currentWorkingDirectory) } - updatedProperties.append(CodingKeys.toolsetPath.stringValue) - } - - guard !updatedProperties.isEmpty else { - observabilityScope.emit( - error: """ - No properties of Swift SDK `\(sdkID)` for target triple `\(targetTriple)` were updated \ - since none were specified. Pass `--help` flag to see the list of all available properties. - """ - ) - return - } - - var swiftSDK = swiftSDK - swiftSDK.pathsConfiguration = configuration - try configurationStore.updateConfiguration(sdkID: sdkID, swiftSDK: swiftSDK) - - observabilityScope.emit( - info: """ - These properties of Swift SDK `\(sdkID)` for target triple \ - `\(targetTriple)` were successfully updated: \(updatedProperties.joined(separator: ", ")). - """ - ) - } - - if observabilityScope.errorsReported { + targetTriple: targetTriple, + showConfiguration: shouldShowConfiguration, + resetConfiguration: shouldReset, + config: config + ) { throw ExitCode.failure } } catch { @@ -241,13 +161,3 @@ struct ConfigureSwiftSDK: AsyncParsableCommand { } } } - -extension AbsolutePath { - fileprivate init(validating string: String, relativeTo basePath: AbsolutePath?) throws { - if let basePath { - try self.init(validating: string, relativeTo: basePath) - } else { - try self.init(validating: string) - } - } -} diff --git a/Tests/PackageModelTests/SwiftSDKBundleTests.swift b/Tests/PackageModelTests/SwiftSDKBundleTests.swift index a65ed0df18a..8262336c8c3 100644 --- a/Tests/PackageModelTests/SwiftSDKBundleTests.swift +++ b/Tests/PackageModelTests/SwiftSDKBundleTests.swift @@ -551,4 +551,120 @@ final class SwiftSDKBundleTests: XCTestCase { ), ]) } + + func testConfigureSDKRootPath() async throws { + func createConfigurationStore() async throws -> (SwiftSDKConfigurationStore, FileSystem) { + let (fileSystem, bundles, swiftSDKsDirectory) = try generateTestFileSystem( + bundleArtifacts: [ + .init(id: testArtifactID, supportedTriples: [arm64Triple, i686Triple]), + ] + ) + let system = ObservabilitySystem.makeForTesting() + + var output = [SwiftSDKBundleStore.Output]() + let store = SwiftSDKBundleStore( + swiftSDKsDirectory: swiftSDKsDirectory, + hostToolchainBinDir: "/tmp", + fileSystem: fileSystem, + observabilityScope: system.topScope, + outputHandler: { + output.append($0) + } + ) + + let archiver = MockArchiver() + for bundle in bundles { + try await store.install(bundlePathOrURL: bundle.path, archiver) + } + + let hostTriple = try Triple("arm64-apple-macosx14.0") + let sdk = try store.selectBundle( + matching: testArtifactID, + hostTriple: hostTriple + ) + + XCTAssertEqual(sdk.targetTriple, targetTriple) + XCTAssertEqual(output, [ + .installationSuccessful( + bundlePathOrURL: bundles[0].path, + bundleName: AbsolutePath(bundles[0].path).components.last! + ) + ]) + + let config = try SwiftSDKConfigurationStore( + hostTimeTriple: hostTriple, + swiftSDKBundleStore: store + ) + + return (config, fileSystem) + } + + do { + let (config, _) = try await createConfigurationStore() + let args = SwiftSDK.PathsConfiguration() + let configSuccess = try config.configure( + sdkID: testArtifactID, + targetTriple: nil, + showConfiguration: false, + resetConfiguration: false, + config: args + ) + XCTAssertEqual(configSuccess, false, "Expected failure for SwiftSDKConfigurationStore.configure with no updated properties") + } + + let targetTripleConfigPath = AbsolutePath("/sdks/configuration/\(testArtifactID)_\(targetTriple.tripleString).json") + + #if os(Windows) + let sdkRootPath = "C:\\some\\sdk\\root\\path" + #else + let sdkRootPath = "/some/sdk/root/path" + #endif + + do { + let (config, fileSystem) = try await createConfigurationStore() + var args = SwiftSDK.PathsConfiguration() + args.sdkRootPath = sdkRootPath + let configSuccess = try config.configure( + sdkID: testArtifactID, + targetTriple: targetTriple.tripleString, + showConfiguration: false, + resetConfiguration: false, + config: args + ) + XCTAssertTrue(configSuccess) + XCTAssertTrue(fileSystem.isFile(targetTripleConfigPath)) + + let updatedConfig = try config.readConfiguration( + sdkID: testArtifactID, + targetTriple: targetTriple + ) + XCTAssertEqual(args.sdkRootPath, updatedConfig?.pathsConfiguration.sdkRootPath?.pathString) + } + + do { + let (config, fileSystem) = try await createConfigurationStore() + var args = SwiftSDK.PathsConfiguration() + args.sdkRootPath = sdkRootPath + // an empty targetTriple will configure all triples + let configSuccess = try config.configure( + sdkID: testArtifactID, + targetTriple: nil, + showConfiguration: false, + resetConfiguration: false, + config: args + ) + XCTAssertTrue(configSuccess) + XCTAssertTrue(fileSystem.isFile(targetTripleConfigPath)) + + let resetSuccess = try config.configure( + sdkID: testArtifactID, + targetTriple: nil, + showConfiguration: false, + resetConfiguration: true, + config: args + ) + XCTAssertTrue(resetSuccess, "Reset configuration should succeed") + XCTAssertFalse(fileSystem.isFile(targetTripleConfigPath), "Reset configuration should clear configuration folder") + } + } }