From 4c5911a37fa4909857b3f571eec22c2023d22784 Mon Sep 17 00:00:00 2001 From: Chris McGee <87777443+cmcgee1024@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:52:56 -0500 Subject: [PATCH 1/6] Add documentation related build and checks to GitHub workflows (#186) There's nothing in the pull request verification that checks if the command-line references are up-to-date. Add a job to the pull request update workflow that verifies this. There's nothing in the release build workflow that generates the documentation and uploads the documentation artifacts so that they can be published. Add a step to the build release job that generates the documentation so that it can be published to swift.org. Overcome git dubious repo error. Give the uploaded artifacts unique names to avoid collisions. --- .github/workflows/build_release.yml | 13 +++++++++++-- .github/workflows/pull_request.yml | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index c45dd6e..c2639bd 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -23,10 +23,19 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Build Artifact + - name: Build Release Artifact run: swift run build-swiftly-release ${{ inputs.skip }} ${{ inputs.version }} - - name: Upload Artifact + - name: Upload Release Artifact uses: actions/upload-artifact@v4 with: + name: swiftly-release-x86_64 path: .build/release/swiftly-*.tar.gz if-no-files-found: error + - name: Build Documentation Artifacts + run: swift package --allow-writing-to-directory .build/docs generate-documentation --target SwiftlyDocs --output-path .build/docs + - name: Upload Documentation Artifacts + uses: actions/upload-artifact@v4 + with: + name: swiftly-docs + path: .build/docs/** + if-no-files-found: error diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ae055bb..562f6e6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -43,6 +43,7 @@ jobs: - name: Upload Artifact uses: actions/upload-artifact@v4 with: + name: swiftly-release-x86_64 path: .build/release/swiftly-*.tar.gz if-no-files-found: error retention-days: 1 @@ -57,3 +58,25 @@ jobs: linux_pre_build_command: ./scripts/prep-gh-action.sh linux_build_command: swift run swiftformat --lint --dryrun . || (echo "Please run 'swift run swiftformat .' to format the source code."; exit 1) enable_windows_checks: false + + docscheck: + name: Documentation Check + runs-on: ubuntu-latest + container: + image: "swift:6.0-noble" + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Prepare the action + run: ./scripts/prep-gh-action.sh && ./scripts/install-libarchive.sh + - name: Generate Swiftly CLI Reference and Check for Differences + run: swift package plugin --allow-writing-to-package-directory generate-docs-reference && git config --global --add safe.directory $(pwd) && git diff --exit-code Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md || (echo "The documentation hasn't been updated with the latest swiftly command-line reference. Please run 'swift package plugin generate-docs-reference' and commit/push the changes."; exit 1) + - name: Generate Documentation Set + run: swift package --allow-writing-to-directory .build/docs generate-documentation --target SwiftlyDocs --output-path .build/docs + - name: Upload Documentation Artifacts + uses: actions/upload-artifact@v4 + with: + name: swiftly-docs + path: .build/docs/** + if-no-files-found: error + retention-days: 1 From 27940566bd22fe74150726d813003a3d28457f9a Mon Sep 17 00:00:00 2001 From: Chris McGee <87777443+cmcgee1024@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:14:28 -0500 Subject: [PATCH 2/6] Streamline the swiftly init process (#177) The init process just installs swiftly itself at the moment. Most users will immediately install a swift toolchain, most likely the latest available one. On Linux, there's confusing gpg messages that most users don't need to be aware. The init process will provide a summary of things that are going to happen to the user's system, including the addition of GnuPG keys on Linux, and the installation of the latest swift toolchain so that they can agree, or abort the entire process. When the process runs the user is given line-level and high level processes, not internal details. Add a verbose mode for more details, such as the messages that come from GnuPG on Linux. Add an option to the init subcommand to allow swiftly to be installed without the latest available swift toolchain so that advanced users can decide how to install a toolchain themselves after the swiftly installation. Update the documentation with the more automated workflow. Add more detail to the getting started guide. Rework the getting started guide to have a platform selector for linux/macOS and hide platform-specific details Permit bare swiftly command to start the init workflow when not installed. Quiet the macOS installer messages behind the verbose flag. Provide more verbose untarring messages in Linux behind the verbose flag. --- .../SwiftlyDocs.docc/automated-install.md | 16 ++- .../SwiftlyDocs.docc/getting-started.md | 49 +++++---- .../SwiftlyDocs.docc/swiftly-cli-reference.md | 54 ++++++++-- README.md | 6 +- Sources/LinuxPlatform/Linux.swift | 15 ++- Sources/MacOSPlatform/MacOS.swift | 13 ++- Sources/Swiftly/Init.swift | 88 +++++++++++------ Sources/Swiftly/Install.swift | 21 ++-- Sources/Swiftly/Proxy.swift | 33 ++++++- Sources/Swiftly/SelfUpdate.swift | 12 ++- Sources/Swiftly/Swiftly.swift | 3 + Sources/Swiftly/Update.swift | 1 + Sources/SwiftlyCore/Platform.swift | 99 +++++++++++++++---- Tests/SwiftlyTests/InitTests.swift | 10 +- Tests/SwiftlyTests/PlatformTests.swift | 10 +- Tests/SwiftlyTests/RunTests.swift | 2 +- Tests/SwiftlyTests/SelfUpdateTests.swift | 2 +- 17 files changed, 313 insertions(+), 121 deletions(-) diff --git a/Documentation/SwiftlyDocs.docc/automated-install.md b/Documentation/SwiftlyDocs.docc/automated-install.md index c698a74..ceebc2d 100644 --- a/Documentation/SwiftlyDocs.docc/automated-install.md +++ b/Documentation/SwiftlyDocs.docc/automated-install.md @@ -4,18 +4,26 @@ Swiftly can be installed automatically in places like build/CI systems. This guide will help you to script to the installation of swiftly and toolchains so that it can be unattended. We assume that you have working understanding of your build system. The examples are based on a typical Unix environment. -First, download a swiftly binary from a trusted source, such as your artifact repository, or a well-known website for the operating system (e.g. Linux) and processor architecture (e.g. arm64, or x86_64). Here's an example using the popular curl command. +First, download the swiftly binary from swift.org for your operating system (e.g. Linux) and processor architecture (e.g. arm64, or x86_64). Here's an example using the popular curl command. ``` -curl -L > swiftly +curl -L > swiftly.tar.gz +tar zxf swiftly.tar.gz +``` + +On macOS you can download the pkg file and extract it like this from the command-line: + +``` +curl -L > swiftly.pkg +installer -pkg swiftly.pkg -target CurrentUserHomeDirectory ``` > Tip: If you are using Linux you will need the "ca-certificates" package for the root certificate authorities that will establish the trust that swiftly needs to make API requests that it needs. This package is frequently pre-installed on end-user environments, but may not be present in more minimal installations. -Once swiftly is downloaded you can run the init subcommand to finish the installation. This command will use the default initialization options and proceed without prompting. +Once swiftly is downloaded you can run the init subcommand to finish the installation. This command will print verbose outputs, assume yes for all prompts, and skip the automatic installation of the latest swift toolchain: ``` -./swiftly init --assume-yes +./swiftly init --verbose --assume-yes --skip-install # the swiftly binary is extracted to ~/local/bin/swiftly on macOS ``` Swiftly is installed, but the current shell may not yet be updated with the new environment variables, such as the PATH. The init command prints instructions on how to update the current shell environment without opening a new shell. This is an example of the output taken from Linux, but the details might be different for other OSes, username, or shell. diff --git a/Documentation/SwiftlyDocs.docc/getting-started.md b/Documentation/SwiftlyDocs.docc/getting-started.md index 422f785..930d4c6 100644 --- a/Documentation/SwiftlyDocs.docc/getting-started.md +++ b/Documentation/SwiftlyDocs.docc/getting-started.md @@ -1,31 +1,46 @@ # Getting Started with Swiftly -To download swiftly and install Swift, run the following in your terminal, then follow the on-screen instructions: +Start using swiftly and swift. -``` -curl -L https://swiftlang.github.io/swiftly/swiftly-install.sh | bash -``` +To get started with swiftly you can download it from [swift.org](https://swift.org/download), and extract the package. -Alternatively, you can download the swiftly binary and install itself like this: +@TabNavigator { + @Tab("Linux") { + If you are using Linux then you can verify and extract the archive like this: -``` -swiftly init -``` + ``` + sha256sum swiftly-x.y.z.tar.gz # Check that the hash matches what's reported on swift.org + tar zxf swiftly-x.y.z.tar.gz + ``` -Once swiftly is installed you can use it to install the latest available swift toolchain like this: + Now run swiftly init to finish the installation: -``` -$ swiftly install latest + ``` + ./swiftly init + ``` + } -Fetching the latest stable Swift release... -Installing Swift 5.8.1 -Downloaded 488.5 MiB of 488.5 MiB -Extracting toolchain... -Swift 5.8.1 installed successfully! + @Tab("macOS") { + On macOS you can either run the pkg installer from the command-line like this or just run the package by double-clicking on it (not recommended): + ``` + installer -pkg swift-x.y.z.pkg -target CurrentUserHomeDirectory + ``` + + Now run swiftly init to finish the installation: + + ``` + $HOME/usr/local/bin/swiftly init + ``` + } +} + +Swiftly will install itself and download the latest available Swift toolchain. Follow the prompts for any additional steps. Once everything is done you can begin using swift. + +``` $ swift --version -Swift version 5.8.1 (swift-5.8.1-RELEASE) +Swift version 6.0.1 (swift-6.0.1-RELEASE) Target: x86_64-unknown-linux-gnu $ swift build # Build with the latest (5.8.1) toolchain diff --git a/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md b/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md index 827a1ec..4e988fd 100644 --- a/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md +++ b/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md @@ -23,7 +23,7 @@ swiftly [--version] [--help] Install a new toolchain. ``` -swiftly install [] [--use] [--verify|no-verify] [--post-install-file=] [--assume-yes] [--version] [--help] +swiftly install [] [--use] [--verify|no-verify] [--post-install-file=] [--assume-yes] [--verbose] [--version] [--help] ``` **version:** @@ -81,6 +81,11 @@ written to this file as commands that can be run after the installation. *Disable confirmation prompts by assuming 'yes'* +**--verbose:** + +*Enable verbose reporting from swiftly* + + **--version:** *Show the version.* @@ -143,7 +148,7 @@ Note that listing available snapshots before the latest release (major and minor Set the in-use toolchain. If no toolchain is provided, print the currently in-use toolchain, if any. ``` -swiftly use [--print-location] [--global-default] [--assume-yes] [] [--version] [--help] +swiftly use [--print-location] [--global-default] [--assume-yes] [--verbose] [] [--version] [--help] ``` **--print-location:** @@ -161,6 +166,11 @@ swiftly use [--print-location] [--global-default] [--assume-yes] [] [ *Disable confirmation prompts by assuming 'yes'* +**--verbose:** + +*Enable verbose reporting from swiftly* + + **toolchain:** *The toolchain to use.* @@ -210,7 +220,7 @@ Likewise, the latest snapshot associated with a given development branch can be Remove an installed toolchain. ``` -swiftly uninstall [--assume-yes] [--version] [--help] +swiftly uninstall [--assume-yes] [--verbose] [--version] [--help] ``` **toolchain:** @@ -249,6 +259,11 @@ Finally, all installed toolchains can be uninstalled by specifying 'all': *Disable confirmation prompts by assuming 'yes'* +**--verbose:** + +*Enable verbose reporting from swiftly* + + **--version:** *Show the version.* @@ -309,7 +324,7 @@ The installed snapshots for a given devlopment branch can be listed by specifyin Update an installed toolchain to a newer version. ``` -swiftly update [] [--assume-yes] [--verify|no-verify] [--post-install-file=] [--version] [--help] +swiftly update [] [--assume-yes] [--verbose] [--verify|no-verify] [--post-install-file=] [--version] [--help] ``` **toolchain:** @@ -355,6 +370,11 @@ A specific snapshot toolchain can be updated by including the date: *Disable confirmation prompts by assuming 'yes'* +**--verbose:** + +*Enable verbose reporting from swiftly* + + **--verify|no-verify:** *Verify the toolchain's PGP signature before proceeding with installation.* @@ -385,7 +405,7 @@ written to this file as commands that can be run after the installation. Perform swiftly initialization into your user account. ``` -swiftly init [--no-modify-profile] [--overwrite] [--platform=] [--assume-yes] [--version] [--help] +swiftly init [--no-modify-profile] [--overwrite] [--platform=] [--skip-install] [--assume-yes] [--verbose] [--version] [--help] ``` **--no-modify-profile:** @@ -400,7 +420,12 @@ swiftly init [--no-modify-profile] [--overwrite] [--platform=] [--assu **--platform=\:** -*Specify the current Linux platform for swiftly.* +*Specify the current Linux platform for swiftly* + + +**--skip-install:** + +*Skip installing the latest toolchain* **--assume-yes:** @@ -408,6 +433,11 @@ swiftly init [--no-modify-profile] [--overwrite] [--platform=] [--assu *Disable confirmation prompts by assuming 'yes'* +**--verbose:** + +*Enable verbose reporting from swiftly* + + **--version:** *Show the version.* @@ -425,9 +455,19 @@ swiftly init [--no-modify-profile] [--overwrite] [--platform=] [--assu Update the version of swiftly itself. ``` -swiftly self-update [--version] [--help] +swiftly self-update [--assume-yes] [--verbose] [--version] [--help] ``` +**--assume-yes:** + +*Disable confirmation prompts by assuming 'yes'* + + +**--verbose:** + +*Enable verbose reporting from swiftly* + + **--version:** *Show the version.* diff --git a/README.md b/README.md index 10f301b..1c58d06 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,8 @@ Ongoing maintenance and stewardship of this project is led by the [SSWG](https:/ ### Installation -To download swiftly and install Swift, run the following in your terminal, then follow the on-screen instructions. -``` -curl -L https://swiftlang.github.io/swiftly/swiftly-install.sh | bash -``` +Download the swiftly package from [swift.org](https://swift.org/download) and it can install itself with init: -Alternatively, you can download the swiftly binary and it can install itself: ``` swiftly init ``` diff --git a/Sources/LinuxPlatform/Linux.swift b/Sources/LinuxPlatform/Linux.swift index 3934ea5..824786f 100644 --- a/Sources/LinuxPlatform/Linux.swift +++ b/Sources/LinuxPlatform/Linux.swift @@ -327,7 +327,7 @@ public struct Linux: Platform { } } - public func install(from tmpFile: URL, version: ToolchainVersion) throws { + public func install(from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws { guard tmpFile.fileExists() else { throw Error(message: "\(tmpFile) doesn't exist") } @@ -348,7 +348,14 @@ public struct Linux: Platform { let relativePath = name.drop { c in c != "/" }.dropFirst() // prepend /path/to/swiftlyHomeDir/toolchains/ to each file name - return toolchainDir.appendingPathComponent(String(relativePath)) + let destination = toolchainDir.appendingPathComponent(String(relativePath)) + + if verbose { + SwiftlyCore.print("\(destination.path)") + } + + // prepend /path/to/swiftlyHomeDir/toolchains/ to each file name + return destination } } @@ -390,7 +397,7 @@ public struct Linux: Platform { FileManager.default.temporaryDirectory.appendingPathComponent("swiftly-\(UUID())") } - public func verifySignature(httpClient: SwiftlyHTTPClient, archiveDownloadURL: URL, archive: URL) async throws { + public func verifySignature(httpClient: SwiftlyHTTPClient, archiveDownloadURL: URL, archive: URL, verbose: Bool) async throws { SwiftlyCore.print("Downloading toolchain signature...") let sigFile = self.getTempFilePath() let _ = FileManager.default.createFile(atPath: sigFile.path, contents: nil) @@ -405,7 +412,7 @@ public struct Linux: Platform { SwiftlyCore.print("Verifying toolchain signature...") do { - try self.runProgram("gpg", "--verify", sigFile.path, archive.path) + try self.runProgram("gpg", "--verify", sigFile.path, archive.path, quiet: !verbose) } catch { throw Error(message: "Signature verification failed: \(error).") } diff --git a/Sources/MacOSPlatform/MacOS.swift b/Sources/MacOSPlatform/MacOS.swift index 761e066..b0ad224 100644 --- a/Sources/MacOSPlatform/MacOS.swift +++ b/Sources/MacOSPlatform/MacOS.swift @@ -49,7 +49,7 @@ public struct MacOS: Platform { nil } - public func install(from tmpFile: URL, version: ToolchainVersion) throws { + public func install(from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws { guard tmpFile.fileExists() else { throw Error(message: "\(tmpFile) doesn't exist") } @@ -60,23 +60,26 @@ public struct MacOS: Platform { if SwiftlyCore.mockedHomeDir == nil { SwiftlyCore.print("Installing package in user home directory...") - try runProgram("installer", "-pkg", tmpFile.path, "-target", "CurrentUserHomeDirectory") + try runProgram("installer", "-verbose", "-pkg", tmpFile.path, "-target", "CurrentUserHomeDirectory", quiet: !verbose) } else { // In the case of a mock for testing purposes we won't use the installer, perferring a manual process because // the installer will not install to an arbitrary path, only a volume or user home directory. + SwiftlyCore.print("Expanding pkg...") let tmpDir = self.getTempFilePath() let toolchainDir = self.swiftlyToolchainsDir.appendingPathComponent("\(version.identifier).xctoolchain", isDirectory: true) if !toolchainDir.fileExists() { try FileManager.default.createDirectory(at: toolchainDir, withIntermediateDirectories: false) } - try runProgram("pkgutil", "--expand", tmpFile.path, tmpDir.path) + try runProgram("pkgutil", "--verbose", "--expand", tmpFile.path, tmpDir.path, quiet: !verbose) // There's a slight difference in the location of the special Payload file between official swift packages // and the ones that are mocked here in the test framework. var payload = tmpDir.appendingPathComponent("Payload") if !payload.fileExists() { payload = tmpDir.appendingPathComponent("\(version.identifier)-osx-package.pkg/Payload") } - try runProgram("tar", "-C", toolchainDir.path, "-xf", payload.path) + + SwiftlyCore.print("Untarring pkg Payload...") + try runProgram("tar", "-C", toolchainDir.path, "-xvf", payload.path, quiet: !verbose) } } @@ -146,7 +149,7 @@ public struct MacOS: Platform { FileManager.default.temporaryDirectory.appendingPathComponent("swiftly-\(UUID()).pkg") } - public func verifySignature(httpClient _: SwiftlyHTTPClient, archiveDownloadURL _: URL, archive _: URL) async throws { + public func verifySignature(httpClient _: SwiftlyHTTPClient, archiveDownloadURL _: URL, archive _: URL, verbose _: Bool) async throws { // No signature verification is required on macOS since the pkg files have their own signing // mechanism and the swift.org downloadables are trusted by stock macOS installations. } diff --git a/Sources/Swiftly/Init.swift b/Sources/Swiftly/Init.swift index 374984e..8483ad4 100644 --- a/Sources/Swiftly/Init.swift +++ b/Sources/Swiftly/Init.swift @@ -14,39 +14,53 @@ internal struct Init: SwiftlyCommand { installation is found, the swiftly executable will be updated, but the rest of the installation will not be modified. """) var overwrite: Bool = false - @Option(name: .long, help: "Specify the current Linux platform for swiftly.") + @Option(name: .long, help: "Specify the current Linux platform for swiftly") var platform: String? + @Flag(help: "Skip installing the latest toolchain") + var skipInstall: Bool = false @OptionGroup var root: GlobalOptions + private enum CodingKeys: String, CodingKey { + case noModifyProfile, overwrite, platform, skipInstall, root + } + public mutating func validate() throws {} internal mutating func run() async throws { - try await Self.execute(assumeYes: self.root.assumeYes, noModifyProfile: self.noModifyProfile, overwrite: self.overwrite, platform: self.platform) + try await Self.execute(assumeYes: self.root.assumeYes, noModifyProfile: self.noModifyProfile, overwrite: self.overwrite, platform: self.platform, verbose: self.root.verbose, skipInstall: self.skipInstall) } /// Initialize the installation of swiftly. - internal static func execute(assumeYes: Bool, noModifyProfile: Bool, overwrite: Bool, platform: String?) async throws { + internal static func execute(assumeYes: Bool, noModifyProfile: Bool, overwrite: Bool, platform: String?, verbose: Bool, skipInstall: Bool) async throws { try Swiftly.currentPlatform.verifySwiftlySystemPrerequisites() - let config = try? Config.load() + var config = try? Config.load() - if let config = config, !overwrite && config.version != SwiftlyCore.version { + if let config, !overwrite && config.version != SwiftlyCore.version { // We don't support downgrades, and we don't yet support upgrades throw Error(message: "An existing swiftly installation was detected. You can try again with '--overwrite' to overwrite it.") } // Give the user the prompt and the choice to abort at this point. if !assumeYes { +#if os(Linux) + let sigMsg = " In the process of installing the new toolchain swiftly will add swift.org GnuPG keys into your keychain to verify the integrity of the downloads." +#else + let sigMsg = "" +#endif + let installMsg = if !skipInstall { + "\nOnce swiftly is installed it will install the latest available swift toolchain.\(sigMsg)\n" + } else { "" } + SwiftlyCore.print(""" Swiftly will be installed into the following locations: \(Swiftly.currentPlatform.swiftlyHomeDir.path) - Data and configuration files directory including toolchains \(Swiftly.currentPlatform.swiftlyBinDir.path) - Executables installation directory - Note that the locations can be changed with SWIFTLY_HOME and SWIFTLY_BIN environment variables and run - this again. - + These locations can be changed with SWIFTLY_HOME and SWIFTLY_BIN environment variables and run this again. + \(installMsg) """) if SwiftlyCore.readLine(prompt: "Proceed with the installation? [Y/n] ") == "n" { @@ -54,6 +68,7 @@ internal struct Init: SwiftlyCommand { } } + // Ensure swiftly doesn't overwrite any existing executables without getting confirmation first. let swiftlyBinDir = Swiftly.currentPlatform.swiftlyBinDir let swiftlyBinDirContents = (try? FileManager.default.contentsOfDirectory(atPath: swiftlyBinDir.path)) ?? [String]() let willBeOverwritten = Set(["swiftly"]).intersection(swiftlyBinDirContents) @@ -114,32 +129,17 @@ internal struct Init: SwiftlyCommand { // Force the configuration to be present. Generate it if it doesn't already exist or overwrite is set if overwrite || config == nil { let pd = try await Swiftly.currentPlatform.detectPlatform(disableConfirmation: assumeYes, platform: platform) - var config = Config(inUse: nil, installedToolchains: [], platform: pd) + var c = Config(inUse: nil, installedToolchains: [], platform: pd) // Stamp the current version of swiftly on this config - config.version = SwiftlyCore.version - try config.save() + c.version = SwiftlyCore.version + try c.save() + config = c } - let swiftlyBin = Swiftly.currentPlatform.swiftlyBinDir.appendingPathComponent("swiftly", isDirectory: false) - - let cmd = URL(fileURLWithPath: CommandLine.arguments[0]) - let systemManagedSwiftlyBin = try Swiftly.currentPlatform.systemManagedBinary(CommandLine.arguments[0]) - - // Don't move the binary if it's already in the right place, this is being invoked inside an xctest, or it is a system managed binary - if cmd != swiftlyBin && !cmd.path.hasSuffix("xctest") && systemManagedSwiftlyBin == nil { - SwiftlyCore.print("Moving swiftly into the installation directory...") - - if swiftlyBin.fileExists() { - try FileManager.default.removeItem(at: swiftlyBin) - } + guard var config else { throw Error(message: "Configuration could not be set") } - do { - try FileManager.default.moveItem(at: cmd, to: swiftlyBin) - } catch { - try FileManager.default.copyItem(at: cmd, to: swiftlyBin) - SwiftlyCore.print("Swiftly has been copied into the installation directory. You can remove '\(cmd.path)'. It is no longer needed.") - } - } + // Move our executable over to the correct place + try Swiftly.currentPlatform.installSwiftlyBin() if overwrite || !FileManager.default.fileExists(atPath: envFile.path) { SwiftlyCore.print("Creating shell environment file for the user...") @@ -208,6 +208,14 @@ internal struct Init: SwiftlyCommand { addEnvToProfile = true } + var postInstall: String? + var pathChanged = false + + if !skipInstall { + let latestVersion = try await Install.resolve(config: config, selector: ToolchainSelector.latest) + (postInstall, pathChanged) = try await Install.execute(version: latestVersion, &config, useInstalledToolchain: true, verifySignature: true, verbose: verbose, assumeYes: assumeYes) + } + if addEnvToProfile { try Data(sourceLine.utf8).append(to: profileHome) @@ -217,6 +225,26 @@ internal struct Init: SwiftlyCommand { """) } + + if pathChanged { + SwiftlyCore.print(""" + Your shell caches items on your path for better performance. Swiftly has added items to your path that may not get picked up right away. You can run this command to update your shell to get these items. + + hash -r + + """) + } + + if let postInstall { + SwiftlyCore.print(""" + There are some dependencies that should be installed before using this toolchain. + You can run the following script as the system administrator (e.g. root) to prepare + your system: + + \(postInstall) + + """) + } } } } diff --git a/Sources/Swiftly/Install.swift b/Sources/Swiftly/Install.swift index 489b136..7de678f 100644 --- a/Sources/Swiftly/Install.swift +++ b/Sources/Swiftly/Install.swift @@ -99,6 +99,7 @@ struct Install: SwiftlyCommand { &config, useInstalledToolchain: self.use, verifySignature: self.verify, + verbose: self.root.verbose, assumeYes: self.root.assumeYes ) @@ -116,7 +117,7 @@ struct Install: SwiftlyCommand { guard let postInstallFile = self.postInstallFile else { throw Error(message: """ - There are some system dependencies that should be installed before using this toolchain. + There are some dependencies that should be installed before using this toolchain. You can run the following script as the system administrator (e.g. root) to prepare your system: @@ -133,6 +134,7 @@ struct Install: SwiftlyCommand { _ config: inout Config, useInstalledToolchain: Bool, verifySignature: Bool, + verbose: Bool, assumeYes: Bool ) async throws -> (postInstall: String?, pathChanged: Bool) { guard !config.installedToolchains.contains(version) else { @@ -232,30 +234,23 @@ struct Install: SwiftlyCommand { try await Swiftly.currentPlatform.verifySignature( httpClient: SwiftlyCore.httpClient, archiveDownloadURL: url, - archive: tmpFile + archive: tmpFile, + verbose: verbose ) } - try Swiftly.currentPlatform.install(from: tmpFile, version: version) + try Swiftly.currentPlatform.install(from: tmpFile, version: version, verbose: verbose) var pathChanged = false - // Don't create the proxies in the tests - if CommandLine.arguments.count > 0 && !CommandLine.arguments[0].hasSuffix("xctest") { + // Create proxies if we have a location where we can point them + if let proxyTo = try? Swiftly.currentPlatform.findSwiftlyBin() { // Ensure swiftly doesn't overwrite any existing executables without getting confirmation first. - let swiftlyBin = Swiftly.currentPlatform.swiftlyBinDir.appendingPathComponent("swiftly", isDirectory: false) - let systemManagedSwiftlyBin = try Swiftly.currentPlatform.systemManagedBinary(CommandLine.arguments[0]) let swiftlyBinDir = Swiftly.currentPlatform.swiftlyBinDir let swiftlyBinDirContents = (try? FileManager.default.contentsOfDirectory(atPath: swiftlyBinDir.path)) ?? [String]() let toolchainBinDir = Swiftly.currentPlatform.findToolchainBinDir(version) let toolchainBinDirContents = try FileManager.default.contentsOfDirectory(atPath: toolchainBinDir.path) - let proxyTo = if let systemManagedSwiftlyBin = systemManagedSwiftlyBin { - systemManagedSwiftlyBin - } else { - swiftlyBin.path - } - let existingProxies = swiftlyBinDirContents.filter { bin in do { let linkTarget = try FileManager.default.destinationOfSymbolicLink(atPath: swiftlyBinDir.appendingPathComponent(bin).path) diff --git a/Sources/Swiftly/Proxy.swift b/Sources/Swiftly/Proxy.swift index aca9440..72b642c 100644 --- a/Sources/Swiftly/Proxy.swift +++ b/Sources/Swiftly/Proxy.swift @@ -11,9 +11,36 @@ public enum Proxy { } guard binName != "swiftly" else { - // Treat this as a swiftly invocation - await Swiftly.main() - return + // Treat this as a swiftly invocation, but first check that we are installed, bootstrapping + // the installation process if we aren't. + let configResult = Result { try Config.load() } + + switch configResult { + case .success: + await Swiftly.main() + return + case let .failure(err): + guard CommandLine.arguments.count > 0 else { fatalError("argv is not set") } + + if CommandLine.arguments.count == 1 { + // User ran swiftly with no extra arguments in an uninstalled environment, so we jump directly into + // an simple init. + try await Init.execute(assumeYes: false, noModifyProfile: false, overwrite: false, platform: nil, verbose: false, skipInstall: false) + return + } else if CommandLine.arguments.count >= 2 && CommandLine.arguments[1] == "init" { + // Let the user run the init command with their arguments, if any. + await Swiftly.main() + return + } else if CommandLine.arguments.count == 2 && (CommandLine.arguments[1] == "--help" || CommandLine.arguments[1] == "--experimental-dump-help") { + // Allow the showing of help information + await Swiftly.main() + return + } else { + // We've been invoked outside the "init" subcommand and we're not yet configured. + // This will throw if the configuration couldn't be loaded and give the user an actionable message. + throw err + } + } } var config = try Config.load() diff --git a/Sources/Swiftly/SelfUpdate.swift b/Sources/Swiftly/SelfUpdate.swift index b8534f4..e41dea6 100644 --- a/Sources/Swiftly/SelfUpdate.swift +++ b/Sources/Swiftly/SelfUpdate.swift @@ -10,7 +10,11 @@ internal struct SelfUpdate: SwiftlyCommand { abstract: "Update the version of swiftly itself." ) - private enum CodingKeys: CodingKey {} + @OptionGroup var root: GlobalOptions + + private enum CodingKeys: String, CodingKey { + case root + } internal mutating func run() async throws { try validateSwiftly() @@ -20,10 +24,10 @@ internal struct SelfUpdate: SwiftlyCommand { throw Error(message: "Self update doesn't work when swiftly has been installed externally. Please keep it updated from the source where you installed it in the first place.") } - let _ = try await Self.execute() + let _ = try await Self.execute(verbose: self.root.verbose) } - public static func execute() async throws -> SwiftlyVersion { + public static func execute(verbose: Bool) async throws -> SwiftlyVersion { SwiftlyCore.print("Checking for swiftly updates...") let swiftlyRelease = try await SwiftlyCore.httpClient.getSwiftlyRelease() @@ -91,7 +95,7 @@ internal struct SelfUpdate: SwiftlyCommand { } animation.complete(success: true) - try await Swiftly.currentPlatform.verifySignature(httpClient: SwiftlyCore.httpClient, archiveDownloadURL: downloadURL, archive: tmpFile) + try await Swiftly.currentPlatform.verifySignature(httpClient: SwiftlyCore.httpClient, archiveDownloadURL: downloadURL, archive: tmpFile, verbose: verbose) try Swiftly.currentPlatform.extractSwiftlyAndInstall(from: tmpFile) SwiftlyCore.print("Successfully updated swiftly to \(version) (was \(SwiftlyCore.version))") diff --git a/Sources/Swiftly/Swiftly.swift b/Sources/Swiftly/Swiftly.swift index 1652c9a..e0badf8 100644 --- a/Sources/Swiftly/Swiftly.swift +++ b/Sources/Swiftly/Swiftly.swift @@ -11,6 +11,9 @@ public struct GlobalOptions: ParsableArguments { @Flag(name: [.customShort("y"), .long], help: "Disable confirmation prompts by assuming 'yes'") var assumeYes: Bool = false + @Flag(help: "Enable verbose reporting from swiftly") + var verbose: Bool = false + public init() {} } diff --git a/Sources/Swiftly/Update.swift b/Sources/Swiftly/Update.swift index 43e56b5..98540e5 100644 --- a/Sources/Swiftly/Update.swift +++ b/Sources/Swiftly/Update.swift @@ -113,6 +113,7 @@ struct Update: SwiftlyCommand { &config, useInstalledToolchain: config.inUse == parameters.oldToolchain, verifySignature: self.verify, + verbose: self.root.verbose, assumeYes: self.root.assumeYes ) diff --git a/Sources/SwiftlyCore/Platform.swift b/Sources/SwiftlyCore/Platform.swift index cad42f9..a82b4ad 100644 --- a/Sources/SwiftlyCore/Platform.swift +++ b/Sources/SwiftlyCore/Platform.swift @@ -64,7 +64,7 @@ public protocol Platform { /// Installs a toolchain from a file on disk pointed to by the given URL. /// After this completes, a user can “use” the toolchain. - func install(from: URL, version: ToolchainVersion) throws + func install(from: URL, version: ToolchainVersion, verbose: Bool) throws /// Extract swiftly from the provided downloaded archive and install /// ourselves from that. @@ -98,8 +98,7 @@ public protocol Platform { /// Downloads the signature file associated with the archive and verifies it matches the downloaded archive. /// Throws an error if the signature does not match. - /// On Linux, signature verification will be skipped if gpg is not installed. - func verifySignature(httpClient: SwiftlyHTTPClient, archiveDownloadURL: URL, archive: URL) async throws + func verifySignature(httpClient: SwiftlyHTTPClient, archiveDownloadURL: URL, archive: URL, verbose: Bool) async throws /// Detect the platform definition for this platform. func detectPlatform(disableConfirmation: Bool, platform: String?) async throws -> PlatformDefinition @@ -268,32 +267,98 @@ extension Platform { } } - public func systemManagedBinary(_ cmd: String) throws -> String? { + // Install ourselves in the final location + public func installSwiftlyBin() throws { + // First, let's find out where we are. + let cmd = CommandLine.arguments[0] + let cmdAbsolute = if cmd.hasPrefix("/") { + cmd + } else { + ([FileManager.default.currentDirectoryPath] + (ProcessInfo.processInfo.environment["PATH"]?.components(separatedBy: ":") ?? [])).map { + $0 + "/" + cmd + }.filter { + FileManager.default.fileExists(atPath: $0) + }.first + } + + // We couldn't find ourselves in the usual places. Assume that no installation is necessary + // since we were most likely invoked at SWIFTLY_BIN_DIR already. + guard let cmdAbsolute else { + return + } + + // Proceed to installation only if we're in the user home directory, or a non-system location. let userHome = FileManager.default.homeDirectoryForCurrentUser - let binLocs = [cmd] + ProcessInfo.processInfo.environment["PATH"]!.components(separatedBy: ":").map { $0 + "/" + cmd } - var bin: String? - for binLoc in binLocs { - if FileManager.default.fileExists(atPath: binLoc) { - bin = binLoc - break - } + guard cmdAbsolute.hasPrefix(userHome.path + "/") || + (!cmdAbsolute.hasPrefix("/usr/") && !cmdAbsolute.hasPrefix("/opt/") && !cmdAbsolute.hasPrefix("/bin/")) + else { + return + } + + // Proceed only if we're not running in the context of a test. + guard !cmdAbsolute.hasSuffix("xctest") else { + return + } + + // We're already running from where we would be installing ourselves. + guard case let swiftlyHomeBin = self.swiftlyBinDir.appendingPathComponent("swiftly", isDirectory: false).path, cmdAbsolute != swiftlyHomeBin else { + return + } + + SwiftlyCore.print("Installing swiftly in \(swiftlyHomeBin)...") + + if FileManager.default.fileExists(atPath: swiftlyHomeBin) { + try FileManager.default.removeItem(atPath: swiftlyHomeBin) } - guard let bin = bin else { - throw Error(message: "Could not locate source of \(cmd) binary in either the PATH, relative, or absolute path") + + do { + try FileManager.default.moveItem(atPath: cmdAbsolute, toPath: swiftlyHomeBin) + } catch { + try FileManager.default.copyItem(atPath: cmdAbsolute, toPath: swiftlyHomeBin) + SwiftlyCore.print("Swiftly has been copied into the installation directory. You can remove '\(cmdAbsolute)'. It is no longer needed.") + } + } + + // Find the location where swiftly should be executed. + public func findSwiftlyBin() throws -> String? { + let swiftlyHomeBin = self.swiftlyBinDir.appendingPathComponent("swiftly", isDirectory: false).path + + // First, let's find out where we are. + let cmd = CommandLine.arguments[0] + let cmdAbsolute = if cmd.hasPrefix("/") { + cmd + } else { + ([FileManager.default.currentDirectoryPath] + (ProcessInfo.processInfo.environment["PATH"]?.components(separatedBy: ":") ?? [])).map { + $0 + "/" + cmd + }.filter { + FileManager.default.fileExists(atPath: $0) + }.first } - // If the binary is in the user's home directory, or is not in system locations ("/usr", "/opt", "/bin") - // then it is expected to be outside of a system package location and we manage the binary ourselves. - if bin.hasPrefix(userHome.path + "/") || (!bin.hasPrefix("/usr") && !bin.hasPrefix("/opt") && !bin.hasPrefix("/bin")) { + // We couldn't find ourselves in the usual places, so if we're not going to be installing + // swiftly then we can assume that we are running from the final location. + if cmdAbsolute == nil && FileManager.default.fileExists(atPath: swiftlyHomeBin) { + return swiftlyHomeBin + } + + // If we are system managed then we know where swiftly should be. + let userHome = FileManager.default.homeDirectoryForCurrentUser + if let cmdAbsolute, !cmdAbsolute.hasPrefix(userHome.path + "/") && (cmdAbsolute.hasPrefix("/usr/") || cmdAbsolute.hasPrefix("/opt/") || cmdAbsolute.hasPrefix("/bin/")) { + return cmdAbsolute + } + + // If we're running inside an xctest then we don't have a location for this swiftly. + guard let cmdAbsolute, !cmdAbsolute.hasSuffix("xctest") else { return nil } - return bin + return FileManager.default.fileExists(atPath: swiftlyHomeBin) ? swiftlyHomeBin : nil } public func findToolchainBinDir(_ toolchain: ToolchainVersion) -> URL { self.findToolchainLocation(toolchain).appendingPathComponent("usr/bin") } + #endif } diff --git a/Tests/SwiftlyTests/InitTests.swift b/Tests/SwiftlyTests/InitTests.swift index b7d8c72..c4169d9 100644 --- a/Tests/SwiftlyTests/InitTests.swift +++ b/Tests/SwiftlyTests/InitTests.swift @@ -27,7 +27,7 @@ final class InitTests: SwiftlyTests { } // WHEN: swiftly is invoked to init the user account and finish swiftly installation - var initCmd = try self.parseCommand(Init.self, ["init", "--assume-yes"]) + var initCmd = try self.parseCommand(Init.self, ["init", "--assume-yes", "--skip-install"]) try await initCmd.run() // THEN: it creates a valid configuration at the correct version @@ -67,7 +67,7 @@ final class InitTests: SwiftlyTests { // GIVEN: a user account with swiftly already installed try? FileManager.default.removeItem(at: Swiftly.currentPlatform.swiftlyConfigFile) - var initCmd = try self.parseCommand(Init.self, ["init", "--assume-yes"]) + var initCmd = try self.parseCommand(Init.self, ["init", "--assume-yes", "--skip-install"]) try await initCmd.run() // Add some customizations to files and directories @@ -79,7 +79,7 @@ final class InitTests: SwiftlyTests { try Data("".utf8).append(to: Swiftly.currentPlatform.swiftlyToolchainsDir.appendingPathComponent("foo.txt")) // WHEN: swiftly is initialized with overwrite enabled - initCmd = try self.parseCommand(Init.self, ["init", "--assume-yes", "--overwrite"]) + initCmd = try self.parseCommand(Init.self, ["init", "--assume-yes", "--skip-install", "--overwrite"]) try await initCmd.run() // THEN: everything is overwritten in initialization @@ -95,7 +95,7 @@ final class InitTests: SwiftlyTests { // GIVEN: a user account with swiftly already installed try? FileManager.default.removeItem(at: Swiftly.currentPlatform.swiftlyConfigFile) - var initCmd = try self.parseCommand(Init.self, ["init", "--assume-yes"]) + var initCmd = try self.parseCommand(Init.self, ["init", "--assume-yes", "--skip-install"]) try await initCmd.run() // Add some customizations to files and directories @@ -107,7 +107,7 @@ final class InitTests: SwiftlyTests { try Data("".utf8).append(to: Swiftly.currentPlatform.swiftlyToolchainsDir.appendingPathComponent("foo.txt")) // WHEN: swiftly init is invoked a second time - initCmd = try self.parseCommand(Init.self, ["init", "--assume-yes"]) + initCmd = try self.parseCommand(Init.self, ["init", "--assume-yes", "--skip-install"]) var threw = false do { try await initCmd.run() diff --git a/Tests/SwiftlyTests/PlatformTests.swift b/Tests/SwiftlyTests/PlatformTests.swift index 01e5841..2eff8c2 100644 --- a/Tests/SwiftlyTests/PlatformTests.swift +++ b/Tests/SwiftlyTests/PlatformTests.swift @@ -22,7 +22,7 @@ final class PlatformTests: SwiftlyTests { // GIVEN: a toolchain has been downloaded var (mockedToolchainFile, version) = try await self.mockToolchainDownload(version: "5.7.1") // WHEN: the platform installs the toolchain - try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version) + try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version, verbose: true) // THEN: the toolchain is extracted in the toolchains directory var toolchains = try FileManager.default.contentsOfDirectory(at: Swiftly.currentPlatform.swiftlyToolchainsDir, includingPropertiesForKeys: nil) XCTAssertEqual(1, toolchains.count) @@ -30,7 +30,7 @@ final class PlatformTests: SwiftlyTests { // GIVEN: a second toolchain has been downloaded (mockedToolchainFile, version) = try await self.mockToolchainDownload(version: "5.8.0") // WHEN: the platform installs the toolchain - try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version) + try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version, verbose: true) // THEN: the toolchain is added to the toolchains directory toolchains = try FileManager.default.contentsOfDirectory(at: Swiftly.currentPlatform.swiftlyToolchainsDir, includingPropertiesForKeys: nil) XCTAssertEqual(2, toolchains.count) @@ -38,7 +38,7 @@ final class PlatformTests: SwiftlyTests { // GIVEN: an identical toolchain has been downloaded (mockedToolchainFile, version) = try await self.mockToolchainDownload(version: "5.8.0") // WHEN: the platform installs the toolchain - try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version) + try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version, verbose: true) // THEN: the toolchains directory remains the same toolchains = try FileManager.default.contentsOfDirectory(at: Swiftly.currentPlatform.swiftlyToolchainsDir, includingPropertiesForKeys: nil) XCTAssertEqual(2, toolchains.count) @@ -49,9 +49,9 @@ final class PlatformTests: SwiftlyTests { try await self.rollbackLocalChanges { // GIVEN: toolchains have been downloaded, and installed var (mockedToolchainFile, version) = try await self.mockToolchainDownload(version: "5.8.0") - try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version) + try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version, verbose: true) (mockedToolchainFile, version) = try await self.mockToolchainDownload(version: "5.6.3") - try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version) + try Swiftly.currentPlatform.install(from: mockedToolchainFile, version: version, verbose: true) // WHEN: one of the toolchains is uninstalled try Swiftly.currentPlatform.uninstall(version) // THEN: there is only one remaining toolchain installed diff --git a/Tests/SwiftlyTests/RunTests.swift b/Tests/SwiftlyTests/RunTests.swift index 47337fd..38a33a3 100644 --- a/Tests/SwiftlyTests/RunTests.swift +++ b/Tests/SwiftlyTests/RunTests.swift @@ -43,7 +43,7 @@ final class RunTests: SwiftlyTests { try await self.withMockedHome(homeName: Self.homeName, toolchains: Self.allToolchains) { // The toolchains directory should be the fist entry on the path var run = try self.parseCommand(Run.self, ["run", try await Swiftly.currentPlatform.getShell(), "-c", "echo $PATH"]) - var output = try await run.runWithMockedIO() + let output = try await run.runWithMockedIO() XCTAssert(output.count == 1) XCTAssert(output[0].contains(Swiftly.currentPlatform.swiftlyToolchainsDir.path)) } diff --git a/Tests/SwiftlyTests/SelfUpdateTests.swift b/Tests/SwiftlyTests/SelfUpdateTests.swift index 1adb49a..628ee9b 100644 --- a/Tests/SwiftlyTests/SelfUpdateTests.swift +++ b/Tests/SwiftlyTests/SelfUpdateTests.swift @@ -21,7 +21,7 @@ final class SelfUpdateTests: SwiftlyTests { func runSelfUpdateTest(latestVersion: SwiftlyVersion) async throws { try await self.withTestHome { try await self.withMockedSwiftlyVersion(latestSwiftlyVersion: latestVersion) { - let updatedVersion = try await SelfUpdate.execute() + let updatedVersion = try await SelfUpdate.execute(verbose: true) XCTAssertEqual(latestVersion, updatedVersion) } } From 78b0490cf2ed5cf6e0de01fc8ad35e7f389df17d Mon Sep 17 00:00:00 2001 From: Chris McGee <87777443+cmcgee1024@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:29:43 -0500 Subject: [PATCH 3/6] Fix a documentation bug involving sha sum (#194) The documentation has an extraneous entry that talks about sha sums. Remove that reference. --- Documentation/SwiftlyDocs.docc/getting-started.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/SwiftlyDocs.docc/getting-started.md b/Documentation/SwiftlyDocs.docc/getting-started.md index 930d4c6..da4a073 100644 --- a/Documentation/SwiftlyDocs.docc/getting-started.md +++ b/Documentation/SwiftlyDocs.docc/getting-started.md @@ -9,7 +9,6 @@ To get started with swiftly you can download it from [swift.org](https://swift.o If you are using Linux then you can verify and extract the archive like this: ``` - sha256sum swiftly-x.y.z.tar.gz # Check that the hash matches what's reported on swift.org tar zxf swiftly-x.y.z.tar.gz ``` @@ -30,7 +29,7 @@ To get started with swiftly you can download it from [swift.org](https://swift.o Now run swiftly init to finish the installation: ``` - $HOME/usr/local/bin/swiftly init + ~/usr/local/bin/swiftly init ``` } } From b8e7af6494df6cf1e9fa63461139da6b88dbf615 Mon Sep 17 00:00:00 2001 From: Chris McGee <87777443+cmcgee1024@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:22:16 -0500 Subject: [PATCH 4/6] Make release package file names more uname friendly on Linux (#193) --- Tools/build-swiftly-release/BuildSwiftlyRelease.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift index 1ca1069..a604725 100644 --- a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift +++ b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift @@ -374,7 +374,7 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { #if arch(arm64) let releaseArchive = "\(releaseDir)/swiftly-\(version)-aarch64.tar.gz" #else - let releaseArchive = "\(releaseDir)/swiftly-\(version).tar.gz" + let releaseArchive = "\(releaseDir)/swiftly-\(version)-x86_64.tar.gz" #endif try runProgram(tar, "--directory=\(releaseDir)", "-czf", releaseArchive, "swiftly", "LICENSE.txt") From f5d1bab790d7a14ac2388ccd0c1587be5d8d9b05 Mon Sep 17 00:00:00 2001 From: Chris McGee <87777443+cmcgee1024@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:53:40 -0500 Subject: [PATCH 5/6] Restore amazon linux 2 as the default release build platform (#192) Make using RHEL UBI9 an option to the build release script, default is false. Update the workflows to use UBI9 since AL2 doesn't work in GitHub workflows. --- .github/workflows/build_release.yml | 4 ++-- .github/workflows/pull_request.yml | 3 +-- .../BuildSwiftlyRelease.swift | 22 ++++++++++++++----- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index c2639bd..654158b 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -7,7 +7,7 @@ on: description: "Version of swiftly to build release artifacts" required: true type: string - default: "0.3.0" + default: "0.4.0-dev" skip: description: "Perform release checks, such as the git tag, and swift version, or '--skip' to skip that." required: true @@ -24,7 +24,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Build Release Artifact - run: swift run build-swiftly-release ${{ inputs.skip }} ${{ inputs.version }} + run: swift run build-swiftly-release --use-rhel-ubi9 ${{ inputs.skip }} ${{ inputs.version }} - name: Upload Release Artifact uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 562f6e6..52fc776 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -23,7 +23,6 @@ jobs: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: - # Amazon Linux 2 won't work with GH infrastructure linux_os_versions: "[\"jammy\", \"focal\", \"rhel-ubi9\", \"noble\", \"bookworm\", \"fedora39\"]" # We only care about the current stable release, because that's where we make our swiftly releases linux_exclude_swift_versions: "[{\"swift_version\": \"nightly-main\"},{\"swift_version\": \"nightly-6.0\"},{\"swift_version\": \"5.8\"},{\"swift_version\": \"5.9\"},{\"swift_version\": \"5.10\"}]" @@ -39,7 +38,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Build Artifact - run: swift run build-swiftly-release --skip "999.0.0" + run: swift run build-swiftly-release --use-rhel-ubi9 --skip "999.0.0" - name: Upload Artifact uses: actions/upload-artifact@v4 with: diff --git a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift index a604725..e8cb1a9 100644 --- a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift +++ b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift @@ -126,7 +126,7 @@ public func getShell() async throws -> String { } #endif -public func isRHEL9() -> Bool { +public func isSupportedLinux(useRhelUbi9: Bool) -> Bool { let osReleaseFiles = ["/etc/os-release", "/usr/lib/os-release"] var releaseFile: String? for file in osReleaseFiles { @@ -165,8 +165,14 @@ public func isRHEL9() -> Bool { return false } - guard let versionID, versionID.hasPrefix("9"), (id + idlike).contains("rhel") else { - return false + if useRhelUbi9 { + guard let versionID, versionID.hasPrefix("9"), (id + idlike).contains("rhel") else { + return false + } + } else { + guard let versionID = versionID, versionID == "2", (id + idlike).contains("amzn") else { + return false + } } return true @@ -188,6 +194,9 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { @Option(help: "Package identifier of macOS package") var identifier: String = "org.swift.swiftly" +#elseif os(Linux) + @Flag(name: .long, help: "Use RHEL UBI9 as the supported Linux to build a release instead of Amazon Linux 2") + var useRhelUbi9: Bool = false #endif @Argument(help: "Version of swiftly to build the release.") @@ -286,11 +295,12 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { } func buildLinuxRelease() async throws { +#if os(Linux) // Check system requirements - guard isRHEL9() else { - // TODO: see if docker can be used to spawn an Amazon Linux 2 container to continue the release building process - throw Error(message: "Linux releases must be made from Amazon Linux 2 because it has the oldest version of glibc for maximum compatibility with other versions of Linux") + guard isSupportedLinux(useRhelUbi9: self.useRhelUbi9) else { + throw Error(message: "Linux releases must be made from specific distributions so that the binary can be used everyone else because it has the oldest version of glibc for maximum compatibility with other versions of Linux. Please try again with \(!self.useRhelUbi9 ? "Amazon Linux 2" : "RedHat UBI 9").") } +#endif // TODO: turn these into checks that the system meets the criteria for being capable of using the toolchain + checking for packages, not tools let curl = try await self.assertTool("curl", message: "Please install curl with `yum install curl`") From 150e93a11443cdcb1cd73e4975bf3ac6451ce195 Mon Sep 17 00:00:00 2001 From: Joseph Heck Date: Thu, 2 Jan 2025 05:47:29 -0800 Subject: [PATCH 6/6] Update README.md (#198) re-add old installation process/instructions --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c58d06..7ed58a0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,13 @@ Ongoing maintenance and stewardship of this project is led by the [SSWG](https:/ ### Installation -Download the swiftly package from [swift.org](https://swift.org/download) and it can install itself with init: +Install swiftly using a script (hosted from this repository) using the command: + +``` +curl -L https://swiftlang.github.io/swiftly/swiftly-install.sh | bash +``` + +In the future, download the swiftly package from [swift.org](https://swift.org/download) and it can install itself with init: ``` swiftly init