mirror of
https://github.com/moons-14/dotfiles.git
synced 2026-10-06 00:38:12 +09:00
flake: add coding-agent validation workflow
This commit is contained in:
Executable
+526
@@ -0,0 +1,526 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Change-aware, non-activating validation for this Nix dotfiles repository."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections import defaultdict, deque
|
||||
from contextlib import contextmanager
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import tomllib
|
||||
from typing import Any, Iterator, Sequence
|
||||
|
||||
GLOBAL_FILES = {"flake.nix", "flake.lock", "hosts/default.nix"}
|
||||
GLOBAL_PREFIXES = ("flake/", "libs/", "overlays/")
|
||||
FULL_EVAL_PREFIXES = GLOBAL_PREFIXES + ("scripts/", "shells/", "tests/")
|
||||
FULL_BUILD_PREFIXES = GLOBAL_PREFIXES + ("scripts/", "shells/", "tests/")
|
||||
DOC_SUFFIXES = (".md", ".png", ".jpg", ".jpeg", ".webp")
|
||||
|
||||
|
||||
class ValidationError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def command_text(command: Sequence[str]) -> str:
|
||||
return " ".join(json.dumps(part) if any(c.isspace() for c in part) else part for part in command)
|
||||
|
||||
|
||||
def run(
|
||||
command: Sequence[str],
|
||||
*,
|
||||
cwd: Path,
|
||||
capture: bool = False,
|
||||
check: bool = True,
|
||||
input_text: str | None = None,
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
print(f"+ {command_text(command)}", file=sys.stderr)
|
||||
return subprocess.run(
|
||||
list(command),
|
||||
cwd=cwd,
|
||||
check=check,
|
||||
text=True,
|
||||
input=input_text,
|
||||
stdout=subprocess.PIPE if capture else None,
|
||||
stderr=subprocess.PIPE if capture else None,
|
||||
)
|
||||
|
||||
|
||||
def git(root: Path, *args: str, check: bool = True) -> str:
|
||||
return run(["git", *args], cwd=root, capture=True, check=check).stdout
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
return Path(run(["git", "rev-parse", "--show-toplevel"], cwd=Path.cwd(), capture=True).stdout.strip()).resolve()
|
||||
|
||||
|
||||
def normalize(root: Path, raw: str) -> str:
|
||||
candidate = Path(raw)
|
||||
absolute = candidate.resolve() if candidate.is_absolute() else (root / candidate).resolve()
|
||||
try:
|
||||
return absolute.relative_to(root).as_posix()
|
||||
except ValueError as error:
|
||||
raise ValidationError(f"path escapes repository root: {raw}") from error
|
||||
|
||||
|
||||
def nonempty_lines(value: str) -> list[str]:
|
||||
return [line for line in value.splitlines() if line]
|
||||
|
||||
|
||||
def tracked_paths(root: Path) -> set[str]:
|
||||
return set(nonempty_lines(git(root, "ls-files")))
|
||||
|
||||
|
||||
def untracked_paths(root: Path) -> set[str]:
|
||||
return set(nonempty_lines(git(root, "ls-files", "--others", "--exclude-standard")))
|
||||
|
||||
|
||||
def expand_paths(root: Path, raw_paths: Sequence[str], available: set[str]) -> list[str]:
|
||||
result: set[str] = set()
|
||||
for raw in raw_paths:
|
||||
relative = normalize(root, raw)
|
||||
target = root / relative
|
||||
if target.is_dir():
|
||||
prefix = f"{relative.rstrip('/')}/" if relative else ""
|
||||
result.update(path for path in available if path.startswith(prefix))
|
||||
else:
|
||||
result.add(relative)
|
||||
return sorted(result)
|
||||
|
||||
|
||||
def collect_paths(root: Path, args: argparse.Namespace) -> tuple[list[str], set[str]]:
|
||||
tracked = tracked_paths(root)
|
||||
untracked = untracked_paths(root)
|
||||
if args.paths:
|
||||
paths = expand_paths(root, args.paths, tracked | untracked)
|
||||
elif args.all_files:
|
||||
paths = sorted(tracked)
|
||||
elif args.base:
|
||||
paths = nonempty_lines(
|
||||
git(root, "diff", "--name-only", "--no-renames", "--diff-filter=ACMRTUXBD", f"{args.base}...HEAD", "--")
|
||||
)
|
||||
else:
|
||||
paths = nonempty_lines(git(root, "diff", "--name-only", "--no-renames", "--diff-filter=ACMRTUXBD", "HEAD", "--"))
|
||||
paths += sorted(untracked)
|
||||
return sorted(set(paths)), untracked
|
||||
|
||||
|
||||
def remove_path(path: Path) -> None:
|
||||
if path.is_symlink() or path.is_file():
|
||||
path.unlink()
|
||||
elif path.is_dir():
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def flake_reference(root: Path, changed: Sequence[str]) -> Iterator[str]:
|
||||
"""Create a clean HEAD snapshot and overlay only task-owned worktree paths."""
|
||||
with tempfile.TemporaryDirectory(prefix="dotfiles-flake-") as temporary:
|
||||
source = Path(temporary) / "source"
|
||||
source.mkdir()
|
||||
archive = subprocess.run(
|
||||
["git", "archive", "--format=tar", "HEAD"],
|
||||
cwd=root,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
).stdout
|
||||
with tarfile.open(fileobj=io.BytesIO(archive), mode="r:") as tar:
|
||||
tar.extractall(source, filter="data")
|
||||
for relative in changed:
|
||||
src, dst = root / relative, source / relative
|
||||
remove_path(dst)
|
||||
if src.is_symlink():
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
dst.symlink_to(os.readlink(src))
|
||||
elif src.is_file():
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(src, dst)
|
||||
yield f"path:{source}"
|
||||
|
||||
|
||||
def load_metadata(root: Path, reference: str) -> dict[str, Any]:
|
||||
result = run(
|
||||
["nix", "eval", "--json", "--show-trace", f"{reference}#lib.validationMetadata"],
|
||||
cwd=root,
|
||||
capture=True,
|
||||
)
|
||||
metadata = json.loads(result.stdout)
|
||||
if metadata.get("schemaVersion") != 1:
|
||||
raise ValidationError("unsupported validation metadata schema")
|
||||
return metadata
|
||||
|
||||
|
||||
def reverse_dependencies(units: dict[str, Any]) -> dict[str, set[str]]:
|
||||
result: dict[str, set[str]] = defaultdict(set)
|
||||
for unit_id, unit in units.items():
|
||||
for dependency in unit.get("includes", []):
|
||||
result[dependency].add(unit_id)
|
||||
return result
|
||||
|
||||
|
||||
def users_of(unit_id: str, reverse: dict[str, set[str]]) -> set[str]:
|
||||
result, queue = {unit_id}, deque([unit_id])
|
||||
while queue:
|
||||
for dependent in reverse.get(queue.popleft(), set()):
|
||||
if dependent not in result:
|
||||
result.add(dependent)
|
||||
queue.append(dependent)
|
||||
return result
|
||||
|
||||
|
||||
def owner_for(path: str, units: dict[str, Any]) -> str | None:
|
||||
matches = []
|
||||
for unit_id, unit in units.items():
|
||||
directory = unit["directory"].rstrip("/")
|
||||
if path == directory or path.startswith(f"{directory}/"):
|
||||
matches.append((len(directory), unit_id))
|
||||
return max(matches)[1] if matches else None
|
||||
|
||||
|
||||
def path_classes(path: str, unit: dict[str, Any]) -> set[str]:
|
||||
directory = unit["directory"].rstrip("/")
|
||||
relative = path[len(directory) :].lstrip("/")
|
||||
if relative == "nixos.nix" or relative == "home/nixos.nix":
|
||||
return {"nixos"}
|
||||
if relative == "darwin.nix" or relative == "home/darwin.nix":
|
||||
return {"darwin"}
|
||||
if relative == "meta.nix":
|
||||
return {"nixos", "darwin"}
|
||||
if relative in {"common.nix", "home.nix", "home/common.nix"}:
|
||||
return {"nixos", "darwin"}
|
||||
fragments = set(unit.get("fragments", []))
|
||||
classes: set[str] = set()
|
||||
if fragments & {"common", "nixos", "home", "homeCommon", "homeNixos"}:
|
||||
classes.add("nixos")
|
||||
if fragments & {"common", "darwin", "home", "homeCommon", "homeDarwin"}:
|
||||
classes.add("darwin")
|
||||
return classes or {"nixos", "darwin"}
|
||||
|
||||
|
||||
def is_docs_only(path: str) -> bool:
|
||||
return path.endswith(DOC_SUFFIXES)
|
||||
|
||||
|
||||
def plan(metadata: dict[str, Any], paths: Sequence[str], *, all_hosts: bool = False) -> dict[str, Any]:
|
||||
hosts: dict[str, Any] = metadata["hosts"]
|
||||
units: dict[str, Any] = metadata["units"]
|
||||
reverse = reverse_dependencies(units)
|
||||
affected_units: set[str] = set()
|
||||
unit_classes: dict[str, set[str]] = defaultdict(set)
|
||||
affected_hosts = {"nixos": set(), "darwin": set()}
|
||||
global_change = all_hosts
|
||||
requires_full_eval = False
|
||||
requires_full_build = False
|
||||
requires_nix = False
|
||||
|
||||
for path in paths:
|
||||
if path in GLOBAL_FILES or path.startswith(GLOBAL_PREFIXES):
|
||||
global_change = True
|
||||
requires_nix = True
|
||||
if path.startswith(FULL_EVAL_PREFIXES) or path in GLOBAL_FILES:
|
||||
requires_full_eval = True
|
||||
if path.startswith(FULL_BUILD_PREFIXES) or path in {"flake.nix", "flake.lock"}:
|
||||
requires_full_build = True
|
||||
if path.endswith(".nix") or path == "flake.lock":
|
||||
requires_nix = True
|
||||
if path == "scripts/dotfiles-check.py":
|
||||
requires_nix = requires_full_eval = requires_full_build = True
|
||||
|
||||
if path.startswith("hosts/") and path != "hosts/default.nix":
|
||||
parts = PurePosixPath(path).parts
|
||||
if len(parts) > 1 and parts[1] in hosts:
|
||||
affected_hosts[hosts[parts[1]]["kind"]].add(parts[1])
|
||||
requires_nix = True
|
||||
|
||||
owner = owner_for(path, units)
|
||||
if owner:
|
||||
classes = path_classes(path, units[owner])
|
||||
for unit_id in users_of(owner, reverse):
|
||||
affected_units.add(unit_id)
|
||||
unit_classes[unit_id].update(classes)
|
||||
requires_nix = requires_nix or not is_docs_only(path)
|
||||
elif path.startswith("modules/") and not is_docs_only(path):
|
||||
global_change = True
|
||||
requires_nix = True
|
||||
|
||||
if global_change:
|
||||
for name, host in hosts.items():
|
||||
affected_hosts[host["kind"]].add(name)
|
||||
else:
|
||||
for name, host in hosts.items():
|
||||
selected = set(host["selectedUnits"])
|
||||
if any(unit_id in selected and host["kind"] in unit_classes[unit_id] for unit_id in affected_units):
|
||||
affected_hosts[host["kind"]].add(name)
|
||||
|
||||
synthetic = {"nixos": set(), "darwin": set()}
|
||||
for unit_id in affected_units:
|
||||
for kind in unit_classes[unit_id]:
|
||||
if not any(unit_id in hosts[name]["selectedUnits"] for name in affected_hosts[kind]):
|
||||
synthetic[kind].add(unit_id)
|
||||
|
||||
systems = sorted({host["system"] for host in hosts.values()})
|
||||
native_systems = {system: False for system in systems}
|
||||
if requires_full_build:
|
||||
native_systems = {system: requires_nix for system in systems}
|
||||
else:
|
||||
for kind, names in affected_hosts.items():
|
||||
for name in names:
|
||||
native_systems[hosts[name]["system"]] = True
|
||||
for kind, unit_ids in synthetic.items():
|
||||
if unit_ids:
|
||||
candidates = sorted(host["system"] for host in hosts.values() if host["kind"] == kind)
|
||||
if candidates:
|
||||
native_systems[candidates[0]] = True
|
||||
|
||||
return {
|
||||
"schemaVersion": 1,
|
||||
"paths": sorted(paths),
|
||||
"affectedUnits": sorted(affected_units),
|
||||
"affectedHosts": {kind: sorted(names) for kind, names in affected_hosts.items()},
|
||||
"syntheticUnits": {kind: sorted(names) for kind, names in synthetic.items()},
|
||||
"requiresNixValidation": requires_nix,
|
||||
"requiresFullEvaluation": requires_full_eval,
|
||||
"requiresFullNativeBuild": requires_full_build,
|
||||
"nativeBuildSystems": native_systems,
|
||||
}
|
||||
|
||||
|
||||
def render_plan(value: dict[str, Any], reference: str) -> None:
|
||||
print(f"flake: {reference}")
|
||||
print("paths:", ", ".join(value["paths"]) or "(none)")
|
||||
print("units:", ", ".join(value["affectedUnits"]) or "(none)")
|
||||
for kind in ("nixos", "darwin"):
|
||||
print(f"{kind} hosts:", ", ".join(value["affectedHosts"][kind]) or "(none)")
|
||||
print(f"{kind} synthetic:", ", ".join(value["syntheticUnits"][kind]) or "(none)")
|
||||
print("full evaluation:", value["requiresFullEvaluation"])
|
||||
print("full native build:", value["requiresFullNativeBuild"])
|
||||
|
||||
|
||||
def validate_skill(path: Path) -> None:
|
||||
text = path.read_text()
|
||||
if not text.startswith("---\n"):
|
||||
raise ValidationError(f"missing Skill frontmatter: {path}")
|
||||
try:
|
||||
frontmatter = text.split("---\n", 2)[1]
|
||||
name = next(line.split(":", 1)[1].strip() for line in frontmatter.splitlines() if line.startswith("name:"))
|
||||
except (IndexError, StopIteration) as error:
|
||||
raise ValidationError(f"invalid Skill frontmatter: {path}") from error
|
||||
if name != path.parent.name:
|
||||
raise ValidationError(f"Skill name {name!r} does not match directory {path.parent.name!r}")
|
||||
|
||||
|
||||
def static_checks(root: Path, paths: Sequence[str], untracked: set[str]) -> None:
|
||||
for relative in paths:
|
||||
path = root / relative
|
||||
if not path.is_file():
|
||||
continue
|
||||
if relative.endswith(".nix"):
|
||||
run(["nix-instantiate", "--parse", str(path)], cwd=root, capture=True)
|
||||
elif relative.endswith(".json"):
|
||||
json.loads(path.read_text())
|
||||
elif relative.endswith(".toml"):
|
||||
tomllib.loads(path.read_text())
|
||||
elif relative.endswith(".py"):
|
||||
compile(path.read_text(), relative, "exec")
|
||||
if path.name == "SKILL.md":
|
||||
validate_skill(path)
|
||||
if relative in untracked:
|
||||
for number, line in enumerate(path.read_text(errors="replace").splitlines(), 1):
|
||||
if line.rstrip() != line:
|
||||
raise ValidationError(f"trailing whitespace: {relative}:{number}")
|
||||
|
||||
diff_paths = [path for path in paths if path not in untracked]
|
||||
if diff_paths:
|
||||
run(["git", "diff", "--check", "HEAD", "--", *diff_paths], cwd=root)
|
||||
|
||||
|
||||
def run_fast(root: Path, reference: str, paths: Sequence[str], untracked: set[str], *, all_files: bool) -> None:
|
||||
static_checks(root, paths, untracked)
|
||||
command = ["nix", "develop", f"{reference}#validation", "--command", "pre-commit", "run"]
|
||||
command += ["--all-files"] if all_files else (["--files", *paths] if paths else [])
|
||||
if paths or all_files:
|
||||
run(command, cwd=root)
|
||||
|
||||
|
||||
def selection_module_expr(unit_id: str) -> str:
|
||||
quoted = json.dumps(unit_id)
|
||||
return f"(flake.lib.registry.mkSelectionModule [ {quoted} ])"
|
||||
|
||||
|
||||
def synthetic_expr(reference: str, host_name: str, host: dict[str, Any], unit_id: str, *, drv_path: bool) -> str:
|
||||
selection = selection_module_expr(unit_id)
|
||||
modules = [selection]
|
||||
if host.get("homeManager", True):
|
||||
user = json.dumps(host["user"])
|
||||
modules.append(f"{{ home-manager.users.{user}.imports = [ {selection} ]; }}")
|
||||
base = f"flake.{('darwinConfigurations' if host['kind'] == 'darwin' else 'nixosConfigurations')}.{json.dumps(host_name)}"
|
||||
output = "extended.system" if host["kind"] == "darwin" else "extended.config.system.build.toplevel"
|
||||
if drv_path:
|
||||
output += ".drvPath"
|
||||
return f'''let
|
||||
flake = builtins.getFlake {json.dumps(reference)};
|
||||
base = {base};
|
||||
extended = base.extendModules {{ modules = [ {' '.join(modules)} ]; }};
|
||||
in {output}'''
|
||||
|
||||
|
||||
def representative_host(metadata: dict[str, Any], kind: str, current_system: str | None = None) -> tuple[str, dict[str, Any]] | None:
|
||||
candidates = [(name, host) for name, host in metadata["hosts"].items() if host["kind"] == kind]
|
||||
if current_system:
|
||||
candidates = [item for item in candidates if item[1]["system"] == current_system]
|
||||
return sorted(candidates)[0] if candidates else None
|
||||
|
||||
|
||||
def host_drv_attr(reference: str, host: dict[str, Any]) -> str:
|
||||
return f"{reference}#{host['buildAttr']}.drvPath"
|
||||
|
||||
|
||||
def run_eval(
|
||||
root: Path,
|
||||
reference: str,
|
||||
metadata: dict[str, Any],
|
||||
value: dict[str, Any],
|
||||
*,
|
||||
all_systems: bool,
|
||||
) -> None:
|
||||
if all_systems or value["requiresFullEvaluation"]:
|
||||
run(["nix", "flake", "check", reference, "--no-build", "--all-systems", "--keep-going", "--show-trace"], cwd=root)
|
||||
return
|
||||
for kind in ("nixos", "darwin"):
|
||||
for name in value["affectedHosts"][kind]:
|
||||
run(["nix", "eval", "--raw", "--show-trace", host_drv_attr(reference, metadata["hosts"][name])], cwd=root)
|
||||
representative = representative_host(metadata, kind)
|
||||
if representative:
|
||||
name, host = representative
|
||||
for unit_id in value["syntheticUnits"][kind]:
|
||||
run(["nix", "eval", "--raw", "--impure", "--show-trace", "--expr", synthetic_expr(reference, name, host, unit_id, drv_path=True)], cwd=root)
|
||||
|
||||
|
||||
def current_system(root: Path) -> str:
|
||||
return run(["nix", "eval", "--raw", "--impure", "--expr", "builtins.currentSystem"], cwd=root, capture=True).stdout.strip()
|
||||
|
||||
|
||||
def run_full_native(root: Path, reference: str, system: str) -> None:
|
||||
run(
|
||||
["nix", "run", f"{reference}#nix-fast-build", "--", "--flake", f"{reference}#checks.{system}", "--skip-cached", "--no-nom", "--no-link"],
|
||||
cwd=root,
|
||||
)
|
||||
|
||||
|
||||
def run_build(root: Path, reference: str, metadata: dict[str, Any], value: dict[str, Any]) -> None:
|
||||
system = current_system(root)
|
||||
if value["requiresFullNativeBuild"]:
|
||||
run_full_native(root, reference, system)
|
||||
return
|
||||
|
||||
installables: list[str] = []
|
||||
for kind in ("nixos", "darwin"):
|
||||
for name in value["affectedHosts"][kind]:
|
||||
host = metadata["hosts"][name]
|
||||
if host["system"] == system:
|
||||
installables.append(f"{reference}#{host['buildAttr']}")
|
||||
else:
|
||||
print(f"skip incompatible build: {name} ({host['system']})", file=sys.stderr)
|
||||
for check in ("registry", "validation-tool"):
|
||||
installables.append(f"{reference}#checks.{system}.{check}")
|
||||
if installables:
|
||||
run(["nix", "build", "--no-link", "--keep-going", "--print-build-logs", *sorted(set(installables))], cwd=root)
|
||||
|
||||
for kind in ("nixos", "darwin"):
|
||||
representative = representative_host(metadata, kind, system)
|
||||
if representative:
|
||||
name, host = representative
|
||||
for unit_id in value["syntheticUnits"][kind]:
|
||||
run(["nix", "build", "--no-link", "--impure", "--expr", synthetic_expr(reference, name, host, unit_id, drv_path=False)], cwd=root)
|
||||
|
||||
|
||||
def self_test() -> None:
|
||||
metadata = {
|
||||
"schemaVersion": 1,
|
||||
"units": {
|
||||
"applications.foo": {"directory": "modules/applications/foo", "includes": [], "fragments": ["home", "homeNixos"]},
|
||||
"applications.dormant": {"directory": "modules/applications/dormant", "includes": [], "fragments": ["home"]},
|
||||
"profiles.workload.dev": {"directory": "modules/profiles/workload/dev", "includes": ["applications.foo"], "fragments": ["meta"]},
|
||||
},
|
||||
"hosts": {
|
||||
"linux": {"kind": "nixos", "system": "x86_64-linux", "user": "test", "homeManager": True, "selectedUnits": ["profiles.workload.dev"], "buildAttr": "nixosConfigurations.linux.config.system.build.toplevel"},
|
||||
"mac": {"kind": "darwin", "system": "aarch64-darwin", "user": "test", "homeManager": True, "selectedUnits": ["profiles.workload.dev"], "buildAttr": "darwinConfigurations.mac.system"},
|
||||
},
|
||||
}
|
||||
nixos = plan(metadata, ["modules/applications/foo/home/nixos.nix"])
|
||||
assert nixos["affectedHosts"] == {"nixos": ["linux"], "darwin": []}
|
||||
assert nixos["affectedUnits"] == ["applications.foo", "profiles.workload.dev"]
|
||||
common = plan(metadata, ["modules/applications/foo/home.nix"])
|
||||
assert common["affectedHosts"] == {"nixos": ["linux"], "darwin": ["mac"]}
|
||||
dormant = plan(metadata, ["modules/applications/dormant/home.nix"])
|
||||
assert dormant["syntheticUnits"] == {"nixos": ["applications.dormant"], "darwin": ["applications.dormant"]}
|
||||
global_value = plan(metadata, ["flake.nix"])
|
||||
assert global_value["requiresFullEvaluation"] and global_value["requiresFullNativeBuild"]
|
||||
docs = plan(metadata, ["modules/profiles/README.md"])
|
||||
assert not docs["requiresNixValidation"]
|
||||
print("dotfiles-check self-test passed")
|
||||
|
||||
|
||||
def parser() -> argparse.ArgumentParser:
|
||||
result = argparse.ArgumentParser(description=__doc__)
|
||||
result.add_argument("command", choices=("plan", "fast", "eval", "build", "all", "full", "self-test"))
|
||||
result.add_argument("--paths", nargs="+", help="Explicit task-owned repository paths")
|
||||
result.add_argument("--base", help="Compare BASE...HEAD")
|
||||
result.add_argument("--all-files", action="store_true", help="Check every tracked file")
|
||||
result.add_argument("--all-hosts", action="store_true", help="Validate every registered host")
|
||||
result.add_argument("--all-systems", action="store_true", help="Evaluate every flake system")
|
||||
result.add_argument("--json", action="store_true", help="Emit the plan as JSON")
|
||||
return result
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parser().parse_args()
|
||||
if args.command == "self-test":
|
||||
self_test()
|
||||
return 0
|
||||
selectors = sum(bool(value) for value in (args.paths, args.base, args.all_files))
|
||||
if selectors > 1:
|
||||
raise ValidationError("use only one of --paths, --base, or --all-files")
|
||||
if args.command == "full":
|
||||
if selectors or args.all_hosts or args.all_systems:
|
||||
raise ValidationError("full is exhaustive and accepts no scope flags")
|
||||
args.all_files = args.all_hosts = args.all_systems = True
|
||||
|
||||
root = repo_root()
|
||||
paths, untracked = collect_paths(root, args)
|
||||
with flake_reference(root, paths) as reference:
|
||||
metadata = load_metadata(root, reference)
|
||||
value = plan(metadata, paths, all_hosts=args.all_hosts)
|
||||
if args.command == "plan":
|
||||
print(json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True)) if args.json else render_plan(value, reference)
|
||||
return 0
|
||||
if args.command in {"fast", "all", "full"}:
|
||||
run_fast(root, reference, paths, untracked, all_files=args.all_files)
|
||||
if args.command in {"eval", "all", "full"} and value["requiresNixValidation"]:
|
||||
run_eval(root, reference, metadata, value, all_systems=args.all_systems or args.command in {"all", "full"})
|
||||
if args.command in {"build", "all"} and value["requiresNixValidation"]:
|
||||
run_build(root, reference, metadata, value)
|
||||
if args.command == "full":
|
||||
run_full_native(root, reference, current_system(root))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except ValidationError as error:
|
||||
print(f"error: {error}", file=sys.stderr)
|
||||
raise SystemExit(2) from error
|
||||
except subprocess.CalledProcessError as error:
|
||||
if error.stdout:
|
||||
print(error.stdout, file=sys.stderr, end="")
|
||||
if error.stderr:
|
||||
print(error.stderr, file=sys.stderr, end="")
|
||||
raise SystemExit(error.returncode) from error
|
||||
Reference in New Issue
Block a user