fix(pm): honor no_config isolation for bridged index settings in uv runs
stage_runtime (the pinned pm/ self-repair builder) passes no_config=True, but that only appended uv's --no-config, which removes config FILES. The pip-mirror bridge (pm.index_config) reaches uv as env vars instead, so a user's pip.conf index-url still rode into the runtime sync via runtime_environment() -> _base_environment(env=None). Re-resolving the official-index pm/uv.lock against a mirror changes the resolution, and uv sync --locked refuses — breaking the top-level update / doctor / pm-repair recovery paths on any machine with a pip mirror (#124418). When no_config is set, strip forwarded index/transport knobs from the child env: a fully-pinned graph never needs a mirror to resolve, and the default path keeps the bridge so mirrored/air-gapped application installs still work.
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user