field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

ffi_installed.py (8207B)


      1 """Install an owned candidate and verify native inputs without building them."""
      2 
      3 from __future__ import annotations
      4 
      5 import hashlib
      6 import json
      7 import os
      8 import shutil
      9 import sys
     10 import tempfile
     11 from pathlib import Path
     12 from typing import Any
     13 
     14 import ffi_artifacts as artifacts
     15 import ffi_provenance as provenance
     16 import ffi_source as source
     17 import package_contract as contract
     18 
     19 MANIFEST = "TeraFFI/provenance.json"
     20 LOCK = "TeraFFI/source.lock"
     21 
     22 
     23 def installed_paths() -> dict[str, str]:
     24     result = {}
     25     for relative in sorted(artifacts.expected_paths()):
     26         if relative.startswith(artifacts.FRAMEWORK + "/"):
     27             result[relative] = "Tera/Frameworks/" + relative
     28         elif relative == "generated/TeraKitBindings.swift":
     29             result[relative] = "Tera/Generated/TeraKitBindings.swift"
     30         elif relative.startswith(("api/", "source/")) or relative == "abi_symbols.json":
     31             result[relative] = "TeraFFI/" + relative
     32     return result
     33 
     34 
     35 def installed_manifest(candidate: dict[str, Any]) -> dict[str, Any]:
     36     paths = installed_paths()
     37     files = [
     38         {**item, "path": paths[item["path"]]}
     39         for item in candidate["files"]
     40         if item["path"] in paths
     41     ]
     42     return {
     43         "schema": "tera.installed-native-artifacts.v1",
     44         "candidate": candidate,
     45         "files": sorted(files, key=lambda item: item["path"]),
     46         "disposition": "local_installed_not_release_qualified",
     47     }
     48 
     49 
     50 def encoded_lock(
     51     manifest: dict[str, Any], foundation: dict[str, Any], epoch: int
     52 ) -> bytes:
     53     producer = manifest["candidate"]["source"]
     54     values = {
     55         "schema": "tera.installed-source.v1",
     56         "repository": producer["repository"],
     57         "source_tree": producer["tree"],
     58         "manifest_sha256": hashlib.sha256(provenance.encoded(manifest)).hexdigest(),
     59         "source_date_epoch": epoch,
     60     }
     61     text = "".join(f"{key} = {json.dumps(value)}\n" for key, value in values.items())
     62     text += "\n[foundation]\n"
     63     text += "".join(
     64         f"{key} = {json.dumps(foundation[key])}\n"
     65         for key in ("repository", "revision", "version")
     66     )
     67     return text.encode()
     68 
     69 
     70 def verify_files(root: Path, manifest: dict[str, Any]) -> None:
     71     expected = installed_manifest(manifest["candidate"])
     72     if manifest != expected:
     73         raise source.ProvenanceError("installed artifact manifest differs")
     74     paths = set(installed_paths().values())
     75     if {item["path"] for item in manifest["files"]} != paths:
     76         raise source.ProvenanceError("installed artifact inventory differs")
     77     for item in manifest["files"]:
     78         if artifacts.file_record(root, item["path"]) != item:
     79             raise source.ProvenanceError("installed artifact bytes are stale")
     80     verify_directory_inventory(root, paths)
     81 
     82 
     83 def verify_directory_inventory(root: Path, paths: set[str]) -> None:
     84     for relative in (
     85         "Tera/Generated",
     86         "Tera/Frameworks/TeraFFI.xcframework",
     87         "TeraFFI/api",
     88         "TeraFFI/source",
     89     ):
     90         observed = set()
     91         for path in (root / relative).rglob("*"):
     92             if path.is_symlink():
     93                 raise source.ProvenanceError(
     94                     "installed artifact inventory contains a symlink"
     95                 )
     96             if path.is_file():
     97                 observed.add(path.relative_to(root).as_posix())
     98         if observed != {path for path in paths if path.startswith(relative + "/")}:
     99             raise source.ProvenanceError("installed artifact inventory differs")
    100 
    101 
    102 def check(root: Path) -> dict[str, Any]:
    103     manifest = contract._read_json(root / MANIFEST)
    104     verify_files(root, manifest)
    105     config = source.producer_contract(root)
    106     snapshot = source.source_snapshot(root, config["source_inputs"])
    107     foundation = contract._read_toml(root / config["foundation_lock"])
    108     candidate = manifest["candidate"]
    109     if candidate["source"] != {
    110         "repository": config["repository"],
    111         "tree": snapshot["tree"],
    112     }:
    113         raise source.ProvenanceError("installed producer source is stale")
    114     for target in artifacts.TARGETS:
    115         record = contract._read_json(root / "TeraFFI/source" / f"{target}.json")
    116         if (
    117             record["source"] != snapshot
    118             or record["foundation"] != foundation
    119             or record["build"]["target"] != target
    120         ):
    121             raise source.ProvenanceError("installed producer tuple differs")
    122     if contract._read_regular(root / LOCK) != encoded_lock(
    123         manifest, foundation, config["build"]["source_date_epoch"]
    124     ):
    125         raise source.ProvenanceError("installed producer lock is stale")
    126     return manifest
    127 
    128 
    129 def install(
    130     root: Path, candidate_root: Path, records: dict[str, dict[str, Any]]
    131 ) -> None:
    132     if not os.environ.get("EXT_BUILD_RUN_ACTIVE"):
    133         raise source.ProvenanceError("native installation requires extbuild")
    134     local_destination(root, "TeraFFI")
    135     candidate = artifacts.check(candidate_root, records)
    136     manifest = installed_manifest(candidate)
    137     config = source.producer_contract(root)
    138     foundation = contract._read_toml(root / config["foundation_lock"])
    139     # Final-directory swaps stay on the repository filesystem. A failed install
    140     # restores every prior directory/file, and publishes its manifest last.
    141     with tempfile.TemporaryDirectory(
    142         prefix=".install-", dir=root / "TeraFFI"
    143     ) as temporary:
    144         staging = Path(temporary) / "next"
    145         for relative, destination in installed_paths().items():
    146             path = staging / destination
    147             path.parent.mkdir(parents=True, exist_ok=True)
    148             shutil.copyfile(candidate_root / relative, path)
    149         (staging / MANIFEST).write_bytes(provenance.encoded(manifest))
    150         (staging / LOCK).write_bytes(
    151             encoded_lock(manifest, foundation, config["build"]["source_date_epoch"])
    152         )
    153         verify_files(staging, manifest)
    154         replace_installation(root, staging, Path(temporary) / "previous")
    155     print(
    156         "owned native candidate installed and source-verified; not release qualification"
    157     )
    158 
    159 
    160 def local_destination(root: Path, relative: str) -> Path:
    161     current = root
    162     for part in Path(relative).parts:
    163         current = current / part
    164         if current.is_symlink():
    165             raise source.ProvenanceError("native installation path contains a symlink")
    166     return current
    167 
    168 
    169 def replace_installation(root: Path, staging: Path, previous: Path) -> None:
    170     paths = (
    171         "Tera/Generated",
    172         "Tera/Frameworks/TeraFFI.xcframework",
    173         "TeraFFI/api",
    174         "TeraFFI/source",
    175         "TeraFFI/abi_symbols.json",
    176         LOCK,
    177         MANIFEST,
    178     )
    179     destinations = {relative: local_destination(root, relative) for relative in paths}
    180     changed = []
    181     try:
    182         for relative in paths:
    183             destination = destinations[relative]
    184             backup = previous / relative
    185             destination.parent.mkdir(parents=True, exist_ok=True)
    186             existed = destination.exists()
    187             if existed:
    188                 backup.parent.mkdir(parents=True, exist_ok=True)
    189                 destination.rename(backup)
    190             changed.append((relative, existed))
    191             (staging / relative).rename(destination)
    192         check(root)
    193     except Exception:
    194         for relative, existed in reversed(changed):
    195             destination = root / relative
    196             if destination.is_dir():
    197                 shutil.rmtree(destination)
    198             else:
    199                 destination.unlink(missing_ok=True)
    200             if existed:
    201                 (previous / relative).rename(destination)
    202         raise
    203 
    204 
    205 def main() -> int:
    206     root = Path(__file__).resolve().parent.parent
    207     try:
    208         check(root)
    209     except (
    210         source.ProvenanceError,
    211         contract.PackageContractError,
    212         OSError,
    213         ValueError,
    214         KeyError,
    215         TypeError,
    216     ) as error:
    217         print(
    218             f"installed native artifacts: {error}; run make ffi-bootstrap",
    219             file=sys.stderr,
    220         )
    221         return 1
    222     print(
    223         "installed native artifacts match the owned source tree and exact foundation lock"
    224     )
    225     return 0
    226 
    227 
    228 if __name__ == "__main__":
    229     raise SystemExit(main())