Skip to content

Installation workflow improvements #249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<platform>] [--skip-install] [--assume-yes] [--verbose] [--version] [--help]
swiftly init [--no-modify-profile] [--overwrite] [--platform=<platform>] [--skip-install] [--quiet-shell-followup] [--assume-yes] [--verbose] [--version] [--help]
```

**--no-modify-profile:**
Expand All @@ -428,6 +428,11 @@ swiftly init [--no-modify-profile] [--overwrite] [--platform=<platform>] [--skip
*Skip installing the latest toolchain*


**--quiet-shell-followup:**

*Quiet shell follow up commands*


**--assume-yes:**

*Disable confirmation prompts by assuming 'yes'*
Expand Down
12 changes: 7 additions & 5 deletions Sources/Swiftly/Init.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,23 @@ 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

private enum CodingKeys: String, CodingKey {
case noModifyProfile, overwrite, platform, skipInstall, root
case noModifyProfile, overwrite, platform, skipInstall, root, quietShellFollowup
}

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()
Expand Down Expand Up @@ -238,7 +240,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 && !quietShellFollowup {
try Data(sourceLine.utf8).append(to: profileHome)

SwiftlyCore.print("""
Expand All @@ -248,7 +250,7 @@ internal struct Init: SwiftlyCommand {
""")
}

if pathChanged {
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.

Expand Down
2 changes: 1 addition & 1 deletion Sources/Swiftly/Proxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
28 changes: 27 additions & 1 deletion Tools/build-swiftly-release/BuildSwiftlyRelease.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,14 @@ public func runProgram(_ args: String..., quiet: Bool = false) throws {
}

public func runProgramOutput(_ program: String, _ args: String...) async throws -> String? {
print("\(program) \(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()
Expand All @@ -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)")
}

Expand Down Expand Up @@ -406,6 +408,10 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
try? FileManager.default.createDirectory(atPath: swiftlyLicenseDir, withIntermediateDirectories: true)
try await self.collectLicenses(swiftlyLicenseDir)

let cwd = FileManager.default.currentDirectoryPath

let pkgFile = URL(fileURLWithPath: cwd + "/.build/release/swiftly-\(self.version).pkg")

if let cert {
try runProgram(
pkgbuild,
Expand Down Expand Up @@ -435,5 +441,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: "<choices-outline>", with: "<domains enable_anywhere=\"false\" enable_currentUserHome=\"true\" enable_localSystem=\"false\"/><choices-outline>")
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)
}
}