Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn for builds in non-build and workspace root pyproject.toml #11394

Merged
merged 3 commits into from
Feb 17, 2025
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/uv-build-frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ uv-python = { workspace = true }
uv-static = { workspace = true }
uv-types = { workspace = true }
uv-virtualenv = { workspace = true }
uv-warnings = { workspace = true }

anstream = { workspace = true }
fs-err = { workspace = true }
Expand Down
41 changes: 39 additions & 2 deletions crates/uv-build-frontend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use uv_pypi_types::{Requirement, VerbatimParsedUrl};
use uv_python::{Interpreter, PythonEnvironment};
use uv_static::EnvVars;
use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, SourceBuildTrait};
use uv_warnings::warn_user_once;

pub use crate::error::{Error, MissingHeaderCause};

Expand All @@ -56,6 +57,10 @@ struct PyProjectToml {
build_system: Option<BuildSystem>,
/// Project metadata
project: Option<Project>,
/// Tool configuration
///
/// Only read for diagnostics.
tool: Option<serde_json::Value>,
}

/// The `[project]` section of a pyproject.toml as specified in PEP 621.
Expand Down Expand Up @@ -514,8 +519,40 @@ impl SourceBuild {
requirements,
}
} else {
// If a `pyproject.toml` is present, but `[build-system]` is missing, proceed with
// a PEP 517 build using the default backend, to match `pip` and `build`.
// If a `pyproject.toml` and a `setup.py` are 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, fail the build instead of building a
// wheel with UNKNOWN package name through setuptools.
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.as_object())
.and_then(|tool| tool.get("uv"))
.and_then(|uv| uv.as_object())
.and_then(|uv| uv.get("workspace"))
.is_some();
if looks_like_workspace_root {
warn_user_once!(
"`{}` appears to be a workspace root without a Python project, \
consider using `uv sync` or installing workspace members \
individually, or add a `[build-system]` table to `pyproject.toml`.",
source_tree.to_path_buf().simplified_display(),
);
} else {
warn_user_once!(
"`{}` does not appear to be a Python project, as neither \
`pyproject.toml` with a `[build-system]` table, \
nor `setup.py`, nor `setup.cfg` are present in the directory",
source_tree.to_path_buf().simplified_display(),
);
};
}
default_backend.clone()
};
Ok((backend, pyproject_toml.project))
Expand Down
101 changes: 101 additions & 0 deletions crates/uv/tests/it/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use indoc::indoc;
use insta::assert_snapshot;
use predicates::prelude::predicate;
use std::env::current_dir;
use std::process::Command;
use zip::ZipArchive;

#[test]
Expand Down Expand Up @@ -1823,3 +1824,103 @@ fn build_with_hardlink() -> Result<()> {
"###);
Ok(())
}

/// This is bad project layout that is allowed: A project that defines PEP 621 metadata, but no
/// PEP 517 build system not a setup.py, so we fall back to setuptools implicitly.
#[test]
fn build_unconfigured_setuptools() -> Result<()> {
let context = TestContext::new("3.12");
context
.temp_dir
.child("pyproject.toml")
.write_str(indoc! {r#"
[project]
name = "greet"
version = "0.1.0"
"#})?;
context
.temp_dir
.child("src/greet/__init__.py")
.write_str("print('Greetings!')")?;

// This is not technically a `uv build` test, we use it to contrast this passing case with the
// failing cases later.
uv_snapshot!(context.filters(), context.pip_install().arg("."), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ greet==0.1.0 (from file://[TEMP_DIR]/)
"###);

uv_snapshot!(context.filters(), Command::new(context.interpreter()).arg("-c").arg("import greet"), @r###"
success: true
exit_code: 0
----- stdout -----
Greetings!

----- stderr -----
"###);
Ok(())
}

/// In a project layout with a virtual root, an easy mistake to make is running `uv pip install .`
/// in the root.
#[test]
fn build_workspace_virtual_root() -> Result<()> {
let context = TestContext::new("3.12");
context
.temp_dir
.child("pyproject.toml")
.write_str(indoc! {r#"
[tool.uv.workspace]
members = ["packages/*"]
"#})?;

uv_snapshot!(context.filters(), context.build().arg("--no-build-logs"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Building source distribution...
warning: `[TEMP_DIR]/` appears to be a workspace root without a Python project, consider using `uv sync` or installing workspace members individually, or add a `[build-system]` table to `pyproject.toml`.
Building wheel from source distribution...
Successfully built dist/cache-0.0.0.tar.gz
Successfully built dist/unknown-0.0.0-py3-none-any.whl
"###);
Ok(())
}

/// There is a `pyproject.toml`, but it does not define any build information nor is there a
/// `setup.{py,cfg}`.
#[test]
fn build_pyproject_toml_not_a_project() -> Result<()> {
let context = TestContext::new("3.12");
context
.temp_dir
.child("pyproject.toml")
.write_str(indoc! {"
# Some other content we don't know about
[tool.black]
line-length = 88
"})?;

uv_snapshot!(context.filters(), context.build().arg("--no-build-logs"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Building source distribution...
warning: `[TEMP_DIR]/` does not appear to be a Python project, as neither `pyproject.toml` with a `[build-system]` table, nor `setup.py`, nor `setup.cfg` are present in the directory
Building wheel from source distribution...
Successfully built dist/cache-0.0.0.tar.gz
Successfully built dist/unknown-0.0.0-py3-none-any.whl
"###);
Ok(())
}
4 changes: 4 additions & 0 deletions crates/uv/tests/it/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> {
pyproject_toml.write_str(indoc! {r#"
[tool.uv]
unknown = "field"

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
"#})?;

let mut filters = context.filters();
Expand Down
Loading