VulpesOS/tundra/tools/resolve-manifest.py
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

100 lines
4.5 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: MPL-2.0
"""Resolve the upstream Gonk reference manifest; no source checkout or build."""
import argparse
import concurrent.futures
import copy
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import xml.etree.ElementTree as ET
from tundra import ROOT, checked_source, flatten_manifest, locked, now, save
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--target", choices=("sargo", "emulator-10"), default="sargo")
parser.add_argument("--jobs", type=int, default=4)
args = parser.parse_args()
if not 1 <= args.jobs <= 8:
parser.error("Use 1 to 8 network workers")
folder = checked_source("capyloon-manifests")
projects = flatten_manifest(folder, args.target + ".xml")
destination = ROOT / "out" / args.target
report_file = destination / "resolved-reference.json"
manifest_file = destination / "reference-pinned.xml"
# Never leave an earlier, apparently valid manifest after a failed resolution.
manifest_file.unlink(missing_ok=True)
prior = json.loads(report_file.read_text()) if report_file.exists() else {}
cache = {(x["url"], x["revision"]): x for x in prior.get("projects", []) if x.get("commit")}
def resolve(project):
item = dict(project)
key = (item["url"], item["revision"])
if key in cache:
return dict(item, commit=cache[key]["commit"])
ref = item["revision"]
if re.fullmatch(r"[0-9a-f]{40}", ref or ""):
# Already pinned upstream; the object will be checked when fetched.
return dict(item, commit=ref, verification="upstream-pin; object not fetched")
patterns = [ref, ref + "^{}"] if ref.startswith("refs/") else ["refs/heads/" + ref, "refs/tags/" + ref, "refs/tags/" + ref + "^{}"]
try:
p = subprocess.run(["git", "ls-remote", item["url"], *patterns],
env=dict(os.environ, GIT_TERMINAL_PROMPT="0"),
capture_output=True, text=True, timeout=45, check=True)
refs = [line.split() for line in p.stdout.splitlines()]
if not refs:
raise ValueError("Upstream revision does not exist")
commit = next((sha for sha, name in refs if name.endswith("^{}")), refs[0][0])
if not re.fullmatch(r"[0-9a-f]{40}", commit):
raise ValueError("Invalid upstream object ID")
return dict(item, commit=commit)
except (ValueError, subprocess.SubprocessError) as error:
detail = getattr(error, "stderr", None) or str(error)
return dict(item, error=detail[-1500:])
results = []
with concurrent.futures.ThreadPoolExecutor(max_workers=args.jobs) as pool:
futures = [pool.submit(resolve, project) for project in projects]
for future in concurrent.futures.as_completed(futures):
results.append(future.result())
save(report_file, {"recordedAt": now(), "complete": False,
"manifestCommit": locked("capyloon-manifests")["commit"],
"projects": results})
if len(results) % 25 == 0:
print(f"Resolved {len(results)}/{len(projects)} references", flush=True)
results.sort(key=lambda x: x["path"])
errors = [x for x in results if "error" in x]
save(report_file, {"recordedAt": now(), "complete": not errors,
"manifestCommit": locked("capyloon-manifests")["commit"],
"target": args.target, "referenceOnly": True, "projects": results})
if errors:
for item in errors:
print(f"Unavailable: {item['path']}: {item['error']}")
return 1
root = ET.Element("manifest")
revisions = {x["path"]: x["commit"] for x in results}
def expand(name):
for element in ET.parse(folder / name).getroot():
if element.tag == "include":
expand(element.attrib["name"])
else:
element = copy.deepcopy(element)
if element.tag == "project":
element.set("revision", revisions[element.get("path", element.attrib["name"])])
root.append(element)
expand(args.target + ".xml")
ET.indent(root)
ET.ElementTree(root).write(manifest_file, encoding="utf-8", xml_declaration=True)
print(f"Pinned reference manifest: {manifest_file}")
return 0
if __name__ == "__main__":
sys.exit(main())