diff --git a/.github/workflows/setup-dev-drive.ps1 b/.github/workflows/setup-dev-drive.ps1 index c003b4e1fb5bd..35e7808134c2f 100644 --- a/.github/workflows/setup-dev-drive.ps1 +++ b/.github/workflows/setup-dev-drive.ps1 @@ -70,7 +70,7 @@ assign letter=V Write-Output "Using Dev Drive at $Volume" } -$Tmp = "$($Drive)\uv-tmp" +$Tmp = "$($Drive)\uv\tmp" # Create the directory ahead of time in an attempt to avoid race-conditions New-Item $Tmp -ItemType Directory @@ -87,6 +87,6 @@ Write-Output ` "TEMP=$($Tmp)" ` "RUSTUP_HOME=$($Drive)/.rustup" ` "CARGO_HOME=$($Drive)/.cargo" ` - "UV_WORKSPACE=$($Drive)/uv" ` + "UV_WORKSPACE=$($Drive)/uv/repo" ` "PATH=$($Drive)/.cargo/bin;$env:PATH" ` >> $env:GITHUB_ENV diff --git a/crates/uv-test/src/lib.rs b/crates/uv-test/src/lib.rs index 42315ca0b53ea..4e2e01de938ae 100755 --- a/crates/uv-test/src/lib.rs +++ b/crates/uv-test/src/lib.rs @@ -570,6 +570,17 @@ impl TestContext { self } + /// Add filters for the system temporary directory. + #[must_use] + pub fn with_filtered_system_tmp(mut self) -> Self { + self.filters.extend( + Self::path_patterns(std::env::temp_dir()) + .into_iter() + .map(|pattern| (pattern, "[SYSTEM_TEMP_DIR]/".to_string())), + ); + self + } + /// Add a filter for (bytecode) compilation file counts #[must_use] pub fn with_filtered_compiled_file_count(mut self) -> Self { @@ -1782,14 +1793,19 @@ impl TestContext { /// Generate an escaped regex pattern for the given path. fn path_pattern(path: impl AsRef) -> String { - format!( - // Trim the trailing separator for cross-platform directories filters - r"{}\\?/?", - regex::escape(&path.as_ref().simplified_display().to_string()) - // Make separators platform agnostic because on Windows we will display - // paths with Unix-style separators sometimes - .replace(r"\\", r"(\\|\/)") - ) + let escaped = regex::escape(&path.as_ref().simplified_display().to_string()) + // Make separators platform agnostic because on Windows we will display + // paths with Unix-style separators sometimes + .replace(r"\\", r"(\\|\/)"); + + if cfg!(windows) { + // On Windows, paths are case-insensitive. Different tools may output + // different cases for the drive letter (e.g., `D:\` vs `d:\`), so we + // make the entire pattern case-insensitive. + format!(r"(?i:{escaped})\\?/?") + } else { + format!(r"{escaped}\\?/?") + } } pub fn python_path(&self) -> OsString { diff --git a/crates/uv/tests/it/python_module.rs b/crates/uv/tests/it/python_module.rs index 639084237b4a1..a102d2ecc4d6e 100644 --- a/crates/uv/tests/it/python_module.rs +++ b/crates/uv/tests/it/python_module.rs @@ -394,6 +394,110 @@ fn find_uv_bin_user_bin() { ); } +#[test] +fn find_uv_bin_pip_build_env() -> anyhow::Result<()> { + let vendor_url = uv_test::build_vendor_links_url(); + + let context = uv_test::test_context!("3.12") + .with_filtered_python_names() + .with_filtered_virtualenv_bin() + .with_filtered_exe_suffix() + .with_filtered_counts() + .with_filtered_system_tmp() + .with_filter(user_scheme_bin_filter()) + // Target installs always use "bin" on all platforms. On Windows, + // `with_filtered_virtualenv_bin` only filters "Scripts", not "bin" + .with_filter((r"[\\/]bin".to_string(), "/[BIN]".to_string())) + .with_filter((r"pip-build-env-[a-z0-9_]+", "pip-build-env-[HASH]")); + + // Build fake-uv into a wheel + let wheel_dir = context.temp_dir.child("wheels"); + wheel_dir.create_dir_all()?; + + uv_snapshot!(context.filters(), context.build() + .arg(context.workspace_root.join("test/packages/fake-uv")) + .arg("--wheel") + .arg("--out-dir") + .arg(wheel_dir.path()) + .arg("--python") + .arg("3.12"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building wheel (uv build backend)... + Successfully built wheels/uv-0.1.0-py3-none-any.whl + " + ); + + // Create a project whose build requires fake-uv + let project = context.temp_dir.child("project"); + project.child("project").child("__init__.py").touch()?; + + project.child("pyproject.toml").write_str(&formatdoc! { r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + + [build-system] + requires = ["setuptools", "uv==0.1.0"] + build-backend = "setuptools.build_meta" + "# + })?; + + // The setup.py calls find_uv_bin during the build as a side effect + project.child("setup.py").write_str(indoc! { r#" + import sys + from setuptools import setup + from uv import find_uv_bin + print("FOUND_UV=" + find_uv_bin(), file=sys.stderr) + setup() + "# + })?; + + // Use `pip install` to trigger a real pip build environment. + // The `-v` flag is required so pip surfaces subprocess stderr (where + // `setup.py` prints `FOUND_UV=...`). + let result = context + .tool_run() + .arg("pip") + .arg("install") + .arg(project.path()) + .arg("-v") + .arg("--no-index") + .arg("--find-links") + .arg(&vendor_url) + .arg("--find-links") + .arg(wheel_dir.path()) + .assert() + .success(); + + // Extract the FOUND_UV= lines from stderr (where setup.py prints them). + let stderr = String::from_utf8(result.get_output().stderr.clone()).unwrap(); + let found_uv_lines: String = stderr + .lines() + .filter(|line| line.contains("FOUND_UV=")) + .map(str::trim) + .collect::>() + .join("\n"); + + assert!( + !found_uv_lines.is_empty(), + "expected FOUND_UV= in pip output, got:\n{stderr}" + ); + + let snapshot = uv_test::apply_filters(found_uv_lines, context.filters()); + insta::assert_snapshot!(snapshot, @" + FOUND_UV=[SYSTEM_TEMP_DIR]/pip-build-env-[HASH]/overlay/[BIN]/uv + FOUND_UV=[SYSTEM_TEMP_DIR]/pip-build-env-[HASH]/overlay/[BIN]/uv + FOUND_UV=[SYSTEM_TEMP_DIR]/pip-build-env-[HASH]/overlay/[BIN]/uv + "); + + Ok(()) +} + #[test] fn find_uv_bin_error_message() { let mut context = uv_test::test_context!("3.12")