Skip to content

Make the toolchains directory location configurable #326

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
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
4 changes: 3 additions & 1 deletion Sources/LinuxPlatform/Linux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ public struct Linux: Platform {
}

public var swiftlyToolchainsDir: URL {
self.swiftlyHomeDir.appendingPathComponent("toolchains", isDirectory: true)
SwiftlyCore.mockedHomeDir.map { $0.appendingPathComponent("toolchains", isDirectory: true) }
?? ProcessInfo.processInfo.environment["SWIFTLY_TOOLCHAINS_DIR"].map { URL(fileURLWithPath: $0) }
?? FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".local/share/swiftly/toolchains")
}

public var toolchainFileExtension: String {
Expand Down
47 changes: 38 additions & 9 deletions Sources/MacOSPlatform/MacOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public struct MacOS: Platform {
.appendingPathComponent(".swiftly", isDirectory: true)
}

public var defaultToolchainsDirectory: URL {
FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent("Library/Developer/Toolchains", isDirectory: true)
}

public var swiftlyBinDir: URL {
SwiftlyCore.mockedHomeDir.map { $0.appendingPathComponent("bin", isDirectory: true) }
?? ProcessInfo.processInfo.environment["SWIFTLY_BIN_DIR"].map { URL(fileURLWithPath: $0) }
Expand All @@ -27,8 +32,9 @@ public struct MacOS: Platform {

public var swiftlyToolchainsDir: URL {
SwiftlyCore.mockedHomeDir.map { $0.appendingPathComponent("Toolchains", isDirectory: true) }
// The toolchains are always installed here by the installer. We bypass the installer in the case of test mocks
?? FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/Developer/Toolchains", isDirectory: true)
?? ProcessInfo.processInfo.environment["SWIFTLY_TOOLCHAINS_DIR"].map { URL(fileURLWithPath: $0) }
// This is where the installer will put the toolchains, and where Xcode can find them
?? self.defaultToolchainsDirectory
}

public var toolchainFileExtension: String {
Expand All @@ -54,22 +60,45 @@ public struct MacOS: Platform {
throw SwiftlyError(message: "\(tmpFile) doesn't exist")
}

if !self.swiftlyToolchainsDir.fileExists() {
try FileManager.default.createDirectory(at: self.swiftlyToolchainsDir, withIntermediateDirectories: false)
let toolchainsDir = self.swiftlyToolchainsDir

if !toolchainsDir.fileExists() {
try FileManager.default.createDirectory(
at: toolchainsDir, withIntermediateDirectories: true
)
}

if SwiftlyCore.mockedHomeDir == nil {
if toolchainsDir == self.defaultToolchainsDirectory {
// If the toolchains go into the default user location then we use the installer to install them
SwiftlyCore.print("Installing package in user home directory...")
try runProgram("installer", "-verbose", "-pkg", tmpFile.path, "-target", "CurrentUserHomeDirectory", quiet: !verbose)
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.
// Otherwise, we extract the pkg into the requested toolchains directory.
SwiftlyCore.print("Expanding pkg...")
let tmpDir = self.getTempFilePath()
let toolchainDir = self.swiftlyToolchainsDir.appendingPathComponent("\(version.identifier).xctoolchain", isDirectory: true)
let toolchainDir = toolchainsDir.appendingPathComponent(
"\(version.identifier).xctoolchain", isDirectory: true
)

if !toolchainDir.fileExists() {
try FileManager.default.createDirectory(at: toolchainDir, withIntermediateDirectories: false)
}

SwiftlyCore.print("Checking package signature...")
do {
try runProgram("pkgutil", "--check-signature", tmpFile.path, quiet: !verbose)
} catch {
// If this is not a test that uses mocked toolchains then we must throw this error and abort installation
guard SwiftlyCore.mockedHomeDir != nil else {
throw error
}

// We permit the signature verification to fail during testing
SwiftlyCore.print("Signature verification failed, which is allowable during testing with mocked toolchains")
}
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.
Expand Down
82 changes: 47 additions & 35 deletions Sources/Swiftly/Init.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ internal struct Init: SwiftlyCommand {

// Give the user the prompt and the choice to abort at this point.
if !assumeYes {
let toolchainsDir = Swiftly.currentPlatform.swiftlyToolchainsDir

var msg = """
Welcome to swiftly, the Swift toolchain manager for Linux and macOS!

Expand All @@ -76,12 +78,20 @@ internal struct Init: SwiftlyCommand {

\(Swiftly.currentPlatform.swiftlyHomeDir.path) - Directory for configuration files
\(Swiftly.currentPlatform.swiftlyBinDir.path) - Links to the binaries of the active toolchain
\(Swiftly.currentPlatform.swiftlyToolchainsDir.path) - Directory hosting installed toolchains
\(toolchainsDir.path) - Directory hosting installed toolchains

These locations can be changed by setting the environment variables
SWIFTLY_HOME_DIR and SWIFTLY_BIN_DIR before running 'swiftly init' again.
SWIFTLY_HOME_DIR, SWIFTLY_BIN_DIR, and SWIFTLY_TOOLCHAINS_DIR before running 'swiftly init' again.

"""
#if os(macOS)
if toolchainsDir != FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/Developer/Toolchains", isDirectory: true) {
msg += """

NOTE: The toolchains are not being installed in a standard macOS location, so Xcode may not be able to find them.
"""
}
#endif
if !skipInstall {
msg += """

Expand Down Expand Up @@ -177,6 +187,7 @@ internal struct Init: SwiftlyCommand {
env = """
set -x SWIFTLY_HOME_DIR "\(Swiftly.currentPlatform.swiftlyHomeDir.path)"
set -x SWIFTLY_BIN_DIR "\(Swiftly.currentPlatform.swiftlyBinDir.path)"
set -x SWIFTLY_TOOLCHAINS_DIR "\(Swiftly.currentPlatform.swiftlyToolchainsDir.path)"
if not contains "$SWIFTLY_BIN_DIR" $PATH
set -x PATH "$SWIFTLY_BIN_DIR" $PATH
end
Expand All @@ -186,6 +197,7 @@ internal struct Init: SwiftlyCommand {
env = """
export SWIFTLY_HOME_DIR="\(Swiftly.currentPlatform.swiftlyHomeDir.path)"
export SWIFTLY_BIN_DIR="\(Swiftly.currentPlatform.swiftlyBinDir.path)"
export SWIFTLY_TOOLCHAINS_DIR="\(Swiftly.currentPlatform.swiftlyToolchainsDir.path)"
if [[ ":$PATH:" != *":$SWIFTLY_BIN_DIR:"* ]]; then
export PATH="$SWIFTLY_BIN_DIR:$PATH"
fi
Expand Down Expand Up @@ -241,50 +253,50 @@ 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)
}
}

if !quietShellFollowup {
SwiftlyCore.print("""
To begin using installed swiftly from your current shell, first run the following command:
\(sourceLine)
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 !quietShellFollowup {
SwiftlyCore.print("""
To begin using installed swiftly from your current shell, first run the following command:
\(sourceLine)

// Fish doesn't have path caching, so this might only be needed for bash/zsh
if pathChanged && !quietShellFollowup && !shell.hasSuffix("fish") {
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 update your
shell's environment by running
""")
}

hash -r
// Fish doesn't have path caching, so this might only be needed for bash/zsh
if pathChanged && !quietShellFollowup && !shell.hasSuffix("fish") {
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 update your
shell's environment by running

or restarting your shell.
hash -r

""")
}
or restarting your shell.

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)
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)

""")
}
}
}
5 changes: 4 additions & 1 deletion Sources/SwiftlyCore/Platform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,10 @@ extension Platform {

let tcPath = self.findToolchainLocation(toolchain).appendingPathComponent("usr/bin")
guard tcPath.fileExists() else {
throw SwiftlyError(message: "Toolchain \(toolchain) could not be located. You can try `swiftly uninstall \(toolchain)` to uninstall it and then `swiftly install \(toolchain)` to install it again.")
throw SwiftlyError(
message:
"Toolchain \(toolchain) could not be located in \(tcPath). You can try `swiftly uninstall \(toolchain)` to uninstall it and then `swiftly install \(toolchain)` to install it again."
)
}

var pathComponents = (newEnv["PATH"] ?? "").split(separator: ":").map { String($0) }
Expand Down