SDK commands

This commit is contained in:
Kabir Oberai
2024-12-12 23:19:02 +05:30
Unverified
parent 44483b33af
commit 0ac28bca0d
3 changed files with 243 additions and 19 deletions
+3 -1
View File
@@ -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
),
+1 -18
View File
@@ -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()
}
}
@@ -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<Version> {
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..<upper
}
}
}
var current: String
var metadata: [String: Metadata]
static func all(
httpFactory: HTTPClientFactory = defaultHTTPClientFactory
) async throws -> 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[..<space]
guard let version = Version(tolerant: output) else {
throw Console.Error("Could not parse Swift version: '\(output)'")
}
return version
}
}
struct InstallSDKOperation {
func run() async throws {
#if os(macOS) && false
print("Skipping SDK install; the iOS SDK ships with Xcode on macOS")
#else
let arch = try SDKArch.current
let list = try await DarwinSDKVersions.all()
guard let item = list.metadata[list.current] else {
throw Console.Error("""
Couldn't find SDK metadata for '\(list.current)'. You may need to update Supersign.
""")
}
let acceptedRange = try item.versionRange
let swiftVersion = try await SwiftVersion.current()
guard acceptedRange.contains(swiftVersion) else {
throw Console.Error("""
You currently have Swift version \(swiftVersion) installed. \
The Darwin Swift SDK currently requires Swift ≥\(acceptedRange.lowerBound), \
<\(acceptedRange.upperBound).
""")
}
if let sdk = try DarwinSDK.current() {
let installed = sdk.version
if list.current != installed {
print("Installed Darwin SDK version is '\(installed)' but latest is '\(list.current)'. Updating...")
try FileManager.default.removeItem(at: sdk.bundle)
} else {
print("Darwin SDK is up to date.")
return
}
} else {
print("Darwin Swift SDK not found on disk. Installing...")
}
let install = Process()
install.executableURL = URL(fileURLWithPath: "/usr/bin/env")
install.arguments = [
"swift", "sdk", "install",
"""
https://github.com/kabiroberai/swift-sdk-darwin/releases/download/\(list.current)/\
darwin-linux-\(arch.releaseName).artifactbundle.zip
""",
"--checksum", item.checksum(for: arch)
]
try install.run()
await install.waitForExit()
guard install.terminationStatus == 0 else {
throw Console.Error("Failed to install SDK")
}
#endif
}
}