Own canonical offline private Go module build tooling
Authored-By: Codex (GPT-6) <noreply@openai.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Export exact committed offline-bootstrap tooling; never download anything."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
SOURCE = "https://code.fritzlab.net/action/image-build"
|
||||
FILES = {
|
||||
"tools/private-modules/main.go": "tools/private-modules.go",
|
||||
"tools/private-modules/main_test.go": "tools/private-modules_test.go",
|
||||
}
|
||||
|
||||
|
||||
def committed(root, *args):
|
||||
return subprocess.check_output(["git", "-C", str(root), *args])
|
||||
|
||||
|
||||
def export(root, target, revision, check=False):
|
||||
if not re.fullmatch(r"[0-9a-f]{40}", revision):
|
||||
raise ValueError("source revision must be a full Git commit ID")
|
||||
resolved = committed(root, "rev-parse", revision + "^{commit}").decode().strip()
|
||||
if resolved != revision:
|
||||
raise ValueError("source revision is not a commit")
|
||||
result = {}
|
||||
entries = []
|
||||
for source, destination in FILES.items():
|
||||
tree = committed(root, "ls-tree", revision, "--", source).decode()
|
||||
if not tree.startswith("100644 blob ") or not tree.endswith("\t" + source + "\n"):
|
||||
raise ValueError("source must be one regular committed file")
|
||||
data = committed(root, "show", revision + ":" + source)
|
||||
if not data or len(data) > 128 * 1024:
|
||||
raise ValueError("source exceeds its bounded export")
|
||||
result[destination] = data
|
||||
entries.append({"source": source, "destination": destination,
|
||||
"sha256": hashlib.sha256(data).hexdigest()})
|
||||
result["tools/private-modules.source.json"] = (json.dumps({
|
||||
"version": 1, "repository": SOURCE, "revision": revision, "files": entries,
|
||||
}, indent=2) + "\n").encode()
|
||||
result["tools/private-modules.sha256"] = "".join(
|
||||
hashlib.sha256(data).hexdigest() + " " + path + "\n"
|
||||
for path, data in sorted(result.items())
|
||||
).encode()
|
||||
target = target.resolve(strict=True)
|
||||
tools_dir = target / "tools"
|
||||
if tools_dir.is_symlink():
|
||||
raise ValueError("target tools directory must not be a symlink")
|
||||
if not check:
|
||||
tools_dir.mkdir(exist_ok=True)
|
||||
for path, data in result.items():
|
||||
destination = target / path
|
||||
if destination.is_symlink():
|
||||
raise ValueError("target file must not be a symlink")
|
||||
if check:
|
||||
if destination.read_bytes() != data:
|
||||
raise ValueError("export differs from pinned canonical source: " + path)
|
||||
else:
|
||||
destination.write_bytes(data)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("target", type=pathlib.Path)
|
||||
parser.add_argument("--revision", required=True)
|
||||
parser.add_argument("--check", action="store_true")
|
||||
args = parser.parse_args()
|
||||
export(pathlib.Path(__file__).resolve().parents[1], args.target,
|
||||
args.revision, args.check)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user