From a47787ccd04e507b41f42c055484076c08d5571f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 17:52:52 +0000 Subject: [PATCH 1/2] Follow uv's pylock refusal instead of pinning past it uv 0.13.2+ refuses the `environments = []` pylock Pipenv writes, so CI pinned uv at 0.13.1 to keep the Pipenv pylock leg green. A maintainer asked for that test to adapt to upstream rather than pin around the change (#1639). The #1368 check now installs the wired pylock with pip 26.2.2 (pip reads `-r pylock.toml` since 26.1 and accepts Pipenv's empty environments list) and must get the PATCHED six. It then installs it with uv: when uv accepts it, uv must get the PATCHED six too; when uv refuses it, uv must refuse Pipenv's untouched pylock with the same error, so the wiring never introduces a refusal. That frees the setup-uv pins to move to the current release, 0.13.5. They stay exact, as scripts/check-tool-pins.py requires. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01PwwQCHPjEtXHWXW4SqeBh7 --- .github/workflows/ci.yml | 9 +- .github/workflows/pipenv-compatibility.yml | 9 +- .../tests/e2e_vex_build/pipenv.rs | 120 ++++++++++++++---- 3 files changed, 103 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd976e031..f158116d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1503,11 +1503,10 @@ jobs: if: matrix.pipenv != '' || matrix.pip != '' uses: astral-sh/setup-uv@eb1897b8dc4b5d5bfe39a428a8f2304605e0983c # v7.0.0 with: - # Pin uv: it is the PEP 751 oracle the pylock tests install with, - # and uv 0.13.2 started refusing Pipenv's `environments = []` - # pylock ("target environment is not supported by the lockfile"). - # Bump deliberately, after checking the Pipenv pylock legs pass. - version: "0.13.1" + # Exact pin (scripts/check-tool-pins.py). uv 0.13.2+ refuses the + # `environments = []` pylock Pipenv writes; the pylock test checks + # that refusal matches Pipenv's own pylock, so keep this current. + version: "0.13.5" - name: Install the Pythons the Pipenv / pip legs run on if: matrix.pipenv != '' || matrix.pip != '' diff --git a/.github/workflows/pipenv-compatibility.yml b/.github/workflows/pipenv-compatibility.yml index a786e3276..26361f436 100644 --- a/.github/workflows/pipenv-compatibility.yml +++ b/.github/workflows/pipenv-compatibility.yml @@ -102,11 +102,10 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 with: - # Pin uv: it is the PEP 751 oracle the pylock tests install with, - # and uv 0.13.2 started refusing Pipenv's `environments = []` - # pylock ("target environment is not supported by the lockfile"). - # Bump deliberately, after checking the Pipenv pylock legs pass. - version: "0.13.1" + # Exact pin (scripts/check-tool-pins.py). uv 0.13.2+ refuses the + # `environments = []` pylock Pipenv writes; the pylock test checks + # that refusal matches Pipenv's own pylock, so keep this current. + version: "0.13.5" - name: Install the Pythons the Pipenv releases run on run: uv python install 3.8 3.12 - name: Build the CLI diff --git a/crates/socket-patch-cli/tests/e2e_vex_build/pipenv.rs b/crates/socket-patch-cli/tests/e2e_vex_build/pipenv.rs index b04ac2cd3..08039ff51 100644 --- a/crates/socket-patch-cli/tests/e2e_vex_build/pipenv.rs +++ b/crates/socket-patch-cli/tests/e2e_vex_build/pipenv.rs @@ -31,6 +31,7 @@ use crate::vex_e2e_common; use crate::vex_pipenv_pip_real; use std::path::{Path, PathBuf}; +use std::process::Output; use serde_json::Value; use vex_pipenv_pip_real::*; @@ -450,32 +451,15 @@ fn pylock_flow(pipenv: &Pipenv, root: &Path) { assert!(ok, "{what}: the fresh install must carry the PATCHED six"); // #1368: a PEP 751 installer reading the wired pylock gets the PATCHED - // six too, from the committed wheel. + // six too, from the committed wheel. pip must install it. uv must too, + // unless it refuses Pipenv's own untouched pylock the same way: uv + // 0.13.2+ reads the `environments = []` Pipenv writes as "no supported + // environment", which is upstream's call, not the wiring's. let pep751 = root.join("pylock").join("pep751"); - copy_tree(&proj, &pep751, &[".venv"]); - make_venv( - &pipenv.uv, - python.to_str().unwrap(), - &pep751.join(".venv"), - &[], - ) - .unwrap_or_else(|e| panic!("{what}: pylock venv: {e}")); - let py = venv_bin(&pep751.join(".venv"), "python"); - let out = tool( - &pipenv.uv, - &pep751, - &[ - "pip", - "install", - "--python", - py.to_str().unwrap(), - "-r", - "pylock.toml", - ], - &[], - ); - assert_ok(&out, &format!("{what}: `uv pip install -r pylock.toml`")); - let (_, bytes, is_patched) = six_oracle(&py, &pep751) + let python = python.to_str().unwrap(); + let (out, py) = pylock_install(&pipenv.uv, python, &proj, &pep751.join("pip"), true); + assert_ok(&out, &format!("{what}: `pip install -r pylock.toml`")); + let (_, bytes, is_patched) = six_oracle(&py, &pep751.join("pip")) .unwrap_or_else(|| panic!("{what}: six not importable from the pylock install")); let ok = is_patched && bytes == patched; record( @@ -486,6 +470,92 @@ fn pylock_flow(pipenv: &Pipenv, root: &Path) { if ok { "pass" } else { "FAIL" }, ); assert!(ok, "{what}: the pylock install must carry the PATCHED six"); + + let (out, py) = pylock_install(&pipenv.uv, python, &proj, &pep751.join("uv"), false); + let verdict = if out.status.success() { + let (_, bytes, is_patched) = six_oracle(&py, &pep751.join("uv")) + .unwrap_or_else(|| panic!("{what}: six not importable from the uv pylock install")); + assert!( + is_patched && bytes == patched, + "{what}: the uv pylock install must carry the PATCHED six" + ); + "pass" + } else { + let pristine_dir = pep751.join("uv-pristine"); + copy_tree(&proj, &pristine_dir, &[".venv"]); + std::fs::write(pristine_dir.join("pylock.toml"), &pristine_pylock).unwrap(); + let (pristine_out, _) = pylock_install( + &pipenv.uv, + python, + &pristine_dir, + &pep751.join("uv-p"), + false, + ); + let wired_err = error_lines(&out); + assert!( + !pristine_out.status.success() + && !wired_err.is_empty() + && wired_err == error_lines(&pristine_out), + "{what}: uv refuses the wired pylock but not Pipenv's own: {}\n--- pristine\n{}", + out_text(&out), + out_text(&pristine_out) + ); + "upstream-refused" + }; + record( + "pipenv", + v, + "use_pylock/vendored", + "pylock-uv-install", + verdict, + ); +} + +/// Installs `src`'s `pylock.toml` into a fresh venv in a copy of `src` at +/// `dir`, with pip (`-r pylock.toml` needs pip 26.1+; pinned exactly like +/// every other oracle here) or uv. Returns the installer's output and the +/// venv's python. +fn pylock_install( + uv: &Path, + python: &str, + src: &Path, + dir: &Path, + with_pip: bool, +) -> (Output, PathBuf) { + copy_tree(src, dir, &[".venv"]); + let venv = dir.join(".venv"); + let pins: &[&str] = if with_pip { &["pip==26.2.2"] } else { &[] }; + make_venv(uv, python, &venv, pins).unwrap_or_else(|e| panic!("pylock venv: {e}")); + let py = venv_bin(&venv, "python"); + let out = if with_pip { + tool( + &py, + dir, + &["-m", "pip", "install", "-r", "pylock.toml"], + &[], + ) + } else { + let args = [ + "pip", + "install", + "--python", + py.to_str().unwrap(), + "-r", + "pylock.toml", + ]; + tool(uv, dir, &args, &[]) + }; + (out, py) +} + +/// The `error:` lines of an installer's stderr: what it refused, without +/// the paths it printed around it. +fn error_lines(out: &Output) -> Vec { + String::from_utf8_lossy(&out.stderr) + .lines() + .filter(|l| l.starts_with("error:")) + .map(str::to_owned) + .collect() } #[test] From 19b868d343ed161bd03144bd73a98918e41d335d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 18:34:23 +0000 Subject: [PATCH 2/2] Port #1710's Windows yarnrc test expectation The full-scope dispatch of this PR fails test (windows-latest, 1) on global_folder_values_read_like_yarn, the same failure main's nightly has. #1710 fixes it (a leading backslash is root-relative on Windows) and is waiting on review; port its test-only change so this PR's full-scope run can go green. It no-ops once #1710 lands. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01PwwQCHPjEtXHWXW4SqeBh7 --- .../src/patch/redirect/yarnrc_tests.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/socket-patch-core/src/patch/redirect/yarnrc_tests.rs b/crates/socket-patch-core/src/patch/redirect/yarnrc_tests.rs index b39393c0c..6e975243d 100644 --- a/crates/socket-patch-core/src/patch/redirect/yarnrc_tests.rs +++ b/crates/socket-patch-core/src/patch/redirect/yarnrc_tests.rs @@ -586,16 +586,19 @@ fn global_folder_values_read_like_yarn() { Vec::::new() ); // An escaped expression stays verbatim (`ini` reads `\\` as `\` - // first). + // first). On Windows the leading `\` makes it root-relative: it keeps + // the cwd's drive and replaces the rest, as `path.resolve` does + // (`/w` has no drive, so nothing of it is kept). + let escaped = if cfg!(windows) { + "\\${HOME}/x" + } else { + "/w/\\${HOME}/x" + }; for text in [ "global-folder=\\${HOME}/x\n", "global-folder=\\\\${HOME}/x\n", ] { - assert_eq!( - npmrc(Host::new(), text), - paths(&["/w/\\${HOME}/x"]), - "{text:?}" - ); + assert_eq!(npmrc(Host::new(), text), paths(&[escaped]), "{text:?}"); } // `ini` ends an unquoted value at a comment and unquotes a quoted one. assert_eq!(