nmarshall commited on
Commit
171bd70
·
1 Parent(s): d61efed

xtask: reject implicit system python

Browse files
Files changed (3) hide show
  1. docs/RESEARCH_LEDGER.md +7 -0
  2. docs/TESTING.md +1 -1
  3. xtask/src/lib.rs +57 -4
docs/RESEARCH_LEDGER.md CHANGED
@@ -2007,3 +2007,10 @@ RE-863 hook correction: pre-commit/pre-push now require a Nix-store `PYTHON`, pr
2007
  RE-863 final review: targeted xtask dependency-registry test passes, including all five new source→Rust edges and shared transition composition. Both hooks pass shell syntax checks and reject missing PYTHON before invoking any interpreter.
2008
 
2009
  Batch RE-854–863 boundary: the single `cargo xtask test-full --since cedc6d02` run passed after commit ten through the flake shell and its declared Nix Python3.13.15: 1,688 Python tests, 157 Rust tests (assets 10, gameplay 119, xtask 28), 51 deterministic producers, storage policy, 425 documentation files and whitespace checks. No generated report drift; notes amended into task ten. Original assets unchanged. Remaining commit/push hooks use the explicit Nix-store interpreter.
 
 
 
 
 
 
 
 
2007
  RE-863 final review: targeted xtask dependency-registry test passes, including all five new source→Rust edges and shared transition composition. Both hooks pass shell syntax checks and reject missing PYTHON before invoking any interpreter.
2008
 
2009
  Batch RE-854–863 boundary: the single `cargo xtask test-full --since cedc6d02` run passed after commit ten through the flake shell and its declared Nix Python3.13.15: 1,688 Python tests, 157 Rust tests (assets 10, gameplay 119, xtask 28), 51 deterministic producers, storage policy, 425 documentation files and whitespace checks. No generated report drift; notes amended into task ten. Original assets unchanged. Remaining commit/push hooks use the explicit Nix-store interpreter.
2010
+
2011
+
2012
+ ## 2026-10-27 — Block remaining implicit system-Python runner entry points
2013
+
2014
+ Prompt investigation found the ordinary shell resolves python3 to /usr/bin/python3, while xtask still had a PATH fallback and two Rust integration tests invoked that name directly. The Git hooks were already guarded. The runner and real Python-using tests now require the explicit Nix-store interpreter, rejecting missing/system paths without launching them. Direct analysis-script shebangs still require the documented flake shell. No active Python/xcrun process was observed at inspection; that snapshot cannot attribute an already-dispatched installer prompt.
2015
+
2016
+ Validation: all30 xtask tests and the focused storage group (21 Python tests) pass using the flake-declared Nix Python. A real runner invocation with PYTHON unset exits with nix-develop guidance before starting Python. Full suite was not repeated for this focused runner correction.
docs/TESTING.md CHANGED
@@ -2,7 +2,7 @@
2
 
3
  ## Development toolchain
4
 
5
- Run analysis and validation inside `nix develop`. The pinned flake declares Python as a development dependency and sets `PYTHON` to its absolute Nix-store executable, which `cargo xtask` uses. Do not use Python from macOS Command Line Tools. For a one-shot run, use `nix develop --command cargo xtask test-group <name>`; the same environment supplies Python for direct analysis scripts and their subprocesses. The shell also supplies Rust, Cargo, Git and pkg-config on macOS and Linux.
6
 
7
  The Rust `xtask` workspace package selects and runs the existing Python research tests and Rust crate tests. It has no third-party Rust dependencies. Python remains necessary for the existing analyzers/tests and a syntax-only AST reader; selection, Git inspection, scheduling, determinism checks and command execution live in Rust.
8
 
 
2
 
3
  ## Development toolchain
4
 
5
+ Run analysis and validation inside `nix develop`. The pinned flake declares Python as a development dependency and sets `PYTHON` to its absolute Nix-store executable, which `cargo xtask` and its Python-using Rust integration tests require. Without it, they stop before launching an interpreter; there is no PATH/system fallback. Do not use Python from macOS Command Line Tools. For a one-shot run, use `nix develop --command cargo xtask test-group <name>`; the same environment supplies Python for direct analysis scripts and their subprocesses. The shell also supplies Rust, Cargo, Git and pkg-config on macOS and Linux. Directly executing an analysis script outside this shell can still follow its `env python3` shebang, so run those scripts inside the flake too.
6
 
7
  The Rust `xtask` workspace package selects and runs the existing Python research tests and Rust crate tests. It has no third-party Rust dependencies. Python remains necessary for the existing analyzers/tests and a syntax-only AST reader; selection, Git inspection, scheduling, determinism checks and command execution live in Rust.
8
 
xtask/src/lib.rs CHANGED
@@ -7,6 +7,52 @@ use std::process::Command;
7
  type Result<T> = std::result::Result<T, String>;
