diff --git a/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md b/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md index 31e2fd1e..72b7159c 100644 --- a/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md +++ b/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md @@ -464,7 +464,7 @@ swiftly init [--no-modify-profile] [--overwrite] [--platform=] [--skip Update the version of swiftly itself. ``` -swiftly self-update [--assume-yes] [--verbose] [--version] [--help] +swiftly self-update [--assume-yes] [--verbose] [--version] [--help] ``` **--assume-yes:** diff --git a/Sources/Swiftly/SelfUpdate.swift b/Sources/Swiftly/SelfUpdate.swift index 49a76e12..e262aa23 100644 --- a/Sources/Swiftly/SelfUpdate.swift +++ b/Sources/Swiftly/SelfUpdate.swift @@ -5,6 +5,12 @@ import SwiftlyWebsiteAPI @preconcurrency import TSCBasic import TSCUtility +extension SwiftlyVersion: ExpressibleByArgument { + public init?(argument: String) { + try? self.init(parsing: argument) + } +} + struct SelfUpdate: SwiftlyCommand { public static let configuration = CommandConfiguration( abstract: "Update the version of swiftly itself." @@ -12,8 +18,10 @@ struct SelfUpdate: SwiftlyCommand { @OptionGroup var root: GlobalOptions + @Option(help: .hidden) var toVersion: SwiftlyVersion + private enum CodingKeys: String, CodingKey { - case root + case root, toVersion } mutating func run() async throws { @@ -31,50 +39,80 @@ struct SelfUpdate: SwiftlyCommand { ) } - let _ = try await Self.execute(ctx, verbose: self.root.verbose) + let _ = try await Self.execute(ctx, verbose: self.root.verbose, version: self.toVersion) } - public static func execute(_ ctx: SwiftlyCoreContext, verbose: Bool) async throws + public static func execute(_ ctx: SwiftlyCoreContext, verbose: Bool, version swiftlyVersion: SwiftlyVersion?) async throws -> SwiftlyVersion { - await ctx.print("Checking for swiftly updates...") + var downloadURL: Foundation.URL? + var version: SwiftlyVersion? = swiftlyVersion - let swiftlyRelease = try await ctx.httpClient.getCurrentSwiftlyRelease() + if let version { +#if os(macOS) + downloadURL = URL(string: "https://download.swift.org/swiftly/darwin/swiftly-\(version).pkg") +#endif - guard try swiftlyRelease.swiftlyVersion > SwiftlyCore.version else { - await ctx.print("Already up to date.") - return SwiftlyCore.version +#if os(Linux) +#if arch(x86_64) + downloadURL = URL(string: "https://download.swift.org/swiftly/linux/swiftly-\(version)-x86_64.tar.gz") +#elseif arch(arm64) + downloadURL = URL(string: "https://download.swift.org/swiftly/linux/swiftly-\(version)-aarch64.tar.gz") +#else + fatalError("Unsupported architecture") +#endif +#else + fatalError("Unsupported OS") +#endif + + guard version > SwiftlyCore.version else { + await ctx.print("Self-update does not support downgrading to an older version or re-installing the current version. Current version is \(SwiftlyCore.version) and requested version is \(version).") + return SwiftlyCore.version + } + + await ctx.print("Self-update requested to swiftly version \(version)") } - var downloadURL: Foundation.URL? - for platform in swiftlyRelease.platforms { -#if os(macOS) - guard platform.isDarwin else { - continue + if downloadURL == nil { + await ctx.print("Checking for swiftly updates...") + + let swiftlyRelease = try await ctx.httpClient.getCurrentSwiftlyRelease() + + guard try swiftlyRelease.swiftlyVersion > SwiftlyCore.version else { + await ctx.print("Already up to date.") + return SwiftlyCore.version } + for platform in swiftlyRelease.platforms { +#if os(macOS) + guard platform.isDarwin else { + continue + } #elseif os(Linux) - guard platform.isLinux else { - continue - } + guard platform.isLinux else { + continue + } #endif #if arch(x86_64) - downloadURL = try platform.x86_64URL + downloadURL = try platform.x86_64URL #elseif arch(arm64) - downloadURL = try platform.arm64URL + downloadURL = try platform.arm64URL #endif - } + } - guard let downloadURL else { - throw SwiftlyError( - message: - "The newest release of swiftly is incompatible with your current OS and/or processor architecture." - ) - } + guard let downloadURL else { + throw SwiftlyError( + message: + "The newest release of swiftly is incompatible with your current OS and/or processor architecture." + ) + } + + version = try swiftlyRelease.swiftlyVersion - let version = try swiftlyRelease.swiftlyVersion + await ctx.print("A new version of swiftly is available: \(version!)") + } - await ctx.print("A new version is available: \(version)") + guard let version, let downloadURL else { fatalError() } let tmpFile = fs.mktemp() try await fs.create(file: tmpFile, contents: nil) diff --git a/Tests/SwiftlyTests/SelfUpdateTests.swift b/Tests/SwiftlyTests/SelfUpdateTests.swift index 0f5e355d..badbf5a3 100644 --- a/Tests/SwiftlyTests/SelfUpdateTests.swift +++ b/Tests/SwiftlyTests/SelfUpdateTests.swift @@ -21,7 +21,7 @@ import Testing func runSelfUpdateTest(latestVersion: SwiftlyVersion) async throws { try await SwiftlyTests.withTestHome { try await SwiftlyTests.withMockedSwiftlyVersion(latestSwiftlyVersion: latestVersion) { - let updatedVersion = try await SelfUpdate.execute(SwiftlyTests.ctx, verbose: true) + let updatedVersion = try await SelfUpdate.execute(SwiftlyTests.ctx, verbose: true, version: nil) #expect(latestVersion == updatedVersion) } }