Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
20 changes: 15 additions & 5 deletions crates/uv-build-backend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mod metadata;

use crate::metadata::{BuildBackendSettings, PyProjectToml, ValidationError, DEFAULT_EXCLUDES};
pub use metadata::PyProjectToml;

use crate::metadata::{BuildBackendSettings, ValidationError, DEFAULT_EXCLUDES};
use flate2::write::GzEncoder;
use flate2::Compression;
use fs_err::File;
Expand Down Expand Up @@ -304,7 +306,9 @@ pub fn build_wheel(
) -> Result<WheelFilename, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
pyproject_toml.check_build_system(uv_version);
for warning in pyproject_toml.check_build_system(uv_version) {
warn_user_once!("{warning}");
}
let settings = pyproject_toml
.settings()
.cloned()
Expand Down Expand Up @@ -465,7 +469,9 @@ pub fn build_editable(
) -> Result<WheelFilename, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
pyproject_toml.check_build_system(uv_version);
for warning in pyproject_toml.check_build_system(uv_version) {
warn_user_once!("{warning}");
}
let settings = pyproject_toml
.settings()
.cloned()
Expand Down Expand Up @@ -601,7 +607,9 @@ pub fn build_source_dist(
) -> Result<SourceDistFilename, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
pyproject_toml.check_build_system(uv_version);
for warning in pyproject_toml.check_build_system(uv_version) {
warn_user_once!("{warning}");
}
let settings = pyproject_toml
.settings()
.cloned()
Expand Down Expand Up @@ -851,7 +859,9 @@ pub fn metadata(
) -> Result<String, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
pyproject_toml.check_build_system(uv_version);
for warning in pyproject_toml.check_build_system(uv_version) {
warn_user_once!("{warning}");
}

