mirror of https://github.com/astral-sh/uv
Better warning for no direct build (#15898)
**Setup** ``` $ git clone https://github.com/wheelnext/variant_aarch64 $ cd variant_aarch64 $ git checkout 1d047e667dbce4c74878a68c653a6b41bc3d3684 ``` **Before** ``` $ uv build -v [...] DEBUG Not using uv build backend direct build of , no pyproject.toml: TOML parse error at line 5, column 1 | 5 | [project] | ^^^^^^^^^ missing field `version` [...] ``` **After** ``` $ uv build -v [...] DEBUG Not using uv build backend direct build of ``, pyproject.toml does not match: The value for `build_system.build-backend` should be `"uv_build"`, not `"flit_core.buildapi"` [...] ``` The empty string gets fixed in https://github.com/astral-sh/uv/pull/15897 --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
parent
d805d4a370
commit
eb5ec95396
|
|
@ -64,6 +64,12 @@ pub enum ValidationError {
|
||||||
|
|
||||||
/// Check if the build backend is matching the currently running uv version.
|
/// Check if the build backend is matching the currently running uv version.
|
||||||
pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
|
pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
struct PyProjectToml {
|
||||||
|
build_system: BuildSystem,
|
||||||
|
}
|
||||||
|
|
||||||
let pyproject_toml: PyProjectToml =
|
let pyproject_toml: PyProjectToml =
|
||||||
match fs_err::read_to_string(source_tree.join("pyproject.toml"))
|
match fs_err::read_to_string(source_tree.join("pyproject.toml"))
|
||||||
.map_err(|err| err.to_string())
|
.map_err(|err| err.to_string())
|
||||||
|
|
@ -73,12 +79,14 @@ pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
|
||||||
Ok(pyproject_toml) => pyproject_toml,
|
Ok(pyproject_toml) => pyproject_toml,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
debug!(
|
debug!(
|
||||||
"Not using uv build backend direct build of {name}, no pyproject.toml: {err}"
|
"Not using uv build backend direct build for source tree `{name}`, \
|
||||||
|
failed to parse pyproject.toml: {err}"
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match pyproject_toml
|
match pyproject_toml
|
||||||
|
.build_system
|
||||||
.check_build_system(uv_version::version())
|
.check_build_system(uv_version::version())
|
||||||
.as_slice()
|
.as_slice()
|
||||||
{
|
{
|
||||||
|
|
@ -87,10 +95,10 @@ pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
|
||||||
// Any warning -> no match
|
// Any warning -> no match
|
||||||
[first, others @ ..] => {
|
[first, others @ ..] => {
|
||||||
debug!(
|
debug!(
|
||||||
"Not using uv build backend direct build of {name}, pyproject.toml does not match: {first}"
|
"Not using uv build backend direct build of `{name}`, pyproject.toml does not match: {first}"
|
||||||
);
|
);
|
||||||
for other in others {
|
for other in others {
|
||||||
trace!("Further uv build backend direct build of {name} mismatch: {other}");
|
trace!("Further uv build backend direct build of `{name}` mismatch: {other}");
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -161,83 +169,9 @@ impl PyProjectToml {
|
||||||
self.tool.as_ref()?.uv.as_ref()?.build_backend.as_ref()
|
self.tool.as_ref()?.uv.as_ref()?.build_backend.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns user-facing warnings if the `[build-system]` table looks suspicious.
|
/// See [`BuildSystem::check_build_system`].
|
||||||
///
|
|
||||||
/// Example of a valid table:
|
|
||||||
///
|
|
||||||
/// ```toml
|
|
||||||
/// [build-system]
|
|
||||||
/// requires = ["uv_build>=0.4.15,<0.5.0"]
|
|
||||||
/// build-backend = "uv_build"
|
|
||||||
/// ```
|
|
||||||
pub fn check_build_system(&self, uv_version: &str) -> Vec<String> {
|
pub fn check_build_system(&self, uv_version: &str) -> Vec<String> {
|
||||||
let mut warnings = Vec::new();
|
self.build_system.check_build_system(uv_version)
|
||||||
if self.build_system.build_backend.as_deref() != Some("uv_build") {
|
|
||||||
warnings.push(format!(
|
|
||||||
r#"The value for `build_system.build-backend` should be `"uv_build"`, not `"{}"`"#,
|
|
||||||
self.build_system.build_backend.clone().unwrap_or_default()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let uv_version =
|
|
||||||
Version::from_str(uv_version).expect("uv's own version is not PEP 440 compliant");
|
|
||||||
let next_minor = uv_version.release().get(1).copied().unwrap_or_default() + 1;
|
|
||||||
let next_breaking = Version::new([0, next_minor]);
|
|
||||||
|
|
||||||
let expected = || {
|
|
||||||
format!(
|
|
||||||
"Expected a single uv requirement in `build-system.requires`, found `{}`",
|
|
||||||
toml::to_string(&self.build_system.requires).unwrap_or_default()
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let [uv_requirement] = &self.build_system.requires.as_slice() else {
|
|
||||||
warnings.push(expected());
|
|
||||||
return warnings;
|
|
||||||
};
|
|
||||||
if uv_requirement.name.as_str() != "uv-build" {
|
|
||||||
warnings.push(expected());
|
|
||||||
return warnings;
|
|
||||||
}
|
|
||||||
let bounded = match &uv_requirement.version_or_url {
|
|
||||||
None => false,
|
|
||||||
Some(VersionOrUrl::Url(_)) => {
|
|
||||||
// We can't validate the url
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Some(VersionOrUrl::VersionSpecifier(specifier)) => {
|
|
||||||
// We don't check how wide the range is (that's up to the user), we just
|
|
||||||
// check that the current version is compliant, to avoid accidentally using a
|
|
||||||
// too new or too old uv, and we check that an upper bound exists. The latter
|
|
||||||
// is very important to allow making breaking changes in uv without breaking
|
|
||||||
// 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.
|
|
||||||
warnings.push(format!(
|
|
||||||
r#"`build_system.requires = ["{uv_requirement}"]` does not contain the
|
|
||||||
current uv version {uv_version}"#,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ranges::from(specifier.clone())
|
|
||||||
.bounding_range()
|
|
||||||
.map(|bounding_range| bounding_range.1 != Bound::Unbounded)
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !bounded {
|
|
||||||
warnings.push(format!(
|
|
||||||
"`build_system.requires = [\"{}\"]` is missing an \
|
|
||||||
upper bound on the `uv_build` version such as `<{next_breaking}`. \
|
|
||||||
Without bounding the `uv_build` version, the source distribution will break \
|
|
||||||
when a future, breaking version of `uv_build` is released.",
|
|
||||||
// Use an underscore consistently, to avoid confusing users between a package name with dash and a
|
|
||||||
// module name with underscore
|
|
||||||
uv_requirement.verbatim()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
warnings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate and convert a `pyproject.toml` to core metadata.
|
/// Validate and convert a `pyproject.toml` to core metadata.
|
||||||
|
|
@ -782,18 +716,6 @@ pub(crate) enum Contact {
|
||||||
Email { email: String },
|
Email { email: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `[build-system]` section of a pyproject.toml as specified in PEP 517.
|
|
||||||
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
struct BuildSystem {
|
|
||||||
/// PEP 508 dependencies required to execute the build system.
|
|
||||||
requires: Vec<SerdeVerbatim<Requirement<VerbatimParsedUrl>>>,
|
|
||||||
/// A string naming a Python object that will be used to perform the build.
|
|
||||||
build_backend: Option<String>,
|
|
||||||
/// <https://peps.python.org/pep-0517/#in-tree-build-backends>
|
|
||||||
backend_path: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The `tool` section as specified in PEP 517.
|
/// The `tool` section as specified in PEP 517.
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
|
@ -810,6 +732,100 @@ pub(crate) struct ToolUv {
|
||||||
build_backend: Option<BuildBackendSettings>,
|
build_backend: Option<BuildBackendSettings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The `[build-system]` section of a pyproject.toml as specified in PEP 517.
|
||||||
|
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
struct BuildSystem {
|
||||||
|
/// PEP 508 dependencies required to execute the build system.
|
||||||
|
requires: Vec<SerdeVerbatim<Requirement<VerbatimParsedUrl>>>,
|
||||||
|
/// A string naming a Python object that will be used to perform the build.
|
||||||
|
build_backend: Option<String>,
|
||||||
|
/// <https://peps.python.org/pep-0517/#in-tree-build-backends>
|
||||||
|
backend_path: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuildSystem {
|
||||||
|
/// Check if the `[build-system]` table matches the uv build backend expectations and return
|
||||||
|
/// a list of warnings if it looks suspicious.
|
||||||
|
///
|
||||||
|
/// Example of a valid table:
|
||||||
|
///
|
||||||
|
/// ```toml
|
||||||
|
/// [build-system]
|
||||||
|
/// requires = ["uv_build>=0.4.15,<0.5.0"]
|
||||||
|
/// build-backend = "uv_build"
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn check_build_system(&self, uv_version: &str) -> Vec<String> {
|
||||||
|
let mut warnings = Vec::new();
|
||||||
|
if self.build_backend.as_deref() != Some("uv_build") {
|
||||||
|
warnings.push(format!(
|
||||||
|
r#"The value for `build_system.build-backend` should be `"uv_build"`, not `"{}"`"#,
|
||||||
|
self.build_backend.clone().unwrap_or_default()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let uv_version =
|
||||||
|
Version::from_str(uv_version).expect("uv's own version is not PEP 440 compliant");
|
||||||
|
let next_minor = uv_version.release().get(1).copied().unwrap_or_default() + 1;
|
||||||
|
let next_breaking = Version::new([0, next_minor]);
|
||||||
|
|
||||||
|
let expected = || {
|
||||||
|
format!(
|
||||||
|
"Expected a single uv requirement in `build-system.requires`, found `{}`",
|
||||||
|
toml::to_string(&self.requires).unwrap_or_default()
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let [uv_requirement] = &self.requires.as_slice() else {
|
||||||
|
warnings.push(expected());
|
||||||
|
return warnings;
|
||||||
|
};
|
||||||
|
if uv_requirement.name.as_str() != "uv-build" {
|
||||||
|
warnings.push(expected());
|
||||||
|
return warnings;
|
||||||
|
}
|
||||||
|
let bounded = match &uv_requirement.version_or_url {
|
||||||
|
None => false,
|
||||||
|
Some(VersionOrUrl::Url(_)) => {
|
||||||
|
// We can't validate the url
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Some(VersionOrUrl::VersionSpecifier(specifier)) => {
|
||||||
|
// We don't check how wide the range is (that's up to the user), we just
|
||||||
|
// check that the current version is compliant, to avoid accidentally using a
|
||||||
|
// too new or too old uv, and we check that an upper bound exists. The latter
|
||||||
|
// is very important to allow making breaking changes in uv without breaking
|
||||||
|
// 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.
|
||||||
|
warnings.push(format!(
|
||||||
|
r#"`build_system.requires = ["{uv_requirement}"]` does not contain the
|
||||||
|
current uv version {uv_version}"#,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ranges::from(specifier.clone())
|
||||||
|
.bounding_range()
|
||||||
|
.map(|bounding_range| bounding_range.1 != Bound::Unbounded)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !bounded {
|
||||||
|
warnings.push(format!(
|
||||||
|
"`build_system.requires = [\"{}\"]` is missing an \
|
||||||
|
upper bound on the `uv_build` version such as `<{next_breaking}`. \
|
||||||
|
Without bounding the `uv_build` version, the source distribution will break \
|
||||||
|
when a future, breaking version of `uv_build` is released.",
|
||||||
|
// Use an underscore consistently, to avoid confusing users between a package name with dash and a
|
||||||
|
// module name with underscore
|
||||||
|
uv_requirement.verbatim()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue