Skip to content

Commit 14e57ca

Browse files
committed
Better warning for no direct build
**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 #15897
1 parent 422863f commit 14e57ca

File tree

2 files changed

+109
-92
lines changed

2 files changed

+109
-92
lines changed

crates/uv-build-backend/src/metadata.rs

Lines changed: 107 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ pub enum ValidationError {
6464

6565
/// Check if the build backend is matching the currently running uv version.
6666
pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
67+
#[derive(Deserialize)]
68+
#[serde(rename_all = "kebab-case")]
69+
struct PyProjectToml {
70+
build_system: BuildSystem,
71+
}
72+
6773
let pyproject_toml: PyProjectToml =
6874
match fs_err::read_to_string(source_tree.join("pyproject.toml"))
6975
.map_err(|err| err.to_string())
@@ -73,12 +79,14 @@ pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
7379
Ok(pyproject_toml) => pyproject_toml,
7480
Err(err) => {
7581
debug!(
76-
"Not using uv build backend direct build of {name}, no pyproject.toml: {err}"
82+
"Not using uv build backend direct build of `{name}`, \
83+
failed to parse pyproject.toml: {err}"
7784
);
7885
return false;
7986
}
8087
};
8188
match pyproject_toml
89+
.build_system
8290
.check_build_system(uv_version::version())
8391
.as_slice()
8492
{
@@ -87,10 +95,10 @@ pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
8795
// Any warning -> no match
8896
[first, others @ ..] => {
8997
debug!(
90-
"Not using uv build backend direct build of {name}, pyproject.toml does not match: {first}"
98+
"Not using uv build backend direct build of `{name}`, pyproject.toml does not match: {first}"
9199
);
92100
for other in others {
93-
trace!("Further uv build backend direct build of {name} mismatch: {other}");
101+
trace!("Further uv build backend direct build of `{name}` mismatch: {other}");
94102
}
95103
false
96104
}
@@ -161,83 +169,9 @@ impl PyProjectToml {
161169
self.tool.as_ref()?.uv.as_ref()?.build_backend.as_ref()
162170
}
163171

164-
/// Returns user-facing warnings if the `[build-system]` table looks suspicious.
165-
///
166-
/// Example of a valid table:
167-
///
168-
/// ```toml
169-
/// [build-system]
170-
/// requires = ["uv_build>=0.4.15,<0.5.0"]
171-
/// build-backend = "uv_build"
172-
/// ```
172+
/// See [`BuildSystem::check_build_system`].
173173
pub fn check_build_system(&self, uv_version: &str) -> Vec<String> {
174-
let mut warnings = Vec::new();
175-
if self.build_system.build_backend.as_deref() != Some("uv_build") {
176-
warnings.push(format!(
177-
r#"The value for `build_system.build-backend` should be `"uv_build"`, not `"{}"`"#,
178-
self.build_system.build_backend.clone().unwrap_or_default()
179-
));
180-
}
181-
182-
let uv_version =
183-
Version::from_str(uv_version).expect("uv's own version is not PEP 440 compliant");
184-
let next_minor = uv_version.release().get(1).copied().unwrap_or_default() + 1;
185-
let next_breaking = Version::new([0, next_minor]);
186-
187-
let expected = || {
188-
format!(
189-
"Expected a single uv requirement in `build-system.requires`, found `{}`",
190-
toml::to_string(&self.build_system.requires).unwrap_or_default()
191-
)
192-
};
193-
194-
let [uv_requirement] = &self.build_system.requires.as_slice() else {
195-
warnings.push(expected());
196-
return warnings;
197-
};
198-
if uv_requirement.name.as_str() != "uv-build" {
199-
warnings.push(expected());
200-
return warnings;
201-
}
202-
let bounded = match &uv_requirement.version_or_url {
203-
None => false,
204-
Some(VersionOrUrl::Url(_)) => {
205-
// We can't validate the url
206-
true
207-
}
208-
Some(VersionOrUrl::VersionSpecifier(specifier)) => {
209-
// We don't check how wide the range is (that's up to the user), we just
210-
// check that the current version is compliant, to avoid accidentally using a
211-
// too new or too old uv, and we check that an upper bound exists. The latter
212-
// is very important to allow making breaking changes in uv without breaking
213-
// the existing immutable source distributions on pypi.
214-
if !specifier.contains(&uv_version) {
215-
// This is allowed to happen when testing prereleases, but we should still warn.
216-
warnings.push(format!(
217-
r#"`build_system.requires = ["{uv_requirement}"]` does not contain the
218-
current uv version {uv_version}"#,
219-
));
220-
}
221-
Ranges::from(specifier.clone())
222-
.bounding_range()
223-
.map(|bounding_range| bounding_range.1 != Bound::Unbounded)
224-
.unwrap_or(false)
225-
}
226-
};
227-
228-
if !bounded {
229-
warnings.push(format!(
230-
"`build_system.requires = [\"{}\"]` is missing an \
231-
upper bound on the `uv_build` version such as `<{next_breaking}`. \
232-
Without bounding the `uv_build` version, the source distribution will break \
233-
when a future, breaking version of `uv_build` is released.",
234-
// Use an underscore consistently, to avoid confusing users between a package name with dash and a
235-
// module name with underscore
236-
uv_requirement.verbatim()
237-
));
238-
}
239-
240-
warnings
174+
self.build_system.check_build_system(uv_version)
241175
}
242176

243177
/// Validate and convert a `pyproject.toml` to core metadata.
@@ -782,18 +716,6 @@ pub(crate) enum Contact {
782716
Email { email: String },
783717
}
784718

785-
/// The `[build-system]` section of a pyproject.toml as specified in PEP 517.
786-
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
787-
#[serde(rename_all = "kebab-case")]
788-
struct BuildSystem {
789-
/// PEP 508 dependencies required to execute the build system.
790-
requires: Vec<SerdeVerbatim<Requirement<VerbatimParsedUrl>>>,
791-
/// A string naming a Python object that will be used to perform the build.
792-
build_backend: Option<String>,
793-
/// <https://peps.python.org/pep-0517/#in-tree-build-backends>
794-
backend_path: Option<Vec<String>>,
795-
}
796-
797719
/// The `tool` section as specified in PEP 517.
798720
#[derive(Deserialize, Debug, Clone)]
799721
#[serde(rename_all = "kebab-case")]
@@ -810,6 +732,100 @@ pub(crate) struct ToolUv {
810732
build_backend: Option<BuildBackendSettings>,
811733
}
812734

735+
/// The `[build-system]` section of a pyproject.toml as specified in PEP 517.
736+
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
737+
#[serde(rename_all = "kebab-case")]
738+
struct BuildSystem {
739+
/// PEP 508 dependencies required to execute the build system.
740+
requires: Vec<SerdeVerbatim<Requirement<VerbatimParsedUrl>>>,
741+
/// A string naming a Python object that will be used to perform the build.
742+
build_backend: Option<String>,
743+
/// <https://peps.python.org/pep-0517/#in-tree-build-backends>
744+
backend_path: Option<Vec<String>>,
745+
}
746+
747+
impl BuildSystem {
748+
/// Check if the `[build-system]` table matches the uv build backend expectations and return
749+
/// a list of warnings if it looks suspicious.
750+
///
751+
/// Example of a valid table:
752+
///
753+
/// ```toml
754+
/// [build-system]
755+
/// requires = ["uv_build>=0.4.15,<0.5.0"]
756+
/// build-backend = "uv_build"
757+
/// ```
758+
pub(crate) fn check_build_system(&self, uv_version: &str) -> Vec<String> {
759+
let mut warnings = Vec::new();
760+
if self.build_backend.as_deref() != Some("uv_build") {
761+
warnings.push(format!(
762+
r#"The value for `build_system.build-backend` should be `"uv_build"`, not `"{}"`"#,
763+
self.build_backend.clone().unwrap_or_default()
764+
));
765+
}
766+
767+
let uv_version =
768+
Version::from_str(uv_version).expect("uv's own version is not PEP 440 compliant");
769+
let next_minor = uv_version.release().get(1).copied().unwrap_or_default() + 1;
770+
let next_breaking = Version::new([0, next_minor]);
771+
772+
let expected = || {
773+
format!(
774+
"Expected a single uv requirement in `build-system.requires`, found `{}`",
775+
toml::to_string(&self.requires).unwrap_or_default()
776+
)
777+
};
778+
779+
let [uv_requirement] = &self.requires.as_slice() else {
780+
warnings.push(expected());
781+
return warnings;
782+
};
783+
if uv_requirement.name.as_str() != "uv-build" {
784+
warnings.push(expected());
785+
return warnings;
786+
}
787+
let bounded = match &uv_requirement.version_or_url {
788+
None => false,
789+
Some(VersionOrUrl::Url(_)) => {
790+
// We can't validate the url
791+
true
792+
}
793+
Some(VersionOrUrl::VersionSpecifier(specifier)) => {
794+
// We don't check how wide the range is (that's up to the user), we just
795+
// check that the current version is compliant, to avoid accidentally using a
796+
// too new or too old uv, and we check that an upper bound exists. The latter
797+
// is very important to allow making breaking changes in uv without breaking
798+
// the existing immutable source distributions on pypi.
799+
if !specifier.contains(&uv_version) {
800+
// This is allowed to happen when testing prereleases, but we should still warn.
801+
warnings.push(format!(
802+
r#"`build_system.requires = ["{uv_requirement}"]` does not contain the
803+
current uv version {uv_version}"#,
804+
));
805+
}
806+
Ranges::from(specifier.clone())
807+
.bounding_range()
808+
.map(|bounding_range| bounding_range.1 != Bound::Unbounded)
809+
.unwrap_or(false)
810+
}
811+
};
812+
813+
if !bounded {
814+
warnings.push(format!(
815+
"`build_system.requires = [\"{}\"]` is missing an \
816+
upper bound on the `uv_build` version such as `<{next_breaking}`. \
817+
Without bounding the `uv_build` version, the source distribution will break \
818+
when a future, breaking version of `uv_build` is released.",
819+
// Use an underscore consistently, to avoid confusing users between a package name with dash and a
820+
// module name with underscore
821+
uv_requirement.verbatim()
822+
));
823+
}
824+
825+
warnings
826+
}
827+
}
828+
813829
#[cfg(test)]
814830
mod tests {
815831
use super::*;

crates/uv/src/commands/build_frontend.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,8 @@ async fn build_package(
629629
}
630630

631631
BuildAction::List
632-
} else if !force_pep517 && check_direct_build(source.path(), source.path().user_display()) {
632+
} else if !force_pep517 && check_direct_build(source.path(), source.path().user_display())
633+
{
633634
BuildAction::DirectBuild
634635
} else {
635636
BuildAction::Pep517

0 commit comments

Comments
 (0)