From fa594433395dc05ae8026fb6f94faa289deba7e2 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 27 Jul 2026 10:03:02 +0200 Subject: [PATCH] analyze: run condor compute jobs via .venv/bin/giant, not uv run uv isn't installed on the HTCondor worker docker image, so `uv run` fails there. giant is already an installed console script in the repo's uv-synced .venv, so exec it directly instead. write_submit now fails fast with a clear message if .venv/bin/giant is missing. --- giant/analysis/condor.py | 10 +++++++++- tests/test_condor.py | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/giant/analysis/condor.py b/giant/analysis/condor.py index d349117..91c0023 100644 --- a/giant/analysis/condor.py +++ b/giant/analysis/condor.py @@ -316,7 +316,7 @@ class SubmitConfig: _WRAPPER = """#!/bin/bash set -euo pipefail cd {repo_dir} -exec uv run giant analyze compute-one --id "$1" --chunk "$2" --run-dir {run_dir} +exec {repo_dir}/.venv/bin/giant analyze compute-one --id "$1" --chunk "$2" --run-dir {run_dir} """ @@ -375,6 +375,14 @@ def write_submit(cfg: SubmitConfig, ids: list[str] | None = None) -> Path: Returns the submit description path (``/analyze.sub``). Does not submit — call ``condor_submit`` on the returned file. """ + venv_giant = cfg.repo_dir / ".venv" / "bin" / "giant" + if not venv_giant.exists(): + raise FileNotFoundError( + f"{venv_giant} not found — condor jobs run it directly (no `uv` on " + f"the worker image), so run `uv sync --extra cpu` in {cfg.repo_dir} " + "before submitting." + ) + ids = ids or catalog_ids() run_dir = cfg.run_dir (run_dir / "logs").mkdir(parents=True, exist_ok=True) diff --git a/tests/test_condor.py b/tests/test_condor.py index 1dc5ad2..89838a8 100644 --- a/tests/test_condor.py +++ b/tests/test_condor.py @@ -53,6 +53,14 @@ def _write_inputs(tmp_path: Path) -> Path: return yaml_path +def _fake_venv(repo_dir: Path) -> None: + """Stand in for a `uv sync`'d venv: write_submit checks `.venv/bin/giant` exists.""" + giant = repo_dir / ".venv" / "bin" / "giant" + giant.parent.mkdir(parents=True, exist_ok=True) + giant.write_text("#!/bin/bash\n") + giant.chmod(0o755) + + def _prep( rollout_yaml: Path, run_dir: str | Path | None = None, chunks: int = 1 ) -> Path: @@ -166,6 +174,7 @@ def test_compute_reduced_rejects_out_of_range_chunk(tmp_path: Path): def test_write_submit_description(tmp_path: Path): run_dir = _prep(_write_inputs(tmp_path)) + _fake_venv(tmp_path) cfg = SubmitConfig(run_dir=run_dir, accounting_group="cms", repo_dir=tmp_path) txt = write_submit(cfg).read_text() assert "universe = docker" in txt @@ -185,8 +194,16 @@ def test_write_submit_description(tmp_path: Path): assert "--chunk" in body and "--run-dir" in body +def test_write_submit_requires_synced_venv(tmp_path: Path): + run_dir = _prep(_write_inputs(tmp_path)) + cfg = SubmitConfig(run_dir=run_dir, accounting_group="cms", repo_dir=tmp_path) + with pytest.raises(FileNotFoundError, match="uv sync"): + write_submit(cfg) + + def test_write_submit_remote_flag(tmp_path: Path): run_dir = _prep(_write_inputs(tmp_path)) + _fake_venv(tmp_path) cfg = SubmitConfig( run_dir=run_dir, accounting_group="cms", repo_dir=tmp_path, remote=True ) @@ -198,6 +215,7 @@ def test_write_submit_remote_flag(tmp_path: Path): def test_write_submit_chunks_respect_chunkable(tmp_path: Path): assert get_spec("router_gating").chunkable is False run_dir = _prep(_write_inputs(tmp_path), chunks=4) + _fake_venv(tmp_path) cfg = SubmitConfig( run_dir=run_dir, accounting_group="cms", repo_dir=tmp_path, n_chunks=4 ) @@ -227,6 +245,7 @@ def test_write_submit_walltime_grows_with_chunk_rows(tmp_path: Path): run_dir = _prep(_write_inputs(tmp_path), chunks=2) meta = RunMeta.load(run_dir / "run_meta.json") + _fake_venv(tmp_path) cfg = SubmitConfig( run_dir=run_dir, accounting_group="cms", repo_dir=tmp_path, n_chunks=2 )