109 lines
4.8 KiB
Python
109 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Resolve Rust-supported SDK tags for each stable/oldstable release."""
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
|
|
# OpenWrt packages/lang/rust/rust-values.mk: RUST_ARCH_DEPENDS.
|
|
RUST_ARCHES = {
|
|
"aarch64", "arm", "i386", "loongarch64", "mips", "mips64",
|
|
"mips64el", "mipsel", "powerpc", "powerpc64", "riscv64", "x86_64",
|
|
}
|
|
|
|
|
|
def fetch(url):
|
|
with urllib.request.urlopen(url, timeout=60) as response:
|
|
return response.read().decode()
|
|
|
|
|
|
def sdk_tags():
|
|
token = json.loads(fetch("https://ghcr.io/token?service=ghcr.io&scope=repository:openwrt/sdk:pull"))["token"]
|
|
url = "https://ghcr.io/v2/openwrt/sdk/tags/list?n=1000"
|
|
tags = set()
|
|
while url:
|
|
request = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
|
|
with urllib.request.urlopen(request, timeout=60) as response:
|
|
tags.update(json.load(response)["tags"])
|
|
link = response.headers.get("Link", "")
|
|
next_page = re.search(r'<([^>]+)>;\s*rel="next"', link)
|
|
url = urllib.parse.urljoin(url, next_page[1]) if next_page else None
|
|
return tags
|
|
|
|
|
|
def release_targets(version, tags):
|
|
base = f"https://downloads.openwrt.org/releases/{version}"
|
|
# Directory names distinguish target/subtarget tags from architecture aliases.
|
|
boards = re.findall(r'href="([a-z0-9_+-]+)/"', fetch(f"{base}/targets/"))
|
|
suffix = f"-{version}"
|
|
targets = sorted({f"{board}/{tag[len(board) + 1:-len(suffix)]}"
|
|
for board in boards for tag in tags
|
|
if tag.startswith(f"{board}-") and tag.endswith(suffix)})
|
|
if not targets:
|
|
raise ValueError(f"No SDK images available for OpenWrt {version}")
|
|
architectures = json.loads(fetch(f"{base}/.targets.json"))
|
|
result = {}
|
|
for target in targets:
|
|
if not re.fullmatch(r"[a-z0-9_+-]+/[a-z0-9_+-]+", target):
|
|
raise ValueError(f"Invalid SDK target: {target!r}")
|
|
if target in architectures:
|
|
arch = architectures[target]
|
|
arch = arch if arch in RUST_ARCHES else arch.split("_", 1)[0]
|
|
else:
|
|
# SDK-only targets (e.g. Malta) have no profiles in .targets.json.
|
|
board, subtarget = target.split("/")
|
|
source = f"https://raw.githubusercontent.com/openwrt/openwrt/v{version}/target/linux"
|
|
definition = fetch(f"{source}/{board}/{subtarget}/target.mk")
|
|
match = re.search(r"^ARCH\s*:?=\s*([a-z0-9_]+)\s*$", definition, re.MULTILINE)
|
|
if not match:
|
|
definition = fetch(f"{source}/{board}/Makefile")
|
|
match = re.search(r"^ARCH\s*:?=\s*([a-z0-9_]+)\s*$", definition, re.MULTILINE)
|
|
if not match:
|
|
raise ValueError(f"Cannot determine architecture for {target} on {version}")
|
|
arch = match[1]
|
|
result[target] = arch
|
|
return result
|
|
|
|
|
|
def resolve(versions, targets, tags):
|
|
versions = [versions[k] for k in ("stable_version", "oldstable_version") if versions.get(k)]
|
|
if not versions:
|
|
raise ValueError("OpenWrt returned no stable or oldstable version")
|
|
targets = [t.strip() for t in targets.split(",") if t.strip()]
|
|
if not targets:
|
|
raise ValueError("No targets selected")
|
|
if "all" in targets and targets != ["all"]:
|
|
raise ValueError("Select all or a comma-separated target list")
|
|
for target in targets:
|
|
if target != "all" and not re.fullmatch(r"[a-z0-9_+-]+/[a-z0-9_+-]+", target):
|
|
raise ValueError(f"Invalid target: {target!r}")
|
|
for version in versions:
|
|
if not re.fullmatch(r"[a-zA-Z0-9_.-]+", version):
|
|
raise ValueError(f"Invalid version: {version!r}")
|
|
matrix = []
|
|
for version in dict.fromkeys(versions):
|
|
available = release_targets(version, tags)
|
|
selected = available if targets == ["all"] else dict.fromkeys(targets)
|
|
supported = [t for t in selected if available.get(t) in RUST_ARCHES]
|
|
skipped = [t for t in selected if t not in supported]
|
|
print(f"OpenWrt {version}: {len(supported)} targets selected; "
|
|
f"{len(available)} SDK images available", file=sys.stderr)
|
|
if skipped:
|
|
print(f"Skipped unavailable or Rust-unsupported targets: {', '.join(skipped)}", file=sys.stderr)
|
|
matrix.extend({"target": t.replace("/", "-"), "version": version} for t in supported)
|
|
if not matrix:
|
|
raise ValueError("No available Rust-supported SDK targets selected")
|
|
return {"include": matrix}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
versions = json.loads(fetch("https://downloads.openwrt.org/.versions.json"))
|
|
targets = os.environ.get("TARGETS", "all")
|
|
matrix = json.dumps(resolve(versions, targets, sdk_tags()), separators=(",", ":"))
|
|
print(matrix)
|
|
with open(os.environ["GITHUB_OUTPUT"], "a") as output:
|
|
output.write(f"matrix={matrix}\n")
|