Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a download runtimes command #267

Merged
merged 1 commit into from
Feb 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Sources/XcodesKit/Downloader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ public enum Downloader {
case urlSession
case aria2(Path)

public init(aria2Path: String?) {
guard let aria2Path = aria2Path.flatMap(Path.init) ?? Current.shell.findExecutable("aria2c"), aria2Path.exists else {
self = .urlSession
return
}
self = .aria2(aria2Path)
}

func download(url: URL, to destination: Path, progressChanged: @escaping (Progress) -> Void) -> Promise<URL> {
switch self {
case .urlSession:
Expand Down
24 changes: 18 additions & 6 deletions Sources/XcodesKit/RuntimeInstaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public class RuntimeInstaller {
str += " (\(runtime.build))"
}
if runtime.state == .legacyDownload || runtime.state == .diskImage {
str += " (Downloaded)"
str += " (Installed)"
} else if runtime.state == .bundled {
str += " (Bundled with selected Xcode)"
}
Expand All @@ -92,11 +92,15 @@ public class RuntimeInstaller {
Current.logging.log("\nNote: Bundled runtimes are indicated for the currently selected Xcode, more bundled runtimes may exist in other Xcode(s)")
}

public func downloadRuntime(identifier: String, to destinationDirectory: Path, with downloader: Downloader) async throws {
let matchedRuntime = try await getMatchingRuntime(identifier: identifier)

_ = try await downloadOrUseExistingArchive(runtime: matchedRuntime, to: destinationDirectory, downloader: downloader)
}


public func downloadAndInstallRuntime(identifier: String, to destinationDirectory: Path, with downloader: Downloader, shouldDelete: Bool) async throws {
let downloadables = try await runtimeList.downloadableRuntimes().downloadables
guard let matchedRuntime = downloadables.first(where: { $0.visibleIdentifier == identifier || $0.simulatorVersion.buildUpdate == identifier }) else {
throw Error.unavailableRuntime(identifier)
}
let matchedRuntime = try await getMatchingRuntime(identifier: identifier)

if matchedRuntime.contentType == .package && !Current.shell.isRoot() {
throw Error.rootNeeded
Expand All @@ -115,6 +119,14 @@ public class RuntimeInstaller {
}
}

private func getMatchingRuntime(identifier: String) async throws -> DownloadableRuntime {
let downloadables = try await runtimeList.downloadableRuntimes().downloadables
guard let runtime = downloadables.first(where: { $0.visibleIdentifier == identifier || $0.simulatorVersion.buildUpdate == identifier }) else {
throw Error.unavailableRuntime(identifier)
}
return runtime
}

private func installFromImage(dmgUrl: URL) async throws {
Current.logging.log("Installing Runtime")
try await Current.shell.installRuntimeImage(dmgUrl).asVoid().async()
Expand Down Expand Up @@ -187,7 +199,7 @@ public class RuntimeInstaller {
}

if Current.files.fileExistsAtPath(destination.string), aria2DownloadIsIncomplete == false {
Current.logging.log("Found existing Runtime that will be used for installation at \(destination).")
Current.logging.log("Found existing Runtime that will be used, at \(destination).")
return destination.url
}
if runtime.authentication == .virtual {
Expand Down
58 changes: 39 additions & 19 deletions Sources/xcodes/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,7 @@ struct Xcodes: AsyncParsableCommand {
installation = .version(versionString)
}

var downloader = Downloader.urlSession
if let aria2Path = aria2.flatMap(Path.init) ?? Current.shell.findExecutable("aria2c"),
aria2Path.exists,
noAria2 == false {
downloader = .aria2(aria2Path)
}
let downloader = noAria2 ? Downloader.urlSession : Downloader(aria2Path: aria2)

let destination = getDirectory(possibleDirectory: directory, default: .environmentDownloads)

Expand Down Expand Up @@ -241,12 +236,7 @@ struct Xcodes: AsyncParsableCommand {
installation = .version(versionString)
}

var downloader = Downloader.urlSession
if let aria2Path = aria2.flatMap(Path.init) ?? Current.shell.findExecutable("aria2c"),
aria2Path.exists,
noAria2 == false {
downloader = .aria2(aria2Path)
}
let downloader = noAria2 ? Downloader.urlSession : Downloader(aria2Path: aria2)

let destination = getDirectory(possibleDirectory: directory)

Expand Down Expand Up @@ -368,7 +358,7 @@ struct Xcodes: AsyncParsableCommand {
struct Runtimes: AsyncParsableCommand {
static var configuration = CommandConfiguration(
abstract: "List all simulator runtimes that are available to install",
subcommands: [Install.self]
subcommands: [Download.self, Install.self]
)

@Flag(help: "Include beta runtimes available to install")
Expand Down Expand Up @@ -406,19 +396,49 @@ struct Xcodes: AsyncParsableCommand {
func run() async throws {
Rainbow.enabled = Rainbow.enabled && globalColor.color

var downloader = Downloader.urlSession
if let aria2Path = aria2.flatMap(Path.init) ?? Current.shell.findExecutable("aria2c"),
aria2Path.exists,
noAria2 == false {
downloader = .aria2(aria2Path)
}
let downloader = noAria2 ? Downloader.urlSession : Downloader(aria2Path: aria2)

let destination = getDirectory(possibleDirectory: directory, default: .environmentDownloads)

try await runtimeInstaller.downloadAndInstallRuntime(identifier: version, to: destination, with: downloader, shouldDelete: !keepArchive)
Current.logging.log("Finished")
}
}

struct Download: AsyncParsableCommand {
static var configuration = CommandConfiguration(
abstract: "Download a specific simulator runtime"
)

@Argument(help: "The runtime to download")
var version: String

@Option(help: "The path to an aria2 executable. Searches $PATH by default.",
completion: .file())
var aria2: String?

@Flag(help: "Don't use aria2 to download the runtime, even if its available.")
var noAria2: Bool = false

@Option(help: "The directory to download the runtime archive to. Defaults to ~/Downloads.",
completion: .directory)
var directory: String?

@OptionGroup
var globalColor: GlobalColorOption

func run() async throws {
Rainbow.enabled = Rainbow.enabled && globalColor.color

let downloader = noAria2 ? Downloader.urlSession : Downloader(aria2Path: aria2)

let destination = getDirectory(possibleDirectory: directory, default: .environmentDownloads)

try await runtimeInstaller.downloadRuntime(identifier: version, to: destination, with: downloader)
Current.logging.log("Finished")
}
}

}

struct Select: ParsableCommand {
Expand Down
12 changes: 6 additions & 6 deletions Tests/XcodesKitTests/Fixtures/LogOutput-Runtime_NoBetas.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- iOS --
iOS 12.4 (Downloaded)
iOS 13.0 (Downloaded)
iOS 13.1 (Downloaded)
iOS 12.4 (Installed)
iOS 13.0 (Installed)
iOS 13.1 (Installed)
iOS 13.2.2
iOS 13.3
iOS 13.4
Expand All @@ -18,7 +18,7 @@ iOS 15.0
iOS 15.2
iOS 15.4
iOS 15.5 (Bundled with selected Xcode)
iOS 15.5 (Downloaded)
iOS 15.5 (Installed)
iOS 16.0
-- watchOS --
watchOS 6.0
Expand All @@ -31,9 +31,9 @@ watchOS 7.4
watchOS 8.0
watchOS 8.3
watchOS 8.5 (Bundled with selected Xcode)
watchOS 9.0-beta4 (Downloaded)
watchOS 9.0-beta4 (Installed)
watchOS 9.0 (20R362)
watchOS 9.0 (UnknownBuildNumber) (Downloaded)
watchOS 9.0 (UnknownBuildNumber) (Installed)
-- tvOS --
tvOS 12.4
tvOS 13.0
Expand Down
12 changes: 6 additions & 6 deletions Tests/XcodesKitTests/Fixtures/LogOutput-Runtimes.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- iOS --
iOS 12.4 (Downloaded)
iOS 13.0 (Downloaded)
iOS 13.1 (Downloaded)
iOS 12.4 (Installed)
iOS 13.0 (Installed)
iOS 13.1 (Installed)
iOS 13.2.2
iOS 13.3
iOS 13.4
Expand All @@ -18,7 +18,7 @@ iOS 15.0
iOS 15.2
iOS 15.4
iOS 15.5 (Bundled with selected Xcode)
iOS 15.5 (Downloaded)
iOS 15.5 (Installed)
iOS 16.0
-- watchOS --
watchOS 6.0
Expand All @@ -34,10 +34,10 @@ watchOS 8.5 (Bundled with selected Xcode)
watchOS 9.0-beta1
watchOS 9.0-beta2
watchOS 9.0-beta3
watchOS 9.0-beta4 (Downloaded)
watchOS 9.0-beta4 (Installed)
watchOS 9.0-beta5
watchOS 9.0 (20R362)
watchOS 9.0 (UnknownBuildNumber) (Downloaded)
watchOS 9.0 (UnknownBuildNumber) (Installed)
watchOS 9.1-beta1
-- tvOS --
tvOS 12.4
Expand Down