#!/usr/bin/env python3 """Validate current repository files without changing the Git index.""" from collections import deque import json import os from pathlib import Path import shutil import subprocess import sys import tempfile ROOT = Path(__file__).resolve().parent.parent def main(): os.chdir(ROOT) run_dir = Path(tempfile.mkdtemp(prefix="nixos-validate-", dir="/tmp")) print(f"Logs: {run_dir}", flush=True) def run(label, command): log = run_dir / f"{label}.log" print(f"{label} ...", flush=True) with log.open("wb") as output: result = subprocess.run(command, stdout=output, stderr=subprocess.STDOUT) if result.returncode: print(f"{label}: FAILED ({result.returncode}); {log}", flush=True) with log.open(errors="replace") as output: excerpt = "".join(deque(output, maxlen=40)) print(excerpt[-12000:]) raise SystemExit(result.returncode) print(f"{label}: OK", flush=True) # Git supplies selection only: contents always come from the working tree. paths = subprocess.check_output( ["git", "ls-files", "--cached", "--others", "--exclude-standard", "-z"] ).split(b"\0") files = sorted({os.fsdecode(path) for path in paths if path}) files = [name for name in files if os.path.lexists(ROOT / name)] with tempfile.TemporaryDirectory(prefix="nixos-source-", dir="/tmp") as temp: source = Path(temp) / "source" def snapshot(): if source.exists(): shutil.rmtree(source) source.mkdir() for name in files: original = ROOT / name destination = source / name destination.parent.mkdir(parents=True, exist_ok=True) if original.is_symlink(): destination.symlink_to(os.readlink(original)) elif original.is_file(): shutil.copy2(original, destination) else: raise RuntimeError(f"Unsupported source entry: {name}") snapshot() flake = f"path:{source}" nix = ["nix", "--quiet"] locked = ["--no-update-lock-file"] # The repository currently exposes only x86_64-linux outputs. nix_files = [str(ROOT / name) for name in files if name.endswith(".nix")] run("format", nix + ["run", *locked, f"{flake}#formatter.x86_64-linux", "--", *nix_files]) snapshot() run("check", nix + ["flake", "check", "--no-build", *locked, flake]) hosts_file = run_dir / "hosts.json" # Keep machine-readable output separate from Nix diagnostics. with hosts_file.open("wb") as output, (run_dir / "hosts.log").open("wb") as errors: result = subprocess.run( nix + ["eval", *locked, "--json", f"{flake}#nixosConfigurations", "--apply", "builtins.attrNames"], stdout=output, stderr=errors, ) if result.returncode: print(f"Host discovery failed; see {run_dir / 'hosts.log'}") raise SystemExit(result.returncode) available = json.loads(hosts_file.read_text()) hosts = list(dict.fromkeys(sys.argv[1:])) or available unknown = set(hosts) - set(available) if unknown: raise RuntimeError(f"Unknown hosts: {', '.join(sorted(unknown))}") for host in hosts: run(f"build-{host}", nix + ["build", *locked, "--no-link", f"{flake}#nixosConfigurations.{host}.config.system.build.toplevel"]) if not sys.argv[1:]: run("build-workbench", nix + ["build", *locked, "--no-link", f"{flake}#devShells.x86_64-linux.workbench"]) print(f"Validation passed: {', '.join(hosts)}. Logs: {run_dir}", flush=True) if __name__ == "__main__": try: main() except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error: sys.exit(str(error))