Skip to content

Commit 2cd4cad

Browse files
committed
Create a special xcode selector that will delegates to xcrun
1 parent 5645873 commit 2cd4cad

File tree

10 files changed

+66
-18
lines changed

10 files changed

+66
-18
lines changed

Sources/MacOSPlatform/MacOS.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,15 @@ public struct MacOS: Platform {
207207
return "/bin/zsh"
208208
}
209209

210-
public func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> URL
211-
{
212-
self.swiftlyToolchainsDir(ctx).appendingPathComponent("\(toolchain.identifier).xctoolchain")
210+
public func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> URL {
211+
if toolchain == .xcodeVersion {
212+
// Print the toolchain location with the help of xcrun
213+
if let xcrunLocation = try? await self.runProgramOutput("/usr/bin/xcrun", "-f", "swift") {
214+
return URL(filePath: xcrunLocation.replacingOccurrences(of: "\n", with: "")).deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
215+
}
216+
}
217+
218+
return self.swiftlyToolchainsDir(ctx).appendingPathComponent("\(toolchain.identifier).xctoolchain")
213219
}
214220

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

Sources/Swiftly/Config.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public struct Config: Codable, Equatable {
5252

5353
public func listInstalledToolchains(selector: ToolchainSelector?) -> [ToolchainVersion] {
5454
guard let selector else {
55-
return Array(self.installedToolchains)
55+
return Array(self.installedToolchains) + [.xcodeVersion]
5656
}
5757

5858
if case .latest = selector {
@@ -63,7 +63,7 @@ public struct Config: Codable, Equatable {
6363
return ts
6464
}
6565

66-
return self.installedToolchains.filter { toolchain in
66+
return (self.installedToolchains + [.xcodeVersion]).filter { toolchain in
6767
selector.matches(toolchain: toolchain)
6868
}
6969
}

Sources/Swiftly/Install.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ struct Install: SwiftlyCommand {
216216
case .main:
217217
category = "development"
218218
}
219+
case .xcode:
220+
fatalError("unreachable: xcode toolchain cannot be installed with swiftly")
219221
}
220222

221223
let animation = PercentProgressAnimation(
@@ -282,7 +284,7 @@ struct Install: SwiftlyCommand {
282284
let swiftlyBinDir = Swiftly.currentPlatform.swiftlyBinDir(ctx)
283285
let swiftlyBinDirContents =
284286
(try? FileManager.default.contentsOfDirectory(atPath: swiftlyBinDir.path)) ?? [String]()
285-
let toolchainBinDir = Swiftly.currentPlatform.findToolchainBinDir(ctx, version)
287+
let toolchainBinDir = try await Swiftly.currentPlatform.findToolchainBinDir(ctx, version)
286288
let toolchainBinDirContents = try FileManager.default.contentsOfDirectory(
287289
atPath: toolchainBinDir.path)
288290

@@ -429,6 +431,8 @@ struct Install: SwiftlyCommand {
429431
}
430432

431433
return .snapshot(firstSnapshot)
434+
case .xcode:
435+
throw SwiftlyError(message: "xcode toolchains are not available from swift.org")
432436
}
433437
}
434438
}

Sources/Swiftly/List.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ struct List: SwiftlyCommand {
9797
for toolchain in toolchains where toolchain.isSnapshot() {
9898
await printToolchain(toolchain)
9999
}
100+
101+
await ctx.print("")
102+
await printToolchain(ToolchainVersion.xcode)
100103
}
101104
}
102105
}

