Refactor `extract_pep517_backend`

Warn when using `uv_build` settings without `uv_build`

To help with cases such as https://github.com/astral-sh/uv/issues/15655.

A question is when to show this warning. I've used sources as a proxy as URL dependencies are likely those controlled by the user, and they include workspace and `git clone`d path dependencies.

Fixes #15740

Update crates/uv/tests/it/build_backend.rs

Co-authored-by: Zanie Blue <contact@zanie.dev>

Review

De-deduplicate message
This commit is contained in:
konstin 2025-09-09 14:18:47 +02:00
parent 3bb7f67c71
commit ad12ba6907
2 changed files with 177 additions and 99 deletions

View File

@ -109,6 +109,8 @@ struct Tool {
#[serde(rename_all = "kebab-case")]
struct ToolUv {
workspace: Option<de::IgnoredAny>,
/// To warn users about ignored build backend settings.
build_backend: Option<de::IgnoredAny>,
}
impl BackendPath {
@ -308,6 +310,7 @@ impl SourceBuild {
&source_tree,
install_path,
fallback_package_name,
fallback_package_version,
locations,
source_strategy,
workspace_cache,
@ -560,17 +563,62 @@ impl SourceBuild {
source_tree: &Path,
install_path: &Path,
package_name: Option<&PackageName>,
package_version: Option<&Version>,
locations: &IndexLocations,
source_strategy: SourceStrategy,
workspace_cache: &WorkspaceCache,
credentials_cache: &CredentialsCache,
) -> Result<(Pep517Backend, Option<Project>), Box<Error>> {
match fs::read_to_string(source_tree.join("pyproject.toml")) {
let pyproject_toml = match fs::read_to_string(source_tree.join("pyproject.toml")) {
Ok(toml) => {
let pyproject_toml = toml_edit::Document::from_str(&toml)
.map_err(Error::InvalidPyprojectTomlSyntax)?;
let pyproject_toml = PyProjectToml::deserialize(pyproject_toml.into_deserializer())
.map_err(Error::InvalidPyprojectTomlSchema)?;
PyProjectToml::deserialize(pyproject_toml.into_deserializer())
.map_err(Error::InvalidPyprojectTomlSchema)?
}
Err(err) if err.kind() == io::ErrorKind::NotFound => {
// We require either a `pyproject.toml` or a `setup.py` file at the top level.
if !source_tree.join("setup.py").is_file() {
return Err(Box::new(Error::InvalidSourceDist(
source_tree.to_path_buf(),
)));
}
// If no `pyproject.toml` is present, by default, proceed with a PEP 517 build using
// the default backend, to match `build`. `pip` uses `setup.py` directly in this
// case, but plans to make PEP 517 builds the default in the future.
// See: https://github.com/pypa/pip/issues/9175.
return Ok((DEFAULT_BACKEND.clone(), None));
}
Err(err) => return Err(Box::new(err.into())),
};
if source_strategy == SourceStrategy::Enabled
&& pyproject_toml
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.map(|uv| uv.build_backend.is_some())
.unwrap_or(false)
&& pyproject_toml
.build_system
.as_ref()
.and_then(|build_backend| build_backend.build_backend.as_deref())
!= Some("uv_build")
&& let Some(package_name) =
package_name.or(pyproject_toml.project.as_ref().map(|project| &project.name))
&& let Some(package_version) = package_version.or(pyproject_toml
.project
.as_ref()
.and_then(|project| project.version.as_ref()))
{
// Show name/version where available to avoid showing a (duplicate) warning with
// a temporary path.
warn_user_once!(
"`pyproject.toml` of {package_name}=={package_version} defines settings for \
`uv_build` in `tool.uv.build-backend`, but does not use `uv_build`",
);
}
let backend = if let Some(build_system) = pyproject_toml.build_system {
// If necessary, lower the requirements.
@ -592,8 +640,7 @@ impl SourceBuild {
locations,
source_strategy,
workspace_cache,
credentials_cache,
)
credentials_cache,)
.await
.map_err(Error::Lowering)?;
build_requires.requires_dist
@ -666,23 +713,6 @@ impl SourceBuild {
};
Ok((backend, pyproject_toml.project))
}
Err(err) if err.kind() == io::ErrorKind::NotFound => {
// We require either a `pyproject.toml` or a `setup.py` file at the top level.
if !source_tree.join("setup.py").is_file() {
return Err(Box::new(Error::InvalidSourceDist(
source_tree.to_path_buf(),
)));
}
// If no `pyproject.toml` is present, by default, proceed with a PEP 517 build using
// the default backend, to match `build`. `pip` uses `setup.py` directly in this
// case, but plans to make PEP 517 builds the default in the future.
// See: https://github.com/pypa/pip/issues/9175.
Ok((DEFAULT_BACKEND.clone(), None))
}
Err(err) => Err(Box::new(err.into())),
}
}
/// Try calling `prepare_metadata_for_build_wheel` to get the metadata without executing the
/// actual build.

View File

@ -1222,3 +1222,51 @@ fn invalid_pyproject_toml() -> Result<()> {
Ok(())
}
/// Warn for cases where `tool.uv.build-backend` is used without the corresponding build backend
/// entry.
#[test]
fn tool_uv_build_backend_without_build_backend() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {r#"
[project]
name = "project"
version = "0.1.0"
[tool.uv]
package = true
[tool.uv.build-backend.data]
data = "assets"
"#})?;
uv_snapshot!(context.filters(), context.build().arg("--no-build-logs"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Building source distribution...
warning: `pyproject.toml` of project==0.1.0 defines settings for `uv_build` in `tool.uv.build-backend`, but does not use `uv_build`
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
");
uv_snapshot!(context.filters(), context.pip_install().arg("."), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
warning: `pyproject.toml` of project==0.1.0 defines settings for `uv_build` in `tool.uv.build-backend`, but does not use `uv_build`
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ project==0.1.0 (from file://[TEMP_DIR]/)
");
Ok(())
}