Skip to content

Commit a8a107e

Browse files
committed
Refactor config loading, use in use toolchain when linking
1 parent bc17ef1 commit a8a107e

File tree

9 files changed

+79
-163
lines changed

9 files changed

+79
-163
lines changed

Sources/Swiftly/Install.swift

Lines changed: 41 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ struct Install: SwiftlyCommand {
8888
}
8989

9090
var config = try await Config.load(ctx)
91+
let toolchainVersion = try await Self.determineToolchainVersion(ctx, version: self.version, config: &config)
9192

9293
let (postInstallScript, pathChanged) = try await Self.execute(
9394
ctx,
@@ -109,12 +110,29 @@ struct Install: SwiftlyCommand {
109110

110111
// Fish doesn't cache its path, so this instruction is not necessary.
111112
if pathChanged && !shell.hasSuffix("fish") {
112-
await ctx.print(Messages.refreshShell)
113+
await ctx.print(
114+
"""
115+
NOTE: Swiftly has updated some elements in your path and your shell may not yet be
116+
aware of the changes. You can update your shell's environment by running
117+
118+
hash -r
119+
120+
or restarting your shell.
121+
122+
""")
113123
}
114124

115125
if let postInstallScript {
116126
guard let postInstallFile = self.postInstallFile else {
117-
throw SwiftlyError(message: Messages.postInstall(postInstallScript))
127+
throw SwiftlyError(
128+
message: """
129+
130+
There are some dependencies that should be installed before using this toolchain.
131+
You can run the following script as the system administrator (e.g. root) to prepare
132+
your system:
133+
134+
\(postInstallScript)
135+
""")
118136
}
119137

120138
try Data(postInstallScript.utf8).write(
@@ -132,21 +150,23 @@ struct Install: SwiftlyCommand {
132150
var pathChanged = false
133151

134152
// Create proxies if we have a location where we can point them
135-
if let proxyTo = try? Swiftly.currentPlatform.findSwiftlyBin(ctx) {
153+
if let proxyTo = try? await Swiftly.currentPlatform.findSwiftlyBin(ctx) {
136154
// Ensure swiftly doesn't overwrite any existing executables without getting confirmation first.
137155
let swiftlyBinDir = Swiftly.currentPlatform.swiftlyBinDir(ctx)
138156
let swiftlyBinDirContents =
139-
(try? FileManager.default.contentsOfDirectory(atPath: swiftlyBinDir.path)) ?? [String]()
157+
(try? await fs.ls(atPath: swiftlyBinDir)) ?? [String]()
140158
let toolchainBinDir = Swiftly.currentPlatform.findToolchainBinDir(ctx, version)
141-
let toolchainBinDirContents = try FileManager.default.contentsOfDirectory(
142-
atPath: toolchainBinDir.path)
159+
let toolchainBinDirContents = try await fs.ls(atPath: toolchainBinDir)
143160

144-
let existingProxies = swiftlyBinDirContents.filter { bin in
161+
var existingProxies: [String] = []
162+
163+
for bin in swiftlyBinDirContents {
145164
do {
146-
let linkTarget = try FileManager.default.destinationOfSymbolicLink(
147-
atPath: swiftlyBinDir.appendingPathComponent(bin).path)
148-
return linkTarget == proxyTo
149-
} catch { return false }
165+
let linkTarget = try await fs.readlink(atPath: swiftlyBinDir / bin)
166+
if linkTarget == proxyTo {
167+
existingProxies.append(bin)
168+
}
169+
} catch { continue }
150170
}
151171

152172
let overwrite = Set(toolchainBinDirContents).subtracting(existingProxies).intersection(
@@ -155,7 +175,7 @@ struct Install: SwiftlyCommand {
155175
await ctx.print("The following existing executables will be overwritten:")
156176

157177
for executable in overwrite {
158-
await ctx.print(" \(swiftlyBinDir.appendingPathComponent(executable).path)")
178+
await ctx.print(" \(swiftlyBinDir / executable)")
159179
}
160180

161181
guard await ctx.promptForConfirmation(defaultBehavior: false) else {
@@ -171,16 +191,13 @@ struct Install: SwiftlyCommand {
171191
overwrite)
172192

173193
for p in proxiesToCreate {
174-
let proxy = Swiftly.currentPlatform.swiftlyBinDir(ctx).appendingPathComponent(p)
194+
let proxy = Swiftly.currentPlatform.swiftlyBinDir(ctx) / p
175195

176-
if proxy.fileExists() {
177-
try FileManager.default.removeItem(at: proxy)
196+
if try await fs.exists(atPath: proxy) {
197+
try await fs.remove(atPath: proxy)
178198
}
179199

180-
try FileManager.default.createSymbolicLink(
181-
atPath: proxy.path,
182-
withDestinationPath: proxyTo
183-
)
200+
try await fs.symlink(atPath: proxy, linkPath: proxyTo)
184201

185202
pathChanged = true
186203
}
@@ -330,120 +347,15 @@ struct Install: SwiftlyCommand {
330347

331348
try await Swiftly.currentPlatform.install(ctx, from: tmpFile, version: version, verbose: verbose)
332349

333-
var pathChanged = false
334-
335-
// Create proxies if we have a location where we can point them
336-
if let proxyTo = try? await Swiftly.currentPlatform.findSwiftlyBin(ctx) {
337-
// Ensure swiftly doesn't overwrite any existing executables without getting confirmation first.
338-
let swiftlyBinDir = Swiftly.currentPlatform.swiftlyBinDir(ctx)
339-
let swiftlyBinDirContents =
340-
(try? await fs.ls(atPath: swiftlyBinDir)) ?? [String]()
341-
let toolchainBinDir = Swiftly.currentPlatform.findToolchainBinDir(ctx, version)
342-
let toolchainBinDirContents = try await fs.ls(atPath: toolchainBinDir)
343-
344-
var existingProxies: [String] = []
345-
346-
for bin in swiftlyBinDirContents {
347-
do {
348-
let linkTarget = try await fs.readlink(atPath: swiftlyBinDir / bin)
349-
if linkTarget == proxyTo {
350-
existingProxies.append(bin)
351-
}
352-
} catch { continue }
353-
}
354-
355-
let overwrite = Set(toolchainBinDirContents).subtracting(existingProxies).intersection(
356-
swiftlyBinDirContents)
357-
if !overwrite.isEmpty && !assumeYes {
358-
await ctx.print("The following existing executables will be overwritten:")
359-
360-
for executable in overwrite {
361-
await ctx.print(" \(swiftlyBinDir / executable)")
362-
}
363-
364-
<<<<<<< HEAD
365-
guard await ctx.promptForConfirmation(defaultBehavior: false) else {
366-
throw SwiftlyError(message: "Toolchain installation has been cancelled")
367-
}
368-
}
369-
370-
if verbose {
371-
await ctx.print("Setting up toolchain proxies...")
372-
}
373-
374-
let proxiesToCreate = Set(toolchainBinDirContents).subtracting(swiftlyBinDirContents).union(
375-
overwrite)
376-
377-
for p in proxiesToCreate {
378-
let proxy = Swiftly.currentPlatform.swiftlyBinDir(ctx) / p
379-
380-
if try await fs.exists(atPath: proxy) {
381-
try await fs.remove(atPath: proxy)
382-
}
383-
384-
try await fs.symlink(atPath: proxy, linkPath: proxyTo)
385-
386-
pathChanged = true
387-
}
388-
}
389-
390-
config.installedToolchains.insert(version)
391-
392-
=======
393-
let downloadedMiB = Double(progress.receivedBytes) / (1024.0 * 1024.0)
394-
let totalMiB = Double(progress.totalBytes!) / (1024.0 * 1024.0)
395-
396-
lastUpdate = Date()
397-
398-
animation.update(
399-
step: progress.receivedBytes,
400-
total: progress.totalBytes!,
401-
text:
402-
"Downloaded \(String(format: "%.1f", downloadedMiB)) MiB of \(String(format: "%.1f", totalMiB)) MiB"
403-
)
404-
}
405-
)
406-
} catch let notFound as DownloadNotFoundError {
407-
throw SwiftlyError(message: "\(version) does not exist at URL \(notFound.url), exiting")
408-
} catch {
409-
animation.complete(success: false)
410-
throw error
411-
}
412-
animation.complete(success: true)
413-
414-
if verifySignature {
415-
try await Swiftly.currentPlatform.verifyToolchainSignature(
350+
let pathChanged = try await Self.setupProxies(
416351
ctx,
417-
toolchainFile: toolchainFile,
418-
archive: tmpFile,
419-
verbose: verbose
352+
version: version,
353+
verbose: verbose,
354+
assumeYes: assumeYes
420355
)
421-
}
422-
423-
try await Swiftly.currentPlatform.install(ctx, from: tmpFile, version: version, verbose: verbose)
424-
425-
let pathChanged = try await Self.setupProxies(
426-
ctx,
427-
version: version,
428-
verbose: verbose,
429-
assumeYes: assumeYes
430-
)
431-
432-
config.installedToolchains.insert(version)
433356

434-
try config.save(ctx)
435-
436-
// If this is the first installed toolchain, mark it as in-use regardless of whether the
437-
// --use argument was provided.
438-
if useInstalledToolchain {
439-
try await Use.execute(ctx, version, globalDefault: false, &config)
440-
}
357+
config.installedToolchains.insert(version)
441358

442-
// We always update the global default toolchain if there is none set. This could
443-
// be the only toolchain that is installed, which makes it the only choice.
444-
if config.inUse == nil {
445-
config.inUse = version
446-
>>>>>>> 5d7eb2d (Add ability to temporarily disable swiftly)
447359
try config.save(ctx)
448360

449361
// If this is the first installed toolchain, mark it as in-use regardless of whether the

Sources/Swiftly/Link.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@ struct Link: SwiftlyCommand {
2323
}
2424

2525
mutating func run(_ ctx: SwiftlyCoreContext) async throws {
26-
try validateSwiftly(ctx)
26+
let versionUpdateReminder = try await validateSwiftly(ctx)
27+
defer {
28+
versionUpdateReminder()
29+
}
2730

28-
var config = try Config.load(ctx)
31+
var config = try await Config.load(ctx)
2932
let toolchainVersion = try await Install.determineToolchainVersion(
3033
ctx,
31-
version: nil,
34+
version: config.inUse?.name,
3235
config: &config
3336
)
3437

@@ -40,7 +43,11 @@ struct Link: SwiftlyCommand {
4043
)
4144

4245
if pathChanged {
43-
await ctx.print(Messages.refreshShell)
46+
await ctx.print("""
47+
Linked swiftly to \(toolchainVersion.name).
48+
49+
\(Messages.refreshShell)
50+
""")
4451
}
4552
}
4653
}

Sources/Swiftly/List.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,11 @@ struct List: SwiftlyCommand {
4343
versionUpdateReminder()
4444
}
4545

46+
var config = try await Config.load(ctx)
4647
let selector = try self.toolchainSelector.map { input in
4748
try ToolchainSelector(parsing: input)
4849
}
4950

50-
var config = try await Config.load(ctx)
51-
5251
let toolchains = config.listInstalledToolchains(selector: selector).sorted { $0 > $1 }
5352
let (inUse, _) = try await selectToolchain(ctx, config: &config)
5453

Sources/Swiftly/ListAvailable.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,11 @@ struct ListAvailable: SwiftlyCommand {
4949
versionUpdateReminder()
5050
}
5151

52+
var config = try await Config.load(ctx)
5253
let selector = try self.toolchainSelector.map { input in
5354
try ToolchainSelector(parsing: input)
5455
}
5556

56-
var config = try await Config.load(ctx)
57-
5857
let tc: [ToolchainVersion]
5958

6059
switch selector {

Sources/Swiftly/Run.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,13 @@ struct Run: SwiftlyCommand {
6262
defer {
6363
versionUpdateReminder()
6464
}
65+
var config = try await Config.load(ctx)
6566

6667
// Handle the specific case where help is requested of the run subcommand
6768
if command == ["--help"] {
6869
throw CleanExit.helpRequest(self)
6970
}
7071

71-
var config = try await Config.load(ctx)
72-
7372
let (command, selector) = try Self.extractProxyArguments(command: self.command)
7473

7574
let toolchain: ToolchainVersion?

Sources/Swiftly/Swiftly.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import SystemPackage
1010

1111
typealias fs = SwiftlyCore.FileSystem
1212

13-
extension FilePath: ExpressibleByArgument {
13+
extension FilePath: @retroactive ExpressibleByArgument {
1414
public init?(argument: String) {
1515
self.init(argument)
1616
}

Sources/Swiftly/Unlink.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ArgumentParser
22
import Foundation
33
import SwiftlyCore
4+
import SystemPackage
45

56
struct Unlink: SwiftlyCommand {
67
public static let configuration = CommandConfiguration(
@@ -25,25 +26,28 @@ struct Unlink: SwiftlyCommand {
2526
}
2627

2728
mutating func run(_ ctx: SwiftlyCoreContext) async throws {
28-
try validateSwiftly(ctx)
29+
let versionUpdateReminder = try await validateSwiftly(ctx)
30+
defer {
31+
versionUpdateReminder()
32+
}
2933

3034
var pathChanged = false
31-
if let proxyTo = try? Swiftly.currentPlatform.findSwiftlyBin(ctx) {
35+
if let proxyTo = try? await Swiftly.currentPlatform.findSwiftlyBin(ctx) {
3236
let swiftlyBinDir = Swiftly.currentPlatform.swiftlyBinDir(ctx)
33-
let swiftlyBinDirContents = (try? FileManager.default.contentsOfDirectory(atPath: swiftlyBinDir.path)) ?? [String]()
37+
let swiftlyBinDirContents = (try? FileManager.default.contentsOfDirectory(atPath: swiftlyBinDir.string)) ?? [String]()
3438

3539
let existingProxies = swiftlyBinDirContents.filter { bin in
3640
do {
37-
let linkTarget = try FileManager.default.destinationOfSymbolicLink(atPath: swiftlyBinDir.appendingPathComponent(bin).path)
38-
return linkTarget == proxyTo
41+
let linkTarget = try FileManager.default.destinationOfSymbolicLink(atPath: (swiftlyBinDir / bin).string)
42+
return linkTarget == proxyTo.string
3943
} catch { return false }
4044
}
4145

4246
for p in existingProxies {
43-
let proxy = Swiftly.currentPlatform.swiftlyBinDir(ctx).appendingPathComponent(p)
47+
let proxy = Swiftly.currentPlatform.swiftlyBinDir(ctx) / p
4448

45-
if proxy.fileExists() {
46-
try FileManager.default.removeItem(at: proxy)
49+
if try await fs.exists(atPath: proxy) {
50+
try await fs.remove(atPath: proxy)
4751
pathChanged = true
4852
}
4953
}

Tests/SwiftlyTests/LinkTests.swift

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import Testing
77
/// Tests that enabling swiftly results in swiftlyBinDir being populated with symlinks.
88
@Test func testLink() async throws {
99
try await SwiftlyTests.withTestHome {
10-
let fm = FileManager.default
1110
let swiftlyBinDir = Swiftly.currentPlatform.swiftlyBinDir(SwiftlyTests.ctx)
12-
let swiftlyBinaryPath = swiftlyBinDir.appendingPathComponent("swiftly")
13-
let swiftVersionFilename = SwiftlyTests.ctx.currentDirectory.appendingPathComponent(".swift-version")
11+
let swiftlyBinaryPath = swiftlyBinDir / "swiftly"
12+
let swiftVersionFilename = SwiftlyTests.ctx.currentDirectory / ".swift-version"
1413

1514
// Configure a mock toolchain
1615
let versionString = "6.0.3"
@@ -20,20 +19,18 @@ import Testing
2019
// And start creating a mock folder structure for that toolchain.
2120
try "swiftly binary".write(to: swiftlyBinaryPath, atomically: true, encoding: .utf8)
2221

23-
let toolchainDir = Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, toolchainVersion)
24-
.appendingPathComponent("usr")
25-
.appendingPathComponent("bin")
26-
try fm.createDirectory(at: toolchainDir, withIntermediateDirectories: true)
22+
let toolchainDir = Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, toolchainVersion) / "usr" / "bin"
23+
try await fs.mkdir([.parents], atPath: toolchainDir)
2724

2825
let proxies = ["swift-build", "swift-test", "swift-run"]
2926
for proxy in proxies {
30-
let proxyPath = toolchainDir.appendingPathComponent(proxy)
31-
try fm.createSymbolicLink(at: proxyPath, withDestinationURL: swiftlyBinaryPath)
27+
let proxyPath = toolchainDir / proxy
28+
try await fs.symlink(atPath: proxyPath, linkPath: swiftlyBinaryPath)
3229
}
3330

3431
_ = try await SwiftlyTests.runWithMockedIO(Link.self, ["link"])
3532

36-
let enabledSwiftlyBinDirContents = try fm.contentsOfDirectory(atPath: swiftlyBinDir.path).sorted()
33+
let enabledSwiftlyBinDirContents = try await fs.ls(atPath: swiftlyBinDir).sorted()
3734
let expectedProxies = (["swiftly"] + proxies).sorted()
3835
#expect(enabledSwiftlyBinDirContents == expectedProxies)
3936
}

0 commit comments

Comments
 (0)