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())