Skip to content

Commit c82a975

Browse files
authored
Create a special Xcode selector for macOS (#317)
* Filter out xcode toolchain from the output on non-macOS * Add documentation for the xcode toolchain selector * Add tests for the xcode toolchain selector and version * Fix up the tests and system toolchain list output * Categorize the xcode toolchain version as a system toolchain type * Setup proxies on use of a toolchain
1 parent c64b62d commit c82a975

19 files changed

+235
-71
lines changed

Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ Likewise, the latest snapshot associated with a given development branch can be
230230
$ swiftly use 5.7-snapshot
231231
$ swiftly use main-snapshot
232232

233+
macOS ONLY: There is a special selector for swiftly to use your Xcode toolchain. If there are multiple versions of Xcode then swiftly will use the currently selected toolchain from xcode-select.
234+
235+
$ swiftly use xcode
236+
233237

234238
**--version:**
235239

Sources/MacOSPlatform/MacOS.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,16 @@ public struct MacOS: Platform {
201201
return "/bin/zsh"
202202
}
203203

204-
public func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> FilePath
204+
public func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> FilePath
205205
{
206-
self.swiftlyToolchainsDir(ctx) / "\(toolchain.identifier).xctoolchain"
206+
if toolchain == .xcodeVersion {
207+
// Print the toolchain location with the help of xcrun
208+
if let xcrunLocation = try? await self.runProgramOutput("/usr/bin/xcrun", "-f", "swift") {
209+
return FilePath(xcrunLocation.replacingOccurrences(of: "\n", with: "")).removingLastComponent().removingLastComponent().removingLastComponent()
210+
}
211+
}
212+
213+
return self.swiftlyToolchainsDir(ctx) / "\(toolchain.identifier).xctoolchain"
207214
}
208215

209216
public static let currentPlatform: any Platform = MacOS()

Sources/Swiftly/Config.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,14 @@ public struct Config: Codable, Equatable, Sendable {
4949
}
5050

5151
public func listInstalledToolchains(selector: ToolchainSelector?) -> [ToolchainVersion] {
52+
#if os(macOS)
53+
let systemToolchains: [ToolchainVersion] = [.xcodeVersion]
54+
#else
55+
let systemToolchains: [ToolchainVersion] = []
56+
#endif
57+
5258
guard let selector else {
53-
return Array(self.installedToolchains)
59+
return Array(self.installedToolchains) + systemToolchains
5460
}
5561

5662
if case .latest = selector {
@@ -61,7 +67,7 @@ public struct Config: Codable, Equatable, Sendable {
6167
return ts
6268
}
6369

64-
return self.installedToolchains.filter { toolchain in
70+
return (self.installedToolchains + systemToolchains).filter { toolchain in
6571
selector.matches(toolchain: toolchain)
6672
}
6773
}

Sources/Swiftly/Init.swift

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -281,18 +281,8 @@ struct Init: SwiftlyCommand {
281281
""")
282282
}
283283

284-
// Fish doesn't have path caching, so this might only be needed for bash/zsh
285-
if pathChanged && !quietShellFollowup && !shell.hasSuffix("fish") {
286-
await ctx.message("""
287-
Your shell caches items on your path for better performance. Swiftly has added
288-
items to your path that may not get picked up right away. You can update your
289-
shell's environment by running
290-
291-
hash -r
292-
293-
or restarting your shell.
294-
295-
""")
284+
if pathChanged && !quietShellFollowup {
285+
try await Self.handlePathChange(ctx)
296286
}
297287

298288
if let postInstall {

Sources/Swiftly/Install.swift

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -121,26 +121,8 @@ struct Install: SwiftlyCommand {
121121
progressFile: self.progressFile
122122
)
123123

124-
let shell =
125-
if let s = ProcessInfo.processInfo.environment["SHELL"]
126-
{
127-
s
128-
} else {
129-
try await Swiftly.currentPlatform.getShell()
130-
}
131-
132-
// Fish doesn't cache its path, so this instruction is not necessary.
133-
if pathChanged && !shell.hasSuffix("fish") {
134-
await ctx.message(
135-
"""
136-
NOTE: Swiftly has updated some elements in your path and your shell may not yet be
137-
aware of the changes. You can update your shell's environment by running
138-
139-
hash -r
140-
141-
or restarting your shell.
142-
143-
""")
124+
if pathChanged {
125+
try await Self.handlePathChange(ctx)
144126
}
145127

146128
if let postInstallScript {
@@ -176,7 +158,7 @@ struct Install: SwiftlyCommand {
176158
let swiftlyBinDir = Swiftly.currentPlatform.swiftlyBinDir(ctx)
177159
let swiftlyBinDirContents =
178160
(try? await fs.ls(atPath: swiftlyBinDir)) ?? [String]()
179-
let toolchainBinDir = Swiftly.currentPlatform.findToolchainBinDir(ctx, version)
161+
let toolchainBinDir = try await Swiftly.currentPlatform.findToolchainBinDir(ctx, version)
180162
let toolchainBinDirContents = try await fs.ls(atPath: toolchainBinDir)
181163

182164
var existingProxies: [String] = []
@@ -316,6 +298,8 @@ struct Install: SwiftlyCommand {
316298
case .main:
317299
category = "development"
318300
}
301+
case .xcode:
302+
fatalError("unreachable: xcode toolchain cannot be installed with swiftly")
319303
}
320304

321305
let animation: ProgressReporterProtocol? =
@@ -408,7 +392,7 @@ struct Install: SwiftlyCommand {
408392
verbose: verbose
409393
)
410394

411-
let pathChanged = try await Self.setupProxies(
395+
var pathChanged = try await Self.setupProxies(
412396
ctx,
413397
version: version,
414398
verbose: verbose,
@@ -422,7 +406,8 @@ struct Install: SwiftlyCommand {
422406
// If this is the first installed toolchain, mark it as in-use regardless of whether the
423407
// --use argument was provided.
424408
if useInstalledToolchain {
425-
try await Use.execute(ctx, version, globalDefault: false, &config)
409+
let pc = try await Use.execute(ctx, version, globalDefault: false, verbose: verbose, &config)
410+
pathChanged = pathChanged || pc
426411
}
427412

428413
// We always update the global default toolchain if there is none set. This could
@@ -523,6 +508,8 @@ struct Install: SwiftlyCommand {
523508
}
524509

525510
return .snapshot(firstSnapshot)
511+
case .xcode:
512+
throw SwiftlyError(message: "xcode toolchains are not available from swift.org")
526513
}
527514
}
528515
}

Sources/Swiftly/List.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ struct List: SwiftlyCommand {
5555
let toolchains = config.listInstalledToolchains(selector: selector).sorted { $0 > $1 }
5656
let (inUse, _) = try await selectToolchain(ctx, config: &config)
5757

58-
let installedToolchainInfos = toolchains.compactMap { toolchain -> InstallToolchainInfo? in
58+
var installedToolchainInfos = toolchains.compactMap { toolchain -> InstallToolchainInfo? in
5959
InstallToolchainInfo(
6060
version: toolchain,
6161
inUse: inUse == toolchain,

Sources/Swiftly/OutputSchema.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ struct AvailableToolchainInfo: OutputData {
125125
try versionContainer.encode(major, forKey: .major)
126126
try versionContainer.encode(minor, forKey: .minor)
127127
}
128+
case .xcode:
129+
try versionContainer.encode("system", forKey: .type)
128130
}
129131
}
130132
}
@@ -233,6 +235,8 @@ struct InstallToolchainInfo: OutputData {
233235
try versionContainer.encode(major, forKey: .major)
234236
try versionContainer.encode(minor, forKey: .minor)
235237
}
238+
case .xcode:
239+
try versionContainer.encode("system", forKey: .type)
236240
}
237241
}
238242

@@ -279,6 +283,9 @@ struct InstallToolchainInfo: OutputData {
279283
branch: branch,
280284
date: date
281285
))
286+
case "system":
287+
// The only system toolchain that exists at the moment is the xcode version
288+
self.version = .xcode
282289
default:
283290
throw DecodingError.dataCorruptedError(
284291
forKey: ToolchainVersionCodingKeys.type,
@@ -314,6 +321,8 @@ struct InstalledToolchainsListInfo: OutputData {
314321
"main development snapshot"
315322
case let .snapshot(.release(major, minor), nil):
316323
"\(major).\(minor) development snapshot"
324+
case .xcode:
325+
"xcode"
317326
default:
318327
"matching"
319328
}
@@ -334,6 +343,13 @@ struct InstalledToolchainsListInfo: OutputData {
334343
lines.append("Installed snapshot toolchains")
335344
lines.append("-----------------------------")
336345
lines.append(contentsOf: snapshotToolchains.map(\.description))
346+
347+
#if os(macOS)
348+
lines.append("")
349+
lines.append("Available system toolchains")
350+
lines.append("---------------------------")
351+
lines.append(ToolchainVersion.xcode.description)
352+
#endif
337353
}
338354

339355
return lines.joined(separator: "\n")

Sources/Swiftly/Swiftly.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,28 @@ extension SwiftlyCommand {
133133
}
134134
}
135135
}
136+
137+
public static func handlePathChange(_ ctx: SwiftlyCoreContext) async throws {
138+
let shell =
139+
if let s = ProcessInfo.processInfo.environment["SHELL"]
140+
{
141+
s
142+
} else {
143+
try await Swiftly.currentPlatform.getShell()
144+
}
145+
146+
// Fish doesn't cache its path, so this instruction is not necessary.
147+
if !shell.hasSuffix("fish") {
148+
await ctx.message(
149+
"""
150+
NOTE: Swiftly has updated some elements in your path and your shell may not yet be
151+
aware of the changes. You can update your shell's environment by running
152+
153+
hash -r
154+
155+
or restarting your shell.
156+
157+
""")
158+
}
159+
}
136160
}

Sources/Swiftly/Uninstall.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ struct Uninstall: SwiftlyCommand {
5555

5656
let startingConfig = try await Config.load(ctx)
5757

58-
let toolchains: [ToolchainVersion]
58+
var toolchains: [ToolchainVersion]
5959
if self.toolchain == "all" {
6060
// Sort the uninstalled toolchains such that the in-use toolchain will be uninstalled last.
6161
// This avoids printing any unnecessary output from using new toolchains while the uninstall is in progress.
@@ -72,8 +72,11 @@ struct Uninstall: SwiftlyCommand {
7272
toolchains = installedToolchains
7373
}
7474

75+
// Filter out the xcode toolchain here since it is not uninstallable
76+
toolchains.removeAll(where: { $0 == .xcodeVersion })
77+
7578
guard !toolchains.isEmpty else {
76-
await ctx.message("No toolchains matched \"\(self.toolchain)\"")
79+
await ctx.message("No toolchains can be uninstalled that match \"\(self.toolchain)\"")
7780
return
7881
}
7982

@@ -105,6 +108,9 @@ struct Uninstall: SwiftlyCommand {
105108
case let .snapshot(s):
106109
// If a snapshot was previously in use, switch to the latest snapshot associated with that branch.
107110
selector = .snapshot(branch: s.branch, date: nil)
111+
case .xcode:
112+
// Xcode will not be in the list of installed toolchains, so this is only here for completeness
113+
selector = .xcode
108114
}
109115

110116
if let toUse = config.listInstalledToolchains(selector: selector)
@@ -113,7 +119,10 @@ struct Uninstall: SwiftlyCommand {
113119
?? config.listInstalledToolchains(selector: .latest).filter({ !toolchains.contains($0) }).max()
114120
?? config.installedToolchains.filter({ !toolchains.contains($0) }).max()
115121
{
116-
try await Use.execute(ctx, toUse, globalDefault: true, &config)
122+
let pathChanged = try await Use.execute(ctx, toUse, globalDefault: true, verbose: self.root.verbose, &config)
123+
if pathChanged {
124+
try await Self.handlePathChange(ctx)
125+
}
117126
} else {
118127
// If there are no more toolchains installed, just unuse the currently active toolchain.
119128
config.inUse = nil

Sources/Swiftly/Update.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ struct Update: SwiftlyCommand {
192192
default:
193193
fatalError("unreachable")
194194
}
195+
case let .xcode:
196+
throw SwiftlyError(message: "xcode cannot be updated from swiftly")
195197
}
196198
}
197199

0 commit comments

Comments
 (0)