Skip to content

Commit 87823cf

Browse files
committed
Build backend: Add direct builds to the resolver and installer
This is like #9556, but at the level of all other builds, including the resolver and installer. 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 instead: No temporary virtual env, no temp venv sync, no python subprocess calls, no uv subprocess calls. This fast path is gated through preview. Since the uv wheel is not available at test time, I've manually confirmed the feature by comparing `uv venv && cargo run pip install . -v --preview --reinstall .` and `uv venv && cargo run pip install . -v --reinstall .`. Do we need a global option to disable the fast path? There is one for `uv build` because `--force-pep517` moves `uv build` much closer to a `pip install` from source that a user of a library would experience.See discussion at #9610 (comment)
1 parent a5705e3 commit 87823cf

5 files changed

Lines changed: 114 additions & 12 deletions

File tree

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/uv-dispatch/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ doctest = false
1717
workspace = true
1818

1919
[dependencies]
20+
uv-build-backend = { workspace = true }
2021
uv-build-frontend = { workspace = true }
2122
uv-cache = { workspace = true }
2223
uv-client = { workspace = true }
@@ -30,9 +31,11 @@ uv-pypi-types = { workspace = true }
3031
uv-python = { workspace = true }
3132
uv-resolver = { workspace = true }
3233
uv-types = { workspace = true }
34+
uv-version = { workspace = true }
3335

3436
anyhow = { workspace = true }
3537
futures = { workspace = true }
3638
itertools = { workspace = true }
3739
rustc-hash = { workspace = true }
40+
tokio = { workspace = true }
3841
tracing = { workspace = true }

crates/uv-dispatch/src/lib.rs

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use anyhow::{anyhow, Context, Result};
99
use futures::FutureExt;
1010
use itertools::Itertools;
1111
use rustc_hash::FxHashMap;
12-
use tracing::{debug, instrument};
13-
12+
use tracing::{debug, instrument, trace};
13+
use uv_build_backend::check_direct_build;
1414
use uv_build_frontend::{SourceBuild, SourceBuildContext};
1515
use uv_cache::Cache;
1616
use uv_client::RegistryClient;
@@ -397,6 +397,67 @@ impl<'a> BuildContext for BuildDispatch<'a> {
397397
.await?;
398398
Ok(builder)
399399
}
400+
401+
async fn direct_build<'data>(
402+
&'data self,
403+
source: &'data Path,
404+
subdirectory: Option<&'data Path>,
405+
output_dir: &'data Path,
406+
build_kind: BuildKind,
407+
version_id: Option<String>,
408+
) -> Result<Option<String>> {
409+
// Direct builds are a preview feature with the uv build backend.
410+
if self.preview.is_disabled() {
411+
trace!("Preview is disabled, not checking for direct build");
412+
return Ok(None);
413+
}
414+
415+
let source_tree = if let Some(subdir) = subdirectory {
416+
source.join(subdir)
417+
} else {
418+
source.to_path_buf()
419+
};
420+
421+
// Only perform the direct build if the backend is uv in a compatible version.
422+
let identifier = version_id.unwrap_or_else(|| source_tree.display().to_string());
423+
if !check_direct_build(&source_tree, &identifier) {
424+
trace!("Requirements for direct build not matched: {identifier}");
425+
return Ok(None);
426+
}
427+
428+
debug!("Performing direct build for {identifier}");
429+
430+
let output_dir = output_dir.to_path_buf();
431+
let filename = tokio::task::spawn_blocking(move || -> Result<String> {
432+
let filename = match build_kind {
433+
BuildKind::Wheel => uv_build_backend::build_wheel(
434+
&source_tree,
435+
&output_dir,
436+
None,
437+
uv_version::version(),
438+
)?
439+
.to_string(),
440+
BuildKind::Sdist => uv_build_backend::build_source_dist(
441+
&source_tree,
442+
&output_dir,
443+
uv_version::version(),
444+
)?
445+
.to_string(),
446+
BuildKind::Editable => uv_build_backend::build_editable(
447+
&source_tree,
448+
&output_dir,
449+
None,
450+
uv_version::version(),
451+
)?
452+
.to_string(),
453+
};
454+
Ok(filename)
455+
})
456+
.await??
457+
.to_string();
458+
459+
Ok(Some(filename))
460+
}
400461
}
401462

402463
/// Shared state used during resolution and installation.

crates/uv-distribution/src/source/mod.rs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,27 +1803,46 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
18031803
fs::create_dir_all(&cache_shard)
18041804
.await
18051805
.map_err(Error::CacheWrite)?;
1806-
let disk_filename = self
1806+
// Try a direct build if that isn't disabled and the uv build backend is used.
1807+
let disk_filename = if let Some(name) = self
18071808
.build_context
1808-
.setup_build(
1809+
.direct_build(
18091810
source_root,
18101811
subdirectory,
1811-
source_root,
1812-
Some(source.to_string()),
1813-
source.as_dist(),
1814-
source_strategy,
1812+
temp_dir.path(),
18151813
if source.is_editable() {
18161814
BuildKind::Editable
18171815
} else {
18181816
BuildKind::Wheel
18191817
},
1820-
BuildOutput::Debug,
1818+
Some(source.to_string()),
18211819
)
18221820
.await
18231821
.map_err(Error::Build)?
1824-
.wheel(temp_dir.path())
1825-
.await
1826-
.map_err(Error::Build)?;
1822+
{
1823+
name
1824+
} else {
1825+
self.build_context
1826+
.setup_build(
1827+
source_root,
1828+
subdirectory,
1829+
source_root,
1830+
Some(source.to_string()),
1831+
source.as_dist(),
1832+
source_strategy,
1833+
if source.is_editable() {
1834+
BuildKind::Editable
1835+
} else {
1836+
BuildKind::Wheel
1837+
},
1838+
BuildOutput::Debug,
1839+
)
1840+
.await
1841+
.map_err(Error::Build)?
1842+
.wheel(temp_dir.path())
1843+
.await
1844+
.map_err(Error::Build)?
1845+
};
18271846

18281847
// Read the metadata from the wheel.
18291848
let filename = WheelFilename::from_str(&disk_filename)?;

crates/uv-types/src/traits.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,21 @@ pub trait BuildContext {
121121
build_kind: BuildKind,
122122
build_output: BuildOutput,
123123
) -> impl Future<Output = Result<Self::SourceDistBuilder>> + 'a;
124+
125+
/// Build by calling directly into the uv build backend without PEP 517, if possible.
126+
///
127+
/// Checks if the source tree uses uv as build backend. If not, it returns `Ok(None)`, otherwise
128+
/// it builds and returns the name of the built file.
129+
///
130+
/// `version_id` is for error reporting only.
131+
fn direct_build<'a>(
132+
&'a self,
133+
source: &'a Path,
134+
subdirectory: Option<&'a Path>,
135+
output_dir: &'a Path,
136+
build_kind: BuildKind,
137+
version_id: Option<String>,
138+
) -> impl Future<Output = Result<Option<String>>> + 'a;
124139
}
125140

126141
/// A wrapper for `uv_build::SourceBuild` to avoid cyclical crate dependencies.

0 commit comments

Comments
 (0)