Skip to content

Commit 475203c

Browse files
authored
Make the toolchains directory location configurable (#326)
Make the toolchains directory location configurable (#324) Using a new environment variable SWIFTLY_TOOLCHAINS_DIR that follows a similar pattern as SWIFTLY_HOME_DIR and SWIFTLY_BIN_DIR make it possible for the user to configure the toolchain location on init in the same way. On macOS, this level of configuration requires a different approach to extracting the toolchain whenever the installation location is anything other than the default. Use the pkgutil utility of macOS to extract the toolchain in the location specified by the user. Installing the toolchains outside of the installer in a custom location means that Xcode may not be able to pick them up easily. Make a note of that in the init screen so that users are aware. When using the init `--no-modify-profile` flag the init was skiping steps like installing the latest toolchain, and emitting the notes about updating the current shell environment. Separate this logic so that the user can have these steps performed with the flag.
1 parent 92652d9 commit 475203c

File tree

4 files changed

+92
-46
lines changed

4 files changed

+92
-46
lines changed

Sources/LinuxPlatform/Linux.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ public struct Linux: Platform {
3737
}
3838

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

4345
public var toolchainFileExtension: String {

Sources/MacOSPlatform/MacOS.swift

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public struct MacOS: Platform {
1818
.appendingPathComponent(".swiftly", isDirectory: true)
1919
}
2020

21+
public var defaultToolchainsDirectory: URL {
22+
FileManager.default.homeDirectoryForCurrentUser
23+
.appendingPathComponent("Library/Developer/Toolchains", isDirectory: true)
24+
}
25+
2126
public var swiftlyBinDir: URL {
2227
SwiftlyCore.mockedHomeDir.map { $0.appendingPathComponent("bin", isDirectory: true) }
2328
?? ProcessInfo.processInfo.environment["SWIFTLY_BIN_DIR"].map { URL(fileURLWithPath: $0) }
@@ -27,8 +32,9 @@ public struct MacOS: Platform {
2732

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

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

57-
if !self.swiftlyToolchainsDir.fileExists() {
58-
try FileManager.default.createDirectory(at: self.swiftlyToolchainsDir, withIntermediateDirectories: false)
63+
let toolchainsDir = self.swiftlyToolchainsDir
64+
65+
if !toolchainsDir.fileExists() {
66+
try FileManager.default.createDirectory(
67+
at: toolchainsDir, withIntermediateDirectories: true
68+
)
5969
}
6070

61-
if SwiftlyCore.mockedHomeDir == nil {
71+
if toolchainsDir == self.defaultToolchainsDirectory {
72+
// If the toolchains go into the default user location then we use the installer to install them
6273
SwiftlyCore.print("Installing package in user home directory...")
63-
try runProgram("installer", "-verbose", "-pkg", tmpFile.path, "-target", "CurrentUserHomeDirectory", quiet: !verbose)
74+
try runProgram(
75+
"installer", "-verbose", "-pkg", tmpFile.path, "-target", "CurrentUserHomeDirectory",
76+
quiet: !verbose
77+
)
6478
} else {
65-
// In the case of a mock for testing purposes we won't use the installer, perferring a manual process because
66-
// the installer will not install to an arbitrary path, only a volume or user home directory.
79+
// Otherwise, we extract the pkg into the requested toolchains directory.
6780
SwiftlyCore.print("Expanding pkg...")
6881
let tmpDir = self.getTempFilePath()
69-
let toolchainDir = self.swiftlyToolchainsDir.appendingPathComponent("\(version.identifier).xctoolchain", isDirectory: true)
82+
let toolchainDir = toolchainsDir.appendingPathComponent(
83+
"\(version.identifier).xctoolchain", isDirectory: true
84+
)
85+
7086
if !toolchainDir.fileExists() {
7187
try FileManager.default.createDirectory(at: toolchainDir, withIntermediateDirectories: false)
7288
}
89+
90+
SwiftlyCore.print("Checking package signature...")
91+
do {
92+
try runProgram("pkgutil", "--check-signature", tmpFile.path, quiet: !verbose)
93+
} catch {
94+
// If this is not a test that uses mocked toolchains then we must throw this error and abort installation
95+
guard SwiftlyCore.mockedHomeDir != nil else {
96+
throw error
97+
}
98+
99+
// We permit the signature verification to fail during testing
100+
SwiftlyCore.print("Signature verification failed, which is allowable during testing with mocked toolchains")
101+
}
73102
try runProgram("pkgutil", "--verbose", "--expand", tmpFile.path, tmpDir.path, quiet: !verbose)
74103
// There's a slight difference in the location of the special Payload file between official swift packages
75104
// and the ones that are mocked here in the test framework.

Sources/Swiftly/Init.swift

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ internal struct Init: SwiftlyCommand {
6565

6666
// Give the user the prompt and the choice to abort at this point.
6767
if !assumeYes {
68+
let toolchainsDir = Swiftly.currentPlatform.swiftlyToolchainsDir
69+
6870
var msg = """
6971
Welcome to swiftly, the Swift toolchain manager for Linux and macOS!
7072
@@ -76,12 +78,20 @@ internal struct Init: SwiftlyCommand {
7678
7779
\(Swiftly.currentPlatform.swiftlyHomeDir.path) - Directory for configuration files
7880
\(Swiftly.currentPlatform.swiftlyBinDir.path) - Links to the binaries of the active toolchain
79-
\(Swiftly.currentPlatform.swiftlyToolchainsDir.path) - Directory hosting installed toolchains
81+
\(toolchainsDir.path) - Directory hosting installed toolchains
8082
8183
These locations can be changed by setting the environment variables
82-
SWIFTLY_HOME_DIR and SWIFTLY_BIN_DIR before running 'swiftly init' again.
84+
SWIFTLY_HOME_DIR, SWIFTLY_BIN_DIR, and SWIFTLY_TOOLCHAINS_DIR before running 'swiftly init' again.
8385
8486
"""
87+
#if os(macOS)
88+
if toolchainsDir != FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/Developer/Toolchains", isDirectory: true) {
89+
msg += """
90+
91+
NOTE: The toolchains are not being installed in a standard macOS location, so Xcode may not be able to find them.
92+
"""
93+
}
94+
#endif
8595
if !skipInstall {
8696
msg += """
8797
@@ -177,6 +187,7 @@ internal struct Init: SwiftlyCommand {
177187
env = """
178188
set -x SWIFTLY_HOME_DIR "\(Swiftly.currentPlatform.swiftlyHomeDir.path)"
179189
set -x SWIFTLY_BIN_DIR "\(Swiftly.currentPlatform.swiftlyBinDir.path)"
190+
set -x SWIFTLY_TOOLCHAINS_DIR "\(Swiftly.currentPlatform.swiftlyToolchainsDir.path)"
180191
if not contains "$SWIFTLY_BIN_DIR" $PATH
181192
set -x PATH "$SWIFTLY_BIN_DIR" $PATH
182193
end
@@ -186,6 +197,7 @@ internal struct Init: SwiftlyCommand {
186197
env = """
187198
export SWIFTLY_HOME_DIR="\(Swiftly.currentPlatform.swiftlyHomeDir.path)"
188199
export SWIFTLY_BIN_DIR="\(Swiftly.currentPlatform.swiftlyBinDir.path)"
200+
export SWIFTLY_TOOLCHAINS_DIR="\(Swiftly.currentPlatform.swiftlyToolchainsDir.path)"
189201
if [[ ":$PATH:" != *":$SWIFTLY_BIN_DIR:"* ]]; then
190202
export PATH="$SWIFTLY_BIN_DIR:$PATH"
191203
fi
@@ -241,50 +253,50 @@ internal struct Init: SwiftlyCommand {
241253
addEnvToProfile = true
242254
}
243255

244-
var postInstall: String?
245-
var pathChanged = false
246-
247-
if !skipInstall {
248-
let latestVersion = try await Install.resolve(config: config, selector: ToolchainSelector.latest)
249-
(postInstall, pathChanged) = try await Install.execute(version: latestVersion, &config, useInstalledToolchain: true, verifySignature: true, verbose: verbose, assumeYes: assumeYes)
250-
}
251-
252256
if addEnvToProfile {
253257
try Data(sourceLine.utf8).append(to: profileHome)
258+
}
259+
}
254260

255-
if !quietShellFollowup {
256-
SwiftlyCore.print("""
257-
To begin using installed swiftly from your current shell, first run the following command:
258-
\(sourceLine)
261+
var postInstall: String?
262+
var pathChanged = false
259263

260-
""")
261-
}
262-
}
264+
if !skipInstall {
265+
let latestVersion = try await Install.resolve(config: config, selector: ToolchainSelector.latest)
266+
(postInstall, pathChanged) = try await Install.execute(version: latestVersion, &config, useInstalledToolchain: true, verifySignature: true, verbose: verbose, assumeYes: assumeYes)
267+
}
268+
269+
if !quietShellFollowup {
270+
SwiftlyCore.print("""
271+
To begin using installed swiftly from your current shell, first run the following command:
272+
\(sourceLine)
263273
264-
// Fish doesn't have path caching, so this might only be needed for bash/zsh
265-
if pathChanged && !quietShellFollowup && !shell.hasSuffix("fish") {
266-
SwiftlyCore.print("""
267-
Your shell caches items on your path for better performance. Swiftly has added
268-
items to your path that may not get picked up right away. You can update your
269-
shell's environment by running
274+
""")
275+
}
270276

271-
hash -r
277+
// Fish doesn't have path caching, so this might only be needed for bash/zsh
278+
if pathChanged && !quietShellFollowup && !shell.hasSuffix("fish") {
279+
SwiftlyCore.print("""
280+
Your shell caches items on your path for better performance. Swiftly has added
281+
items to your path that may not get picked up right away. You can update your
282+
shell's environment by running
272283
273-
or restarting your shell.
284+
hash -r
274285
275-
""")
276-
}
286+
or restarting your shell.
277287
278-
if let postInstall {
279-
SwiftlyCore.print("""
280-
There are some dependencies that should be installed before using this toolchain.
281-
You can run the following script as the system administrator (e.g. root) to prepare
282-
your system:
288+
""")
289+
}
283290

284-
\(postInstall)
291+
if let postInstall {
292+
SwiftlyCore.print("""
293+
There are some dependencies that should be installed before using this toolchain.
294+
You can run the following script as the system administrator (e.g. root) to prepare
295+
your system:
285296
286-
""")
287-
}
297+
\(postInstall)
298+
299+
""")
288300
}
289301
}
290302
}

Sources/SwiftlyCore/Platform.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ extension Platform {
144144

145145
let tcPath = self.findToolchainLocation(toolchain).appendingPathComponent("usr/bin")
146146
guard tcPath.fileExists() else {
147-
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.")
147+
throw SwiftlyError(
148+
message:
149+
"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."
150+
)
148151
}
149152

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

0 commit comments

Comments
 (0)