Skip to content

Commit b0dc1b3

Browse files
authored
Use swift.org API for getting releases and some snapshot toolchains (#153)
See issue #149 Using the new swift.org API is more accurate than the GitHub API, also it isn't subject to the same rate limits. Use the swift.org API instead of the GitHub API for fetching metadata about the released swift toolchains, and the newer swift branches for the snapshots. However, there is currently no metadata for snapshots on older branches than 6.0 or main. In this case use the GitHub API to get the metadata, but only for swiftly install and not the swiftly list-available commands, since the latter is very expensive when using the GitHub API.
1 parent eba9bd9 commit b0dc1b3

File tree

15 files changed

+358
-847
lines changed

15 files changed

+358
-847
lines changed

Documentation/SwiftlyDocs.docc/swiftly-cli-reference.md

+46-2
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ Likewise, the latest snapshot associated with a given development branch can be
6161

6262
**--token=\<token\>:**
6363

64-
*A GitHub authentiation token to use for any GitHub API requests.*
65-
64+
*A GitHub authentication token to use for any GitHub API requests.*
6665

6766
This is useful to avoid GitHub's low rate limits. If an installation
6867
fails with an "unauthorized" status code, it likely means the rate limit has been hit.
@@ -93,6 +92,51 @@ written to this file as commands that can be run after the installation.
9392

9493

9594

95+
## list-available
96+
97+
List toolchains available for install.
98+
99+
```
100+
swiftly list-available [<toolchain-selector>] [--version] [--help]
101+
```
102+
103+
**toolchain-selector:**
104+
105+
*A filter to use when listing toolchains.*
106+
107+
108+
The toolchain selector determines which toolchains to list. If no selector is provided, all available release toolchains will be listed:
109+
110+
$ swiftly list-available
111+
112+
The available toolchains associated with a given major version can be listed by specifying the major version as the selector:
113+
114+
$ swiftly list-available 5
115+
116+
Likewise, the available toolchains associated with a given minor version can be listed by specifying the minor version as the selector:
117+
118+
$ swiftly list-available 5.2
119+
120+
The installed snapshots for a given devlopment branch can be listed by specifying the branch as the selector:
121+
122+
$ swiftly list-available main-snapshot
123+
$ swiftly list-available 6.0-snapshot
124+
125+
Note that listing available snapshots before 6.0 is unsupported.
126+
127+
128+
**--version:**
129+
130+
*Show the version.*
131+
132+
133+
**--help:**
134+
135+
*Show help information.*
136+
137+
138+
139+
96140
## use
97141

98142
Set the active toolchain. If no toolchain is provided, print the currently in-use toolchain, if any.

Package.swift

-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ let package = Package(
9393
dependencies: ["Swiftly"],
9494
exclude: ghApiCacheExcludedResources,
9595
resources: ghApiCacheResources + [
96-
.embedInCode("gh-api-cache/swift-releases-page1.json"),
9796
.embedInCode("mock-signing-key-private.pgp"),
9897
]
9998
),

Sources/LinuxPlatform/Linux.swift

+10-10
Original file line numberDiff line numberDiff line change
@@ -436,15 +436,15 @@ public struct Linux: Platform {
436436

437437
switch choice {
438438
case "1":
439-
return PlatformDefinition(name: "ubuntu2204", nameFull: "ubuntu22.04", namePretty: "Ubuntu 22.04")
439+
return PlatformDefinition.ubuntu2204
440440
case "2":
441-
return PlatformDefinition(name: "ubuntu2004", nameFull: "ubuntu20.04", namePretty: "Ubuntu 20.04")
441+
return PlatformDefinition.ubuntu2004
442442
case "3":
443-
return PlatformDefinition(name: "ubuntu1804", nameFull: "ubuntu18.04", namePretty: "Ubuntu 18.04")
443+
return PlatformDefinition.ubuntu1804
444444
case "4":
445-
return PlatformDefinition(name: "ubi9", nameFull: "ubi9", namePretty: "RHEL 9")
445+
return PlatformDefinition.rhel9
446446
case "5":
447-
return PlatformDefinition(name: "amazonlinux2", nameFull: "amazonlinux2", namePretty: "Amazon Linux 2")
447+
return PlatformDefinition.amazonlinux2
448448
default:
449449
fatalError("Installation canceled")
450450
}
@@ -455,15 +455,15 @@ public struct Linux: Platform {
455455
if let platform = platform {
456456
switch platform {
457457
case "ubuntu22.04":
458-
return PlatformDefinition(name: "ubuntu2204", nameFull: "ubuntu22.04", namePretty: "Ubuntu 22.04")
458+
return PlatformDefinition.ubuntu2204
459459
case "ubuntu20.04":
460-
return PlatformDefinition(name: "ubuntu2004", nameFull: "ubuntu20.04", namePretty: "Ubuntu 20.04")
460+
return PlatformDefinition.ubuntu2004
461461
case "ubuntu18.04":
462-
return PlatformDefinition(name: "ubuntu1804", nameFull: "ubuntu18.04", namePretty: "Ubuntu 18.04")
462+
return PlatformDefinition.ubuntu1804
463463
case "amazonlinux2":
464-
return PlatformDefinition(name: "amazonlinux2", nameFull: "amazonlinux2", namePretty: "Amazon Linux 2")
464+
return PlatformDefinition.amazonlinux2
465465
case "rhel9":
466-
return PlatformDefinition(name: "ubi9", nameFull: "ubi9", namePretty: "RHEL 9")
466+
return PlatformDefinition.rhel9
467467
default:
468468
fatalError("Unrecognized platform \(platform)")
469469
}

Sources/MacOSPlatform/MacOS.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ public struct MacOS: Platform {
208208

209209
public func detectPlatform(disableConfirmation _: Bool, platform _: String?) async -> PlatformDefinition {
210210
// No special detection required on macOS platform
211-
PlatformDefinition(name: "xcode", nameFull: "osx", namePretty: "macOS")
211+
PlatformDefinition.macOS
212212
}
213213

214214
public func getShell() async throws -> String {

Sources/Swiftly/Install.swift

+7-8
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,8 @@ struct Install: SwiftlyCommand {
4747
var use: Bool = false
4848

4949
@Option(help: ArgumentHelp(
50-
"A GitHub authentiation token to use for any GitHub API requests.",
50+
"A GitHub authentication token to use for any GitHub API requests.",
5151
discussion: """
52-
5352
This is useful to avoid GitHub's low rate limits. If an installation
5453
fails with an \"unauthorized\" status code, it likely means the rate limit has been hit.
5554
"""
@@ -76,9 +75,9 @@ struct Install: SwiftlyCommand {
7675
try validateSwiftly()
7776

7877
let selector = try ToolchainSelector(parsing: self.version)
79-
SwiftlyCore.httpClient.githubToken = self.token
80-
let toolchainVersion = try await self.resolve(selector: selector)
8178
var config = try Config.load()
79+
SwiftlyCore.httpClient.githubToken = self.token
80+
let toolchainVersion = try await self.resolve(config: config, selector: selector)
8281
let postInstallScript = try await Self.execute(
8382
version: toolchainVersion,
8483
&config,
@@ -227,12 +226,12 @@ struct Install: SwiftlyCommand {
227226

228227
/// Utilize the GitHub API along with the provided selector to select a toolchain for install.
229228
/// TODO: update this to use an official swift.org API
230-
func resolve(selector: ToolchainSelector) async throws -> ToolchainVersion {
229+
func resolve(config: Config, selector: ToolchainSelector) async throws -> ToolchainVersion {
231230
switch selector {
232231
case .latest:
233232
SwiftlyCore.print("Fetching the latest stable Swift release...")
234233

235-
guard let release = try await SwiftlyCore.httpClient.getReleaseToolchains(limit: 1).first else {
234+
guard let release = try await SwiftlyCore.httpClient.getReleaseToolchains(platform: config.platform, limit: 1).first else {
236235
throw Error(message: "couldn't get latest releases")
237236
}
238237
return .stable(release)
@@ -251,7 +250,7 @@ struct Install: SwiftlyCommand {
251250
SwiftlyCore.print("Fetching the latest stable Swift \(major).\(minor) release...")
252251
// If a patch was not provided, perform a lookup to get the latest patch release
253252
// of the provided major/minor version pair.
254-
let toolchain = try await SwiftlyCore.httpClient.getReleaseToolchains(limit: 1) { release in
253+
let toolchain = try await SwiftlyCore.httpClient.getReleaseToolchains(platform: config.platform, limit: 1) { release in
255254
release.major == major && release.minor == minor
256255
}.first
257256

@@ -269,7 +268,7 @@ struct Install: SwiftlyCommand {
269268
SwiftlyCore.print("Fetching the latest \(branch) branch snapshot...")
270269
// If a date was not provided, perform a lookup to find the most recent snapshot
271270
// for the given branch.
272-
let snapshot = try await SwiftlyCore.httpClient.getSnapshotToolchains(limit: 1) { snapshot in
271+
let snapshot = try await SwiftlyCore.httpClient.getSnapshotToolchains(platform: config.platform, branch: branch, limit: 1) { snapshot in
273272
snapshot.branch == branch
274273
}.first
275274

Sources/Swiftly/ListAvailable.swift

+18-19
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct ListAvailable: SwiftlyCommand {
1111
discussion: """
1212
1313
The toolchain selector determines which toolchains to list. If no selector \
14-
is provided, all available toolchains will be listed:
14+
is provided, all available release toolchains will be listed:
1515
1616
$ swiftly list-available
1717
@@ -28,9 +28,9 @@ struct ListAvailable: SwiftlyCommand {
2828
The installed snapshots for a given devlopment branch can be listed by specifying the branch as the selector:
2929
3030
$ swiftly list-available main-snapshot
31-
$ swiftly list-available 5.7-snapshot
31+
$ swiftly list-available 6.0-snapshot
3232
33-
Note that listing available snapshots is currently unsupported. It will be introduced in a future release.
33+
Note that listing available snapshots before 6.0 is unsupported.
3434
"""
3535
))
3636
var toolchainSelector: String?
@@ -45,18 +45,24 @@ struct ListAvailable: SwiftlyCommand {
4545
try ToolchainSelector(parsing: input)
4646
}
4747

48-
if let selector {
49-
guard !selector.isSnapshotSelector() else {
50-
SwiftlyCore.print("Listing available snapshots is not currently supported.")
51-
return
48+
let config = try Config.load()
49+
50+
let tc: [ToolchainVersion]
51+
52+
switch selector {
53+
case let .snapshot(branch, _):
54+
if case let .release(major, _) = branch, major < 6 {
55+
throw Error(message: "Listing available snapshots previous to 6.0 is not supported.")
5256
}
53-
}
5457

55-
let toolchains = try await SwiftlyCore.httpClient.getReleaseToolchains()
56-
.map(ToolchainVersion.stable)
57-
.filter { selector?.matches(toolchain: $0) ?? true }
58+
tc = try await SwiftlyCore.httpClient.getSnapshotToolchains(platform: config.platform, branch: branch).map { ToolchainVersion.snapshot($0) }
59+
case .stable, .latest:
60+
tc = try await SwiftlyCore.httpClient.getReleaseToolchains(platform: config.platform).map { ToolchainVersion.stable($0) }
61+
default:
62+
tc = try await SwiftlyCore.httpClient.getReleaseToolchains(platform: config.platform).map { ToolchainVersion.stable($0) }
63+
}
5864

59-
let config = try Config.load()
65+
let toolchains = tc.filter { selector?.matches(toolchain: $0) ?? true }
6066

6167
let installedToolchains = Set(config.listInstalledToolchains(selector: selector))
6268
let activeToolchain = config.inUse
@@ -100,13 +106,6 @@ struct ListAvailable: SwiftlyCommand {
100106
for toolchain in toolchains where toolchain.isStableRelease() {
101107
printToolchain(toolchain)
102108
}
103-
104-
// print("")
105-
// print("Available snapshot toolchains")
106-
// print("-----------------------------")
107-
// for toolchain in toolchains where toolchain.isSnapshot() {
108-
// printToolchain(toolchain)
109-
// }
110109
}
111110
}
112111
}

Sources/Swiftly/Swiftly.swift

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public struct Swiftly: SwiftlyCommand {
2323

2424
subcommands: [
2525
Install.self,
26+
ListAvailable.self,
2627
Use.self,
2728
Uninstall.self,
2829
List.self,

Sources/Swiftly/Update.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ struct Update: SwiftlyCommand {
9090
return
9191
}
9292

93-
guard let newToolchain = try await self.lookupNewToolchain(parameters) else {
93+
guard let newToolchain = try await self.lookupNewToolchain(config, parameters) else {
9494
SwiftlyCore.print("\(parameters.oldToolchain) is already up to date")
9595
return
9696
}
@@ -181,10 +181,10 @@ struct Update: SwiftlyCommand {
181181

182182
/// Tries to find a toolchain version that meets the provided parameters, if one exists.
183183
/// This does not download the toolchain, but it does query the GitHub API to find the suitable toolchain.
184-
private func lookupNewToolchain(_ bounds: UpdateParameters) async throws -> ToolchainVersion? {
184+
private func lookupNewToolchain(_ config: Config, _ bounds: UpdateParameters) async throws -> ToolchainVersion? {
185185
switch bounds {
186186
case let .stable(old, range):
187-
return try await SwiftlyCore.httpClient.getReleaseToolchains(limit: 1) { release in
187+
return try await SwiftlyCore.httpClient.getReleaseToolchains(platform: config.platform, limit: 1) { release in
188188
switch range {
189189
case .latest:
190190
return release > old
@@ -195,7 +195,7 @@ struct Update: SwiftlyCommand {
195195
}
196196
}.first.map(ToolchainVersion.stable)
197197
case let .snapshot(old):
198-
return try await SwiftlyCore.httpClient.getSnapshotToolchains(limit: 1) { snapshot in
198+
return try await SwiftlyCore.httpClient.getSnapshotToolchains(platform: config.platform, branch: old.branch, limit: 1) { snapshot in
199199
snapshot.branch == old.branch && snapshot.date > old.date
200200
}.first.map(ToolchainVersion.snapshot)
201201
}

Sources/SwiftlyCore/HTTPClient+GitHubAPI.swift

-22
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,6 @@ extension SwiftlyHTTPClient {
5757
}
5858
}
5959

60-
/// Get a list of releases on the apple/swift GitHub repository.
61-
/// The releases are returned in "pages" of `perPage` releases (default 100). The page argument specifies the
62-
/// page number.
63-
///
64-
/// The results are returned in lexicographic order.
65-
public func getReleases(page: Int, perPage: Int = 100) async throws -> [GitHubTag] {
66-
let url = "https://api.github.com/repos/apple/swift/releases?per_page=\(perPage)&page=\(page)"
67-
let releases: [GitHubRelease] = try await self.getFromGitHub(url: url)
68-
return releases.filter { !$0.prerelease }.map { $0.toGitHubTag() }
69-
}
70-
7160
/// Get a list of tags on the apple/swift GitHub repository.
7261
/// The tags are returned in pages of 100. The page argument specifies the page number.
7362
///
@@ -78,17 +67,6 @@ extension SwiftlyHTTPClient {
7867
}
7968
}
8069

81-
/// Model of a GitHub REST API release object.
82-
/// See: https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#list-releases
83-
public struct GitHubRelease: Decodable {
84-
fileprivate let name: String
85-
fileprivate let prerelease: Bool
86-
87-
fileprivate func toGitHubTag() -> GitHubTag {
88-
GitHubTag(name: self.name, commit: nil)
89-
}
90-
}
91-
9270
/// Model of a GitHub REST API tag/release object.
9371
public struct GitHubTag: Decodable {
9472
internal struct Commit: Decodable {

0 commit comments

Comments
 (0)