from __future__ import annotations
import argparse
import json
import re
import sys
import urllib.request
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
PLATFORM_RS = REPO_ROOT / "src" / "validate" / "platform.rs"
MATRIX_URL = (
"https://raw.githubusercontent.com/duckdb/extension-ci-tools/"
"{ref}/config/distribution_matrix.json"
)
def rust_list(name: str, text: str) -> list[str] | None:
match = re.search(
rf"pub const {name}:\s*&\[&str\]\s*=\s*&\[(.*?)\];", text, re.DOTALL
)
if not match:
return None
return re.findall(r'"([^"]+)"', match.group(1))
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--ref",
default="main",
help="extension-ci-tools ref to check (default: main)",
)
args = parser.parse_args()
text = PLATFORM_RS.read_text()
ours = rust_list("DUCKDB_CI_PLATFORMS", text)
every = rust_list("DUCKDB_PLATFORMS", text)
opt_in = rust_list("DUCKDB_OPT_IN_PLATFORMS", text)
groups = rust_list("DUCKDB_PLATFORM_GROUPS", text)
if ours is None or every is None or opt_in is None or groups is None:
sys.exit(f"could not parse the platform lists out of {PLATFORM_RS}")
url = MATRIX_URL.format(ref=args.ref)
try:
with urllib.request.urlopen(url, timeout=60) as response:
matrix = json.loads(response.read().decode("utf-8"))
except Exception as error: print(f"::warning::could not fetch {url}: {error}")
return 2
upstream: dict[str, bool] = {}
for group in matrix.values():
for entry in group.get("include", []):
arch = entry.get("duckdb_arch")
if not arch:
print(f"::warning::an entry in {url} has no duckdb_arch")
return 2
upstream[arch] = bool(entry.get("opt_in", False))
if not upstream:
print(f"::warning::{url} listed no architectures — layout may have changed")
return 2
upstream_opt_in = {arch for arch, is_opt_in in upstream.items() if is_opt_in}
missing = sorted(set(upstream) - set(ours))
extra = sorted(set(ours) - set(upstream))
opt_in_missing = sorted(upstream_opt_in - set(opt_in))
opt_in_extra = sorted(set(opt_in) - upstream_opt_in)
upstream_groups = sorted(matrix.keys())
group_missing = sorted(set(upstream_groups) - set(groups))
group_extra = sorted(set(groups) - set(upstream_groups))
not_accepted = sorted(set(ours) - set(every))
print(f"upstream: {len(upstream)} architectures ({len(upstream_opt_in)} opt-in), "
f"{len(upstream_groups)} groups")
print(f"quack-rs: {len(ours)} CI platforms ({len(opt_in)} opt-in), "
f"{len(groups)} groups, {len(every)} accepted names")
problems = False
for label, names in (
("upstream builds it, DUCKDB_CI_PLATFORMS omits it", missing),
("DUCKDB_CI_PLATFORMS lists it, upstream does not build it", extra),
("upstream marks it opt-in, quack-rs does not", opt_in_missing),
("quack-rs marks it opt-in, upstream does not", opt_in_extra),
("a matrix group name is missing from DUCKDB_PLATFORM_GROUPS", group_missing),
("DUCKDB_PLATFORM_GROUPS names a group upstream does not have", group_extra),
("a CI platform is not accepted by validate_platform", not_accepted),
):
if names:
problems = True
print(f"\nFAIL: {label}: {', '.join(names)}")
if problems:
print(
f"\nUpdate the platform lists in {PLATFORM_RS.relative_to(REPO_ROOT)} "
f"to match {url}."
)
return 1
print("\nOK: src/validate/platform.rs matches upstream")
return 0
if __name__ == "__main__":
raise SystemExit(main())