Skip to content

Commit

Permalink
Adopt OpenAPI for fetching current swiftly releases
Browse files Browse the repository at this point in the history
Use the OpenAPI specification for the swift.org API and the
swift OpenAPI generation capabilities to avoid having to hand
craft the HTTP handling and local data types.
  • Loading branch information
cmcgee1024 committed Jan 25, 2025
1 parent c7fe40d commit de41cd9
Show file tree
Hide file tree
Showing 23 changed files with 535 additions and 125 deletions.
51 changes: 48 additions & 3 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "531e10b955219c0de91ada74260f59bff8033189f1a9f9f78b199480c61f466a",
"originHash" : "779fae0370090a6e022cff081d717f7654a191cfc47cf667ed69f23323b09c02",
"pins" : [
{
"identity" : "async-http-client",
Expand All @@ -10,6 +10,15 @@
"version" : "1.21.2"
}
},
{
"identity" : "openapikit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mattpolzin/OpenAPIKit",
"state" : {
"revision" : "749ceba9a6e91d40081f27996fd91fd3de9a8dfe",
"version" : "3.4.0"
}
},
{
"identity" : "swift-algorithms",
"kind" : "remoteSourceControl",
Expand Down Expand Up @@ -42,8 +51,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb",
"version" : "1.1.0"
"revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
"version" : "1.1.4"
}
},
{
Expand Down Expand Up @@ -136,6 +145,33 @@
"version" : "1.0.2"
}
},
{
"identity" : "swift-openapi-async-http-client",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swift-server/swift-openapi-async-http-client",
"state" : {
"revision" : "abfe558a66992ef1e896a577010f957915f30591",
"version" : "1.0.0"
}
},
{
"identity" : "swift-openapi-generator",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-openapi-generator",
"state" : {
"revision" : "84b693f9d0559dc488e691edb4837bafbce2aaea",
"version" : "1.7.0"
}
},
{
"identity" : "swift-openapi-runtime",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-openapi-runtime",
"state" : {
"revision" : "23146bc8710ac5e57abb693113f02dc274cf39b6",
"version" : "1.8.0"
}
},
{
"identity" : "swift-system",
"kind" : "remoteSourceControl",
Expand All @@ -162,6 +198,15 @@
"revision" : "c7ddb09c3381cff833106345721fc314dba49e20",
"version" : "0.49.18"
}
},
{
"identity" : "yams",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/Yams",
"state" : {
"revision" : "3036ba9d69cf1fd04d433527bc339dc0dc75433d",
"version" : "5.1.3"
}
}
],
"version" : 3
Expand Down
8 changes: 8 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
.package(url: "https://github.com/swift-server/async-http-client", from: "1.21.2"),
.package(url: "https://github.com/swift-server/swift-openapi-async-http-client", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.64.0"),
.package(url: "https://github.com/apple/swift-tools-support-core.git", from: "0.7.2"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"),
.package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"),
.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"),
// This dependency provides the correct version of the formatter so that you can run `swift run swiftformat Package.swift Plugins/ Sources/ Tests/`
.package(url: "https://github.com/nicklockwood/SwiftFormat", exact: "0.49.18"),
],
Expand All @@ -38,6 +41,11 @@ let package = Package(
dependencies: [
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "NIOFoundationCompat", package: "swift-nio"),
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
.product(name: "OpenAPIAsyncHTTPClient", package: "swift-openapi-async-http-client"),
],
plugins: [
.plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator"),
]
),
.target(
Expand Down
22 changes: 11 additions & 11 deletions Sources/LinuxPlatform/Linux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public struct Linux: Platform {
You can install the ca-certificates package on your system to fix this.
"""

throw Error(message: msg)
throw SwiftlyError(message: msg)
}
}

Expand Down Expand Up @@ -258,7 +258,7 @@ public struct Linux: Platform {
}
msg += "\n" + Self.skipVerificationMessage

throw Error(message: msg)
throw SwiftlyError(message: msg)
}

// Import the latest swift keys, but only once per session, which will help with the performance in tests
Expand All @@ -270,7 +270,7 @@ public struct Linux: Platform {
}

guard let url = URL(string: "https://www.swift.org/keys/all-keys.asc") else {
throw Error(message: "malformed URL to the swift gpg keys")
throw SwiftlyError(message: "malformed URL to the swift gpg keys")
}

try await httpClient.downloadFile(url: url, to: tmpFile)
Expand Down Expand Up @@ -329,7 +329,7 @@ public struct Linux: Platform {

public func install(from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws {
guard tmpFile.fileExists() else {
throw Error(message: "\(tmpFile) doesn't exist")
throw SwiftlyError(message: "\(tmpFile) doesn't exist")
}

if !self.swiftlyToolchainsDir.fileExists() {
Expand Down Expand Up @@ -361,7 +361,7 @@ public struct Linux: Platform {

public func extractSwiftlyAndInstall(from archive: URL) throws {
guard archive.fileExists() else {
throw Error(message: "\(archive) doesn't exist")
throw SwiftlyError(message: "\(archive) doesn't exist")
}

let tmpDir = self.getTempFilePath()
Expand Down Expand Up @@ -414,7 +414,7 @@ public struct Linux: Platform {
do {
try self.runProgram("gpg", "--verify", sigFile.path, archive.path, quiet: !verbose)
} catch {
throw Error(message: "Signature verification failed: \(error).")
throw SwiftlyError(message: "Signature verification failed: \(error).")
}
}

Expand Down Expand Up @@ -471,7 +471,7 @@ public struct Linux: Platform {
guard let releaseFile = releaseFile else {
let message = "Unable to detect the type of Linux OS and the release"
if disableConfirmation {
throw Error(message: message)
throw SwiftlyError(message: message)
} else {
print(message)
}
Expand All @@ -498,7 +498,7 @@ public struct Linux: Platform {
guard let id, let versionID else {
let message = "Unable to find release information from file \(releaseFile)"
if disableConfirmation {
throw Error(message: message)
throw SwiftlyError(message: message)
} else {
print(message)
}
Expand All @@ -509,7 +509,7 @@ public struct Linux: Platform {
guard versionID == "2" else {
let message = "Unsupported version of Amazon Linux"
if disableConfirmation {
throw Error(message: message)
throw SwiftlyError(message: message)
} else {
print(message)
}
Expand All @@ -521,7 +521,7 @@ public struct Linux: Platform {
guard versionID.hasPrefix("9") else {
let message = "Unsupported version of RHEL"
if disableConfirmation {
throw Error(message: message)
throw SwiftlyError(message: message)
} else {
print(message)
}
Expand All @@ -535,7 +535,7 @@ public struct Linux: Platform {

let message = "Unsupported Linux platform"
if disableConfirmation {
throw Error(message: message)
throw SwiftlyError(message: message)
} else {
print(message)
}
Expand Down
10 changes: 5 additions & 5 deletions Sources/MacOSPlatform/MacOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public struct MacOS: Platform {

public func install(from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws {
guard tmpFile.fileExists() else {
throw Error(message: "\(tmpFile) doesn't exist")
throw SwiftlyError(message: "\(tmpFile) doesn't exist")
}

if !self.swiftlyToolchainsDir.fileExists() {
Expand Down Expand Up @@ -85,7 +85,7 @@ public struct MacOS: Platform {

public func extractSwiftlyAndInstall(from archive: URL) throws {
guard archive.fileExists() else {
throw Error(message: "\(archive) doesn't exist")
throw SwiftlyError(message: "\(archive) doesn't exist")
}

let homeDir: URL
Expand All @@ -111,7 +111,7 @@ public struct MacOS: Platform {
// and the ones that are mocked here in the test framework.
let payload = tmpDir.appendingPathComponent("Payload")
guard payload.fileExists() else {
throw Error(message: "Payload file could not be found at \(tmpDir).")
throw SwiftlyError(message: "Payload file could not be found at \(tmpDir).")
}

try runProgram("tar", "-C", installDir.path, "-xf", payload.path)
Expand All @@ -128,11 +128,11 @@ public struct MacOS: Platform {
let decoder = PropertyListDecoder()
let infoPlist = toolchainDir.appendingPathComponent("Info.plist")
guard let data = try? Data(contentsOf: infoPlist) else {
throw Error(message: "could not open \(infoPlist)")
throw SwiftlyError(message: "could not open \(infoPlist)")
}

guard let pkgInfo = try? decoder.decode(SwiftPkgInfo.self, from: data) else {
throw Error(message: "could not decode plist at \(infoPlist)")
throw SwiftlyError(message: "could not decode plist at \(infoPlist)")
}

try FileManager.default.removeItem(at: toolchainDir)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Swiftly/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public struct Config: Codable, Equatable {
To begin using swiftly you can install it: '\(CommandLine.arguments[0]) init'.
"""
throw Error(message: msg)
throw SwiftlyError(message: msg)
}
}

