Skip to content

Commit ac904f6

Browse files
Init
0 parents  commit ac904f6

File tree

188 files changed

+5935
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

188 files changed

+5935
-0
lines changed

.github/workflows/update-data.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Periodic Data Update
2+
3+
on:
4+
schedule:
5+
- cron: '0 * * * *'
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
update-data:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- name: Run data update scripts
17+
run: |
18+
python ./tools/update-builds.py
19+
python ./tools/update-tag-by-version.py
20+
21+
- name: Commit changes if there are any
22+
run: |
23+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
24+
git config --local user.name "github-actions[bot]"
25+
git add v1/
26+
git diff --quiet && git diff --staged --quiet || git commit -m "Automatic data update $(date +'%Y-%m-%d %H:%M:%S')"
27+
git push

README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Swift SDK index
2+
3+
This repository hosts static information needed for selecting appropriate Swift SDK versions during installation.
4+
5+
## Repository Structure
6+
7+
```
8+
/
9+
└── v1/
10+
├── tag-by-version.json # Maps `swiftc --version | head -n1` output to swift.org release tags
11+
└── builds/ # Build information directory
12+
├── swift-6.0.3-RELEASE.json
13+
├── swift-DEVELOPMENT-SNAPSHOT-2024-10-31-a.json
14+
└── ... (other version files)
15+
```
16+
17+
## Key Files
18+
19+
### `v1/tag-by-version.json`
20+
21+
This file maps `swiftc --version | head -n1` output to swift.org release tags:
22+
23+
```json
24+
{
25+
"Swift version 6.0.3 (swift-6.0.3-RELEASE)": [
26+
"swift-6.0.3-RELEASE"
27+
],
28+
"Swift version 6.1-dev (LLVM 42f3e8ef873e24d, Swift c690fefef71c26a)": [
29+
"swift-DEVELOPMENT-SNAPSHOT-2024-11-28-a",
30+
"swift-DEVELOPMENT-SNAPSHOT-2024-11-29-a"
31+
],
32+
"Swift version 6.2-dev (LLVM 23dd6ab259a178a, Swift a4a3a41b50e111e)": [
33+
"swift-DEVELOPMENT-SNAPSHOT-2025-02-18-a"
34+
]
35+
}
36+
```
37+
38+
### `v1/builds/{TAG}.json`
39+
40+
Contains build information for each tag:
41+
42+
```json
43+
{
44+
"metadata": {
45+
"versions": {
46+
"swift": "swift-6.0.3-RELEASE",
47+
"swiftwasm-build": "8ee6bfafa7a114347e3250da2131c8436b4e7a58"
48+
}
49+
},
50+
"swift-sdks": {
51+
"wasm32-unknown-wasi": {
52+
"id": "6.0.3-RELEASE-wasm32-unknown-wasi",
53+
"url": "https://github.com/swiftwasm/swift/releases/download/swift-wasm-6.0.3-RELEASE/swift-wasm-6.0.3-RELEASE-wasm32-unknown-wasi.artifactbundle.zip",
54+
"checksum": "31d3585b06dd92de390bacc18527801480163188cd7473f492956b5e213a8618"
55+
}
56+
}
57+
}
58+
```
59+
60+
## Example usage
61+
62+
### Get the release tag for the currently selected toolchain
63+
64+
```console
65+
$ curl -sL "https://raw.githubusercontent.com/swiftwasm/swift-sdk-index/refs/heads/main/v1/tag-by-version.json" | jq --arg v "$(swiftc --version | head -n1)" '.[$v]'
66+
[
67+
"swift-6.0.3-RELEASE"
68+
]
69+
```
70+
71+
### Install correct Swift SDK for the currently selected toolchain
72+
73+
```console
74+
$ (
75+
V="$(swiftc --version | head -n1)"; \
76+
TAG="$(curl -sL "https://raw.githubusercontent.com/swiftwasm/swift-sdk-index/refs/heads/main/v1/tag-by-version.json" | jq -r --arg v "$V" '.[$v] | .[-1]')"; \
77+
curl -sL "https://raw.githubusercontent.com/swiftwasm/swift-sdk-index/refs/heads/main/v1/builds/$TAG.json" | \
78+
jq -r '.["swift-sdks"]["wasm32-unknown-wasi"] | "swift sdk install \"\(.url)\" --checksum \"\(.checksum)\""' | sh -x
79+
)
80+
```

