80 lines
4.1 KiB
Python
80 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Linux clean-checkout acceptance in a network namespace with no external network.
|
|
|
|
Uses committed HEAD, prefetched checksum-verified archives and local system tools.
|
|
Does not download dependencies, install packages or change the source checkout.
|
|
"""
|
|
import argparse
|
|
import datetime
|
|
import json
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tarfile
|
|
import time
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
parser.add_argument("--parallel", type=int, default=4)
|
|
args = parser.parse_args()
|
|
if sys.platform != "linux" or not shutil.which("unshare"):
|
|
parser.error("This check requires Linux unshare with user/network namespace support")
|
|
if not 1 <= args.parallel <= 64:
|
|
parser.error("--parallel must be between 1 and 64")
|
|
root, output = Path(__file__).resolve().parents[1], args.output.resolve()
|
|
if output.exists():
|
|
parser.error("Use a new output directory to preserve previous evidence")
|
|
subprocess.run([sys.executable, root / "tools/fetch_dependencies.py", "--verify-only"], check=True)
|
|
if not (root / ".cache/slang/bin/slangc").is_file():
|
|
parser.error("Prefetch Slang before starting the offline check")
|
|
# Verify capability before making a potentially expensive copy.
|
|
subprocess.run(["unshare", "--user", "--map-root-user", "--net", "true"], check=True)
|
|
output.mkdir(parents=True)
|
|
revision = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=root, text=True).strip()
|
|
archive, checkout = output / "source.tar", output / "checkout"
|
|
with archive.open("wb") as stream:
|
|
subprocess.run(["git", "archive", "--format=tar", revision], cwd=root, stdout=stream, check=True)
|
|
with tarfile.open(archive) as content:
|
|
content.extractall(checkout, filter="data")
|
|
archive.unlink()
|
|
shutil.copytree(root / ".cache/downloads", checkout / ".cache/downloads")
|
|
shutil.copytree(root / ".cache/slang", checkout / ".cache/slang", symlinks=True)
|
|
script = output / "offline_driver.py"
|
|
script.write_text('''import json, pathlib, socket, subprocess, sys
|
|
root = pathlib.Path(sys.argv[1])
|
|
# No external interfaces exist in this newly created network namespace.
|
|
with socket.socket() as connection:
|
|
connection.settimeout(2)
|
|
try:
|
|
connection.connect(("1.1.1.1", 443))
|
|
except OSError as error:
|
|
print("External network unavailable:", error, flush=True)
|
|
else:
|
|
raise RuntimeError("Offline check unexpectedly has external connectivity")
|
|
subprocess.run([sys.executable, "tools/fetch_dependencies.py", "--verify-only"], cwd=root, check=True)
|
|
subprocess.run(["cmake", "--preset", "linux-debug"], cwd=root, check=True)
|
|
subprocess.run(["cmake", "--build", "--preset", "linux-debug", "--parallel", sys.argv[2]], cwd=root, check=True)
|
|
subprocess.run(["ctest", "--preset", "linux-debug", "-LE", "gpu", "--timeout", "120"], cwd=root, check=True)
|
|
''', encoding="utf-8")
|
|
started = time.perf_counter()
|
|
with (output / "build.log").open("w", encoding="utf-8") as log:
|
|
result = subprocess.run(["unshare", "--user", "--map-root-user", "--net",
|
|
sys.executable, script, checkout, str(args.parallel)], stdout=log, stderr=subprocess.STDOUT)
|
|
report = {"format": "faset.offline-build", "version": 1, "revision": revision,
|
|
"recorded_at_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
"seconds": time.perf_counter() - started, "returncode": result.returncode,
|
|
"network": "fresh user/network namespace; external connection must fail",
|
|
"source": "git archive of committed HEAD; no existing build directory",
|
|
"inputs": "prefetched archives + Slang; system compiler, SDK and development libraries",
|
|
"tests": "full native build, CPU CTests; GPU/window execution verified separately"}
|
|
(output / "report.json").write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
|
|
print(json.dumps(report, indent=2))
|
|
return result.returncode
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|