From 7505bbfbd5323a19edab76e22ef5066788f661cc Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 16 Aug 2023 15:53:22 +0100 Subject: [PATCH 1/5] Fix backward compatibility in host triple checks It's useful to be able to set host triple in Swift SDKs to `arm64-apple-macosx13.0` to allow cross-compiling from an older version of macOS. This triple is not recognized as directly matching `arm64-apple-macosx14.0` on a newer version of macOS. We should support backward compatibility with Swift SDKs that were built for older version of macOS. --- Sources/Basics/Triple+Basics.swift | 8 ++ Sources/PackageModel/SwiftSDKBundle.swift | 16 +-- .../SwiftSDKBundleTests.swift | 119 +++++++++++++----- 3 files changed, 105 insertions(+), 38 deletions(-) diff --git a/Sources/Basics/Triple+Basics.swift b/Sources/Basics/Triple+Basics.swift index 7da68ca29ac..07462fff64f 100644 --- a/Sources/Basics/Triple+Basics.swift +++ b/Sources/Basics/Triple+Basics.swift @@ -114,6 +114,14 @@ extension Triple { ) } } + + public func matches(_ triple: Triple) -> Bool { + guard self.isMacOSX, let version = self._macOSVersion, let comparedVersion = triple._macOSVersion else { + return self.tripleString == triple.tripleString + } + + return version >= comparedVersion + } } extension Triple { diff --git a/Sources/PackageModel/SwiftSDKBundle.swift b/Sources/PackageModel/SwiftSDKBundle.swift index 1aa3c237240..e11292dcb18 100644 --- a/Sources/PackageModel/SwiftSDKBundle.swift +++ b/Sources/PackageModel/SwiftSDKBundle.swift @@ -76,30 +76,30 @@ public struct SwiftSDKBundle { /// - observabilityScope: observability scope to log warnings about multiple matches. /// - Returns: `Destination` value matching `query` either by artifact ID or target triple, `nil` if none found. public static func selectBundle( - fromBundlesAt destinationsDirectory: AbsolutePath?, + fromBundlesAt swiftSDKsDirectory: AbsolutePath?, fileSystem: FileSystem, matching selector: String, hostTriple: Triple, observabilityScope: ObservabilityScope ) throws -> SwiftSDK { - guard let destinationsDirectory else { + guard let swiftSDKsDirectory else { throw StringError( """ - No directory found for installed Swift SDKs, specify one + No directory found for installed Swift SDKs, specify one \ with `--experimental-swift-sdks-path` option. """ ) } let validBundles = try SwiftSDKBundle.getAllValidBundles( - swiftSDKsDirectory: destinationsDirectory, + swiftSDKsDirectory: swiftSDKsDirectory, fileSystem: fileSystem, observabilityScope: observabilityScope ) guard !validBundles.isEmpty else { throw StringError( - "No valid Swift SDK bundles found at \(destinationsDirectory)." + "No valid Swift SDK bundles found at \(swiftSDKsDirectory)." ) } @@ -110,8 +110,8 @@ public struct SwiftSDKBundle { ) else { throw StringError( """ - No Swift SDK found matching query `\(selector)` and host triple - `\(hostTriple.tripleString)`. Use `swift experimental-sdk list` command to see + No Swift SDK found matching query `\(selector)` and host triple \ + `\(hostTriple.tripleString)`. Use `swift experimental-sdk list` command to see \ available destinations. """ ) @@ -428,7 +428,7 @@ extension [SwiftSDKBundle] { for bundle in self { for (artifactID, variants) in bundle.artifacts { for variant in variants { - guard variant.metadata.supportedTriples.contains(hostTriple) else { + guard variant.metadata.supportedTriples.contains(where: { hostTriple.matches($0) }) else { continue } diff --git a/Tests/PackageModelTests/SwiftSDKBundleTests.swift b/Tests/PackageModelTests/SwiftSDKBundleTests.swift index f94185fc906..64769ba225a 100644 --- a/Tests/PackageModelTests/SwiftSDKBundleTests.swift +++ b/Tests/PackageModelTests/SwiftSDKBundleTests.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import Basics -import PackageModel +@testable import PackageModel import SPMTestSupport import XCTest @@ -21,27 +21,58 @@ import class TSCBasic.InMemoryFileSystem private let testArtifactID = "test-artifact" -private func generateInfoJSON(artifacts: [MockArtifact]) -> SerializedJSON { - """ - { - "artifacts" : { - \(artifacts.map { - """ - "\($0.id)" : { - "type" : "swiftSDK", - "version" : "0.0.1", - "variants" : [ - { - "path" : "\($0.id)/aarch64-unknown-linux", - "supportedTriples" : \($0.supportedTriples.map(\.tripleString)) +private let targetTriple = try! Triple("aarch64-unknown-linux") + +private let jsonEncoder = JSONEncoder() + +private func generateBundleFiles(bundle: MockBundle) throws -> [(String, ByteString)] { + try [ + ( + "\(bundle.path)/info.json", + ByteString(json: """ + { + "artifacts" : { + \(bundle.artifacts.map { + """ + "\($0.id)" : { + "type" : "swiftSDK", + "version" : "0.0.1", + "variants" : [ + { + "path" : "\($0.id)/\(targetTriple.triple)", + "supportedTriples" : \($0.supportedTriples.map(\.tripleString)) + } + ] } - ] - } - """ - }.joined(separator: ",\n") - ) - }, - "schemaVersion" : "1.0" + """ + }.joined(separator: ",\n") + ) + }, + "schemaVersion" : "1.0" + } + """) + ), + + ] + bundle.artifacts.map { + ( + "\(bundle.path)/\($0.id)/\(targetTriple.tripleString)/swift-sdk.json", + ByteString(json: try generateSwiftSDKMetadata(jsonEncoder)) + ) + } +} + +private func generateSwiftSDKMetadata(_ encoder: JSONEncoder) throws -> SerializedJSON { + try """ + { + "schemaVersion": "4.0", + "targetTriples": \( + String( + bytes: encoder.encode([ + targetTriple.tripleString: SwiftSDKMetadataV4.TripleProperties(sdkRootPath: "sdk") + ]), + encoding: .utf8 + )! + ) } """ } @@ -63,15 +94,13 @@ private func generateTestFileSystem(bundleArtifacts: [MockArtifact]) throws -> ( return MockBundle(name: "test\(i).artifactbundle", path: "/\(bundleName)", artifacts: [artifacts]) } - let fileSystem = InMemoryFileSystem( - files: Dictionary(uniqueKeysWithValues: bundles.map { - ( - "\($0.path)/info.json", - ByteString( - json: generateInfoJSON(artifacts: $0.artifacts) - ) - ) - }) + + let fileSystem = try InMemoryFileSystem( + files: Dictionary( + uniqueKeysWithValues: bundles.flatMap { + try generateBundleFiles(bundle: $0) + } + ) ) let swiftSDKsDirectory = try AbsolutePath(validating: "/sdks") @@ -208,4 +237,34 @@ final class SwiftSDKBundleTests: XCTestCase { XCTAssertEqual(validBundles.count, bundles.count) } + + func testBundleSelection() async throws { + let (fileSystem, bundles, swiftSDKsDirectory) = try generateTestFileSystem( + bundleArtifacts: [ + .init(id: "\(testArtifactID)1", supportedTriples: [arm64Triple]), + .init(id: "\(testArtifactID)2", supportedTriples: [i686Triple]) + ] + ) + let system = ObservabilitySystem.makeForTesting() + + for bundle in bundles { + try await SwiftSDKBundle.install( + bundlePathOrURL: bundle.path, + swiftSDKsDirectory: swiftSDKsDirectory, + fileSystem, + MockArchiver(), + system.topScope + ) + } + + let sdk = try SwiftSDKBundle.selectBundle( + fromBundlesAt: swiftSDKsDirectory, + fileSystem: fileSystem, + matching: "\(testArtifactID)1", + hostTriple: Triple("arm64-apple-macosx14.0"), + observabilityScope: system.topScope + ) + + XCTAssertEqual(sdk.targetTriple, targetTriple) + } } From 45c0cff25ad743ca4f371c79b9ac5da9b8c98ab8 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Aug 2023 11:54:13 +0100 Subject: [PATCH 2/5] Rename `matches` to `isRuntimeCompatible` --- Sources/Basics/Triple+Basics.swift | 2 +- Sources/PackageModel/SwiftSDKBundle.swift | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/Basics/Triple+Basics.swift b/Sources/Basics/Triple+Basics.swift index 07462fff64f..09dc9504838 100644 --- a/Sources/Basics/Triple+Basics.swift +++ b/Sources/Basics/Triple+Basics.swift @@ -115,7 +115,7 @@ extension Triple { } } - public func matches(_ triple: Triple) -> Bool { + public func isRuntimeCompatible(with triple: Triple) -> Bool { guard self.isMacOSX, let version = self._macOSVersion, let comparedVersion = triple._macOSVersion else { return self.tripleString == triple.tripleString } diff --git a/Sources/PackageModel/SwiftSDKBundle.swift b/Sources/PackageModel/SwiftSDKBundle.swift index e11292dcb18..2eaaf105b9b 100644 --- a/Sources/PackageModel/SwiftSDKBundle.swift +++ b/Sources/PackageModel/SwiftSDKBundle.swift @@ -428,7 +428,9 @@ extension [SwiftSDKBundle] { for bundle in self { for (artifactID, variants) in bundle.artifacts { for variant in variants { - guard variant.metadata.supportedTriples.contains(where: { hostTriple.matches($0) }) else { + guard variant.metadata.supportedTriples.contains(where: { + hostTriple.isRuntimeCompatible(with: $0) + }) else { continue } From 3c3422a1ce04644c1f6523e96a6faddc664f6235 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 24 Aug 2023 17:21:06 +0100 Subject: [PATCH 3/5] Remove previous `isRuntimeCompatible` implementation --- Sources/Basics/Triple+Basics.swift | 8 -------- Sources/PackageModel/SwiftSDKBundle.swift | 13 +++++++++++-- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Sources/Basics/Triple+Basics.swift b/Sources/Basics/Triple+Basics.swift index 09dc9504838..7da68ca29ac 100644 --- a/Sources/Basics/Triple+Basics.swift +++ b/Sources/Basics/Triple+Basics.swift @@ -114,14 +114,6 @@ extension Triple { ) } } - - public func isRuntimeCompatible(with triple: Triple) -> Bool { - guard self.isMacOSX, let version = self._macOSVersion, let comparedVersion = triple._macOSVersion else { - return self.tripleString == triple.tripleString - } - - return version >= comparedVersion - } } extension Triple { diff --git a/Sources/PackageModel/SwiftSDKBundle.swift b/Sources/PackageModel/SwiftSDKBundle.swift index 2eaaf105b9b..14d34b94d4d 100644 --- a/Sources/PackageModel/SwiftSDKBundle.swift +++ b/Sources/PackageModel/SwiftSDKBundle.swift @@ -428,8 +428,17 @@ extension [SwiftSDKBundle] { for bundle in self { for (artifactID, variants) in bundle.artifacts { for variant in variants { - guard variant.metadata.supportedTriples.contains(where: { - hostTriple.isRuntimeCompatible(with: $0) + guard variant.metadata.supportedTriples.contains(where: { variantTriple in + if + hostTriple.arch == variantTriple.arch && + hostTriple.vendor == variantTriple.vendor && + hostTriple.os == variantTriple.os && + hostTriple.environment == variantTriple.environment + { + return hostTriple.osVersion >= variantTriple.osVersion + } else { + return false + } }) else { continue } From 2fa29e7439775bcea6a2b4300d90504159784c07 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 19 Sep 2023 14:39:53 +0100 Subject: [PATCH 4/5] Move OS component version comparison to `isRuntimeCompatible` --- Sources/Basics/Triple+Basics.swift | 13 +++++++++++++ Sources/PackageModel/SwiftSDKBundle.swift | 11 +---------- Tests/BasicsTests/TripleTests.swift | 7 +++++++ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Sources/Basics/Triple+Basics.swift b/Sources/Basics/Triple+Basics.swift index 7da68ca29ac..115b502a0f2 100644 --- a/Sources/Basics/Triple+Basics.swift +++ b/Sources/Basics/Triple+Basics.swift @@ -183,6 +183,19 @@ extension Triple { return ".resources" } } + + public func isRuntimeCompatible(with triple: Triple) -> Bool { + if + self.arch == triple.arch && + self.vendor == triple.vendor && + self.os == triple.os && + self.environment == triple.environment + { + return self.osVersion >= triple.osVersion + } else { + return false + } + } } extension Triple: CustomStringConvertible { diff --git a/Sources/PackageModel/SwiftSDKBundle.swift b/Sources/PackageModel/SwiftSDKBundle.swift index 14d34b94d4d..129b3c22eb5 100644 --- a/Sources/PackageModel/SwiftSDKBundle.swift +++ b/Sources/PackageModel/SwiftSDKBundle.swift @@ -429,16 +429,7 @@ extension [SwiftSDKBundle] { for (artifactID, variants) in bundle.artifacts { for variant in variants { guard variant.metadata.supportedTriples.contains(where: { variantTriple in - if - hostTriple.arch == variantTriple.arch && - hostTriple.vendor == variantTriple.vendor && - hostTriple.os == variantTriple.os && - hostTriple.environment == variantTriple.environment - { - return hostTriple.osVersion >= variantTriple.osVersion - } else { - return false - } + hostTriple.isRuntimeCompatible(with: variantTriple) }) else { continue } diff --git a/Tests/BasicsTests/TripleTests.swift b/Tests/BasicsTests/TripleTests.swift index aa392c0c5ee..b7909a6a2c3 100644 --- a/Tests/BasicsTests/TripleTests.swift +++ b/Tests/BasicsTests/TripleTests.swift @@ -165,4 +165,11 @@ final class TripleTests: XCTestCase { XCTAssertTriple("x86_64-unknown-windows-msvc", matches: (.x86_64, nil, nil, .win32, .msvc, .coff)) XCTAssertTriple("wasm32-unknown-wasi", matches: (.wasm32, nil, nil, .wasi, nil, .wasm)) } + + func testIsRuntimeCompatibleWith() throws { + try XCTAssertTrue(Triple("x86_64-apple-macosx").isRuntimeCompatible(with: Triple("x86_64-apple-macosx"))) + try XCTAssertTrue(Triple("x86_64-unknown-linux").isRuntimeCompatible(with: Triple("x86_64-unknown-linux"))) + try XCTAssertFalse(Triple("x86_64-apple-macosx").isRuntimeCompatible(with: Triple("x86_64-apple-linux"))) + try XCTAssertTrue(Triple("x86_64-apple-macosx14.0").isRuntimeCompatible(with: Triple("x86_64-apple-macosx13.0"))) + } } From 05a9d1642d8edca1e97d3d172059bafa61054090 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 19 Sep 2023 17:46:48 +0100 Subject: [PATCH 5/5] Add doc comment for `isRuntimeCompatible` --- Sources/Basics/Triple+Basics.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/Basics/Triple+Basics.swift b/Sources/Basics/Triple+Basics.swift index 115b502a0f2..5b552b5111a 100644 --- a/Sources/Basics/Triple+Basics.swift +++ b/Sources/Basics/Triple+Basics.swift @@ -184,7 +184,8 @@ extension Triple { } } - public func isRuntimeCompatible(with triple: Triple) -> Bool { + /// Returns `true` if code compiled for `triple` can run on `self` value of ``Triple``. + public func isRuntimeCompatible(with triple: Triple) -> Bool { if self.arch == triple.arch && self.vendor == triple.vendor &&