From 0ac28bca0dd32b4f2ae20f340074591191b53fa4 Mon Sep 17 00:00:00 2001 From: Kabir Oberai Date: Thu, 12 Dec 2024 23:19:02 +0530 Subject: [PATCH] SDK commands --- Package.swift | 4 +- Sources/SupersignCLISupport/DevCommand.swift | 19 +- .../SupersignCLISupport/DevSDKCommand.swift | 239 ++++++++++++++++++ 3 files changed, 243 insertions(+), 19 deletions(-) create mode 100644 Sources/SupersignCLISupport/DevSDKCommand.swift diff --git a/Package.swift b/Package.swift index dbe6bd5..6f8e7ed 100644 --- a/Package.swift +++ b/Package.swift @@ -46,6 +46,7 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.3.0"), .package(url: "https://github.com/attaswift/BigInt", from: "5.5.0"), + .package(url: "https://github.com/mxcl/Version", from: "2.1.0"), ], targets: [ .systemLibrary(name: "CSupersette"), @@ -118,7 +119,8 @@ let package = Package( "SwiftyMobileDevice", "Supersign", .product(name: "PackLib", package: "swiftpack"), - .product(name: "ArgumentParser", package: "swift-argument-parser") + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "Version", package: "Version"), ], cSettings: cSettings ), diff --git a/Sources/SupersignCLISupport/DevCommand.swift b/Sources/SupersignCLISupport/DevCommand.swift index 8e3f427..e6a50b1 100644 --- a/Sources/SupersignCLISupport/DevCommand.swift +++ b/Sources/SupersignCLISupport/DevCommand.swift @@ -3,12 +3,6 @@ import ArgumentParser import PackLib import Supersign -struct AddSDKOperation { - func run() async throws { - print("TODO: install Swift SDK for iOS") - } -} - struct DevSetupCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "setup", @@ -22,18 +16,7 @@ struct DevSetupCommand: AsyncParsableCommand { func run() async throws { try await AuthOperation(logoutFromExisting: false).run() - try await AddSDKOperation().run() - } -} - -struct DevSDKCommand: AsyncParsableCommand { - static let configuration = CommandConfiguration( - commandName: "sdk", - abstract: "Manages the Swift SDK for iOS" - ) - - func run() async throws { - try await AddSDKOperation().run() + try await InstallSDKOperation().run() } } diff --git a/Sources/SupersignCLISupport/DevSDKCommand.swift b/Sources/SupersignCLISupport/DevSDKCommand.swift new file mode 100644 index 0000000..53378c6 --- /dev/null +++ b/Sources/SupersignCLISupport/DevSDKCommand.swift @@ -0,0 +1,239 @@ +import Foundation +import Supersign +import Version +import ArgumentParser +#if os(Linux) +import FoundationNetworking +#endif + +struct DevSDKCommand: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "sdk", + abstract: "Manages the Darwin Swift SDK", + subcommands: [ + DevSDKInstallCommand.self, + DevSDKRemoveCommand.self, + ], + defaultSubcommand: DevSDKInstallCommand.self + ) +} + +struct DevSDKInstallCommand: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "install", + abstract: "Install the Darwin Swift SDK" + ) + + func run() async throws { + try await InstallSDKOperation().run() + } +} + +struct DevSDKRemoveCommand: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "remove", + abstract: "Remove the Darwin Swift SDK" + ) + + func run() async throws { + guard let sdk = try DarwinSDK.current() else { + throw Console.Error("Cannot remove SDK: no Darwin SDK installed") + } + try FileManager.default.removeItem(at: sdk.bundle) + print("Uninstalled SDK") + } +} + +struct DarwinSDKVersions: Decodable { + private static let url = URL(string: """ + https://raw.githubusercontent.com/kabiroberai/swift-sdk-darwin/refs/heads/main/versions.json + """)! + + private static let decoder = JSONDecoder() + + struct Metadata: Decodable { + var arm64Checksum: String + var x64Checksum: String + var compilerRange: [String] + + func checksum(for arch: SDKArch) -> String { + switch arch { + case .arm64: return arm64Checksum + case .x86_64: return x64Checksum + } + } + + var versionRange: Range { + get throws { + guard compilerRange.count == 2, + let lower = Version(tolerant: compilerRange[0]), + let upper = Version(tolerant: compilerRange[1]) else { + throw Console.Error("Could not parse SDK metadata list. You may need to update Supersign.") + } + return lower.. DarwinSDKVersions { + let data = try await httpFactory.makeClient().makeRequest(HTTPRequest(url: url)).body ?? Data() + return try decoder.decode(self, from: data) + } +} + +struct DarwinSDK { + private static let sdksDir = URL(fileURLWithPath: NSHomeDirectory()) + .appendingPathComponent(".swiftpm/swift-sdks") + .resolvingSymlinksInPath() + + let bundle: URL + let version: String + + init?(bundle: URL) { + self.bundle = bundle + if let version = try? Data(contentsOf: bundle.appendingPathComponent("darwin-sdk-version.txt")) { + self.version = String(decoding: version, as: UTF8.self) + .trimmingCharacters(in: .whitespacesAndNewlines) + } else if bundle.lastPathComponent == "darwin.artifactbundle" { + self.version = "unknown" + } else { + return nil + } + } + + static func current() throws -> DarwinSDK? { + let sdks = (try? FileManager.default.contentsOfDirectory(at: sdksDir, includingPropertiesForKeys: nil)) ?? [] + let darwinSDKs = sdks.compactMap { DarwinSDK(bundle: $0) } + switch darwinSDKs.count { + case 0: + return nil + case 1: + return darwinSDKs[0] + default: + throw Console.Error(""" + You have multiple copies of the Darwin SDK installed. Please delete all but one to continue. + \(darwinSDKs.map { "- \($0.bundle.path) (version '\($0.version)')" }.joined(separator: "\n")) + """) + } + } +} + +enum SDKArch { + case arm64 + case x86_64 + + var releaseName: String { + switch self { + case .arm64: "aarch64" + case .x86_64: "x86_64" + } + } + + static var current: SDKArch { + get throws { + #if arch(arm64) + return .arm64 + #elseif arch(x86_64) + return .x86_64 + #else + throw Console.Error("Unsupported architecture: the Swift SDK supports aarch64 and x86_64.") + #endif + } + } +} + +private enum SwiftVersion {} +extension SwiftVersion { + static func current() async throws -> Version { + let outPipe = Pipe() + let errPipe = Pipe() + let swift = Process() + swift.executableURL = URL(fileURLWithPath: "/usr/bin/env") + swift.arguments = ["swift", "--version"] + swift.standardOutput = outPipe + swift.standardError = errPipe + try swift.run() + async let outputTask = outPipe.fileHandleForReading.readToEnd() + await swift.waitForExit() + guard swift.terminationStatus == 0 else { + throw Console.Error("Failed to obtain Swift version") + } + let outputData = try await outputTask + var output = String(decoding: outputData ?? Data(), as: UTF8.self)[...] + if output.hasPrefix("Apple ") { + output = output.dropFirst("Apple ".count) + } + guard output.hasPrefix("Swift version ") else { + throw Console.Error("Could not parse Swift version: '\(output)'") + } + output = output.dropFirst("Swift version ".count) + guard let space = output.firstIndex(of: " ") else { + throw Console.Error("Could not parse Swift version: '\(output)'") + } + output = output[..