tools/update-builds.py

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/usr/bin/env python3
2+
3+
import json
4+
import os
5+
import requests
6+
import re
7+
from pathlib import Path
8+
from typing import Dict
9+
10+
11+
def request_github_api(url: str) -> list[dict]:
12+
"""
13+
Request the GitHub API and return the JSON response.
14+
"""
15+
headers = {
16+
"Accept": "application/vnd.github.v3+json",
17+
"X-GitHub-Api-Version": "2022-11-28",
18+
}
19+
token = os.getenv("GITHUB_TOKEN")
20+
if token:
21+
headers["Authorization"] = f"Bearer {token}"
22+
response = requests.get(url, headers=headers)
23+
response.raise_for_status()
24+
return response.json()
25+
26+
27+
def extract_versions(release: dict) -> Dict[str, str]:
28+
"""
29+
Extract the version from the release data
30+
"""
31+
versions: Dict[str, str] = {}
32+
patterns = {
33+
"swift": r'^\| `apple/swift` \| https://github.com/apple/swift/releases/tag/(.+) \|$',
34+
"swiftwasm-build": r'^\| `swiftwasm/swiftwasm-build` \| https://github.com/swiftwasm/swiftwasm-build/commit/(.+) \|$'
35+
}
36+
release_notes = release['body']
37+
lines = release_notes.splitlines()
38+
for line in lines:
39+
for key, pattern in patterns.items():
40+
match = re.match(pattern, line)
41+
if match:
42+
versions[key] = match.group(1)
43+
break
44+
for key in patterns.keys():
45+
if key not in versions:
46+
raise Exception(
47+
f"Version info not found for \"{key}\" in release {release['tag_name']}")
48+
49+
return versions
50+
51+
52+
def identify_target(asset_name: str, release: dict) -> str:
53+
"""
54+
Identify the target platform from asset name
55+
56+
e.g.
57+
* swift-wasm-6.1-SNAPSHOT-2025-02-21-a-wasm32-unknown-wasi.artifactbundle.zip -> wasm32-unknown-wasi
58+
* swift-wasm-6.1-SNAPSHOT-2025-02-21-a-wasm32-unknown-wasip1-threads.artifactbundle.zip -> wasm32-unknown-wasip1-threads
59+
"""
60+
release_tag = release['tag_name']
61+
asset_name = asset_name.removeprefix(release_tag + '-')
62+
asset_name = asset_name.removesuffix('.artifactbundle.zip')
63+
return asset_name
64+
65+
66+
def process_release(release: dict, versions: dict) -> dict:
67+
"""
68+
Process release assets and return structured build data
69+
"""
70+
build_info = {
71+
"$schema": "../build.schema.json",
72+
"metadata": {
73+
"versions": versions
74+
},
75+
"swift-sdks": {}
76+
}
77+
78+
for asset in release['assets']:
79+
if not asset['name'].endswith('.artifactbundle.zip'):
80+
continue
81+
target = identify_target(asset['name'], release)
82+
artifact_id = asset['name'].removesuffix(
83+
'.artifactbundle.zip').removeprefix("swift-wasm-")
84+
85+
# Calculate checksum
86+
sha256_url = asset['browser_download_url'] + '.sha256'
87+
sha256_response = requests.get(sha256_url)
88+
if sha256_response.status_code == 404:
89+
checksum = None
90+
else:
91+
checksum = sha256_response.text.strip()
92+
93+
# Add SDK data
94+
sdk_info = {
95+
"id": artifact_id,
96+
"url": asset['browser_download_url'],
97+
}
98+
if checksum:
99+
sdk_info['checksum'] = checksum
100+
build_info['swift-sdks'][target] = sdk_info
101+
102+
return build_info
103+
104+
105+
def update_builds_directory(releases: list[dict]):
106+
"""
107+
Update the v1/builds/ directory with the latest data
108+
"""
109+
builds_dir = Path('v1/builds')
110+
builds_dir.mkdir(parents=True, exist_ok=True)
111+
112+
for release in releases:
113+
versions = extract_versions(release)
114+
path = builds_dir / f"{versions['swift']}.json"
115+
if path.exists():
116+
continue
117+
build_data = process_release(release, versions)
118+
119+
# Skip releases with empty SDK sections
120+
if not build_data['swift-sdks']:
121+
print(f"Skipping {path} because it has no SDKs")
122+
continue
123+
124+
# Write individual release files
125+
print(f"Writing {path}")
126+
with open(path, 'w') as f:
127+
json.dump(build_data, f, indent=4)
128+
129+
130+
def main():
131+
"""
132+
Get the list of releases from the swiftwasm/swift GitHub repo
133+
and update the v1/builds/ directory with the latest data.
134+
135+
TODO: The build file should be updated as a part of the release process
136+
instead of fetching release data from GitHub.
137+
"""
138+
139+
import argparse
140+
141+
parser = argparse.ArgumentParser()
142+
parser.add_argument("--pages", type=int, default=1,
143+
help="The number of pages to retrieve")
144+
args = parser.parse_args()
145+
146+
for page in range(args.pages):
147+
releases = request_github_api(
148+
f"https://api.github.com/repos/swiftwasm/swift/releases?page={page}")
149+
print(f"Number of releases retrieved: {len(releases)}")
150+
update_builds_directory(releases)
151+
print("v1/builds/ directory update completed")
152+
153+
154+
if __name__ == "__main__":
155+
main()

