Skip to content

Commit 5b27dec

Browse files
authored
Build backend: Add fast path (#9556)
Going through PEP 517 to build a package is slow, so when building a package with the uv build backend, we can call into the uv build backend directly. This is the basis for the `uv build --list`. This does not enable the fast path for general source dependencies. There is a possible difference in execution if the latest uv version is newer than the one currently running: The PEP 517 path would use the latest version, while the fast path uses the current version. Please review commit-by-commit ### Benchmark `built_with_uv`, using the fast path: ``` $ hyperfine "~/projects/uv/target/profiling/uv build" Time (mean ± σ): 9.2 ms ± 1.1 ms [User: 4.6 ms, System: 4.6 ms] Range (min … max): 6.4 ms … 12.7 ms 290 runs ``` `hatcling_editable`, with hatchling being optimized for fast startup times: ``` $ hyperfine "~/projects/uv/target/profiling/uv build" Time (mean ± σ): 270.5 ms ± 18.4 ms [User: 230.8 ms, System: 44.5 ms] Range (min … max): 250.7 ms … 298.4 ms 10 runs ```
1 parent 659e86e commit 5b27dec

File tree

8 files changed

+494
-220
lines changed

8 files changed

+494
-220
lines changed

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
mod metadata;
22

3-
use crate::metadata::{BuildBackendSettings, PyProjectToml, ValidationError, DEFAULT_EXCLUDES};
3+
pub use metadata::PyProjectToml;
4+
5+
use crate::metadata::{BuildBackendSettings, ValidationError, DEFAULT_EXCLUDES};
46
use flate2::write::GzEncoder;
57
use flate2::Compression;
68
use fs_err::File;
@@ -304,7 +306,9 @@ pub fn build_wheel(
304306
) -> Result<WheelFilename, Error> {
305307
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
306308
let pyproject_toml = PyProjectToml::parse(&contents)?;
307-
pyproject_toml.check_build_system(uv_version);
309+
for warning in pyproject_toml.check_build_system(uv_version) {
310+
warn_user_once!("{warning}");
311+
}
308312
let settings = pyproject_toml
309313
.settings()
310314
.cloned()
@@ -465,7 +469,9 @@ pub fn build_editable(
465469
) -> Result<WheelFilename, Error> {
466470
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
467471
let pyproject_toml = PyProjectToml::parse(&contents)?;
468-
pyproject_toml.check_build_system(uv_version);
472+
for warning in pyproject_toml.check_build_system(uv_version) {
473+
warn_user_once!("{warning}");
474+
}
469475
let settings = pyproject_toml
470476
.settings()
471477
.cloned()
@@ -601,7 +607,9 @@ pub fn build_source_dist(
601607
) -> Result<SourceDistFilename, Error> {
602608
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
603609
let pyproject_toml = PyProjectToml::parse(&contents)?;
604-
pyproject_toml.check_build_system(uv_version);
610+
for warning in pyproject_toml.check_build_system(uv_version) {
611+
warn_user_once!("{warning}");
612+
}
605613
let settings = pyproject_toml
606614
.settings()
607615
.cloned()
@@ -851,7 +859,9 @@ pub fn metadata(
851859
) -> Result<String, Error> {
852860
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
853861
let pyproject_toml = PyProjectToml::parse(&contents)?;
854-
pyproject_toml.check_build_system(uv_version);
862+
for warning in pyproject_toml.check_build_system(uv_version) {
863+
warn_user_once!("{warning}");
864+
}
855865

856866
let filename = WheelFilename {
857867
name: pyproject_toml.name().clone(),

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

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use uv_normalize::{ExtraName, PackageName};
1212
use uv_pep440::{Version, VersionSpecifiers};
1313
use uv_pep508::{Requirement, VersionOrUrl};
1414
use uv_pypi_types::{Metadata23, VerbatimParsedUrl};
15-
use uv_warnings::warn_user_once;
1615
use version_ranges::Ranges;
1716
use walkdir::WalkDir;
1817

@@ -58,7 +57,7 @@ pub enum ValidationError {
5857
expecting = "The project table needs to follow \
5958
https://packaging.python.org/en/latest/guides/writing-pyproject-toml"
6059
)]
61-
pub(crate) struct PyProjectToml {
60+
pub struct PyProjectToml {
6261
/// Project metadata
6362
project: Project,
6463
/// uv-specific configuration
@@ -92,7 +91,7 @@ impl PyProjectToml {
9291
self.tool.as_ref()?.uv.as_ref()?.build_backend.as_ref()
9392
}
9493

95-
/// Warn if the `[build-system]` table looks suspicious.
94+
/// Returns user-facing warnings if the `[build-system]` table looks suspicious.
9695
///
9796
/// Example of a valid table:
9897
///
@@ -101,16 +100,13 @@ impl PyProjectToml {
101100
/// requires = ["uv>=0.4.15,<5"]
102101
/// build-backend = "uv"
103102
/// ```
104-
///
105-
/// Returns whether all checks passed.
106-
pub(crate) fn check_build_system(&self, uv_version: &str) -> bool {
107-
let mut passed = true;
103+
pub fn check_build_system(&self, uv_version: &str) -> Vec<String> {
104+
let mut warnings = Vec::new();
108105
if self.build_system.build_backend.as_deref() != Some("uv") {
109-
warn_user_once!(
106+
warnings.push(format!(
110107
r#"The value for `build_system.build-backend` should be `"uv"`, not `"{}"`"#,
111108
self.build_system.build_backend.clone().unwrap_or_default()
112-
);
113-
passed = false;
109+
));
114110
}
115111

116112
let uv_version =
@@ -126,12 +122,12 @@ impl PyProjectToml {
126122
};
127123

128124
let [uv_requirement] = &self.build_system.requires.as_slice() else {
129-
warn_user_once!("{}", expected());
130-
return false;
125+
warnings.push(expected());
126+
return warnings;
131127
};
132128
if uv_requirement.name.as_str() != "uv" {
133-
warn_user_once!("{}", expected());
134-
return false;
129+
warnings.push(expected());
130+
return warnings;
135131
}
136132
let bounded = match &uv_requirement.version_or_url {
137133
None => false,
@@ -147,11 +143,10 @@ impl PyProjectToml {
147143
// the existing immutable source distributions on pypi.
148144
if !specifier.contains(&uv_version) {
149145
// This is allowed to happen when testing prereleases, but we should still warn.
150-
warn_user_once!(
146+
warnings.push(format!(
151147
r#"`build_system.requires = ["{uv_requirement}"]` does not contain the
152148
current uv version {uv_version}"#,
153-
);
154-
passed = false;
149+
));
155150
}
156151
Ranges::from(specifier.clone())
157152
.bounding_range()
@@ -161,16 +156,15 @@ impl PyProjectToml {
161156
};
162157

163158
if !bounded {
164-
warn_user_once!(
165-
r#"`build_system.requires = ["{uv_requirement}"]` is missing an
166-
upper bound on the uv version such as `<{next_breaking}`.
167-
Without bounding the uv version, the source distribution will break
168-
when a future, breaking version of uv is released."#,
169-
);
170-
passed = false;
159+
warnings.push(format!(
160+
"`build_system.requires = [\"{uv_requirement}\"]` is missing an \
161+
upper bound on the uv version such as `<{next_breaking}`. \
162+
Without bounding the uv version, the source distribution will break \
163+
when a future, breaking version of uv is released.",
164+
));
171165
}
172166

173-
passed
167+
warnings
174168
}
175169

176170
/// Validate and convert a `pyproject.toml` to core metadata.
@@ -995,7 +989,10 @@ mod tests {
995989
fn build_system_valid() {
996990
let contents = extend_project("");
997991
let pyproject_toml = PyProjectToml::parse(&contents).unwrap();
998-
assert!(pyproject_toml.check_build_system("1.0.0+test"));
992+
assert_snapshot!(
993+
pyproject_toml.check_build_system("1.0.0+test").join("\n"),
994+
@""
995+
);
999996
}
1000997

1001998
#[test]
@@ -1010,7 +1007,10 @@ mod tests {
10101007
build-backend = "uv"
10111008
"#};
10121009
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
1013-
assert!(!pyproject_toml.check_build_system("1.0.0+test"));
1010+
assert_snapshot!(
1011+
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
1012+
@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."###
1013+
);
10141014
}
10151015

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

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

10461052
#[test]
@@ -1055,7 +1061,10 @@ mod tests {
10551061
build-backend = "setuptools"
10561062
"#};
10571063
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
1058-
assert!(!pyproject_toml.check_build_system("1.0.0+test"));
1064+
assert_snapshot!(
1065+
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
1066+
@r###"The value for `build_system.build-backend` should be `"uv"`, not `"setuptools"`"###
1067+
);
10591068
}
10601069

10611070
#[test]

crates/uv-cli/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2178,6 +2178,14 @@ pub struct BuildArgs {
21782178
#[arg(long, overrides_with("build_logs"), hide = true)]
21792179
pub no_build_logs: bool,
21802180

2181+
/// Always build through PEP 517, don't use the fast path for the uv build backend.
2182+
///
2183+
/// By default, uv won't create a PEP 517 build environment for packages using the uv build
2184+
/// backend, but use a fast path that calls into the build backend directly. This option forces
2185+
/// always using PEP 517.
2186+
#[arg(long)]
2187+
pub no_fast_path: bool,
2188+
21812189
/// Constrain build dependencies using the given requirements files when building
21822190
/// distributions.
21832191
///

0 commit comments

Comments
 (0)