Files
crossdesk/apps/ios/scripts/prepare_source_metadata.py
T

96 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Record the source corresponding to an iOS build without changing the checkout."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import subprocess
import sys
from urllib.parse import quote
REPO = Path(__file__).resolve().parents[3]
REPOSITORY_URL = "https://github.com/kunkundi/crossdesk"
MINIRTC_URL = "https://github.com/kunkundi/minirtc"
def git(*args, root=REPO):
return subprocess.check_output(["git", "-C", str(root), *args], text=True).strip()
def component_sources(lock, mini_revision):
"""Resolve MiniRTC source and recipe links without rewriting reviewed notices."""
mini_url = f"{MINIRTC_URL}/tree/{mini_revision}"
sources = {}
for component in lock["components"]:
source = component["source"]
entry = {}
if source["kind"] == "repository" and source["url"] == MINIRTC_URL:
entry.update(version=mini_revision, sourceURL=mini_url)
recipe = component.get("recipe_path", "")
if recipe.startswith("deps/submodules/minirtc/"):
directory = Path(recipe).relative_to("deps/submodules/minirtc").parent
entry["buildSourceURL"] = f"{mini_url}/{directory.as_posix()}"
if entry:
sources[component["id"]] = entry
return sources
def source_metadata(require_release=False, tag=None):
revision = git("rev-parse", "HEAD")
dirty = bool(git("status", "--porcelain", "--untracked-files=normal", "--ignore-submodules=none"))
tags = git("tag", "--points-at", "HEAD").splitlines()
if tag:
if tag not in tags:
raise ValueError(f"Source tag {tag!r} does not point to the build commit")
else:
# 'latest' is a moving alias, never a corresponding-source identifier.
versions = sorted(t for t in tags if t.startswith("v"))
tag = versions[0] if versions else None
if require_release and (dirty or not tag or not tag.startswith("v")):
raise ValueError("Release source must be a clean checkout of a version tag, with matching submodules")
mini_revision = git("rev-parse", "HEAD", root=REPO / "deps/submodules/minirtc")
lock_path = REPO / "apps/ios/licenses/sources.json"
lock = json.loads(lock_path.read_text())
ref = quote(tag if tag and not dirty else revision, safe="")
return {
"schemaVersion": 1,
"repositoryURL": REPOSITORY_URL,
"revision": revision,
"tag": tag,
"isModified": dirty,
"sourceURL": f"{REPOSITORY_URL}/tree/{ref}",
"buildInstructionsURL": f"{REPOSITORY_URL}/blob/{ref}/apps/ios/README.md",
"privacyPolicyURL": f"{REPOSITORY_URL}/blob/{ref}/PRIVACY.md",
"miniRTCRevision": mini_revision,
"miniRTCSourceURL": f"{MINIRTC_URL}/tree/{mini_revision}",
"componentSources": component_sources(lock, mini_revision),
"licenseCatalogSHA256": lock["bundle_sha256"],
"sourceCatalogSHA256": hashlib.sha256(lock_path.read_bytes()).hexdigest(),
"toolchain": lock["toolchain"],
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", required=True, type=Path)
parser.add_argument("--tag", default=os.environ.get("CROSSDESK_SOURCE_TAG") or None)
parser.add_argument("--require-release", action="store_true",
default=os.environ.get("CROSSDESK_RELEASE_BUILD") == "YES")
args = parser.parse_args()
metadata = source_metadata(args.require_release, args.tag)
data = (json.dumps(metadata, ensure_ascii=False, indent=2) + "\n").encode()
args.output.parent.mkdir(parents=True, exist_ok=True)
if not args.output.exists() or args.output.read_bytes() != data:
args.output.write_bytes(data)
print(f"Recorded iOS source: {metadata['revision']} (modified: {metadata['isModified']})")
if __name__ == "__main__":
try:
main()
except (OSError, ValueError, subprocess.CalledProcessError) as error:
print(f"iOS source metadata: {error}", file=sys.stderr)
sys.exit(1)