@@ -64,6 +64,12 @@ pub enum ValidationError {
64
64
65
65
/// Check if the build backend is matching the currently running uv version.
66
66
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
+
67
73
let pyproject_toml: PyProjectToml =
68
74
match fs_err:: read_to_string ( source_tree. join ( "pyproject.toml" ) )
69
75
. map_err ( |err| err. to_string ( ) )
@@ -73,12 +79,14 @@ pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
73
79
Ok ( pyproject_toml) => pyproject_toml,
74
80
Err ( err) => {
75
81
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}"
77
84
) ;
78
85
return false ;
79
86
}
80
87
} ;
81
88
match pyproject_toml
89
+ . build_system
82
90
. check_build_system ( uv_version:: version ( ) )
83
91
. as_slice ( )
84
92
{
@@ -87,10 +95,10 @@ pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
87
95
// Any warning -> no match
88
96
[ first, others @ ..] => {
89
97
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}"
91
99
) ;
92
100
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}" ) ;
94
102
}
95
103
false
96
104
}
@@ -161,83 +169,9 @@ impl PyProjectToml {
161
169
self . tool . as_ref ( ) ?. uv . as_ref ( ) ?. build_backend . as_ref ( )
162
170
}
163
171
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`].
173
173
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)
241
175
}
242
176
243
177
/// Validate and convert a `pyproject.toml` to core metadata.
@@ -782,18 +716,6 @@ pub(crate) enum Contact {
782
716
Email { email : String } ,
783
717
}
784
718
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
-
797
719
/// The `tool` section as specified in PEP 517.
798
720
#[ derive( Deserialize , Debug , Clone ) ]
799
721
#[ serde( rename_all = "kebab-case" ) ]
@@ -810,6 +732,100 @@ pub(crate) struct ToolUv {
810
732
build_backend : Option < BuildBackendSettings > ,
811
733
}
812
734
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
+
813
829
#[ cfg( test) ]
814
830
mod tests {
815
831
use super :: * ;
0 commit comments