let filename = WheelFilename {
name: pyproject_toml.name().clone(),
Expand Down
69 changes: 39 additions & 30 deletions crates/uv-build-backend/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use uv_normalize::{ExtraName, PackageName};
use uv_pep440::{Version, VersionSpecifiers};
use uv_pep508::{Requirement, VersionOrUrl};
use uv_pypi_types::{Metadata23, VerbatimParsedUrl};
use uv_warnings::warn_user_once;
use version_ranges::Ranges;
use walkdir::WalkDir;

Expand Down Expand Up @@ -58,7 +57,7 @@ pub enum ValidationError {
expecting = "The project table needs to follow \
https://packaging.python.org/en/latest/guides/writing-pyproject-toml"
)]
pub(crate) struct PyProjectToml {
pub struct PyProjectToml {
/// Project metadata
project: Project,
/// uv-specific configuration
Expand Down Expand Up @@ -92,7 +91,7 @@ impl PyProjectToml {
self.tool.as_ref()?.uv.as_ref()?.build_backend.as_ref()
}

/// Warn if the `[build-system]` table looks suspicious.
/// Returns user-facing warnings if the `[build-system]` table looks suspicious.
///
/// Example of a valid table:
///
Expand All @@ -101,16 +100,13 @@ impl PyProjectToml {
/// requires = ["uv>=0.4.15,<5"]
/// build-backend = "uv"
/// ```
///
/// Returns whether all checks passed.
pub(crate) fn check_build_system(&self, uv_version: &str) -> bool {
let mut passed = true;
pub fn check_build_system(&self, uv_version: &str) -> Vec<String> {
let mut warnings = Vec::new();
if self.build_system.build_backend.as_deref() != Some("uv") {
warn_user_once!(
warnings.push(format!(
r#"The value for `build_system.build-backend` should be `"uv"`, not `"{}"`"#,
self.build_system.build_backend.clone().unwrap_or_default()
);
passed = false;
));
}

let uv_version =
Expand All @@ -126,12 +122,12 @@ impl PyProjectToml {
};

let [uv_requirement] = &self.build_system.requires.as_slice() else {
warn_user_once!("{}", expected());
return false;
warnings.push(expected());
return warnings;
};
if uv_requirement.name.as_str() != "uv" {
warn_user_once!("{}", expected());
return false;
warnings.push(expected());
return warnings;
}
let bounded = match &uv_requirement.version_or_url {
None => false,
Expand All @@ -147,11 +143,10 @@ impl PyProjectToml {
// the existing immutable source distributions on pypi.
if !specifier.contains(&uv_version) {
// This is allowed to happen when testing prereleases, but we should still warn.
warn_user_once!(
warnings.push(format!(
r#"`build_system.requires = ["{uv_requirement}"]` does not contain the
current uv version {uv_version}"#,
);
passed = false;
));
}
Ranges::from(specifier.clone())
.bounding_range()
Expand All @@ -161,16 +156,15 @@ impl PyProjectToml {
};

if !bounded {
warn_user_once!(
r#"`build_system.requires = ["{uv_requirement}"]` is missing an
upper bound on the uv version such as `<{next_breaking}`.
Without bounding the uv version, the source distribution will break
when a future, breaking version of uv is released."#,
);
passed = false;
warnings.push(format!(
"`build_system.requires = [\"{uv_requirement}\"]` is missing an \
upper bound on the uv version such as `<{next_breaking}`. \
Without bounding the uv version, the source distribution will break \
when a future, breaking version of uv is released.",
));
}

passed
warnings
}

/// Validate and convert a `pyproject.toml` to core metadata.
Expand Down Expand Up @@ -995,7 +989,10 @@ mod tests {
fn build_system_valid() {
let contents = extend_project("");
let pyproject_toml = PyProjectToml::parse(&contents).unwrap();
assert!(pyproject_toml.check_build_system("1.0.0+test"));
assert_snapshot!(
pyproject_toml.check_build_system("1.0.0+test").join("\n"),
@""
);
}

#[test]
Expand All @@ -1010,7 +1007,10 @@ mod tests {
build-backend = "uv"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system("1.0.0+test"));
assert_snapshot!(
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
@r###"`build_system.requires = ["uv"]` is missing an upper bound on the uv version such as `<0.5`. Without bounding the uv version, the source distribution will break when a future, breaking version of uv is released."###
);
}

#[test]
Expand All @@ -1025,7 +1025,10 @@ mod tests {
build-backend = "uv"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system("1.0.0+test"));
assert_snapshot!(
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
@"Expected a single uv requirement in `build-system.requires`, found ``"
);
}

#[test]
Expand All @@ -1040,7 +1043,10 @@ mod tests {
build-backend = "uv"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system("1.0.0+test"));
assert_snapshot!(
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
@"Expected a single uv requirement in `build-system.requires`, found ``"
);
}

#[test]
Expand All @@ -1055,7 +1061,10 @@ mod tests {
build-backend = "setuptools"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
assert!(!pyproject_toml.check_build_system("1.0.0+test"));
assert_snapshot!(
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
@r###"The value for `build_system.build-backend` should be `"uv"`, not `"setuptools"`"###
);
}

#[test]
Expand Down
8 changes: 8 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2178,6 +2178,14 @@ pub struct BuildArgs {
#[arg(long, overrides_with("build_logs"), hide = true)]
pub no_build_logs: bool,

/// Always build through PEP 517, don't use the fast path for the uv build backend.
///
/// By default, uv won't create a PEP 517 build environment for packages using the uv build
/// backend, but use a fast path that calls into the build backend directly. This option forces
/// always using PEP 517.
#[arg(long)]
pub no_fast_path: bool,
Copy link
Member

Choose a reason for hiding this comment

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

I think we should give this a more descriptive name, like --use-external-uv or something. Since we might add other "fast paths" in the future. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea. I thought about something like --pep517, maybe --force-pep517?

Copy link
Member

Choose a reason for hiding this comment

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

--force-pep517 seems reasonable...

Copy link
Member

Choose a reason for hiding this comment

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

Ref #9600


/// Constrain build dependencies using the given requirements files when building
/// distributions.
///
Expand Down
Loading