8
  type Set = BTreeSet<String>;
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  #[derive(Default, Debug)]
11
  pub struct Graph {
12
  nodes: Set,
@@ -505,7 +551,7 @@ fn execute(root: &Path, g: &Graph, p: &Plan, python: &str, runner: &mut impl Run
505
  pub fn run(args: Vec<String>) -> Result<()> {
506
  if args.is_empty() || args.iter().any(|a| matches!(a.as_str(), "--help" | "-h")) {
507
  println!(
508
- "cargo xtask test-changed [--since REV] [--path PATH ...] [--dry-run]\ncargo xtask test-group NAME [--dry-run]\ncargo xtask test-full [--since REV] [--dry-run]\ncargo xtask list-groups\nPYTHON overrides python3. Run from anywhere inside the checkout. Reports regenerate twice before tests."
509
  );
510
  return Ok(());
511
  }
@@ -516,7 +562,7 @@ pub fn run(args: Vec<String>) -> Result<()> {
516
  )?
517
  .trim(),
518
  );
519
- let python = std::env::var("PYTHON").unwrap_or_else(|_| "python3".into());
520
  let mode = &args[0];
521
  let mut index = 1;
522
  let mut group = None;
@@ -980,7 +1026,7 @@ mod tests {
980
  let out = capture(
981
  &t.0,
982
  &Cmd::new(
983
- "python3",
984
  ["-I".into(), scanner.to_string_lossy().into_owned()],
985
  ),
986
  )
@@ -999,7 +1045,14 @@ mod integration_tests {
999
  let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
1000
  let mut g =
1001
  Graph::registry(&fs::read_to_string(root.join("xtask/checks.tsv")).unwrap()).unwrap();
1002
- let output = capture(root, &Cmd::new("python3", ["-I", "xtask/python_graph.py"])).unwrap();
 
 
 
 
 
 
 
1003
  g.scanner(&output).unwrap();
1004
  assert!(g.warnings.is_empty(), "{:?}", g.warnings);
1005
  let health = plan(
 
7
  type Result<T> = std::result::Result<T, String>;
8
  type Set = BTreeSet<String>;
9
 
10
+ fn python_program(value: Option<&str>) -> Result<String> {
11
+ let value = value.ok_or("PYTHON is unset; run this command inside nix develop")?;
12
+ let path = Path::new(value);
13
+ if !value.starts_with("/nix/store/")
14
+ || !value.ends_with("/bin/python3")
15
+ || path.components().any(|part| {
16
+ matches!(
17
+ part,
18
+ std::path::Component::ParentDir | std::path::Component::CurDir
19
+ )
20
+ })
21
+ {
22
+ return Err(
23
+ "PYTHON must be the flake's absolute Nix-store Python executable; run nix develop"
24
+ .into(),
25
+ );
26
+ }
27
+ Ok(value.into())
28
+ }
29
+ fn python_runtime() -> Result<String> {
30
+ python_program(std::env::var("PYTHON").ok().as_deref())
31
+ }
32
+
33
+ #[cfg(test)]
34
+ mod python_tests {
35
+ use super::*;
36
+ #[test]
37
+ fn missing_system_and_relative_python_are_rejected_without_launching() {
38
+ assert!(python_program(None).unwrap_err().contains("nix develop"));
39
+ for value in [
40
+ "",
41
+ "python3",
42
+ "/usr/bin/python3",
43
+ "/Library/Developer/CommandLineTools/usr/bin/python3",
44
+ "/nix/store/../../usr/bin/python3",
45
+ ] {
46
+ assert!(python_program(Some(value)).is_err(), "{value}");
47
+ }
48
+ }
49
+ #[test]
50
+ fn explicit_nix_dependency_is_accepted() {
51
+ let value = "/nix/store/example-python3/bin/python3";
52
+ assert_eq!(python_program(Some(value)).unwrap(), value);
53
+ }
54
+ }
55
+
56
  #[derive(Default, Debug)]
57
  pub struct Graph {
58
  nodes: Set,
 
551
  pub fn run(args: Vec<String>) -> Result<()> {
552
  if args.is_empty() || args.iter().any(|a| matches!(a.as_str(), "--help" | "-h")) {
553
  println!(
554
+ "cargo xtask test-changed [--since REV] [--path PATH ...] [--dry-run]\ncargo xtask test-group NAME [--dry-run]\ncargo xtask test-full [--since REV] [--dry-run]\ncargo xtask list-groups\nPYTHON must come from nix develop; no system-Python fallback. Run from anywhere inside the checkout. Reports regenerate twice before tests."
555
  );
556
  return Ok(());
557
  }
 
562
  )?
563
  .trim(),
564
  );
565
+ let python = python_runtime()?;
566
  let mode = &args[0];
567
  let mut index = 1;
568
  let mut group = None;
 
1026
  let out = capture(
1027
  &t.0,
1028
  &Cmd::new(
1029
+ &python_runtime().expect("run cargo test inside nix develop"),
1030
  ["-I".into(), scanner.to_string_lossy().into_owned()],
1031
  ),
1032
  )
 
1045
  let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
1046
  let mut g =
1047
  Graph::registry(&fs::read_to_string(root.join("xtask/checks.tsv")).unwrap()).unwrap();
1048
+ let output = capture(
1049
+ root,
1050
+ &Cmd::new(
1051
+ &python_runtime().expect("run cargo test inside nix develop"),
1052
+ ["-I", "xtask/python_graph.py"],
1053
+ ),
1054
+ )
1055
+ .unwrap();
1056
  g.scanner(&output).unwrap();
1057
  assert!(g.warnings.is_empty(), "{:?}", g.warnings);
1058
  let health = plan(