diff --git a/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md b/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md index 8896f5d1..ff7d8df4 100644 --- a/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md +++ b/Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md @@ -225,6 +225,10 @@ Likewise, the latest snapshot associated with a given development branch can be $ swiftly use 5.7-snapshot $ swiftly use main-snapshot +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. + + $ swiftly use xcode + **--version:** diff --git a/Sources/MacOSPlatform/MacOS.swift b/Sources/MacOSPlatform/MacOS.swift index fc2ee67e..5f1f313e 100644 --- a/Sources/MacOSPlatform/MacOS.swift +++ b/Sources/MacOSPlatform/MacOS.swift @@ -201,9 +201,16 @@ public struct MacOS: Platform { return "/bin/zsh" } - public func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> FilePath + public func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> FilePath { - self.swiftlyToolchainsDir(ctx) / "\(toolchain.identifier).xctoolchain" + if toolchain == .xcodeVersion { + // Print the toolchain location with the help of xcrun + if let xcrunLocation = try? await self.runProgramOutput("/usr/bin/xcrun", "-f", "swift") { + return FilePath(xcrunLocation.replacingOccurrences(of: "\n", with: "")).removingLastComponent().removingLastComponent().removingLastComponent() + } + } + + return self.swiftlyToolchainsDir(ctx) / "\(toolchain.identifier).xctoolchain" } public static let currentPlatform: any Platform = MacOS() diff --git a/Sources/Swiftly/Config.swift b/Sources/Swiftly/Config.swift index 0e8547c1..54f78222 100644 --- a/Sources/Swiftly/Config.swift +++ b/Sources/Swiftly/Config.swift @@ -52,8 +52,14 @@ public struct Config: Codable, Equatable, Sendable { } public func listInstalledToolchains(selector: ToolchainSelector?) -> [ToolchainVersion] { +#if os(macOS) + let systemToolchains: [ToolchainVersion] = [.xcodeVersion] +#else + let systemToolchains: [ToolchainVersion] = [] +#endif + guard let selector else { - return Array(self.installedToolchains) + return Array(self.installedToolchains) + systemToolchains } if case .latest = selector { @@ -64,7 +70,7 @@ public struct Config: Codable, Equatable, Sendable { return ts } - return self.installedToolchains.filter { toolchain in + return (self.installedToolchains + systemToolchains).filter { toolchain in selector.matches(toolchain: toolchain) } } diff --git a/Sources/Swiftly/Install.swift b/Sources/Swiftly/Install.swift index d8078b61..262b99f0 100644 --- a/Sources/Swiftly/Install.swift +++ b/Sources/Swiftly/Install.swift @@ -174,7 +174,7 @@ struct Install: SwiftlyCommand { let swiftlyBinDir = Swiftly.currentPlatform.swiftlyBinDir(ctx) let swiftlyBinDirContents = (try? await fs.ls(atPath: swiftlyBinDir)) ?? [String]() - let toolchainBinDir = Swiftly.currentPlatform.findToolchainBinDir(ctx, version) + let toolchainBinDir = try await Swiftly.currentPlatform.findToolchainBinDir(ctx, version) let toolchainBinDirContents = try await fs.ls(atPath: toolchainBinDir) var existingProxies: [String] = [] @@ -311,6 +311,8 @@ struct Install: SwiftlyCommand { case .main: category = "development" } + case .xcode: + fatalError("unreachable: xcode toolchain cannot be installed with swiftly") } let animation: ProgressAnimationProtocol = @@ -506,6 +508,8 @@ struct Install: SwiftlyCommand { } return .snapshot(firstSnapshot) + case .xcode: + throw SwiftlyError(message: "xcode toolchains are not available from swift.org") } } } diff --git a/Sources/Swiftly/List.swift b/Sources/Swiftly/List.swift index 7efcb9c8..986cd06a 100644 --- a/Sources/Swiftly/List.swift +++ b/Sources/Swiftly/List.swift @@ -55,7 +55,7 @@ struct List: SwiftlyCommand { let toolchains = config.listInstalledToolchains(selector: selector).sorted { $0 > $1 } let (inUse, _) = try await selectToolchain(ctx, config: &config) - let installedToolchainInfos = toolchains.compactMap { toolchain -> InstallToolchainInfo? in + var installedToolchainInfos = toolchains.compactMap { toolchain -> InstallToolchainInfo? in InstallToolchainInfo( version: toolchain, inUse: inUse == toolchain, diff --git a/Sources/Swiftly/OutputSchema.swift b/Sources/Swiftly/OutputSchema.swift index 3664914a..9a9dd440 100644 --- a/Sources/Swiftly/OutputSchema.swift +++ b/Sources/Swiftly/OutputSchema.swift @@ -125,6 +125,8 @@ struct AvailableToolchainInfo: OutputData { try versionContainer.encode(major, forKey: .major) try versionContainer.encode(minor, forKey: .minor) } + case .xcode: + try versionContainer.encode("system", forKey: .type) } } } @@ -233,6 +235,8 @@ struct InstallToolchainInfo: OutputData { try versionContainer.encode(major, forKey: .major) try versionContainer.encode(minor, forKey: .minor) } + case .xcode: + try versionContainer.encode("system", forKey: .type) } } @@ -279,6 +283,9 @@ struct InstallToolchainInfo: OutputData { branch: branch, date: date )) + case "system": + // The only system toolchain that exists at the moment is the xcode version + self.version = .xcode default: throw DecodingError.dataCorruptedError( forKey: ToolchainVersionCodingKeys.type, @@ -314,6 +321,8 @@ struct InstalledToolchainsListInfo: OutputData { "main development snapshot" case let .snapshot(.release(major, minor), nil): "\(major).\(minor) development snapshot" + case .xcode: + "xcode" default: "matching" } @@ -334,6 +343,13 @@ struct InstalledToolchainsListInfo: OutputData { lines.append("Installed snapshot toolchains") lines.append("-----------------------------") lines.append(contentsOf: snapshotToolchains.map(\.description)) + +#if os(macOS) + lines.append("") + lines.append("Available system toolchains") + lines.append("---------------------------") + lines.append(ToolchainVersion.xcode.description) +#endif } return lines.joined(separator: "\n") diff --git a/Sources/Swiftly/Uninstall.swift b/Sources/Swiftly/Uninstall.swift index a94ea8cb..9eb22b9b 100644 --- a/Sources/Swiftly/Uninstall.swift +++ b/Sources/Swiftly/Uninstall.swift @@ -55,7 +55,7 @@ struct Uninstall: SwiftlyCommand { let startingConfig = try await Config.load(ctx) - let toolchains: [ToolchainVersion] + var toolchains: [ToolchainVersion] if self.toolchain == "all" { // Sort the uninstalled toolchains such that the in-use toolchain will be uninstalled last. // This avoids printing any unnecessary output from using new toolchains while the uninstall is in progress. @@ -72,8 +72,11 @@ struct Uninstall: SwiftlyCommand { toolchains = installedToolchains } + // Filter out the xcode toolchain here since it is not uninstallable + toolchains.removeAll(where: { $0 == .xcodeVersion }) + guard !toolchains.isEmpty else { - await ctx.message("No toolchains matched \"\(self.toolchain)\"") + await ctx.message("No toolchains can be uninstalled that match \"\(self.toolchain)\"") return } @@ -105,6 +108,9 @@ struct Uninstall: SwiftlyCommand { case let .snapshot(s): // If a snapshot was previously in use, switch to the latest snapshot associated with that branch. selector = .snapshot(branch: s.branch, date: nil) + case .xcode: + // Xcode will not be in the list of installed toolchains, so this is only here for completeness + selector = .xcode } if let toUse = config.listInstalledToolchains(selector: selector) diff --git a/Sources/Swiftly/Update.swift b/Sources/Swiftly/Update.swift index a1b0592a..8ae31bdb 100644 --- a/Sources/Swiftly/Update.swift +++ b/Sources/Swiftly/Update.swift @@ -192,6 +192,8 @@ struct Update: SwiftlyCommand { default: fatalError("unreachable") } + case let .xcode: + throw SwiftlyError(message: "xcode cannot be updated from swiftly") } } diff --git a/Sources/Swiftly/Use.swift b/Sources/Swiftly/Use.swift index 2e595c4e..9264049d 100644 --- a/Sources/Swiftly/Use.swift +++ b/Sources/Swiftly/Use.swift @@ -54,6 +54,12 @@ struct Use: SwiftlyCommand { $ swiftly use 5.7-snapshot $ swiftly use main-snapshot + + 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. + + $ swiftly use xcode """ )) var toolchain: String? @@ -87,7 +93,7 @@ struct Use: SwiftlyCommand { } if self.printLocation { - let location = LocationInfo(path: "\(Swiftly.currentPlatform.findToolchainLocation(ctx, selectedVersion))") + let location = LocationInfo(path: "\(try await Swiftly.currentPlatform.findToolchainLocation(ctx, selectedVersion))") try await ctx.output(location) return } @@ -251,7 +257,7 @@ public func selectToolchain(_ ctx: SwiftlyCoreContext, config: inout Config, glo // Check to ensure that the global default in use toolchain matches one of the installed toolchains, and return // no selected toolchain if it doesn't. - guard let defaultInUse = config.inUse, config.installedToolchains.contains(defaultInUse) else { + guard let defaultInUse = config.inUse, config.listInstalledToolchains(selector: nil).contains(defaultInUse) else { return (nil, .globalDefault) } diff --git a/Sources/SwiftlyCore/Platform.swift b/Sources/SwiftlyCore/Platform.swift index be388960..e4782fc3 100644 --- a/Sources/SwiftlyCore/Platform.swift +++ b/Sources/SwiftlyCore/Platform.swift @@ -129,10 +129,10 @@ public protocol Platform: Sendable { func getShell() async throws -> String /// Find the location where the toolchain should be installed. - func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> FilePath + func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> FilePath /// Find the location of the toolchain binaries. - func findToolchainBinDir(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> FilePath + func findToolchainBinDir(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> FilePath } extension Platform { @@ -164,7 +164,7 @@ extension Platform { func proxyEnv(_ ctx: SwiftlyCoreContext, env: [String: String], toolchain: ToolchainVersion) async throws -> [String: String] { var newEnv = env - let tcPath = self.findToolchainLocation(ctx, toolchain) / "usr/bin" + let tcPath = try await self.findToolchainLocation(ctx, toolchain) / "usr/bin" guard try await fs.exists(atPath: tcPath) else { throw SwiftlyError( message: @@ -193,7 +193,7 @@ extension Platform { /// the exit code and program information. /// public func proxy(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, _ command: String, _ arguments: [String], _ env: [String: String] = [:]) async throws { - let tcPath = self.findToolchainLocation(ctx, toolchain) / "usr/bin" + let tcPath = (try await self.findToolchainLocation(ctx, toolchain)) / "usr/bin" let commandTcPath = tcPath / command let commandToRun = if try await fs.exists(atPath: commandTcPath) { @@ -225,7 +225,7 @@ extension Platform { /// the exit code and program information. /// public func proxyOutput(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, _ command: String, _ arguments: [String]) async throws -> String? { - let tcPath = self.findToolchainLocation(ctx, toolchain) / "usr/bin" + let tcPath = (try await self.findToolchainLocation(ctx, toolchain)) / "usr/bin" let commandTcPath = tcPath / command let commandToRun = if try await fs.exists(atPath: commandTcPath) { @@ -479,9 +479,9 @@ extension Platform { return try await fs.exists(atPath: swiftlyHomeBin) ? swiftlyHomeBin : nil } - public func findToolchainBinDir(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> FilePath + public func findToolchainBinDir(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) async throws -> FilePath { - self.findToolchainLocation(ctx, toolchain) / "usr/bin" + (try await self.findToolchainLocation(ctx, toolchain)) / "usr/bin" } #endif diff --git a/Sources/SwiftlyCore/ToolchainVersion.swift b/Sources/SwiftlyCore/ToolchainVersion.swift index 6286adc9..c615fb42 100644 --- a/Sources/SwiftlyCore/ToolchainVersion.swift +++ b/Sources/SwiftlyCore/ToolchainVersion.swift @@ -90,6 +90,7 @@ public enum ToolchainVersion: Sendable { case stable(StableRelease) case snapshot(Snapshot) + case xcode public init(major: Int, minor: Int, patch: Int) { self = .stable(StableRelease(major: major, minor: minor, patch: patch)) @@ -99,6 +100,8 @@ public enum ToolchainVersion: Sendable { self = .snapshot(Snapshot(branch: snapshotBranch, date: date)) } + public static let xcodeVersion: ToolchainVersion = .xcode + static func stableRegex() -> Regex<(Substring, Substring, Substring, Substring)> { try! Regex("^(?:Swift )?(\\d+)\\.(\\d+)\\.(\\d+)$") } @@ -132,6 +135,8 @@ public enum ToolchainVersion: Sendable { throw SwiftlyError(message: "invalid release snapshot version: \(string)") } self = ToolchainVersion(snapshotBranch: .release(major: major, minor: minor), date: String(match.output.3)) + } else if string == "xcode" { + self = ToolchainVersion.xcodeVersion } else { throw SwiftlyError(message: "invalid toolchain version: \"\(string)\"") } @@ -176,6 +181,8 @@ public enum ToolchainVersion: Sendable { case let .release(major, minor): return "\(major).\(minor)-snapshot-\(release.date)" } + case .xcode: + return "xcode" } } @@ -194,6 +201,8 @@ public enum ToolchainVersion: Sendable { case let .release(major, minor): return "swift-\(major).\(minor)-DEVELOPMENT-SNAPSHOT-\(release.date)-a" } + case .xcode: + return "xcode" } } } @@ -214,6 +223,8 @@ extension ToolchainVersion: CustomStringConvertible { return "\(release)" case let .snapshot(snapshot): return "\(snapshot)" + case .xcode: + return "xcode" } } } @@ -231,6 +242,14 @@ extension ToolchainVersion: Comparable { return false case (.stable, .snapshot): return !(rhs < lhs) + case (.xcode, .xcode): + return false + case (.xcode, _): + return false + case (_, .xcode): + return true + default: + return false } } } @@ -254,6 +273,9 @@ public enum ToolchainSelector: Sendable { /// associated with the given branch. case snapshot(branch: ToolchainVersion.Snapshot.Branch, date: String?) + /// Selects the Xcode of the current system. + case xcode + public init(major: Int, minor: Int? = nil, patch: Int? = nil) { self = .stable(major: major, minor: minor, patch: patch) } @@ -267,6 +289,11 @@ public enum ToolchainSelector: Sendable { return } + if input == "xcode" { + self = Self.xcode + return + } + throw SwiftlyError(message: "invalid toolchain selector: \"\(input)\"") } @@ -274,7 +301,7 @@ public enum ToolchainSelector: Sendable { switch self { case .latest, .stable: return true - case .snapshot: + default: return false } } @@ -312,7 +339,8 @@ public enum ToolchainSelector: Sendable { } } return true - + case (.xcode, .xcode): + return true default: return false } @@ -341,6 +369,8 @@ extension ToolchainSelector: CustomStringConvertible { s += "-\(date)" } return s + case .xcode: + return "xcode" } } } diff --git a/Tests/SwiftlyTests/InstallTests.swift b/Tests/SwiftlyTests/InstallTests.swift index 590d9b0a..007697d6 100644 --- a/Tests/SwiftlyTests/InstallTests.swift +++ b/Tests/SwiftlyTests/InstallTests.swift @@ -256,7 +256,7 @@ import Testing } /// Verify that the installed toolchain will be marked as in-use if the --use flag is specified. - @Test(.testHomeMockedToolchain()) func installUseFlag() async throws { + @Test(.mockedSwiftlyVersion(), .testHomeMockedToolchain()) func installUseFlag() async throws { try await SwiftlyTests.installMockedToolchain(toolchain: .oldStable) try await SwiftlyTests.runCommand(Use.self, ["use", ToolchainVersion.oldStable.name]) try await SwiftlyTests.validateInUse(expected: .oldStable) @@ -264,6 +264,13 @@ import Testing try await SwiftlyTests.validateInUse(expected: .newStable) } + /// Verify that xcode can't be installed like regular toolchains + @Test(.testHomeMockedToolchain()) func installXcode() async throws { + try await #expect(throws: SwiftlyError.self) { + try await SwiftlyTests.runCommand(Install.self, ["install", "xcode", "--post-install-file=\(fs.mktemp())"]) + } + } + /// Verify that progress information is written to the progress file when specified. @Test(.testHomeMockedToolchain()) func installProgressFile() async throws { let progressFile = fs.mktemp(ext: ".json") diff --git a/Tests/SwiftlyTests/LinkTests.swift b/Tests/SwiftlyTests/LinkTests.swift index 162337ab..f49ef9aa 100644 --- a/Tests/SwiftlyTests/LinkTests.swift +++ b/Tests/SwiftlyTests/LinkTests.swift @@ -19,7 +19,7 @@ import Testing // And start creating a mock folder structure for that toolchain. try "swiftly binary".write(to: swiftlyBinaryPath, atomically: true, encoding: .utf8) - let toolchainDir = Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, toolchainVersion) / "usr" / "bin" + let toolchainDir = try await Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, toolchainVersion) / "usr" / "bin" try await fs.mkdir(.parents, atPath: toolchainDir) let proxies = ["swift-build", "swift-test", "swift-run"] diff --git a/Tests/SwiftlyTests/ListTests.swift b/Tests/SwiftlyTests/ListTests.swift index e9bc6bee..d828b173 100644 --- a/Tests/SwiftlyTests/ListTests.swift +++ b/Tests/SwiftlyTests/ListTests.swift @@ -49,13 +49,19 @@ import Testing let lines = output.flatMap { $0.split(separator: "\n").map(String.init) } let parsedToolchains = lines.compactMap { outputLine in +#if !os(macOS) Set.allToolchains().first { - outputLine.contains(String(describing: $0)) + outputLine.hasPrefix(String(describing: $0)) } +#else + (Set.allToolchains() + [.xcodeVersion]).first { + outputLine.hasPrefix(String(describing: $0)) + } +#endif } // Ensure extra toolchains weren't accidentally included in the output. - guard parsedToolchains.count == lines.filter({ $0.hasPrefix("Swift") || $0.contains("-snapshot") }).count else { + guard parsedToolchains.count == lines.filter({ $0.hasPrefix("Swift") || $0.contains("-snapshot") || $0.hasPrefix("xcode") }).count else { throw SwiftlyTestError(message: "unexpected listed toolchains in \(output)") } @@ -67,7 +73,11 @@ import Testing @Test func list() async throws { try await self.runListTest { let toolchains = try await self.runList(selector: nil) +#if !os(macOS) #expect(toolchains == Self.sortedReleaseToolchains + Self.sortedSnapshotToolchains) +#else + #expect(toolchains == Self.sortedReleaseToolchains + Self.sortedSnapshotToolchains + [.xcodeVersion]) +#endif } } @@ -161,9 +171,15 @@ import Testing /// Tests that `list` properly handles the case where no toolchains have been installed yet. @Test(.testHome(Self.homeName)) func listEmpty() async throws { +#if !os(macOS) + let systemToolchains: [ToolchainVersion] = [] +#else + let systemToolchains: [ToolchainVersion] = [.xcodeVersion] +#endif + try await SwiftlyTests.withMockedSwiftlyVersion(latestSwiftlyVersion: Self.swiftlyVersion) { var toolchains = try await self.runList(selector: nil) - #expect(toolchains == []) + #expect(toolchains == systemToolchains) toolchains = try await self.runList(selector: "5") #expect(toolchains == []) @@ -173,6 +189,11 @@ import Testing toolchains = try await self.runList(selector: "5.7-snapshot") #expect(toolchains == []) + +#if os(macOS) + toolchains = try await self.runList(selector: "xcode") + #expect(toolchains == systemToolchains) +#endif } } @@ -188,7 +209,13 @@ import Testing from: output[0].data(using: .utf8)! ) - #expect(listInfo.toolchains.count == Set.allToolchains().count) +#if !os(macOS) + let systemToolchains: [ToolchainVersion] = [] +#else + let systemToolchains: [ToolchainVersion] = [.xcodeVersion] +#endif + + #expect(listInfo.toolchains.count == Set.allToolchains().count + systemToolchains.count) for toolchain in listInfo.toolchains { #expect(toolchain.version.name.isEmpty == false) diff --git a/Tests/SwiftlyTests/PlatformTests.swift b/Tests/SwiftlyTests/PlatformTests.swift index 7354e44b..8b3c5a8b 100644 --- a/Tests/SwiftlyTests/PlatformTests.swift +++ b/Tests/SwiftlyTests/PlatformTests.swift @@ -87,6 +87,22 @@ import Testing #expect(0 == toolchains.count) } +#if os(macOS) + @Test(.mockedSwiftlyVersion(), .testHome()) func findXcodeToolchainLocation() async throws { + // GIVEN: the xcode toolchain + // AND there is xcode installed + guard let swiftLocation = try? await Swiftly.currentPlatform.runProgramOutput("xcrun", "-f", "swift"), swiftLocation != "" else { + return + } + + // WHEN: the location of the xcode toolchain can be found + let toolchainLocation = try await Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, .xcodeVersion) + + // THEN: the xcode toolchain matches the currently selected xcode toolchain + #expect(toolchainLocation.string == swiftLocation.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: "/usr/bin/swift", with: "")) + } +#endif + #if os(macOS) || os(Linux) @Test( .mockedSwiftlyVersion(), @@ -106,7 +122,7 @@ import Testing let newEnv = try await Swiftly.currentPlatform.proxyEnv(SwiftlyTests.ctx, env: env, toolchain: .newStable) // THEN: the toolchain's bin directory is added to the beginning of the PATH - #expect(newEnv["PATH"]!.hasPrefix((Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, .newStable) / "usr/bin").string)) + #expect(newEnv["PATH"]!.hasPrefix(((try await Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, .newStable)) / "usr/bin").string)) // AND: the swiftly bin directory is removed from the PATH #expect(!newEnv["PATH"]!.contains(Swiftly.currentPlatform.swiftlyBinDir(SwiftlyTests.ctx).string)) diff --git a/Tests/SwiftlyTests/UninstallTests.swift b/Tests/SwiftlyTests/UninstallTests.swift index d18004ff..711cd7b3 100644 --- a/Tests/SwiftlyTests/UninstallTests.swift +++ b/Tests/SwiftlyTests/UninstallTests.swift @@ -280,4 +280,9 @@ import Testing description: "uninstall did not uninstall all toolchains" ) } + + @Test(.mockedSwiftlyVersion(), .mockHomeToolchains(Self.homeName, toolchains: [])) func uninstallXcode() async throws { + let output = try await SwiftlyTests.runWithMockedIO(Uninstall.self, ["uninstall", "-y", ToolchainVersion.xcodeVersion.name]) + #expect(!output.filter { $0.contains("No toolchains can be uninstalled that match \"xcode\"") }.isEmpty) + } } diff --git a/Tests/SwiftlyTests/UseTests.swift b/Tests/SwiftlyTests/UseTests.swift index d7c5e71b..720ec2de 100644 --- a/Tests/SwiftlyTests/UseTests.swift +++ b/Tests/SwiftlyTests/UseTests.swift @@ -191,6 +191,13 @@ import Testing ) } +#if os(macOS) + /// Tests that the xcode toolchain can be used on macOS + @Test(.mockedSwiftlyVersion(), .mockHomeToolchains()) func useXcode() async throws { + try await self.useAndValidate(argument: ToolchainVersion.xcodeVersion.name, expectedVersion: .xcode) + } +#endif + /// Tests that the `use` command gracefully exits when executed before any toolchains have been installed. @Test(.mockedSwiftlyVersion(), .mockHomeToolchains(toolchains: [])) func useNoInstalledToolchains() async throws { @@ -255,17 +262,26 @@ import Testing Use.self, ["use", "-g", "--print-location"] ) + let location = try await Swiftly.currentPlatform.findToolchainLocation(SwiftlyTests.ctx, toolchain) + #expect( output.contains(where: { - $0.contains( - Swiftly.currentPlatform.findToolchainLocation( - SwiftlyTests.ctx, toolchain - ).string) - })) + $0.contains(location.string) + }) + ) } } } +#if os(macOS) + /// Tests that running a use command without an argument prints the xcode toolchain when it is in use. + @Test(.mockedSwiftlyVersion(), .mockHomeToolchains()) func printInUseXcode() async throws { + try await SwiftlyTests.runCommand(Use.self, ["use", "-g", "xcode"]) + var output = try await SwiftlyTests.runWithMockedIO(Use.self, ["use", "-g"]) + #expect(output.contains(where: { $0.contains("xcode") })) + } +#endif + /// Tests in-use toolchain selected by the .swift-version file. @Test(.mockedSwiftlyVersion()) func swiftVersionFile() async throws { let toolchains = [ @@ -352,9 +368,9 @@ import Testing ) #expect( locationInfo.path - == Swiftly.currentPlatform.findToolchainLocation( + == (try await Swiftly.currentPlatform.findToolchainLocation( SwiftlyTests.ctx, toolchain - ).string) + )).string) } } }