From 673e01306af06ce8b2a17da5eb4ed3c7daea643d Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 16 Jun 2026 10:58:59 -0400 Subject: [PATCH 1/2] Allow uv in PEP 517 build hooks --- crates/uv-build-frontend/src/lib.rs | 1 + crates/uv-static/src/env_vars.rs | 5 +++ crates/uv/src/lib.rs | 45 ++++++++++++++------- crates/uv/tests/build/build.rs | 62 ++++++++++++++++++++++++++++- 4 files changed, 97 insertions(+), 16 deletions(-) diff --git a/crates/uv-build-frontend/src/lib.rs b/crates/uv-build-frontend/src/lib.rs index bf6f50bf4fa..471fc065137 100644 --- a/crates/uv-build-frontend/src/lib.rs +++ b/crates/uv-build-frontend/src/lib.rs @@ -1229,6 +1229,7 @@ impl PythonRunner { .args(["-c", script]) .current_dir(source_tree.simplified()) .envs(environment_variables) + .env(EnvVars::UV_INTERNAL__BUILD_DIR, source_tree) .env(EnvVars::PATH, modified_path) .env(EnvVars::VIRTUAL_ENV, venv.root()) // NOTE: it would be nice to get colored output from build backends, diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 18bff1bda72..8390f987099 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -658,6 +658,11 @@ impl EnvVars { #[attr_added_in("0.2.0")] pub const UV_INTERNAL__PARENT_INTERPRETER: &'static str = "UV_INTERNAL__PARENT_INTERPRETER"; + /// Used to identify the source tree when invoking PEP 517 build hooks. + #[attr_hidden] + #[attr_added_in("0.11.22")] + pub const UV_INTERNAL__BUILD_DIR: &'static str = "UV_INTERNAL__BUILD_DIR"; + /// Used to force showing the derivation tree during resolver error reporting. #[attr_hidden] #[attr_added_in("0.3.0")] diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 482cf6fed4c..2fd4a4ac895 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -538,21 +538,36 @@ async fn run(cli: Cli) -> Result { // the settings that go into the cache constructor, but the check happens before the first // workspace discovery that's used beyond settings discovery. let cache_dir = std::path::absolute(cache.root())?; - if project_dir.starts_with(&cache_dir) { - bail!( - "The project directory `{}` is inside the cache directory `{}`", - project_dir.user_display(), - cache_dir.user_display() - ); - } else if let Ok(cache_dir) = fs_err::canonicalize(&cache_dir) - && let Ok(project_dir) = fs_err::canonicalize(&*project_dir) - && project_dir.starts_with(&cache_dir) - { - bail!( - "The project directory `{}` is inside the cache directory `{}`", - project_dir.user_display(), - cache_dir.user_display() - ); + // PEP 517 hooks run from uv-managed source trees, including source distributions extracted + // into the cache, and can invoke uv recursively. + let project_is_in_build_dir = + std::env::var_os(EnvVars::UV_INTERNAL__BUILD_DIR).is_some_and(|build_dir| { + std::path::absolute(build_dir).is_ok_and(|build_dir| { + project_dir.starts_with(&build_dir) + || fs_err::canonicalize(&*project_dir).is_ok_and(|project_dir| { + fs_err::canonicalize(build_dir) + .is_ok_and(|build_dir| project_dir.starts_with(build_dir)) + }) + }) + }); + if !project_is_in_build_dir { + if project_dir.starts_with(&cache_dir) { + bail!( + "The project directory `{}` is inside the cache directory `{}`", + project_dir.user_display(), + cache_dir.user_display() + ); + } + if let Ok(cache_dir) = fs_err::canonicalize(&cache_dir) + && let Ok(project_dir) = fs_err::canonicalize(&*project_dir) + && project_dir.starts_with(&cache_dir) + { + bail!( + "The project directory `{}` is inside the cache directory `{}`", + project_dir.user_display(), + cache_dir.user_display() + ); + } } let workspace_cache = WorkspaceCache::default(); diff --git a/crates/uv/tests/build/build.rs b/crates/uv/tests/build/build.rs index d0bfc99eb08..08e11446086 100644 --- a/crates/uv/tests/build/build.rs +++ b/crates/uv/tests/build/build.rs @@ -10,7 +10,7 @@ use std::env::current_dir; use std::path::Path; use url::Url; use uv_static::EnvVars; -use uv_test::{DEFAULT_PYTHON_VERSION, apply_filters, uv_snapshot}; +use uv_test::{DEFAULT_PYTHON_VERSION, apply_filters, get_bin, uv_snapshot}; use wiremock::{ Mock, MockServer, ResponseTemplate, matchers::{method, path as url_path}, @@ -148,6 +148,66 @@ fn build_basic() -> Result<()> { Ok(()) } +/// Build hooks can invoke uv while building a wheel from an extracted source distribution. +/// Regression test for . +#[test] +fn build_hook_invokes_uv() -> Result<()> { + let context = uv_test::test_context!("3.12"); + let project = context.temp_dir.child("project"); + + project.child("pyproject.toml").write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + + [tool.hatch.build.hooks.custom] + path = "hatch_build.py" + "#})?; + project.child("src/project/__init__.py").touch()?; + project.child("hatch_build.py").write_str(&formatdoc! {r#" + import subprocess + + from hatchling.builders.hooks.plugin.interface import BuildHookInterface + + + class CustomBuildHook(BuildHookInterface): + def initialize(self, version, build_data): + subprocess.run( + [ + {uv:?}, + "export", + "--quiet", + "--no-dev", + "--no-editable", + "--no-emit-project", + "--output-file", + "requirements.txt", + ], + check=True, + cwd=self.root, + ) + "#, uv = get_bin!().display() })?; + + uv_snapshot!(context.filters(), context.build().current_dir(&project), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building source distribution... + Building wheel from source distribution... + Successfully built dist/project-0.1.0.tar.gz + Successfully built dist/project-0.1.0-py3-none-any.whl + "); + + Ok(()) +} + /// A source distribution must include an in-tree build backend referenced by `backend-path`. /// Regression test for . #[test] From f8202efa1fc6b842ac60603a49287b29875542ac Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Tue, 16 Jun 2026 10:36:17 -0500 Subject: [PATCH 2/2] Apply suggestion from @zanieb --- crates/uv-static/src/env_vars.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 8390f987099..6fa9dc4e8f5 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -660,7 +660,7 @@ impl EnvVars { /// Used to identify the source tree when invoking PEP 517 build hooks. #[attr_hidden] - #[attr_added_in("0.11.22")] + #[attr_added_in("next release")] pub const UV_INTERNAL__BUILD_DIR: &'static str = "UV_INTERNAL__BUILD_DIR"; /// Used to force showing the derivation tree during resolver error reporting.