feeds: Vendor repository indexes
Keep the native APK indexes alongside the expanded package metadata so image builds do not fetch mutable repository state. Generate pins atomically and retry transient generation failures.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
URL_RE = re.compile(r'^(?P<indent>\s*)url = "(?P<url>https://.*/packages\.adb)";$')
|
||||
HASH_RE = re.compile(r'^(?P<indent>\s*)hash = ".*";$')
|
||||
NAME_RE = re.compile(r'^\s*name = "(?P<name>[^"]+)";$')
|
||||
|
||||
|
||||
def vendor_block(lines, start, end, nix_file, indexes_dir):
|
||||
url_match = None
|
||||
hash_index = None
|
||||
name = None
|
||||
|
||||
for index in range(start, end):
|
||||
stripped = lines[index].rstrip("\n")
|
||||
if match := URL_RE.match(stripped):
|
||||
url_match = (index, match)
|
||||
if HASH_RE.match(stripped):
|
||||
hash_index = index
|
||||
if match := NAME_RE.match(stripped):
|
||||
name = match.group("name")
|
||||
|
||||
if url_match is None:
|
||||
return False
|
||||
if hash_index is None or name is None:
|
||||
raise RuntimeError(f"incomplete sourceInfo in {nix_file}")
|
||||
|
||||
url_index, match = url_match
|
||||
destination = indexes_dir / name
|
||||
temporary = destination.with_suffix(destination.suffix + ".tmp")
|
||||
subprocess.run(
|
||||
["curl", "--fail", "--location", "--silent", "--show-error",
|
||||
"--output", temporary, match.group("url")],
|
||||
check=True,
|
||||
)
|
||||
temporary.replace(destination)
|
||||
|
||||
digest = base64.b64encode(hashlib.sha256(destination.read_bytes()).digest()).decode()
|
||||
hash_indent = HASH_RE.match(lines[hash_index].rstrip("\n")).group("indent")
|
||||
relative = os.path.relpath(destination, nix_file.parent)
|
||||
if not relative.startswith("."):
|
||||
relative = f"./{relative}"
|
||||
|
||||
lines[hash_index] = f'{hash_indent}hash = "sha256-{digest}";\n'
|
||||
lines[url_index] = f'{match.group("indent")}url = "file://${{{relative}}}";\n'
|
||||
return True
|
||||
|
||||
|
||||
def vendor_file(nix_file, indexes_dir):
|
||||
lines = nix_file.read_text().splitlines(keepends=True)
|
||||
block_start = None
|
||||
changed = 0
|
||||
|
||||
for index, line in enumerate(lines):
|
||||
if line.strip() == "sourceInfo = {":
|
||||
block_start = index + 1
|
||||
elif block_start is not None and line.strip() == "};":
|
||||
changed += vendor_block(lines, block_start, index, nix_file, indexes_dir)
|
||||
block_start = None
|
||||
|
||||
if changed:
|
||||
nix_file.write_text("".join(lines))
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
raise SystemExit(f"usage: {sys.argv[0]} CACHE-DIRECTORY")
|
||||
|
||||
cache_dir = Path(sys.argv[1]).resolve()
|
||||
if not (cache_dir / "default.nix").is_file():
|
||||
raise SystemExit(f"not an OpenWrt cache directory: {cache_dir}")
|
||||
|
||||
indexes_dir = cache_dir / "indexes"
|
||||
indexes_dir.mkdir(exist_ok=True)
|
||||
|
||||
changed = 0
|
||||
for nix_file in sorted(cache_dir.rglob("*.nix")):
|
||||
changed += vendor_file(nix_file, indexes_dir)
|
||||
|
||||
print(f"Vendored {changed} repository indexes in {cache_dir}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user