From cad1353f1e04f8ad9dc47ede24b6cf18593b2fff Mon Sep 17 00:00:00 2001 From: Chris McGee <87777443+cmcgee1024@users.noreply.github.com> Date: Thu, 27 Feb 2025 17:41:26 -0500 Subject: [PATCH 01/11] Update swiftly version to 1.0.0 and add upgrade routine for 0.4.0 (#237) --- Sources/Swiftly/Init.swift | 9 +++++++-- Sources/SwiftlyCore/SwiftlyCore.swift | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/Swiftly/Init.swift b/Sources/Swiftly/Init.swift index 74924913..e2c6fa9c 100644 --- a/Sources/Swiftly/Init.swift +++ b/Sources/Swiftly/Init.swift @@ -37,8 +37,13 @@ internal struct Init: SwiftlyCommand { var config = try? Config.load() - if var config, !overwrite && config.version == SwiftlyVersion(major: 0, minor: 4, patch: 0, suffix: "dev") { - // This is a simple upgrade from the 0.4.0-dev pre-release + if var config, !overwrite && + ( + config.version == SwiftlyVersion(major: 0, minor: 4, patch: 0, suffix: "dev") || + config.version == SwiftlyVersion(major: 0, minor: 4, patch: 0) + ) + { + // This is a simple upgrade from the 0.4.0-dev pre-release, or 0.4.0 release // Move our executable over to the correct place try Swiftly.currentPlatform.installSwiftlyBin() diff --git a/Sources/SwiftlyCore/SwiftlyCore.swift b/Sources/SwiftlyCore/SwiftlyCore.swift index 1988ef3b..94cf8388 100644 --- a/Sources/SwiftlyCore/SwiftlyCore.swift +++ b/Sources/SwiftlyCore/SwiftlyCore.swift @@ -1,6 +1,6 @@ import Foundation -public let version = SwiftlyVersion(major: 0, minor: 4, patch: 0) +public let version = SwiftlyVersion(major: 1, minor: 0, patch: 0) /// A separate home directory to use for testing purposes. This overrides swiftly's default /// home directory location logic. From 0237d9a6ff198d775f19d1ab44910afe12400a52 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Fri, 28 Feb 2025 16:38:21 -0500 Subject: [PATCH 02/11] Handle macOS system install of pkg Prepare for shell one-liner that update the shell environment --- Sources/Swiftly/Init.swift | 6 ++++-- .../BuildSwiftlyRelease.swift | 6 ++++++ scripts/package/postinstall | 15 +++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100755 scripts/package/postinstall diff --git a/Sources/Swiftly/Init.swift b/Sources/Swiftly/Init.swift index e2c6fa9c..966dda79 100644 --- a/Sources/Swiftly/Init.swift +++ b/Sources/Swiftly/Init.swift @@ -18,6 +18,8 @@ internal struct Init: SwiftlyCommand { var platform: String? @Flag(help: "Skip installing the latest toolchain") var skipInstall: Bool = false + @Flag(help: "Quiet shell follow up commands") + var quietShellFollowup: Bool = false @OptionGroup var root: GlobalOptions @@ -237,7 +239,7 @@ internal struct Init: SwiftlyCommand { (postInstall, pathChanged) = try await Install.execute(version: latestVersion, &config, useInstalledToolchain: true, verifySignature: true, verbose: verbose, assumeYes: assumeYes) } - if addEnvToProfile { + if addEnvToProfile && !self.quietShellFollowup { try Data(sourceLine.utf8).append(to: profileHome) SwiftlyCore.print(""" @@ -247,7 +249,7 @@ internal struct Init: SwiftlyCommand { """) } - if pathChanged { + if pathChanged && !self.quietShellFollowup { 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. diff --git a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift index ec5e203c..9c442c5f 100644 --- a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift +++ b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift @@ -406,6 +406,8 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { try? FileManager.default.createDirectory(atPath: swiftlyLicenseDir, withIntermediateDirectories: true) try await self.collectLicenses(swiftlyLicenseDir) + let cwd = FileManager.default.currentDirectoryPath + if let cert { try runProgram( pkgbuild, @@ -413,6 +415,8 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { swiftlyBinDir + "/..", "--install-location", "usr/local", + "--scripts", + "\(cwd)/scripts/package", "--version", self.version, "--identifier", @@ -428,6 +432,8 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { swiftlyBinDir + "/..", "--install-location", "usr/local", + "--scripts", + "\(cwd)/scripts/package", "--version", self.version, "--identifier", diff --git a/scripts/package/postinstall b/scripts/package/postinstall new file mode 100755 index 00000000..9a5c9fa1 --- /dev/null +++ b/scripts/package/postinstall @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +# If the user happens to install this into the system then headlessly re-install it in their +# user home directory instead so that they can self-update. + +if [ "$DSTROOT" == "/usr/local" ]; then + realuser=$(basename "$HOME") + if [ "$realuser" != "root" ]; then + sudo -u "$realuser" installer -pkg "$PACKAGE_PATH" -target CurrentUserHomeDirectory + pkgutil --forget org.swift.swiftly --volume / || true + rm /usr/local/bin/swiftly + fi +fi From 4e49a4ca7cfb14b0b556a4a6b7db0c295d821e05 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Fri, 28 Feb 2025 16:54:28 -0500 Subject: [PATCH 03/11] Fix compile errors --- Sources/Swiftly/Init.swift | 8 ++++---- Sources/Swiftly/Proxy.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Swiftly/Init.swift b/Sources/Swiftly/Init.swift index 966dda79..077732e9 100644 --- a/Sources/Swiftly/Init.swift +++ b/Sources/Swiftly/Init.swift @@ -30,11 +30,11 @@ internal struct Init: SwiftlyCommand { 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, verbose: self.root.verbose, skipInstall: self.skipInstall) + try await Self.execute(assumeYes: self.root.assumeYes, noModifyProfile: self.noModifyProfile, overwrite: self.overwrite, platform: self.platform, verbose: self.root.verbose, skipInstall: self.skipInstall, quietShellFollowup: self.quietShellFollowup) } /// Initialize the installation of swiftly. - internal static func execute(assumeYes: Bool, noModifyProfile: Bool, overwrite: Bool, platform: String?, verbose: Bool, skipInstall: Bool) async throws { + internal static func execute(assumeYes: Bool, noModifyProfile: Bool, overwrite: Bool, platform: String?, verbose: Bool, skipInstall: Bool, quietShellFollowup: Bool) async throws { try Swiftly.currentPlatform.verifySwiftlySystemPrerequisites() var config = try? Config.load() @@ -239,7 +239,7 @@ internal struct Init: SwiftlyCommand { (postInstall, pathChanged) = try await Install.execute(version: latestVersion, &config, useInstalledToolchain: true, verifySignature: true, verbose: verbose, assumeYes: assumeYes) } - if addEnvToProfile && !self.quietShellFollowup { + if addEnvToProfile && !quietShellFollowup { try Data(sourceLine.utf8).append(to: profileHome) SwiftlyCore.print(""" @@ -249,7 +249,7 @@ internal struct Init: SwiftlyCommand { """) } - if pathChanged && !self.quietShellFollowup { + if pathChanged && !quietShellFollowup { 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. diff --git a/Sources/Swiftly/Proxy.swift b/Sources/Swiftly/Proxy.swift index 9d46fcc9..a389e444 100644 --- a/Sources/Swiftly/Proxy.swift +++ b/Sources/Swiftly/Proxy.swift @@ -25,7 +25,7 @@ public enum Proxy { 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) + try await Init.execute(assumeYes: false, noModifyProfile: false, overwrite: false, platform: nil, verbose: false, skipInstall: false, quietShellFollowup: false) return } else if CommandLine.arguments.count >= 2 && CommandLine.arguments[1] == "init" { // Let the user run the init command with their arguments, if any. From 429cb73fe6b3ffe7f2ac46c1627881db24e69671 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Fri, 28 Feb 2025 16:56:18 -0500 Subject: [PATCH 04/11] Update the documentation --- Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md b/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md index 1ba9b5a6..8a667e17 100644 --- a/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md +++ b/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md @@ -405,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=] [--skip-install] [--assume-yes] [--verbose] [--version] [--help] +swiftly init [--no-modify-profile] [--overwrite] [--platform=] [--skip-install] [--quiet-shell-followup] [--assume-yes] [--verbose] [--version] [--help] ``` **--no-modify-profile:** @@ -428,6 +428,11 @@ swiftly init [--no-modify-profile] [--overwrite] [--platform=] [--skip *Skip installing the latest toolchain* +**--quiet-shell-followup:** + +*Quiet shell follow up commands* + + **--assume-yes:** *Disable confirmation prompts by assuming 'yes'* From bd1f0416df157de7f49d2c024e09436a50444611 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Fri, 28 Feb 2025 17:02:23 -0500 Subject: [PATCH 05/11] Add missing coding key --- Sources/Swiftly/Init.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Swiftly/Init.swift b/Sources/Swiftly/Init.swift index 077732e9..53dca68b 100644 --- a/Sources/Swiftly/Init.swift +++ b/Sources/Swiftly/Init.swift @@ -24,7 +24,7 @@ internal struct Init: SwiftlyCommand { @OptionGroup var root: GlobalOptions private enum CodingKeys: String, CodingKey { - case noModifyProfile, overwrite, platform, skipInstall, root + case noModifyProfile, overwrite, platform, skipInstall, root, quietShellFollowup } public mutating func validate() throws {} From 8325ebc0e4f5a54cbbf47d7a3ce14be952f2d86b Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Fri, 28 Feb 2025 18:10:18 -0500 Subject: [PATCH 06/11] Copy swiftly to the user home directory instead of install it in their home directory --- scripts/package/postinstall | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/package/postinstall b/scripts/package/postinstall index 9a5c9fa1..6af4d351 100755 --- a/scripts/package/postinstall +++ b/scripts/package/postinstall @@ -8,7 +8,7 @@ set -e if [ "$DSTROOT" == "/usr/local" ]; then realuser=$(basename "$HOME") if [ "$realuser" != "root" ]; then - sudo -u "$realuser" installer -pkg "$PACKAGE_PATH" -target CurrentUserHomeDirectory + sudo -u "$realuser" cp /usr/local/bin/swiftly "$HOME" pkgutil --forget org.swift.swiftly --volume / || true rm /usr/local/bin/swiftly fi From d4cdd224a8685be683fca3d151cd210498bb0896 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Tue, 11 Mar 2025 13:21:23 -0400 Subject: [PATCH 07/11] Pivot to a synthesized product build and custom distribution plist --- .../BuildSwiftlyRelease.swift | 30 +++++++++++++++---- scripts/package/postinstall | 15 ---------- 2 files changed, 24 insertions(+), 21 deletions(-) delete mode 100755 scripts/package/postinstall diff --git a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift index 9c442c5f..19e2c92b 100644 --- a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift +++ b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift @@ -215,14 +215,14 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { func checkSwiftRequirement() async throws -> String { guard !self.skip else { - return try await self.assertTool("swift", message: "Please install swift and make sure that it is added to your path.") + return try await self.assertTool("/usr/bin/swift", message: "Please install swift and make sure that it is added to your path.") } guard let requiredSwiftVersion = try? self.findSwiftVersion() else { throw Error(message: "Unable to determine the required swift version for this version of swiftly. Please make sure that you `cd ` and there is a .swift-version file there.") } - let swift = try await self.assertTool("swift", message: "Please install swift \(requiredSwiftVersion) and make sure that it is added to your path.") + let swift = try await self.assertTool("/usr/bin/swift", message: "Please install swift \(requiredSwiftVersion) and make sure that it is added to your path.") // We also need a swift toolchain with the correct version guard let swiftVersion = try await runProgramOutput(swift, "--version"), swiftVersion.contains("Swift version \(requiredSwiftVersion)") else { @@ -408,6 +408,8 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { let cwd = FileManager.default.currentDirectoryPath + let pkgFile = URL(fileURLWithPath: cwd + "/.build/release/swiftly-\(self.version).pkg") + if let cert { try runProgram( pkgbuild, @@ -415,8 +417,6 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { swiftlyBinDir + "/..", "--install-location", "usr/local", - "--scripts", - "\(cwd)/scripts/package", "--version", self.version, "--identifier", @@ -432,8 +432,6 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { swiftlyBinDir + "/..", "--install-location", "usr/local", - "--scripts", - "\(cwd)/scripts/package", "--version", self.version, "--identifier", @@ -441,5 +439,25 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { ".build/release/swiftly-\(self.version).pkg" ) } + + // Re-configure the pkg to prefer installs into the current user's home directory with the help of productbuild. + // Note that command-line installs can override this preference, but the GUI install will limit the choices. + + let pkgFileReconfigured = URL(fileURLWithPath: cwd + "/.build/release/swiftly-\(self.version)-reconfigured.pkg") + let distFile = URL(fileURLWithPath: cwd + "/.build/release/distribution.plist") + + try runProgram("productbuild", "--synthesize", "--package", pkgFile.path, distFile.path) + + var distFileContents = try String(contentsOf: distFile, encoding: .utf8) + distFileContents = distFileContents.replacingOccurrences(of: "", with: "") + try distFileContents.write(to: distFile, atomically: true, encoding: .utf8) + + if let cert = cert { + try runProgram("productbuild", "--distribution", distFile.path, "--package-path", pkgFile.path, "--sign", cert, pkgFileReconfigured.path) + } else { + try runProgram("productbuild", "--distribution", distFile.path, "--package-path", pkgFile.path, pkgFileReconfigured.path) + } + try FileManager.default.removeItem(at: pkgFile) + try FileManager.default.copyItem(atPath: pkgFileReconfigured.path, toPath: pkgFile.path) } } diff --git a/scripts/package/postinstall b/scripts/package/postinstall deleted file mode 100755 index 6af4d351..00000000 --- a/scripts/package/postinstall +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -e - -# If the user happens to install this into the system then headlessly re-install it in their -# user home directory instead so that they can self-update. - -if [ "$DSTROOT" == "/usr/local" ]; then - realuser=$(basename "$HOME") - if [ "$realuser" != "root" ]; then - sudo -u "$realuser" cp /usr/local/bin/swiftly "$HOME" - pkgutil --forget org.swift.swiftly --volume / || true - rm /usr/local/bin/swiftly - fi -fi From 85e23871f3b44f1e631258aeb3e588ac223d19f3 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Wed, 12 Mar 2025 08:29:52 -0400 Subject: [PATCH 08/11] Add more output to diagnose build release issue --- Tools/build-swiftly-release/BuildSwiftlyRelease.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift index 19e2c92b..bde846f7 100644 --- a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift +++ b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift @@ -78,13 +78,14 @@ public func runProgram(_ args: String..., quiet: Bool = false) throws { } public func runProgramOutput(_ program: String, _ args: String...) async throws -> String? { + print("\(args.joined(separator: " "))") + let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/bin/env") process.arguments = [program] + args let outPipe = Pipe() process.standardInput = FileHandle.nullDevice - process.standardError = FileHandle.nullDevice process.standardOutput = outPipe try process.run() @@ -98,6 +99,7 @@ public func runProgramOutput(_ program: String, _ args: String...) async throws process.waitUntilExit() guard process.terminationStatus == 0 else { + print("\(args.first!) exited with non-zero status: \(process.terminationStatus)") throw Error(message: "\(args.first!) exited with non-zero status: \(process.terminationStatus)") } From 53335e15568c77137f385378e149b3cb27216650 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Wed, 12 Mar 2025 08:34:04 -0400 Subject: [PATCH 09/11] Remove swift fully qualified paths --- Tools/build-swiftly-release/BuildSwiftlyRelease.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift index bde846f7..a901dcb3 100644 --- a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift +++ b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift @@ -217,14 +217,14 @@ struct BuildSwiftlyRelease: AsyncParsableCommand { func checkSwiftRequirement() async throws -> String { guard !self.skip else { - return try await self.assertTool("/usr/bin/swift", message: "Please install swift and make sure that it is added to your path.") + return try await self.assertTool("swift", message: "Please install swift and make sure that it is added to your path.") } guard let requiredSwiftVersion = try? self.findSwiftVersion() else { throw Error(message: "Unable to determine the required swift version for this version of swiftly. Please make sure that you `cd ` and there is a .swift-version file there.") } - let swift = try await self.assertTool("/usr/bin/swift", message: "Please install swift \(requiredSwiftVersion) and make sure that it is added to your path.") + let swift = try await self.assertTool("swift", message: "Please install swift \(requiredSwiftVersion) and make sure that it is added to your path.") // We also need a swift toolchain with the correct version guard let swiftVersion = try await runProgramOutput(swift, "--version"), swiftVersion.contains("Swift version \(requiredSwiftVersion)") else { From 4be12e690aba90dd0b9a91f16af5770e83847d49 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Wed, 12 Mar 2025 08:47:14 -0400 Subject: [PATCH 10/11] Properly report the runProgramOutput() command line --- 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 a901dcb3..9f06ade5 100644 --- a/Tools/build-swiftly-release/BuildSwiftlyRelease.swift +++ b/Tools/build-swiftly-release/BuildSwiftlyRelease.swift @@ -78,7 +78,7 @@ public func runProgram(_ args: String..., quiet: Bool = false) throws { } public func runProgramOutput(_ program: String, _ args: String...) async throws -> String? { - print("\(args.joined(separator: " "))") + print("\(program) \(args.joined(separator: " "))") let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/bin/env") From 34948bd663a2060544850eac1c8aeff56818adde Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Thu, 13 Mar 2025 11:07:39 -0400 Subject: [PATCH 11/11] Fix merge error --- Sources/Swiftly/Init.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Sources/Swiftly/Init.swift b/Sources/Swiftly/Init.swift index 4b6f2b57..5ce648bc 100644 --- a/Sources/Swiftly/Init.swift +++ b/Sources/Swiftly/Init.swift @@ -42,18 +42,11 @@ internal struct Init: SwiftlyCommand { if var config, !overwrite && ( config.version == SwiftlyVersion(major: 0, minor: 4, patch: 0, suffix: "dev") || -<<<<<<< HEAD - config.version == SwiftlyVersion(major: 0, minor: 4, patch: 0) - ) - { - // This is a simple upgrade from the 0.4.0-dev pre-release, or 0.4.0 release -======= config.version == SwiftlyVersion(major: 0, minor: 4, patch: 0) || (config.version?.major == 1 && config.version?.minor == 0) ) { // This is a simple upgrade from the 0.4.0 pre-releases, or 1.x ->>>>>>> 726ea90b9c304068d9649abc8718f8752169ce7b // Move our executable over to the correct place try Swiftly.currentPlatform.installSwiftlyBin()