Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/uv-build-frontend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions crates/uv-static/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Comment thread
zanieb marked this conversation as resolved.
Outdated
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")]
Expand Down
45 changes: 30 additions & 15 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,21 +538,36 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
// 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))
})
Comment on lines +546 to +550

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @EliteTK regarding a shared uv-fs utility

})
});
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();
Expand Down
62 changes: 61 additions & 1 deletion crates/uv/tests/build/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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 <https://github.com/astral-sh/uv/issues/19878>.
#[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 <https://github.com/astral-sh/uv/issues/19771>.
#[test]
Expand Down
Loading