Skip to content

Commit d8c39b7

Browse files
authored
Implement the uninstall command on Linux (#25)
1 parent 2af38f1 commit d8c39b7

File tree

10 files changed

+434
-166
lines changed

10 files changed

+434
-166
lines changed

Sources/LinuxPlatform/Linux.swift

+4-5
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ public struct Linux: Platform {
5757
}
5858
}
5959

60-
public func uninstall(version _: ToolchainVersion) throws {}
60+
public func uninstall(_ toolchain: ToolchainVersion) throws {
61+
let toolchainDir = SwiftlyCore.toolchainsDir.appendingPathComponent(toolchain.name)
62+
try FileManager.default.removeItem(at: toolchainDir)
63+
}
6164

6265
public func use(_ toolchain: ToolchainVersion) throws {
6366
let toolchainBinURL = SwiftlyCore.toolchainsDir
@@ -86,10 +89,6 @@ public struct Linux: Platform {
8689
}
8790
}
8891

89-
public func listToolchains(selector _: ToolchainSelector?) -> [ToolchainVersion] {
90-
[]
91-
}
92-
9392
public func listAvailableSnapshots(version _: String?) async -> [Snapshot] {
9493
[]
9594
}

Sources/Swiftly/Uninstall.swift

+57-17
Original file line numberDiff line numberDiff line change
@@ -29,43 +29,83 @@ struct Uninstall: SwiftlyCommand {
2929
3030
$ swiftly uninstall main-snapshot
3131
$ swiftly uninstall 5.7-snapshot
32+
33+
The latest installed stable release can be uninstalled by specifying 'latest':
34+
35+
$ swiftly uninstall latest
3236
"""
3337
))
3438
var toolchain: String
3539

40+
@Flag(
41+
name: [.long, .customShort("y")],
42+
help: "Uninstall all selected toolchains without prompting for confirmation."
43+
)
44+
var assumeYes: Bool = false
45+
3646
mutating func run() async throws {
3747
let selector = try ToolchainSelector(parsing: self.toolchain)
3848
let config = try Config.load()
3949
let toolchains = config.listInstalledToolchains(selector: selector)
4050

4151
guard !toolchains.isEmpty else {
42-
print("no toolchains matched \"\(self.toolchain)\"")
52+
SwiftlyCore.print("No toolchains matched \"\(self.toolchain)\"")
4353
return
4454
}
4555

46-
print("The following toolchains will be uninstalled:")
56+
if !self.assumeYes {
57+
SwiftlyCore.print("The following toolchains will be uninstalled:")
4758

48-
for toolchain in toolchains {
49-
print(" \(toolchain)")
50-
}
51-
52-
print("Proceed? (y/n)", terminator: ": ")
53-
let proceed = readLine(strippingNewline: true) ?? "n"
59+
for toolchain in toolchains {
60+
SwiftlyCore.print(" \(toolchain)")
61+
}
62+
let proceed = SwiftlyCore.readLine(prompt: "Proceed? (y/n)") ?? "n"
5463

55-
guard proceed == "y" else {
56-
print("aborting uninstall")
57-
return
64+
guard proceed == "y" else {
65+
SwiftlyCore.print("Aborting uninstall")
66+
return
67+
}
5868
}
5969

60-
print()
70+
SwiftlyCore.print()
6171

6272
for toolchain in toolchains {
63-
print("Uninstalling \(toolchain)...", terminator: "")
64-
try Swiftly.currentPlatform.uninstall(version: toolchain)
65-
print("done")
73+
SwiftlyCore.print("Uninstalling \(toolchain)...", terminator: "")
74+
try Swiftly.currentPlatform.uninstall(toolchain)
75+
try Config.update { config in
76+
config.installedToolchains.remove(toolchain)
77+
}
78+
SwiftlyCore.print("done")
6679
}
6780

68-
print()
69-
print("\(toolchains.count) toolchain(s) successfully uninstalled")
81+
SwiftlyCore.print()
82+
SwiftlyCore.print("\(toolchains.count) toolchain(s) successfully uninstalled")
83+
84+
var latestConfig = try Config.load()
85+
86+
// If the in-use toolchain was one of the uninstalled toolchains, use the latest installed
87+
// toolchain.
88+
if let previouslyInUse = latestConfig.inUse, toolchains.contains(previouslyInUse) {
89+
let selector: ToolchainSelector
90+
switch previouslyInUse {
91+
case let .stable(sr):
92+
// If a.b.c was previously in use, switch to the latest a.b toolchain.
93+
selector = .stable(major: sr.major, minor: sr.minor, patch: nil)
94+
case let .snapshot(s):
95+
// If a snapshot was previously in use, switch to the latest snapshot associated with that branch.
96+
selector = .snapshot(branch: s.branch, date: nil)
97+
}
98+
99+
if let toUse = latestConfig.listInstalledToolchains(selector: selector).max()
100+
?? latestConfig.listInstalledToolchains(selector: .latest).max()
101+
?? latestConfig.installedToolchains.max()
102+
{
103+
try await Use.execute(toUse)
104+
} else {
105+
// If there are no more toolchains installed, clear the inUse config entry.
106+
latestConfig.inUse = nil
107+
try latestConfig.save()
108+
}
109+
}
70110
}
71111
}

Sources/Swiftly/Update.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ struct Update: SwiftlyCommand {
5757

5858
print("updating \(oldToolchain) -> \(newToolchain)")
5959
try await Install.execute(version: newToolchain)
60-
try Swiftly.currentPlatform.uninstall(version: oldToolchain)
60+
try Swiftly.currentPlatform.uninstall(oldToolchain)
6161
print("successfully updated \(oldToolchain) -> \(newToolchain)")
6262
}
6363

Sources/SwiftlyCore/Platform.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public protocol Platform {
2929

3030
/// Uninstalls a toolchain associated with the given version.
3131
/// If this version is in use, the next latest version will be used afterwards.
32-
func uninstall(version: ToolchainVersion) throws
32+
func uninstall(_ version: ToolchainVersion) throws
3333

3434
/// Select the toolchain associated with the given version.
3535
func use(_ version: ToolchainVersion) throws

Sources/SwiftlyCore/SwiftlyCore.swift

+21-3
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,28 @@ public var outputHandler: (any OutputHandler)?
5656

5757
/// Pass the provided string to the set output handler if any.
5858
/// If no output handler has been set, just print to stdout.
59-
public func print(_ string: String) {
59+
public func print(_ string: String = "", terminator: String? = nil) {
6060
guard let handler = SwiftlyCore.outputHandler else {
61-
Swift.print(string)
61+
if let terminator {
62+
Swift.print(string, terminator: terminator)
63+
} else {
64+
Swift.print(string)
65+
}
6266
return
6367
}
64-
handler.handleOutputLine(string)
68+
handler.handleOutputLine(string + (terminator ?? ""))
69+
}
70+
71+
public protocol InputProvider {
72+
func readLine() -> String?
73+
}
74+
75+
public var inputProvider: (any InputProvider)?
76+
77+
public func readLine(prompt: String) -> String? {
78+
print(prompt, terminator: ": ")
79+
guard let provider = SwiftlyCore.inputProvider else {
80+
return Swift.readLine(strippingNewline: true)
81+
}
82+
return provider.readLine()
6583
}

Sources/SwiftlyCore/ToolchainVerison.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public enum ToolchainVersion {
9191
}
9292

9393
static let stableRegex: Regex<(Substring, Substring, Substring, Substring)> =
94-
try! Regex("^(\\d+)\\.(\\d+)\\.(\\d+)$")
94+
try! Regex("^(?:Swift )?(\\d+)\\.(\\d+)\\.(\\d+)$")
9595

9696
static let mainSnapshotRegex: Regex<(Substring, Substring)> =
9797
try! Regex("^main-snapshot-(\\d{4}-\\d{2}-\\d{2})$")

Tests/SwiftlyTests/ListTests.swift

+2-22
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,6 @@ import XCTest
66
final class ListTests: SwiftlyTests {
77
static let homeName = "useTests"
88

9-
// Below are some constants indicating which versions are installed during setup.
10-
11-
static let oldStable = ToolchainVersion(major: 5, minor: 6, patch: 0)
12-
static let oldStableNewPatch = ToolchainVersion(major: 5, minor: 6, patch: 3)
13-
static let newStable = ToolchainVersion(major: 5, minor: 7, patch: 0)
14-
static let oldMainSnapshot = ToolchainVersion(snapshotBranch: .main, date: "2022-09-10")
15-
static let newMainSnapshot = ToolchainVersion(snapshotBranch: .main, date: "2022-10-22")
16-
static let oldReleaseSnapshot = ToolchainVersion(snapshotBranch: .release(major: 5, minor: 7), date: "2022-08-27")
17-
static let newReleaseSnapshot = ToolchainVersion(snapshotBranch: .release(major: 5, minor: 7), date: "2022-08-30")
18-
19-
static let allToolchains: [ToolchainVersion] = [
20-
ListTests.oldStable,
21-
ListTests.oldStableNewPatch,
22-
ListTests.newStable,
23-
ListTests.oldMainSnapshot,
24-
ListTests.newMainSnapshot,
25-
ListTests.oldReleaseSnapshot,
26-
ListTests.newReleaseSnapshot,
27-
]
28-
299
static let sortedReleaseToolchains: [ToolchainVersion] = [
3010
ListTests.newStable,
3111
ListTests.oldStableNewPatch,
@@ -63,7 +43,7 @@ final class ListTests: SwiftlyTests {
6343
}
6444

6545
var list = try self.parseCommand(List.self, args)
66-
let output = try await list.runWithOutput()
46+
let output = try await list.runWithMockedIO()
6747

6848
let parsedToolchains = output.compactMap { outputLine in
6949
Self.allToolchains.first {
@@ -145,7 +125,7 @@ final class ListTests: SwiftlyTests {
145125
listArgs.append(selector)
146126
}
147127
var list = try self.parseCommand(List.self, listArgs)
148-
let output = try await list.runWithOutput()
128+
let output = try await list.runWithMockedIO()
149129

150130
let inUse = output.filter { $0.contains("in use") }
151131
XCTAssertEqual(inUse, ["\(toolchain) (in use)"])

0 commit comments

Comments
 (0)