Sources/Swiftly/Uninstall.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ struct Uninstall: SwiftlyCommand {
5151
try validateSwiftly(ctx)
5252
let startingConfig = try Config.load(ctx)
5353

54-
let toolchains: [ToolchainVersion]
54+
var toolchains: [ToolchainVersion]
5555
if self.toolchain == "all" {
5656
// Sort the uninstalled toolchains such that the in-use toolchain will be uninstalled last.
5757
// This avoids printing any unnecessary output from using new toolchains while the uninstall is in progress.
@@ -68,6 +68,8 @@ struct Uninstall: SwiftlyCommand {
6868
toolchains = installedToolchains
6969
}
7070

71+
toolchains.removeAll(where: { $0 == .xcodeVersion })
72+
7173
guard !toolchains.isEmpty else {
7274
await ctx.print("No toolchains matched \"\(self.toolchain)\"")
7375
return
@@ -101,6 +103,9 @@ struct Uninstall: SwiftlyCommand {
101103
case let .snapshot(s):
102104
// If a snapshot was previously in use, switch to the latest snapshot associated with that branch.
103105
selector = .snapshot(branch: s.branch, date: nil)
106+
case .xcode:
107+
// Xcode will not be in the list of installed toolchains, so this is only here for completeness
108+
selector = .xcode
104109
}
105110

106111
if let toUse = config.listInstalledToolchains(selector: selector)

Sources/Swiftly/Update.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ struct Update: SwiftlyCommand {
195195
default:
196196
fatalError("unreachable")
197197
}
198+
case let .xcode:
199+
throw SwiftlyError(message: "xcode cannot be updated from swiftly")
198200
}
199201
}
200202

Sources/Swiftly/Use.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ struct Use: SwiftlyCommand {
7878

7979
if self.printLocation {
8080
// Print the toolchain location and exit
81-
await ctx.print("\(Swiftly.currentPlatform.findToolchainLocation(ctx, selectedVersion).path)")
81+
await ctx.print("\(try await Swiftly.currentPlatform.findToolchainLocation(ctx, selectedVersion).path)")
8282
return
8383
}
8484

Sources/SwiftlyCore/Platform.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,10 @@ public protocol Platform: Sendable {
132132
func getShell() async throws -> String
133133

134134
/// Find the location where the toolchain should be installed.
135-
func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> URL
135+
func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> URL
136136

137137
/// Find the location of the toolchain binaries.
138-
func findToolchainBinDir(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> URL
138+
func findToolchainBinDir(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> URL
139139
}
140140

141141
extension Platform {
@@ -164,11 +164,11 @@ extension Platform {
164164
}
165165

166166
#if os(macOS) || os(Linux)
167-
func proxyEnv(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) throws -> [
167+
func proxyEnv(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> [
168168
String:
169169
String
170170
] {
171-
let tcPath = self.findToolchainLocation(ctx, toolchain).appendingPathComponent("usr/bin")
171+
let tcPath = try await self.findToolchainLocation(ctx, toolchain).appendingPathComponent("usr/bin")
172172
guard tcPath.fileExists() else {
173173
throw SwiftlyError(
174174
message:
@@ -196,7 +196,7 @@ extension Platform {
196196
_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, _ command: String,
197197
_ arguments: [String], _ env: [String: String] = [:]
198198
) async throws {
199-
var newEnv = try self.proxyEnv(ctx, toolchain)
199+
var newEnv = try await self.proxyEnv(ctx, toolchain)
200200
for (key, value) in env {
201201
newEnv[key] = value
202202
}
@@ -436,9 +436,9 @@ extension Platform {
436436
return FileManager.default.fileExists(atPath: swiftlyHomeBin) ? swiftlyHomeBin : nil
437437
}
438438

439-
public func findToolchainBinDir(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> URL
439+
public func findToolchainBinDir(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> URL
440440
{
441-
self.findToolchainLocation(ctx, toolchain).appendingPathComponent("usr/bin")
441+
try await self.findToolchainLocation(ctx, toolchain).appendingPathComponent("usr/bin")
442442
}
443443

444444
#endif

Sources/SwiftlyCore/ToolchainVersion.swift

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public enum ToolchainVersion: Sendable {
9090

9191
case stable(StableRelease)
9292
case snapshot(Snapshot)
93+
case xcode
9394

9495
public init(major: Int, minor: Int, patch: Int) {
9596
self = .stable(StableRelease(major: major, minor: minor, patch: patch))
@@ -99,6 +100,8 @@ public enum ToolchainVersion: Sendable {
99100
self = .snapshot(Snapshot(branch: snapshotBranch, date: date))
100101
}
101102

103+
public static let xcodeVersion: ToolchainVersion = .xcode
104+
102105
static func stableRegex() -> Regex<(Substring, Substring, Substring, Substring)> {
103106
try! Regex("^(?:Swift )?(\\d+)\\.(\\d+)\\.(\\d+)$")
104107
}
@@ -132,6 +135,8 @@ public enum ToolchainVersion: Sendable {
132135
throw SwiftlyError(message: "invalid release snapshot version: \(string)")
133136
}
134137
self = ToolchainVersion(snapshotBranch: .release(major: major, minor: minor), date: String(match.output.3))
138+
} else if string == "xcode" {
139+
self = ToolchainVersion.xcodeVersion
135140
} else {
136141
throw SwiftlyError(message: "invalid toolchain version: \"\(string)\"")
137142
}
@@ -176,6 +181,8 @@ public enum ToolchainVersion: Sendable {
176181
case let .release(major, minor):
177182
return "\(major).\(minor)-snapshot-\(release.date)"
178183
}
184+
case .xcode:
185+
return "xcode"
179186
}
180187
}
181188

@@ -194,6 +201,8 @@ public enum ToolchainVersion: Sendable {
194201
case let .release(major, minor):
195202
return "swift-\(major).\(minor)-DEVELOPMENT-SNAPSHOT-\(release.date)-a"
196203
}
204+
case .xcode:
205+
return "xcode"
197206
}
198207
}
199208
}
@@ -214,6 +223,8 @@ extension ToolchainVersion: CustomStringConvertible {
214223
return "\(release)"
215224
case let .snapshot(snapshot):
216225
return "\(snapshot)"
226+
case .xcode:
227+
return "xcode"
217228
}
218229
}
219230
}
@@ -231,6 +242,10 @@ extension ToolchainVersion: Comparable {
231242
return false
232243
case (.stable, .snapshot):
233244
return !(rhs < lhs)
245+
case (.xcode, .xcode):
246+
return true
247+
default:
248+
return false
234249
}
235250
}
236251
}
@@ -254,6 +269,9 @@ public enum ToolchainSelector: Sendable {
254269
/// associated with the given branch.
255270
case snapshot(branch: ToolchainVersion.Snapshot.Branch, date: String?)
256271

272+
/// Selects the Xcode of the current system.
273+
case xcode
274+
257275
public init(major: Int, minor: Int? = nil, patch: Int? = nil) {
258276
self = .stable(major: major, minor: minor, patch: patch)
259277
}
@@ -267,14 +285,19 @@ public enum ToolchainSelector: Sendable {
267285
return
268286
}
269287

288+
if input == "xcode" {
289+
self = Self.xcode
290+
return
291+
}
292+
270293
throw SwiftlyError(message: "invalid toolchain selector: \"\(input)\"")
271294
}
272295

273296
public func isReleaseSelector() -> Bool {
274297
switch self {
275298
case .latest, .stable:
276299
return true
277-
case .snapshot:
300+
default:
278301
return false
279302
}
280303
}
@@ -312,7 +335,8 @@ public enum ToolchainSelector: Sendable {
312335
}
313336
}
314337
return true
315-
338+
case (.xcode, .xcode):
339+
return true
316340
default:
317341
return false
318342
}
@@ -341,6 +365,8 @@ extension ToolchainSelector: CustomStringConvertible {
341365
s += "-\(date)"
342366
}
343367
return s
368+
case .xcode:
369+
return "xcode"
344370
}
345371
}
346372
}

Tests/SwiftlyTests/UseTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,9 @@ import Testing
202202

203203
output = try await SwiftlyTests.runWithMockedIO(Use.self, ["use", "-g", "--print-location"])
204204

205-
#expect(output.contains(where: { $0.contains(Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, toolchain).path) }))
205+
let location = try await Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, toolchain).path
206+
207+
#expect(output.contains(where: { $0.contains(location) }))
206208
}
207209
}
208210
}

0 commit comments

Comments
 (0)