mirror of
https://github.com/logos-co/logos-release-set.git
synced 2026-08-30 22:41:13 +00:00
Two changes, both belonging on main rather than a release branch. One job per (spec, platform) instead of one job per platform running all five specs in sequence — 15 jobs. The results model was already keyed on (spec, platform), so this makes the jobs match it and pays off three ways: a failure names itself in the job list instead of hiding inside a 900-line log; basecamp-ui, which builds Qt, stops gating the four quick specs; and one flaky spec can be re-run without repeating the other fourteen. Each job also produces its own report, so every cell of the validation table links to the report for exactly that cell rather than to a combined per-platform one. select-specs.py grew --only so each job asks the skip question about its own spec. The skip path is unchanged and still tested: against a lock pinning basecamp 0.2.2-RC1 it reports any=false with the reason, and the job records "skipped" without failing. Fifteen HTML reports would bury the two assets that matter on a release, so they travel as one reports.tar.gz while the per-cell links point at the browsable copies on gh-pages. The delivery spec's nodeStarted assertion now polls instead of sleeping 3s. `start` returns when the node accepts the call; the event is emitted later by the completion callback, and 3s was enough on some runs and not others — run 30493468508 failed it on all three platforms with an empty latch, having passed on all three the run before. It now retries for up to 60s and still fails honestly if the event never arrives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
157 lines
6.5 KiB
Python
Executable File
157 lines
6.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Decide which doc-tests can run on a platform, and why the rest cannot.
|
|
|
|
A pinned release may simply not publish an artifact for every platform — a
|
|
release with no linux-arm64 AppImage leaves nothing to smoke-test on an arm64
|
|
Linux runner. That is not a failure of the release set, so the workflow skips
|
|
the affected spec and flags it in the release description instead of going red.
|
|
|
|
This decides that up front, from release-set.lock.json, rather than letting a
|
|
spec discover it half-way through and fail.
|
|
|
|
Usage:
|
|
python3 scripts/select-specs.py release-set.lock.json --platform linux-arm64
|
|
python3 scripts/select-specs.py release-set.lock.json --platform linux-arm64 --format github
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
TOOLS = ["logos-logoscore-cli", "logos-package-downloader", "logos-package-manager"]
|
|
PM_TOOLS = ["logos-package-downloader", "logos-package-manager"]
|
|
|
|
BUILDER = ["logos-module-builder"]
|
|
|
|
# What each spec needs on the runner's own platform.
|
|
# apps — released binaries that must exist for this platform
|
|
# packages — catalog packages that must publish this platform's variant
|
|
# ("*" means every module and UI app in the set)
|
|
# devUtils — dev utils the spec exercises. Not gating (they are consumed as
|
|
# flake refs and publish no per-platform binaries), but recorded
|
|
# so each artifact can point at the doc-tests that covered it.
|
|
SPECS = {
|
|
# Each headless spec also loads `openmetrics` and scrapes /metrics, so that
|
|
# package must publish this platform's variant too.
|
|
"headless-storage-module": {"apps": TOOLS,
|
|
"packages": ["storage_module", "openmetrics"],
|
|
"devUtils": BUILDER},
|
|
"headless-delivery-module": {"apps": TOOLS,
|
|
"packages": ["delivery_module", "openmetrics"],
|
|
"devUtils": BUILDER},
|
|
"headless-blockchain-module": {"apps": TOOLS,
|
|
"packages": ["blockchain_module", "openmetrics"],
|
|
"devUtils": BUILDER},
|
|
# Launches the shipped Basecamp artifact, so that artifact must exist.
|
|
"basecamp-appimage-smoke": {"apps": PM_TOOLS + ["logos-basecamp"],
|
|
"packages": ["*"], "devUtils": []},
|
|
# Builds Basecamp from the pinned commit, so it needs no Basecamp asset.
|
|
"basecamp-ui": {"apps": PM_TOOLS, "packages": ["*"], "devUtils": []},
|
|
}
|
|
|
|
|
|
def specs_covering(name, lock):
|
|
"""Which doc-tests exercise this component."""
|
|
every_package = [i["name"] for i in lock.get("modules", []) + lock.get("uiApps", [])]
|
|
covering = []
|
|
for spec, needs in sorted(SPECS.items()):
|
|
packages = every_package if needs["packages"] == ["*"] else needs["packages"]
|
|
if name in needs["apps"] or name in packages or name in needs.get("devUtils", []):
|
|
covering.append(spec)
|
|
return covering
|
|
|
|
|
|
def index_by_name(lock):
|
|
entries = {}
|
|
for group in ("apps", "devUtils", "modules", "uiApps"):
|
|
for item in lock.get(group, []):
|
|
entries[item["name"]] = item
|
|
return entries
|
|
|
|
|
|
def missing_for(spec, lock, entries, platform):
|
|
"""Reasons this spec cannot run on this platform. Empty list = it can."""
|
|
reasons = []
|
|
needs = SPECS[spec]
|
|
|
|
for name in needs["apps"]:
|
|
item = entries.get(name)
|
|
if item is None:
|
|
reasons.append(f"{name} is not in the release set")
|
|
elif platform not in (item.get("platforms") or []):
|
|
# `apps` entries are pinned by tag; never fall back to a catalog
|
|
# entry's source-repo tag here.
|
|
label = item.get("tag") or item.get("version")
|
|
reasons.append(f"no {platform} artifact published for {name}@{label}")
|
|
|
|
packages = needs["packages"]
|
|
if packages == ["*"]:
|
|
packages = [i["name"] for i in lock.get("modules", []) + lock.get("uiApps", [])]
|
|
for name in packages:
|
|
item = entries.get(name)
|
|
if item is None:
|
|
reasons.append(f"{name} is not in the release set")
|
|
elif platform not in (item.get("platforms") or []):
|
|
reasons.append(
|
|
f"no {platform} variant published for {name}@{item.get('version')}"
|
|
)
|
|
return reasons
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument("lock")
|
|
parser.add_argument("--platform", required=True)
|
|
parser.add_argument("--only", default=None,
|
|
help="consider just this one spec (the workflow runs one "
|
|
"spec per job, so each job asks only about its own)")
|
|
parser.add_argument("--format", choices=("text", "json", "github"), default="text")
|
|
args = parser.parse_args()
|
|
|
|
lock = json.load(open(args.lock, encoding="utf-8"))
|
|
entries = index_by_name(lock)
|
|
|
|
if args.only and args.only not in SPECS:
|
|
sys.exit(f"unknown spec {args.only!r}; known: {', '.join(sorted(SPECS))}")
|
|
considered = [args.only] if args.only else sorted(SPECS)
|
|
|
|
runnable, skipped = [], []
|
|
for spec in considered:
|
|
reasons = missing_for(spec, lock, entries, args.platform)
|
|
if reasons:
|
|
skipped.append({
|
|
"spec": spec,
|
|
"platform": args.platform,
|
|
"status": "skipped",
|
|
# One spec can be blocked by several missing artifacts; the first
|
|
# is the actionable one, the rest are listed for completeness.
|
|
"reason": reasons[0],
|
|
"allReasons": reasons,
|
|
})
|
|
else:
|
|
runnable.append(spec)
|
|
|
|
if args.format == "json":
|
|
json.dump({"run": runnable, "skipped": skipped}, sys.stdout, indent=2)
|
|
sys.stdout.write("\n")
|
|
elif args.format == "github":
|
|
# Consumed by the workflow: a space-separated spec list plus the skip
|
|
# records, written to $GITHUB_OUTPUT.
|
|
specs = " ".join(f"doctests/{name}.test.yaml" for name in runnable)
|
|
print(f"specs={specs}")
|
|
print(f"skipped={json.dumps(skipped)}")
|
|
print(f"any={'true' if runnable else 'false'}")
|
|
else:
|
|
print(f"platform: {args.platform}")
|
|
for name in runnable:
|
|
print(f" RUN {name}")
|
|
for entry in skipped:
|
|
print(f" SKIP {entry['spec']}: {entry['reason']}")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|