mirror of https://github.com/astral-sh/uv
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:
parent
3bb7f67c71
commit
ad12ba6907
|
|
@ -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,111 +563,18 @@ 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)?;
|
||||
|
||||
let backend = if let Some(build_system) = pyproject_toml.build_system {
|
||||
// If necessary, lower the requirements.
|
||||
let requirements = match source_strategy {
|
||||
SourceStrategy::Enabled => {
|
||||
if let Some(name) = pyproject_toml
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|project| &project.name)
|
||||
.or(package_name)
|
||||
{
|
||||
let build_requires = uv_pypi_types::BuildRequires {
|
||||
name: Some(name.clone()),
|
||||
requires_dist: build_system.requires,
|
||||
};
|
||||
let build_requires = BuildRequires::from_project_maybe_workspace(
|
||||
build_requires,
|
||||
install_path,
|
||||
locations,
|
||||
source_strategy,
|
||||
workspace_cache,
|
||||
credentials_cache,
|
||||
)
|
||||
.await
|
||||
.map_err(Error::Lowering)?;
|
||||
build_requires.requires_dist
|
||||
} else {
|
||||
build_system
|
||||
.requires
|
||||
.into_iter()
|
||||
.map(Requirement::from)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
SourceStrategy::Disabled => build_system
|
||||
.requires
|
||||
.into_iter()
|
||||
.map(Requirement::from)
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Pep517Backend {
|
||||
// If `build-backend` is missing, inject the legacy setuptools backend, but
|
||||
// retain the `requires`, to match `pip` and `build`. Note that while PEP 517
|
||||
// says that in this case we "should revert to the legacy behaviour of running
|
||||
// `setup.py` (either directly, or by implicitly invoking the
|
||||
// `setuptools.build_meta:__legacy__` backend)", we found that in practice, only
|
||||
// the legacy setuptools backend is allowed. See also:
|
||||
// https://github.com/pypa/build/blob/de5b44b0c28c598524832dff685a98d5a5148c44/src/build/__init__.py#L114-L118
|
||||
backend: build_system
|
||||
.build_backend
|
||||
.unwrap_or_else(|| "setuptools.build_meta:__legacy__".to_string()),
|
||||
backend_path: build_system.backend_path,
|
||||
requirements,
|
||||
}
|
||||
} else {
|
||||
// If a `pyproject.toml` is present, but `[build-system]` is missing, proceed
|
||||
// with a PEP 517 build using the default backend (`setuptools`), to match `pip`
|
||||
// and `build`.
|
||||
//
|
||||
// If there is no build system defined and there is no metadata source for
|
||||
// `setuptools`, warn. The build will succeed, but the metadata will be
|
||||
// incomplete (for example, the package name will be `UNKNOWN`).
|
||||
if pyproject_toml.project.is_none()
|
||||
&& !source_tree.join("setup.py").is_file()
|
||||
&& !source_tree.join("setup.cfg").is_file()
|
||||
{
|
||||
// Give a specific hint for `uv pip install .` in a workspace root.
|
||||
let looks_like_workspace_root = pyproject_toml
|
||||
.tool
|
||||
.as_ref()
|
||||
.and_then(|tool| tool.uv.as_ref())
|
||||
.and_then(|tool| tool.workspace.as_ref())
|
||||
.is_some();
|
||||
if looks_like_workspace_root {
|
||||
warn_user_once!(
|
||||
"`{}` appears to be a workspace root without a Python project; \
|
||||
consider using `uv sync` to install the workspace, or add a \
|
||||
`[build-system]` table to `pyproject.toml`",
|
||||
source_tree.simplified_display().cyan(),
|
||||
);
|
||||
} else {
|
||||
warn_user_once!(
|
||||
"`{}` does not appear to be a Python project, as the `pyproject.toml` \
|
||||
does not include a `[build-system]` table, and neither `setup.py` \
|
||||
nor `setup.cfg` are present in the directory",
|
||||
source_tree.simplified_display().cyan(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DEFAULT_BACKEND.clone()
|
||||
};
|
||||
Ok((backend, pyproject_toml.project))
|
||||
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.
|
||||
|
|
@ -678,10 +588,130 @@ impl SourceBuild {
|
|||
// 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))
|
||||
return Ok((DEFAULT_BACKEND.clone(), None));
|
||||
}
|
||||
Err(err) => Err(Box::new(err.into())),
|
||||
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.
|
||||
let requirements = match source_strategy {
|
||||
SourceStrategy::Enabled => {
|
||||
if let Some(name) = pyproject_toml
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|project| &project.name)
|
||||
.or(package_name)
|
||||
{
|
||||
let build_requires = uv_pypi_types::BuildRequires {
|
||||
name: Some(name.clone()),
|
||||
requires_dist: build_system.requires,
|
||||
};
|
||||
let build_requires = BuildRequires::from_project_maybe_workspace(
|
||||
build_requires,
|
||||
install_path,
|
||||
locations,
|
||||
source_strategy,
|
||||
workspace_cache,
|
||||
credentials_cache,)
|
||||
.await
|
||||
.map_err(Error::Lowering)?;
|
||||
build_requires.requires_dist
|
||||
} else {
|
||||
build_system
|
||||
.requires
|
||||
.into_iter()
|
||||
.map(Requirement::from)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
SourceStrategy::Disabled => build_system
|
||||
.requires
|
||||
.into_iter()
|
||||
.map(Requirement::from)
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Pep517Backend {
|
||||
// If `build-backend` is missing, inject the legacy setuptools backend, but
|
||||
// retain the `requires`, to match `pip` and `build`. Note that while PEP 517
|
||||
// says that in this case we "should revert to the legacy behaviour of running
|
||||
// `setup.py` (either directly, or by implicitly invoking the
|
||||
// `setuptools.build_meta:__legacy__` backend)", we found that in practice, only
|
||||
// the legacy setuptools backend is allowed. See also:
|
||||
// https://github.com/pypa/build/blob/de5b44b0c28c598524832dff685a98d5a5148c44/src/build/__init__.py#L114-L118
|
||||
backend: build_system
|
||||
.build_backend
|
||||
.unwrap_or_else(|| "setuptools.build_meta:__legacy__".to_string()),
|
||||
backend_path: build_system.backend_path,
|
||||
requirements,
|
||||
}
|
||||
} else {
|
||||
// If a `pyproject.toml` is present, but `[build-system]` is missing, proceed
|
||||
// with a PEP 517 build using the default backend (`setuptools`), to match `pip`
|
||||
// and `build`.
|
||||
//
|
||||
// If there is no build system defined and there is no metadata source for
|
||||
// `setuptools`, warn. The build will succeed, but the metadata will be
|
||||
// incomplete (for example, the package name will be `UNKNOWN`).
|
||||
if pyproject_toml.project.is_none()
|
||||
&& !source_tree.join("setup.py").is_file()
|
||||
&& !source_tree.join("setup.cfg").is_file()
|
||||
{
|
||||
// Give a specific hint for `uv pip install .` in a workspace root.
|
||||
let looks_like_workspace_root = pyproject_toml
|
||||
.tool
|
||||
.as_ref()
|
||||
.and_then(|tool| tool.uv.as_ref())
|
||||
.and_then(|tool| tool.workspace.as_ref())
|
||||
.is_some();
|
||||
if looks_like_workspace_root {
|
||||
warn_user_once!(
|
||||
"`{}` appears to be a workspace root without a Python project; \
|
||||
consider using `uv sync` to install the workspace, or add a \
|
||||
`[build-system]` table to `pyproject.toml`",
|
||||
source_tree.simplified_display().cyan(),
|
||||
);
|
||||
} else {
|
||||
warn_user_once!(
|
||||
"`{}` does not appear to be a Python project, as the `pyproject.toml` \
|
||||
does not include a `[build-system]` table, and neither `setup.py` \
|
||||
nor `setup.cfg` are present in the directory",
|
||||
source_tree.simplified_display().cyan(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DEFAULT_BACKEND.clone()
|
||||
};
|
||||
Ok((backend, pyproject_toml.project))
|
||||
}
|
||||
|
||||
/// Try calling `prepare_metadata_for_build_wheel` to get the metadata without executing the
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue