From eadae2be75ff663c97c81a3de205955c28676c27 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 16 May 2025 15:59:53 +0100 Subject: [PATCH 1/5] Add `experimentalAlias` option to `InstallSwiftSDK` Also add `SwiftToolchainVersion` schema. --- .../SwiftSDKs/SwiftToolchainVersion.swift | 41 +++++++++++++++++++ Sources/SwiftSDKCommand/InstallSwiftSDK.swift | 6 ++- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift diff --git a/Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift b/Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift new file mode 100644 index 00000000000..679c602977b --- /dev/null +++ b/Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +package struct SwiftToolchainVersion: Decodable { + /// Since triples don't encode the platform, we use platform identifiers + /// that match swift.org toolchain distribution names. + enum Platform: Decodable { + case macOS + case ubuntu2004 + case ubuntu2204 + case ubuntu2404 + case debian12 + case amazonLinux2 + case fedora39 + case fedora41 + case ubi9 + } + + enum Architecture: Decodable { + case aarch64 + case x86_64 + } + + /// A Git tag from which this toolchain was built. + let tag: String + + /// CPU architecture on which this toolchain runs. + let architecture: Architecture + + /// Platform identifier on which this toolchain runs. + let platform: Platform +} diff --git a/Sources/SwiftSDKCommand/InstallSwiftSDK.swift b/Sources/SwiftSDKCommand/InstallSwiftSDK.swift index 21fcd8f4beb..9e2c33b7139 100644 --- a/Sources/SwiftSDKCommand/InstallSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/InstallSwiftSDK.swift @@ -33,11 +33,15 @@ struct InstallSwiftSDK: SwiftSDKSubcommand { var locations: LocationOptions @Argument(help: "A local filesystem path or a URL of a Swift SDK bundle to install.") - var bundlePathOrURL: String + var bundlePathOrURL: String? @Option(help: "The checksum of the bundle generated with `swift package compute-checksum`.") var checksum: String? = nil + /// Alias of a Swift SDK to install, which automatically resolves installation URL based on host toolchain version. + @Option(help: .hidden) + var experimentalAlias: String? = nil + @Flag( name: .customLong("color-diagnostics"), inversion: .prefixedNo, From d63a58ffc4b431587d5228d321c1d1ba9d9602c1 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Mon, 19 May 2025 17:38:36 +0100 Subject: [PATCH 2/5] Add tests for `SwiftToolchainVersion` --- .../SwiftSDKs/SwiftToolchainVersion.swift | 155 +++++++++++++++++- Sources/SwiftSDKCommand/CMakeLists.txt | 8 +- Sources/SwiftSDKCommand/SwiftSDKCommand.swift | 10 +- ...SwiftSDK.swift => SwiftSDKConfigure.swift} | 4 +- ...llSwiftSDK.swift => SwiftSDKInstall.swift} | 15 +- ...ListSwiftSDKs.swift => SwiftSDKList.swift} | 4 +- ...oveSwiftSDK.swift => SwiftSDKRemove.swift} | 4 +- .../SwiftToolchainVersionTests.swift | 87 ++++++++++ 8 files changed, 264 insertions(+), 23 deletions(-) rename Sources/SwiftSDKCommand/{ConfigureSwiftSDK.swift => SwiftSDKConfigure.swift} (98%) rename Sources/SwiftSDKCommand/{InstallSwiftSDK.swift => SwiftSDKInstall.swift} (87%) rename Sources/SwiftSDKCommand/{ListSwiftSDKs.swift => SwiftSDKList.swift} (93%) rename Sources/SwiftSDKCommand/{RemoveSwiftSDK.swift => SwiftSDKRemove.swift} (97%) create mode 100644 Tests/PackageModelTests/SwiftToolchainVersionTests.swift diff --git a/Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift b/Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift index 679c602977b..0bd6a374206 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift @@ -10,10 +10,31 @@ // //===----------------------------------------------------------------------===// -package struct SwiftToolchainVersion: Decodable { +import Basics +import class Foundation.JSONDecoder +import struct Foundation.URL + +package struct SwiftToolchainVersion: Equatable, Decodable { + package enum Error: Swift.Error, Equatable { + case versionMetadataNotFound(AbsolutePath) + case unknownSwiftSDKAlias(String) + } + + package init( + tag: String, + branch: String, + architecture: SwiftToolchainVersion.Architecture, + platform: SwiftToolchainVersion.Platform + ) { + self.tag = tag + self.branch = branch + self.architecture = architecture + self.platform = platform + } + /// Since triples don't encode the platform, we use platform identifiers /// that match swift.org toolchain distribution names. - enum Platform: Decodable { + package enum Platform: String, Decodable { case macOS case ubuntu2004 case ubuntu2204 @@ -23,19 +44,141 @@ package struct SwiftToolchainVersion: Decodable { case fedora39 case fedora41 case ubi9 + + var urlDirComponent: String { + switch self { + case .macOS: + "xcode" + case .ubuntu2004: + "ubuntu2004" + case .ubuntu2204: + "ubuntu2204" + case .ubuntu2404: + "ubuntu2404" + case .debian12: + "debian12" + case .amazonLinux2: + "amazonlinux2" + case .fedora39: + "fedora39" + case .fedora41: + "fedora41" + case .ubi9: + "ubi9" + } + } + + var urlFileComponent: String { + switch self { + case .macOS: + "osx" + case .ubuntu2004: + "ubuntu20.04" + case .ubuntu2204: + "ubuntu22.04" + case .ubuntu2404: + "ubuntu24.04" + case .debian12: + "debian12" + case .amazonLinux2: + "amazonlinux2" + case .fedora39: + "fedora39" + case .fedora41: + "fedora41" + case .ubi9: + "ubi9" + } + } } - enum Architecture: Decodable { + package enum Architecture: String, Decodable { case aarch64 case x86_64 + + var urlFileComponent: String { + switch self { + case .aarch64: + "-aarch64" + case .x86_64: + "" + } + } } /// A Git tag from which this toolchain was built. - let tag: String + package let tag: String + + /// Branch from which this toolchain was built. + package let branch: String /// CPU architecture on which this toolchain runs. - let architecture: Architecture + package let architecture: Architecture /// Platform identifier on which this toolchain runs. - let platform: Platform + package let platform: Platform + + package func generateURL(aliasString: String) throws -> String { + guard let swiftSDKAlias = SwiftSDKAlias(aliasString) else { + throw Error.unknownSwiftSDKAlias(aliasString) + } + + return """ + https://download.swift.org/\( + self.branch + )/\( + self.tag + )/\( + self.tag + )_\(swiftSDKAlias.urlFileComponent).artifactbundle.tar.gz + """ + } + + package init(toolchain: some Toolchain, fileSystem: any FileSystem) throws { + let versionMetadataPath = try toolchain.swiftCompilerPath.parentDirectory.parentDirectory.appending( + RelativePath(validating: "lib/swift/version.json") + ) + guard fileSystem.exists(versionMetadataPath) else { + throw Error.versionMetadataNotFound(versionMetadataPath) + } + + self = try JSONDecoder().decode( + path: versionMetadataPath, + fileSystem: fileSystem, + as: Self.self + ) + } +} + +package struct SwiftSDKAlias { + init?(_ string: String) { + guard let kind = Kind(rawValue: string) else { return nil } + self.kind = kind + } + + enum Kind: String { + case staticLinux = "static-linux" + case wasi = "wasi" + case wasiEmbedded = "wasi-embedded" + + var urlFileComponent: String { + switch self { + case .staticLinux, .wasi: + return self.rawValue + case .wasiEmbedded: + return Self.wasi.rawValue + } + } + } + + struct Version { + let rawValue = "0.0.1" + } + + let kind: Kind + let defaultVersion = Version() + + var urlFileComponent: String { + "\(self.kind.urlFileComponent)-\(self.defaultVersion.rawValue)" + } } diff --git a/Sources/SwiftSDKCommand/CMakeLists.txt b/Sources/SwiftSDKCommand/CMakeLists.txt index 4dcfa36a2af..62540b8e255 100644 --- a/Sources/SwiftSDKCommand/CMakeLists.txt +++ b/Sources/SwiftSDKCommand/CMakeLists.txt @@ -12,11 +12,11 @@ add_library(SwiftSDKCommand Configuration/ResetConfiguration.swift Configuration/SetConfiguration.swift Configuration/ShowConfiguration.swift - ConfigureSwiftSDK.swift + SwiftSDKConfigure.swift SwiftSDKSubcommand.swift - InstallSwiftSDK.swift - ListSwiftSDKs.swift - RemoveSwiftSDK.swift + SwiftSDKInstall.swift + SwiftSDKList.swift + SwiftSDKRemove.swift SwiftSDKCommand.swift) target_link_libraries(SwiftSDKCommand PUBLIC ArgumentParser diff --git a/Sources/SwiftSDKCommand/SwiftSDKCommand.swift b/Sources/SwiftSDKCommand/SwiftSDKCommand.swift index e167cc548ac..922b5a0f9b3 100644 --- a/Sources/SwiftSDKCommand/SwiftSDKCommand.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKCommand.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2022-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -20,11 +20,11 @@ package struct SwiftSDKCommand: AsyncParsableCommand { abstract: "Perform operations on Swift SDKs.", version: SwiftVersion.current.completeDisplayString, subcommands: [ - ConfigureSwiftSDK.self, + SwiftSDKConfigure.self, DeprecatedSwiftSDKConfigurationCommand.self, - InstallSwiftSDK.self, - ListSwiftSDKs.self, - RemoveSwiftSDK.self, + SwiftSDKInstall.self, + SwiftSDKList.self, + SwiftSDKRemove.self, ], helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) diff --git a/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift b/Sources/SwiftSDKCommand/SwiftSDKConfigure.swift similarity index 98% rename from Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift rename to Sources/SwiftSDKCommand/SwiftSDKConfigure.swift index 63503962043..016f9860f83 100644 --- a/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKConfigure.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Copyright (c) 2023-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -18,7 +18,7 @@ import PackageModel import var TSCBasic.stdoutStream -struct ConfigureSwiftSDK: AsyncParsableCommand { +struct SwiftSDKConfigure: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "configure", abstract: """ diff --git a/Sources/SwiftSDKCommand/InstallSwiftSDK.swift b/Sources/SwiftSDKCommand/SwiftSDKInstall.swift similarity index 87% rename from Sources/SwiftSDKCommand/InstallSwiftSDK.swift rename to Sources/SwiftSDKCommand/SwiftSDKInstall.swift index 9e2c33b7139..0f5ec723af6 100644 --- a/Sources/SwiftSDKCommand/InstallSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKInstall.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Copyright (c) 2023-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -20,7 +20,7 @@ import PackageModel import var TSCBasic.stdoutStream import class Workspace.Workspace -struct InstallSwiftSDK: SwiftSDKSubcommand { +struct SwiftSDKInstall: SwiftSDKSubcommand { static let configuration = CommandConfiguration( commandName: "install", abstract: """ @@ -77,6 +77,17 @@ struct InstallSwiftSDK: SwiftSDKSubcommand { .throttled(interval: .milliseconds(300)) ) + let bundlePathOrURL = if let experimentalAlias { + try SwiftToolchainVersion( + toolchain: hostToolchain, + fileSystem: self.fileSystem + ).generateURL(aliasString: experimentalAlias) + } else if let bundlePathOrURL { + bundlePathOrURL + } else { + throw InternalError("foo") + } + try await store.install( bundlePathOrURL: bundlePathOrURL, checksum: self.checksum, diff --git a/Sources/SwiftSDKCommand/ListSwiftSDKs.swift b/Sources/SwiftSDKCommand/SwiftSDKList.swift similarity index 93% rename from Sources/SwiftSDKCommand/ListSwiftSDKs.swift rename to Sources/SwiftSDKCommand/SwiftSDKList.swift index 9e19b471195..b11dc192bd7 100644 --- a/Sources/SwiftSDKCommand/ListSwiftSDKs.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKList.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2022 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -16,7 +16,7 @@ import CoreCommands import PackageModel import SPMBuildCore -package struct ListSwiftSDKs: SwiftSDKSubcommand { +package struct SwiftSDKList: SwiftSDKSubcommand { package static let configuration = CommandConfiguration( commandName: "list", abstract: diff --git a/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift b/Sources/SwiftSDKCommand/SwiftSDKRemove.swift similarity index 97% rename from Sources/SwiftSDKCommand/RemoveSwiftSDK.swift rename to Sources/SwiftSDKCommand/SwiftSDKRemove.swift index 5e8c61530ce..76d98cc83cc 100644 --- a/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKRemove.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Copyright (c) 2023-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -16,7 +16,7 @@ import CoreCommands import PackageModel import TSCBasic -package struct RemoveSwiftSDK: SwiftSDKSubcommand { +package struct SwiftSDKRemove: SwiftSDKSubcommand { package static let configuration = CommandConfiguration( commandName: "remove", abstract: """ diff --git a/Tests/PackageModelTests/SwiftToolchainVersionTests.swift b/Tests/PackageModelTests/SwiftToolchainVersionTests.swift new file mode 100644 index 00000000000..28344f2dab7 --- /dev/null +++ b/Tests/PackageModelTests/SwiftToolchainVersionTests.swift @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import _InternalTestSupport +import Testing + +import Foundation +import Basics +import struct TSCBasic.ByteString +import PackageModel + +@Suite +struct SwiftToolchainVersionTests { + let toolchain = MockToolchain() + let versionFilePath: AbsolutePath + let mockFileSystem: InMemoryFileSystem + + init() { + self.versionFilePath = toolchain.swiftCompilerPath.parentDirectory.parentDirectory.appending( + RelativePath("lib/swift/version.json") + ) + + self.mockFileSystem = InMemoryFileSystem( + files: [self.versionFilePath.pathString: ByteString(encodingAsUTF8: """ + { + "tag": "swift-6.1-RELEASE", + "branch": "swift-6.1-branch", + "architecture": "aarch64", + "platform": "ubuntu2004" + } + """)] + ) + } + + @Test + func versionDecoding() throws { + let version = try SwiftToolchainVersion(toolchain: self.toolchain, fileSystem: self.mockFileSystem) + + #expect(version == SwiftToolchainVersion( + tag: "swift-6.1-RELEASE", + branch: "swift-6.1-branch", + architecture: .aarch64, + platform: .ubuntu2004 + )) + } + + @Test + func versionMetadataMissing() { + #expect(throws: SwiftToolchainVersion.Error.versionMetadataNotFound(self.versionFilePath)) { + try SwiftToolchainVersion(toolchain: self.toolchain, fileSystem: InMemoryFileSystem()) + } + } + + @Test + func urlGeneration() throws { + let version = try SwiftToolchainVersion(toolchain: self.toolchain, fileSystem: self.mockFileSystem) + + #expect(throws: SwiftToolchainVersion.Error.unknownSwiftSDKAlias("foo")) { + try version.generateURL(aliasString: "foo") + } + + #expect(try version.generateURL(aliasString: "wasi") == """ + https://download.swift.org/swift-6.1-release/swift-6.1-RELEASE/swift-6.1-RELEASE_wasi-0.0.1.artifactbundle.tar.gz + """ + ) + + #expect(try version.generateURL(aliasString: "wasi-embedded") == """ + https://download.swift.org/swift-6.1-release/swift-6.1-RELEASE/swift-6.1-RELEASE_wasi-0.0.1.artifactbundle.tar.gz + """ + ) + + #expect(try version.generateURL(aliasString: "static-linux") == """ + https://download.swift.org/swift-6.1-release/static-sdk/swift-6.1-RELEASE/swift-6.1-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz + https://download.swift.org/swift-6.1-branch/swift-6.1-RELEASE/swift-6.1-RELEASE_wasi-0.0.1.artifactbundle.tar.gz + """ + ) + } +} From 48c65d2418ec1f97f16edd1730d17799a06777eb Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 20 May 2025 18:55:45 +0100 Subject: [PATCH 3/5] Reorganize files, generate Swift SDK ids, refine tests --- Sources/PackageModel/CMakeLists.txt | 2 + .../SwiftSDKs/SwiftSDKAlias.swift | 92 +++++++++++++++++++ .../SwiftToolchainVersion.swift | 48 ---------- Sources/SwiftSDKCommand/SwiftSDKInstall.swift | 2 +- .../SwiftToolchainVersionTests.swift | 56 +++++++---- 5 files changed, 133 insertions(+), 67 deletions(-) create mode 100644 Sources/PackageModel/SwiftSDKs/SwiftSDKAlias.swift rename Sources/PackageModel/{SwiftSDKs => }/SwiftToolchainVersion.swift (75%) diff --git a/Sources/PackageModel/CMakeLists.txt b/Sources/PackageModel/CMakeLists.txt index 3d5f2f509d3..fb33b037228 100644 --- a/Sources/PackageModel/CMakeLists.txt +++ b/Sources/PackageModel/CMakeLists.txt @@ -53,9 +53,11 @@ add_library(PackageModel SupportedLanguageExtension.swift SwiftLanguageVersion.swift SwiftSDKs/SwiftSDK.swift + SwiftSDKs/SwiftSDKAlias.swift SwiftSDKs/SwiftSDKConfigurationStore.swift SwiftSDKs/SwiftSDKBundle.swift SwiftSDKs/SwiftSDKBundleStore.swift + SwiftToolchainVersion.swift Toolchain.swift ToolchainConfiguration.swift Toolchain+SupportedFeatures.swift diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKAlias.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKAlias.swift new file mode 100644 index 00000000000..a5cb78eb981 --- /dev/null +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKAlias.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +package struct SwiftSDKAlias { + init?(_ string: String) { + guard let kind = Kind(rawValue: string) else { return nil } + self.kind = kind + } + + enum Kind: String { + case staticLinux = "static-linux" + case wasi = "wasi" + case wasiEmbedded = "embedded-wasi" + + var urlFileComponent: String { + switch self { + case .staticLinux, .wasi: + return self.rawValue + case .wasiEmbedded: + return Self.wasi.rawValue + } + } + + var idComponent: String { + self.rawValue + } + + var urlDirComponent: String { + switch self { + case .staticLinux: + "static-sdk" + case .wasi, .wasiEmbedded: + "wasi" + } + } + } + + struct Version: CustomStringConvertible { + let rawValue = "0.0.1" + + var description: String { self.rawValue } + } + + let kind: Kind + let defaultVersion = Version() + + var urlFileComponent: String { + "\(self.kind.urlFileComponent)-\(self.defaultVersion.rawValue)" + } +} + +extension SwiftToolchainVersion { + package func urlForSwiftSDK(aliasString: String) throws -> String { + guard let swiftSDKAlias = SwiftSDKAlias(aliasString) else { + throw Error.unknownSwiftSDKAlias(aliasString) + } + + return """ + https://download.swift.org/\( + self.branch + )/\( + swiftSDKAlias.kind.urlDirComponent + )/\( + self.tag + )/\( + self.tag + )_\(swiftSDKAlias.urlFileComponent).artifactbundle.tar.gz + """ + } + + package func idForSwiftSDK(aliasString: String) throws -> String { + guard let swiftSDKAlias = SwiftSDKAlias(aliasString) else { + throw Error.unknownSwiftSDKAlias(aliasString) + } + + switch swiftSDKAlias.kind { + case .staticLinux: + return "\(self.tag)_\(swiftSDKAlias.kind.idComponent)-\(swiftSDKAlias.defaultVersion)" + case .wasi, .wasiEmbedded: + return "\(self.tag.replacing("swift-", with: ""))-wasm32-\(swiftSDKAlias.kind.idComponent)" + } + } +} diff --git a/Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift b/Sources/PackageModel/SwiftToolchainVersion.swift similarity index 75% rename from Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift rename to Sources/PackageModel/SwiftToolchainVersion.swift index 0bd6a374206..54950927c30 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftToolchainVersion.swift +++ b/Sources/PackageModel/SwiftToolchainVersion.swift @@ -118,22 +118,6 @@ package struct SwiftToolchainVersion: Equatable, Decodable { /// Platform identifier on which this toolchain runs. package let platform: Platform - package func generateURL(aliasString: String) throws -> String { - guard let swiftSDKAlias = SwiftSDKAlias(aliasString) else { - throw Error.unknownSwiftSDKAlias(aliasString) - } - - return """ - https://download.swift.org/\( - self.branch - )/\( - self.tag - )/\( - self.tag - )_\(swiftSDKAlias.urlFileComponent).artifactbundle.tar.gz - """ - } - package init(toolchain: some Toolchain, fileSystem: any FileSystem) throws { let versionMetadataPath = try toolchain.swiftCompilerPath.parentDirectory.parentDirectory.appending( RelativePath(validating: "lib/swift/version.json") @@ -150,35 +134,3 @@ package struct SwiftToolchainVersion: Equatable, Decodable { } } -package struct SwiftSDKAlias { - init?(_ string: String) { - guard let kind = Kind(rawValue: string) else { return nil } - self.kind = kind - } - - enum Kind: String { - case staticLinux = "static-linux" - case wasi = "wasi" - case wasiEmbedded = "wasi-embedded" - - var urlFileComponent: String { - switch self { - case .staticLinux, .wasi: - return self.rawValue - case .wasiEmbedded: - return Self.wasi.rawValue - } - } - } - - struct Version { - let rawValue = "0.0.1" - } - - let kind: Kind - let defaultVersion = Version() - - var urlFileComponent: String { - "\(self.kind.urlFileComponent)-\(self.defaultVersion.rawValue)" - } -} diff --git a/Sources/SwiftSDKCommand/SwiftSDKInstall.swift b/Sources/SwiftSDKCommand/SwiftSDKInstall.swift index 0f5ec723af6..de298785da4 100644 --- a/Sources/SwiftSDKCommand/SwiftSDKInstall.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKInstall.swift @@ -81,7 +81,7 @@ struct SwiftSDKInstall: SwiftSDKSubcommand { try SwiftToolchainVersion( toolchain: hostToolchain, fileSystem: self.fileSystem - ).generateURL(aliasString: experimentalAlias) + ).urlForSwiftSDK(aliasString: experimentalAlias) } else if let bundlePathOrURL { bundlePathOrURL } else { diff --git a/Tests/PackageModelTests/SwiftToolchainVersionTests.swift b/Tests/PackageModelTests/SwiftToolchainVersionTests.swift index 28344f2dab7..c4d7a8e5e3d 100644 --- a/Tests/PackageModelTests/SwiftToolchainVersionTests.swift +++ b/Tests/PackageModelTests/SwiftToolchainVersionTests.swift @@ -23,9 +23,10 @@ struct SwiftToolchainVersionTests { let toolchain = MockToolchain() let versionFilePath: AbsolutePath let mockFileSystem: InMemoryFileSystem + let version: SwiftToolchainVersion - init() { - self.versionFilePath = toolchain.swiftCompilerPath.parentDirectory.parentDirectory.appending( + init() throws { + self.versionFilePath = self.toolchain.swiftCompilerPath.parentDirectory.parentDirectory.appending( RelativePath("lib/swift/version.json") ) @@ -33,21 +34,24 @@ struct SwiftToolchainVersionTests { files: [self.versionFilePath.pathString: ByteString(encodingAsUTF8: """ { "tag": "swift-6.1-RELEASE", - "branch": "swift-6.1-branch", + "branch": "swift-6.1-release", "architecture": "aarch64", "platform": "ubuntu2004" } """)] - ) + ) + + self.version = try SwiftToolchainVersion( + toolchain: self.toolchain, + fileSystem: self.mockFileSystem + ) } @Test func versionDecoding() throws { - let version = try SwiftToolchainVersion(toolchain: self.toolchain, fileSystem: self.mockFileSystem) - - #expect(version == SwiftToolchainVersion( + #expect(self.version == SwiftToolchainVersion( tag: "swift-6.1-RELEASE", - branch: "swift-6.1-branch", + branch: "swift-6.1-release", architecture: .aarch64, platform: .ubuntu2004 )) @@ -61,26 +65,42 @@ struct SwiftToolchainVersionTests { } @Test - func urlGeneration() throws { - let version = try SwiftToolchainVersion(toolchain: self.toolchain, fileSystem: self.mockFileSystem) + func idForSwiftSDKGeneration() throws { + #expect(throws: SwiftToolchainVersion.Error.unknownSwiftSDKAlias("foo")) { + try self.version.idForSwiftSDK(aliasString: "foo") + } + var id = try self.version.idForSwiftSDK(aliasString: "wasi") + #expect(id == "6.1-RELEASE-wasm32-wasi") + + id = try self.version.idForSwiftSDK(aliasString: "embedded-wasi") + #expect(id == "6.1-RELEASE-wasm32-embedded-wasi") + + id = try self.version.idForSwiftSDK(aliasString: "static-linux") + #expect(id == "swift-6.1-RELEASE_static-linux-0.0.1") + } + + @Test + func urlForSwiftSDKGeneration() throws { #expect(throws: SwiftToolchainVersion.Error.unknownSwiftSDKAlias("foo")) { - try version.generateURL(aliasString: "foo") + try self.version.urlForSwiftSDK(aliasString: "foo") } - #expect(try version.generateURL(aliasString: "wasi") == """ - https://download.swift.org/swift-6.1-release/swift-6.1-RELEASE/swift-6.1-RELEASE_wasi-0.0.1.artifactbundle.tar.gz + var url = try self.version.urlForSwiftSDK(aliasString: "wasi") + #expect(url == """ + https://download.swift.org/swift-6.1-release/wasi/swift-6.1-RELEASE/swift-6.1-RELEASE_wasi-0.0.1.artifactbundle.tar.gz """ ) - #expect(try version.generateURL(aliasString: "wasi-embedded") == """ - https://download.swift.org/swift-6.1-release/swift-6.1-RELEASE/swift-6.1-RELEASE_wasi-0.0.1.artifactbundle.tar.gz + url = try self.version.urlForSwiftSDK(aliasString: "embedded-wasi") + #expect(url == """ + https://download.swift.org/swift-6.1-release/wasi/swift-6.1-RELEASE/swift-6.1-RELEASE_wasi-0.0.1.artifactbundle.tar.gz """ ) - - #expect(try version.generateURL(aliasString: "static-linux") == """ + + url = try version.urlForSwiftSDK(aliasString: "static-linux") + #expect(url == """ https://download.swift.org/swift-6.1-release/static-sdk/swift-6.1-RELEASE/swift-6.1-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz - https://download.swift.org/swift-6.1-branch/swift-6.1-RELEASE/swift-6.1-RELEASE_wasi-0.0.1.artifactbundle.tar.gz """ ) } From fea9b11217a70d03520e190ff92ebdbb7c91d35a Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 20 May 2025 19:35:59 +0100 Subject: [PATCH 4/5] Add `swiftSDKAlias` to `BuildOptions`, add error descriptions --- Sources/CoreCommands/Options.swift | 6 ++++++ Sources/CoreCommands/SwiftCommandState.swift | 9 ++++++++- .../PackageModel/SwiftSDKs/SwiftSDKAlias.swift | 2 +- .../PackageModel/SwiftToolchainVersion.swift | 18 +++++++++++++++++- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift index c892694f383..e74c0248891 100644 --- a/Sources/CoreCommands/Options.swift +++ b/Sources/CoreCommands/Options.swift @@ -483,6 +483,12 @@ public struct BuildOptions: ParsableArguments { ) public var swiftSDKSelector: String? + @Option( + name: .customLong("experimental-swift-sdk-alias"), + help: "Alias for a compatible Swift SDK to build with." + ) + public var swiftSDKAlias: String? + /// Which compile-time sanitizers should be enabled. @Option( name: .customLong("sanitize"), diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 51d483ab980..78360ff532d 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -945,6 +945,13 @@ public final class SwiftCommandState { outputHandler: { print($0.description) } ) + let swiftSDKSelector = if let swiftSDKAliasString = self.options.build.swiftSDKAlias { + try SwiftToolchainVersion(toolchain: hostToolchain, fileSystem: self.fileSystem) + .idForSwiftSDK(aliasString: swiftSDKAliasString) + } else { + self.options.build.swiftSDKSelector ?? self.options.build.deprecatedSwiftSDKSelector + } + swiftSDK = try SwiftSDK.deriveTargetSwiftSDK( hostSwiftSDK: hostSwiftSDK, hostTriple: hostToolchain.targetTriple, @@ -953,7 +960,7 @@ public final class SwiftCommandState { customCompileTriple: self.options.build.customCompileTriple, customCompileToolchain: self.options.build.customCompileToolchain, customCompileSDK: self.options.build.customCompileSDK, - swiftSDKSelector: self.options.build.swiftSDKSelector ?? self.options.build.deprecatedSwiftSDKSelector, + swiftSDKSelector: swiftSDKSelector, architectures: self.options.build.architectures, store: store, observabilityScope: self.observabilityScope, diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKAlias.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKAlias.swift index a5cb78eb981..d9026d2f599 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDKAlias.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKAlias.swift @@ -16,7 +16,7 @@ package struct SwiftSDKAlias { self.kind = kind } - enum Kind: String { + enum Kind: String, CaseIterable { case staticLinux = "static-linux" case wasi = "wasi" case wasiEmbedded = "embedded-wasi" diff --git a/Sources/PackageModel/SwiftToolchainVersion.swift b/Sources/PackageModel/SwiftToolchainVersion.swift index 54950927c30..3322b73d4af 100644 --- a/Sources/PackageModel/SwiftToolchainVersion.swift +++ b/Sources/PackageModel/SwiftToolchainVersion.swift @@ -15,9 +15,25 @@ import class Foundation.JSONDecoder import struct Foundation.URL package struct SwiftToolchainVersion: Equatable, Decodable { - package enum Error: Swift.Error, Equatable { + package enum Error: Swift.Error, Equatable, CustomStringConvertible { case versionMetadataNotFound(AbsolutePath) case unknownSwiftSDKAlias(String) + + package var description: String { + switch self { + case .versionMetadataNotFound(let absolutePath): + """ + Toolchain version metadata file not found at path `\(absolutePath.pathString)`. \ + Install a newer version of the Swift toolchain that includes this file. + """ + case .unknownSwiftSDKAlias(let string): + """ + Unknown alias for a Swift SDK: `\(string)`. Supported aliases: \( + SwiftSDKAlias.Kind.allCases.map { "`\($0.rawValue)`" }.joined(separator: ", ") + ). + """ + } + } } package init( From 112cfd606dfae3054a65d5131914bf490d98c434 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 29 May 2025 17:10:13 +0100 Subject: [PATCH 5/5] Add explicit error wording to `SwiftSDKInstall` --- Sources/SwiftSDKCommand/SwiftSDKInstall.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftSDKCommand/SwiftSDKInstall.swift b/Sources/SwiftSDKCommand/SwiftSDKInstall.swift index de298785da4..654f9d0891e 100644 --- a/Sources/SwiftSDKCommand/SwiftSDKInstall.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKInstall.swift @@ -21,6 +21,17 @@ import var TSCBasic.stdoutStream import class Workspace.Workspace struct SwiftSDKInstall: SwiftSDKSubcommand { + enum Error: Swift.Error, CustomStringConvertible { + case swiftSDKNotSpecified + + var description: String { + switch self { + case .swiftSDKNotSpecified: + "Specify either a URL or a local path to a Swift SDK bundle as a positional argument." + } + } + } + static let configuration = CommandConfiguration( commandName: "install", abstract: """ @@ -85,7 +96,7 @@ struct SwiftSDKInstall: SwiftSDKSubcommand { } else if let bundlePathOrURL { bundlePathOrURL } else { - throw InternalError("foo") + throw Error.swiftSDKNotSpecified } try await store.install(