diff --git a/pm/environment.py b/pm/environment.py index 69663053a7..a2f7d03c3c 100644 --- a/pm/environment.py +++ b/pm/environment.py @@ -272,6 +272,16 @@ class PythonEnvironment: # Explicit index credentials survive, but cannot redirect the project, # interpreter or cache selected by the operation. env = _base_environment(self.env) + if self.no_config: + # --no-config only removes config files; the pip-mirror bridge + # (pm.index_config) reaches uv as env vars. The pinned runtime + # builder asked to be isolated from ambient index configuration: + # re-resolving its official-index lockfile against a mirror trips + # `--locked` (#124418), and a fully-pinned graph never needs a + # mirror to resolve. + from pm.index_config import is_forwarded + + env = {key: value for key, value in env.items() if not is_forwarded(key)} env.update(UV_PYTHON=str(self.python), UV_PROJECT_ENVIRONMENT=str(self.destination), UV_CACHE_DIR=str(self.cache), UV_PYTHON_DOWNLOADS="never") with tempfile.TemporaryDirectory(prefix="pm-uv-config-") as config: diff --git a/tests/pm/test_environment_build.py b/tests/pm/test_environment_build.py index 500b1292b6..f0b027d3cf 100644 --- a/tests/pm/test_environment_build.py +++ b/tests/pm/test_environment_build.py @@ -787,3 +787,32 @@ def test_live_apply_keeps_selection_on_failed_union(locked_project, tmp_path, mo assert set(generations.iterdir()) == prior_generations assert (source / "uv.lock").read_bytes() == source_lock assert env == original_env + + +def test_no_config_drops_bridged_index_settings_from_child_env(tmp_path): + """no_config must isolate the pinned runtime builder from ALL ambient uv + configuration: --no-config covers config files, but PM's pip-mirror bridge + injects UV_INDEX_URL as an env var, and re-resolving the official-index + runtime lockfile against that mirror trips `uv sync --locked` (#124418). + The default path keeps the bridge — mirrored networks need it to resolve + their application dependencies at all.""" + import io + from pm.environment import PythonEnvironment + + ambient = dict(os.environ, UV_INDEX_URL="https://fixture-mirror.invalid/simple", + UV_HTTP_TIMEOUT="99") + child = ("import os, sys\n" + "present = sorted(k for k in ('UV_INDEX_URL', 'UV_HTTP_TIMEOUT') if k in os.environ)\n" + "sys.exit(', '.join(present) if present else None)\n") + + def run(**kwargs): + environment = PythonEnvironment( + uv=Path(sys.executable), python=Path(sys.executable), destination=tmp_path / "venv", + cache=tmp_path / "cache", env=ambient, output=io.StringIO(), **kwargs) + return environment._run(["-c", child], cwd=tmp_path, timeout=60) + + isolated = run(no_config=True) + assert isolated.returncode == 0, f"bridged settings leaked into no_config child: {isolated.stderr}" + bridged = run() + assert bridged.returncode == 1, "default path must keep the bridge" + assert "UV_INDEX_URL" in bridged.stderr and "UV_HTTP_TIMEOUT" in bridged.stderr