Expand Down
10 changes: 5 additions & 5 deletions Sources/Swiftly/Init.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ internal struct Init: SwiftlyCommand {

if let config, !overwrite && config.version != SwiftlyCore.version {
// We don't support downgrades, and we don't yet support upgrades
throw Error(message: "An existing swiftly installation was detected. You can try again with '--overwrite' to overwrite it.")
throw SwiftlyError(message: "An existing swiftly installation was detected. You can try again with '--overwrite' to overwrite it.")
}

// Give the user the prompt and the choice to abort at this point.
Expand All @@ -64,7 +64,7 @@ internal struct Init: SwiftlyCommand {
""")

if SwiftlyCore.readLine(prompt: "Proceed with the installation? [Y/n] ") == "n" {
throw Error(message: "Swiftly installation has been cancelled")
throw SwiftlyError(message: "Swiftly installation has been cancelled")
}
}

Expand All @@ -82,7 +82,7 @@ internal struct Init: SwiftlyCommand {
let proceed = SwiftlyCore.readLine(prompt: "Proceed? [y/N]") ?? "n"

guard proceed == "y" else {
throw Error(message: "Swiftly installation has been cancelled")
throw SwiftlyError(message: "Swiftly installation has been cancelled")
}
}

Expand Down Expand Up @@ -121,7 +121,7 @@ internal struct Init: SwiftlyCommand {
do {
try FileManager.default.createDirectory(at: requiredDir, withIntermediateDirectories: true)
} catch {
throw Error(message: "Failed to create required directory \"\(requiredDir.path)\": \(error)")
throw SwiftlyError(message: "Failed to create required directory \"\(requiredDir.path)\": \(error)")
}
}
}
Expand All @@ -136,7 +136,7 @@ internal struct Init: SwiftlyCommand {
config = c
}

guard var config else { throw Error(message: "Configuration could not be set") }
guard var config else { throw SwiftlyError(message: "Configuration could not be set") }

// Move our executable over to the correct place
try Swiftly.currentPlatform.installSwiftlyBin()
Expand Down
22 changes: 11 additions & 11 deletions Sources/Swiftly/Install.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ struct Install: SwiftlyCommand {
} else if let error = error {
throw error
} else {
throw Error(message: "Internal error selecting toolchain to install.")
throw SwiftlyError(message: "Internal error selecting toolchain to install.")
}
} else {
throw Error(message: "Swiftly couldn't determine the toolchain version to install. Please set a version like this and try again: `swiftly install latest`")
throw SwiftlyError(message: "Swiftly couldn't determine the toolchain version to install. Please set a version like this and try again: `swiftly install latest`")
}
}

Expand All @@ -115,7 +115,7 @@ struct Install: SwiftlyCommand {

if let postInstallScript {
guard let postInstallFile = self.postInstallFile else {
throw Error(message: """
throw SwiftlyError(message: """
There are some dependencies that should be installed before using this toolchain.
You can run the following script as the system administrator (e.g. root) to prepare
Expand Down Expand Up @@ -189,7 +189,7 @@ struct Install: SwiftlyCommand {
url += "\(version.identifier)-\(platformFullString).\(Swiftly.currentPlatform.toolchainFileExtension)"

guard let url = URL(string: url) else {
throw Error(message: "Invalid toolchain URL: \(url)")
throw SwiftlyError(message: "Invalid toolchain URL: \(url)")
}

let animation = PercentProgressAnimation(
Expand Down Expand Up @@ -223,7 +223,7 @@ struct Install: SwiftlyCommand {
}
)
} catch let notFound as SwiftlyHTTPClient.DownloadNotFoundError {
throw Error(message: "\(version) does not exist at URL \(notFound.url), exiting")
throw SwiftlyError(message: "\(version) does not exist at URL \(notFound.url), exiting")
} catch {
animation.complete(success: false)
throw error
Expand Down Expand Up @@ -269,7 +269,7 @@ struct Install: SwiftlyCommand {
let proceed = SwiftlyCore.readLine(prompt: "Proceed? [y/N]") ?? "n"

guard proceed == "y" else {
throw Error(message: "Toolchain installation has been cancelled")
throw SwiftlyError(message: "Toolchain installation has been cancelled")
}
}

Expand Down Expand Up @@ -315,13 +315,13 @@ struct Install: SwiftlyCommand {
SwiftlyCore.print("Fetching the latest stable Swift release...")

guard let release = try await SwiftlyCore.httpClient.getReleaseToolchains(platform: config.platform, limit: 1).first else {
throw Error(message: "couldn't get latest releases")
throw SwiftlyError(message: "couldn't get latest releases")
}
return .stable(release)

case let .stable(major, minor, patch):
guard let minor else {
throw Error(
throw SwiftlyError(
message: "Need to provide at least major and minor versions when installing a release toolchain."
)
}
Expand All @@ -338,7 +338,7 @@ struct Install: SwiftlyCommand {
}.first

guard let toolchain else {
throw Error(message: "No release toolchain found matching \(major).\(minor)")
throw SwiftlyError(message: "No release toolchain found matching \(major).\(minor)")
}

return .stable(toolchain)
Expand All @@ -358,15 +358,15 @@ struct Install: SwiftlyCommand {
snapshot.branch == branch
}
} catch let branchNotFoundErr as SwiftlyHTTPClient.SnapshotBranchNotFoundError {
throw Error(message: "You have requested to install a snapshot toolchain from branch \(branchNotFoundErr.branch). It cannot be found on swift.org. Note that snapshots are only available from the current `main` release and the latest x.y (major.minor) release. Try again with a different branch.")
throw SwiftlyError(message: "You have requested to install a snapshot toolchain from branch \(branchNotFoundErr.branch). It cannot be found on swift.org. Note that snapshots are only available from the current `main` release and the latest x.y (major.minor) release. Try again with a different branch.")
} catch {
throw error
}

let firstSnapshot = snapshots.first

guard let firstSnapshot else {
throw Error(message: "No snapshot toolchain found for branch \(branch)")
throw SwiftlyError(message: "No snapshot toolchain found for branch \(branch)")
}

return .snapshot(firstSnapshot)
Expand Down
Loading

0 comments on commit de41cd9

Please sign in to comment.