diff --git a/scripts/nix/test-trace-intel-ghc.py b/scripts/nix/test-trace-intel-ghc.py index e94cc96a8e..67b332ac9e 100644 --- a/scripts/nix/test-trace-intel-ghc.py +++ b/scripts/nix/test-trace-intel-ghc.py @@ -8,6 +8,8 @@ import json import os from pathlib import Path import signal +import shutil +import subprocess import sys import tempfile import time @@ -101,6 +103,36 @@ class LauncherTests(unittest.TestCase): self.assertEqual(sum("--expr" in call[0] for call in calls), 1) self.assertFalse(any("--file" in call[0] for call in calls)) + @unittest.skipUnless(shutil.which("nix") and shutil.which("git"), "requires Nix and Git") + def test_pure_preparation_uses_pinned_flake_source(self): + with tempfile.TemporaryDirectory(prefix="intel-python-source-") as folder: + root = Path(folder) + factory = root / "scripts/nix/native-packages.nix" + factory.parent.mkdir(parents=True) + (root / "flake.nix").write_text('{ outputs = { self }: {}; }\n') + # The factory ignores its input: this fixture checks source access, + # without evaluating haskell.nix or building Python or a compiler. + factory.write_text('{ haskellNix }: system: { python3 = "pinned:${system}"; }\n') + def call(argv): + return subprocess.run(argv, cwd=root, text=True, capture_output=True, timeout=30) + call(["git", "init", "-q", "-b", "master"]).check_returncode() + call(["git", "add", "flake.nix", "scripts/nix/native-packages.nix"]).check_returncode() + call(["git", "-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", + "commit", "-qm", "pinned fixture"]).check_returncode() + revision = call(["git", "rev-parse", "HEAD"]) + revision.check_returncode() + flake = "builtins.getFlake " + TRACE.nix_string("git+" + root.as_uri() + "?rev=" + revision.stdout.strip()) + factory.write_text('throw "read live workspace"\n') + original = ("let f = " + flake + "; in (import " + TRACE.nix_string(factory) + + ' { haskellNix = f.inputs.haskellNix; } "x86_64-darwin").python3') + argv = ["nix", "eval", "--offline", "--option", "pure-eval", "true", "--json", "--expr"] + rejected = call([*argv, original]) + self.assertNotEqual(rejected.returncode, 0) + self.assertIn("forbidden in pure evaluation mode", rejected.stderr) + fixed = call([*argv, TRACE.python_expression(flake)]) + self.assertEqual(fixed.returncode, 0, fixed.stderr) + self.assertEqual(json.loads(fixed.stdout), "pinned:x86_64-darwin") + def test_actual_metadata_output_is_bounded(self): with self.assertRaises(ValueError): TRACE.run_step([sys.executable, "-c", "print('x' * 65536)"], time.monotonic() + 10, 5, True) diff --git a/scripts/nix/trace-intel-ghc.py b/scripts/nix/trace-intel-ghc.py index f633b1a884..d71c541383 100644 --- a/scripts/nix/trace-intel-ghc.py +++ b/scripts/nix/trace-intel-ghc.py @@ -35,6 +35,13 @@ def nix_string(value): return '"' + str(value).replace("\\", "\\\\").replace('"', '\\"').replace("${", "\\${") + '"' +def python_expression(flake): + # Pure evaluation permits the pinned flake source, not the live checkout. + return ("let f = " + flake + + '; in (import (f.outPath + "/scripts/nix/native-packages.nix")' + + ' { haskellNix = f.inputs.haskellNix; } "x86_64-darwin").python3') + + def command_status(arguments, seconds, **streams): # A fresh session gives this command and its descendants a group owned by # this launcher. macOS need not provide GNU timeout (or a Homebrew alias). @@ -146,9 +153,7 @@ def main(): flake = "builtins.getFlake " + nix_string("git+" + repo.as_uri() + "?rev=" + revision) original = ("let f = " + flake + "; in f.hydraJobs.x86_64-darwin." + nix_string("x86_64-darwin-ios:lib:simplex-chat")) - python = ("let f = " + flake + "; in (import " - + nix_string(scripts / "native-packages.nix") - + ' { haskellNix = f.inputs.haskellNix; } "x86_64-darwin").python3') + python = python_expression(flake) # Python is the only added producer input. The observed failed # component already realized its other inputs. Obtain Python from # the local store or substitutes, and skip rather than compile it.