tools/update-tag-by-version.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env python3
2+
import subprocess
3+
from pathlib import Path
4+
import os
5+
import itertools
6+
7+
8+
def derive_version_fingerprint(swift_tag: str) -> list[str]:
9+
"""
10+
Derive a fingerprint from the Swift version tag by
11+
"""
12+
13+
if swift_tag.startswith("swift-") and swift_tag.endswith("-RELEASE"):
14+
# HACK: swiftly doesn't understand swift-x.y.z-RELEASE tags
15+
swift_tag = swift_tag.removeprefix("swift-").removesuffix("-RELEASE")
16+
17+
install = subprocess.run(["swiftly", "install", swift_tag])
18+
install.check_returncode()
19+
20+
toolchain_version = subprocess.run(["swiftly", "run", "swift", "--version", f"+{swift_tag}"], capture_output=True).stdout.decode("utf-8").strip()
21+
result = toolchain_version.split("\n")[0]
22+
subprocess.run(["swiftly", "uninstall", "-y", swift_tag]).check_returncode()
23+
# swift.org toolchain has two fingerprint variants for the same version
24+
return [result, f"Apple {result}"]
25+
26+
def main():
27+
"""
28+
Update the tag-by-version.json file with the latest data from the
29+
v1/builds/ directory.
30+
"""
31+
import json
32+
33+
builds_dir = Path("v1/builds")
34+
tag_by_version_path = Path("v1/tag-by-version.json")
35+
tag_by_version = {}
36+
if tag_by_version_path.exists():
37+
with open(tag_by_version_path, "r") as f:
38+
tag_by_version = json.load(f)
39+
40+
build_files = sorted(builds_dir.glob("*.json"))
41+
for i, path in enumerate(build_files):
42+
print(f"Processing {i+1}/{len(build_files)}: {path}")
43+
with open(path, "r") as f:
44+
build_info = json.load(f)
45+
swift_tag = build_info['metadata']['versions']['swift']
46+
47+
if swift_tag not in set(itertools.chain(*tag_by_version.values())):
48+
fingerprint = derive_version_fingerprint(swift_tag)
49+
for f in fingerprint:
50+
if f not in tag_by_version:
51+
tag_by_version[f] = [swift_tag]
52+
else:
53+
tags = tag_by_version[f]
54+
if swift_tag not in tags:
55+
tags.append(swift_tag)
56+
tag_by_version[f] = sorted(tags)
57+
print(f"Derived fingerprint for {swift_tag}: {f}")
58+
59+
with open(tag_by_version_path, "w") as f:
60+
json.dump(tag_by_version, f, indent=2)
61+
62+
63+
if __name__ == "__main__":
64+
main()

v1/build.schema.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"$schema": "https://json-schema.org/draft-07/schema",
3+
"type": "object",
4+
"properties": {
5+
"metadata": {
6+
"type": "object",
7+
"$comment": "Metadata about the build"
8+
},
9+
"swift-sdks": {
10+
"type": "object",
11+
"$comment": "The Swift SDKs included in the build",
12+
"items": {
13+
"type": "object",
14+
"properties": {
15+
"id": {
16+
"type": "string",
17+
"$comment": "The artifact ID of the Swift SDK artifactbundle"
18+
},
19+
"url": {
20+
"type": "string",
21+
"$comment": "The URL of the Swift SDK artifactbundle"
22+
},
23+
"checksum": {
24+
"type": "string",
25+
"$comment": "The checksum of the Swift SDK artifactbundle"
26+
}
27+
}
28+
}
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)