Skip to main content

uv_types/
traits.rs

1use std::fmt::{Debug, Display, Formatter};
2use std::future::Future;
3use std::ops::Deref;
4use std::path::{Path, PathBuf};
5
6use anyhow::Result;
7use rustc_hash::FxHashSet;
8
9use uv_cache::Cache;
10use uv_configuration::{BuildKind, BuildOptions, BuildOutput, NoSources};
11use uv_distribution_filename::DistFilename;
12use uv_distribution_types::{
13    CachedDist, ConfigSettings, DependencyMetadata, DistributionId, ExtraBuildRequires,
14    ExtraBuildVariables, IndexCapabilities, IndexLocations, InstalledDist, IsBuildBackendError,
15    PackageConfigSettings, Requirement, SourceDist,
16};
17use uv_git::GitResolver;
18use uv_normalize::PackageName;
19use uv_python::{Interpreter, PythonEnvironment};
20use uv_workspace::WorkspaceCache;
21
22use crate::{BuildArena, BuildIsolation, ResolvedRequirements};
23
24/// Controls how source tree requirements influence workspace-member editability during lowering.
25#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
26pub enum SourceTreeEditablePolicy {
27    /// Use project-style semantics when lowering workspace members.
28    ///
29    /// Explicit source-tree editable settings are ignored, preserving the existing implicit
30    /// editable default for workspace members.
31    #[default]
32    Project,
33
34    /// Use tool-style semantics when lowering workspace members.
35    ///
36    /// Explicit source-tree editable settings are preserved, while implicit workspace members
37    /// default to non-editable.
38    Tool,
39}
40
41impl SourceTreeEditablePolicy {
42    /// Return the default editable mode for workspace members lowered under this policy.
43    ///
44    /// `explicit` is the explicit editable choice on the source tree being lowered, if any. In
45    /// `Tool` mode it propagates to workspace siblings; in `Project` mode it is ignored.
46    pub fn workspace_member_editable(self, explicit: Option<bool>) -> bool {
47        match self {
48            Self::Project => true,
49            Self::Tool => explicit.unwrap_or(false),
50        }
51    }
52}
53
54///  Avoids cyclic crate dependencies between resolver, installer and builder.
55///
56/// To resolve the dependencies of a packages, we may need to build one or more source
57/// distributions. To building a source distribution, we need to create a virtual environment from
58/// the same base python as we use for the root resolution, resolve the build requirements
59/// (potentially which nested source distributions, recursing a level deeper), installing
60/// them and then build. The installer, the resolver and the source distribution builder are each in
61/// their own crate. To avoid circular crate dependencies, this type dispatches between the three
62/// crates with its three main methods ([`BuildContext::resolve`], [`BuildContext::install`] and
63/// [`BuildContext::setup_build`]).
64///
65/// The overall main crate structure looks like this:
66///
67/// ```text
68///                    ┌────────────────┐
69///                    │       uv       │
70///                    └───────▲────────┘
71///                            │
72///                            │
73///                    ┌───────┴────────┐
74///         ┌─────────►│  uv-dispatch   │◄─────────┐
75///         │          └───────▲────────┘          │
76///         │                  │                   │
77///         │                  │                   │
78/// ┌───────┴────────┐ ┌───────┴────────┐ ┌────────┴────────────────┐
79/// │  uv-resolver   │ │  uv-installer  │ │    uv-build-frontend    │
80/// └───────▲────────┘ └───────▲────────┘ └────────▲────────────────┘
81///         │                  │                   │
82///         └─────────────┐    │    ┌──────────────┘
83///                    ┌──┴────┴────┴───┐
84///                    │    uv-types    │
85///                    └────────────────┘
86/// ```
87///
88/// Put in a different way, the types here allow `uv-resolver` to depend on `uv-build` and
89/// `uv-build-frontend` to depend on `uv-resolver` without having actual crate dependencies between
90/// them.
91pub trait BuildContext {
92    type SourceDistBuilder: SourceBuildTrait;
93
94    // Note: this function is async deliberately, because downstream code may need to
95    // run async code to get the interpreter, to resolve the Python version.
96    /// Return a reference to the interpreter.
97    fn interpreter(&self) -> impl Future<Output = &Interpreter> + '_;
98
99    /// Return a reference to the cache.
100    fn cache(&self) -> &Cache;
101
102    /// Return a reference to the Git resolver.
103    fn git(&self) -> &GitResolver;
104
105    /// Return a reference to the build arena.
106    fn build_arena(&self) -> &BuildArena<Self::SourceDistBuilder>;
107
108    /// Return a reference to the discovered registry capabilities.
109    fn capabilities(&self) -> &IndexCapabilities;
110
111    /// Return a reference to any pre-defined static metadata.
112    fn dependency_metadata(&self) -> &DependencyMetadata;
113
114    /// Whether building source distributions or installing pre-built wheels is disabled.
115    ///
116    /// This method exists to avoid fetching source distributions if we know we can't build them.
117    fn build_options(&self) -> &BuildOptions;
118
119    /// The isolation mode used for building source distributions.
120    fn build_isolation(&self) -> BuildIsolation<'_>;
121
122    /// The [`ConfigSettings`] used to build distributions.
123    fn config_settings(&self) -> &ConfigSettings;
124
125    /// The [`ConfigSettings`] used to build a specific package.
126    fn config_settings_package(&self) -> &PackageConfigSettings;
127
128    /// Whether to incorporate `tool.uv.sources` when resolving requirements.
129    fn sources(&self) -> &NoSources;
130
131    /// How source tree requirements should influence workspace-member editability.
132    fn source_tree_editable_policy(&self) -> SourceTreeEditablePolicy {
133        SourceTreeEditablePolicy::Project
134    }
135
136    /// The index locations being searched.
137    fn locations(&self) -> &IndexLocations;
138
139    /// Workspace discovery caching.
140    fn workspace_cache(&self) -> &WorkspaceCache;
141
142    /// Get the extra build requirements.
143    fn extra_build_requires(&self) -> &ExtraBuildRequires;
144
145    /// Get the extra build variables.
146    fn extra_build_variables(&self) -> &ExtraBuildVariables;
147
148    /// Resolve the given requirements into a ready-to-install set of package versions.
149    fn resolve<'a>(
150        &'a self,
151        requirements: &'a [Requirement],
152        build_stack: &'a BuildStack,
153    ) -> impl Future<Output = Result<ResolvedRequirements, impl IsBuildBackendError>> + 'a;
154
155    /// Install the given set of package versions into the virtual environment. The environment must
156    /// use the same base Python as [`BuildContext::interpreter`]
157    fn install<'a>(
158        &'a self,
159        requirements: &'a ResolvedRequirements,
160        venv: &'a PythonEnvironment,
161        build_stack: &'a BuildStack,
162    ) -> impl Future<Output = Result<Vec<CachedDist>, impl IsBuildBackendError>> + 'a;
163
164    /// Set up a source distribution build by installing the required dependencies. A wrapper for
165    /// `uv_build::SourceBuild::setup`.
166    ///
167    /// For PEP 517 builds, this calls `get_requires_for_build_wheel`.
168    ///
169    /// Callers are responsible for enforcing [`BuildOptions`] for the source distribution itself.
170    /// Build dependencies are still resolved and installed using [`Self::build_options`].
171    ///
172    /// `version_id` is for error reporting only.
173    /// `dist` is for safety checks and may be null for editable builds.
174    fn setup_build<'a>(
175        &'a self,
176        source: &'a Path,
177        subdirectory: Option<&'a Path>,
178        install_path: &'a Path,
179        stop_discovery_at: Option<&'a Path>,
180        version_id: Option<&'a str>,
181        dist: Option<&'a SourceDist>,
182        sources: &'a NoSources,
183        build_kind: BuildKind,
184        build_output: BuildOutput,
185        build_stack: BuildStack,
186    ) -> impl Future<Output = Result<Self::SourceDistBuilder, impl IsBuildBackendError>> + 'a;
187
188    /// Build by calling directly into the uv build backend without PEP 517, if possible.
189    ///
190    /// Checks if the source tree uses uv as build backend. If not, it returns `Ok(None)`, otherwise
191    /// it builds and returns the name of the built file.
192    ///
193    /// `version_id` is for error reporting only.
194    fn direct_build<'a>(
195        &'a self,
196        source: &'a Path,
197        subdirectory: Option<&'a Path>,
198        output_dir: &'a Path,
199        sources: NoSources,
200        build_kind: BuildKind,
201        version_id: Option<&'a str>,
202    ) -> impl Future<Output = Result<Option<DistFilename>, impl IsBuildBackendError>> + 'a;
203}
204
205/// A wrapper for `uv_build::SourceBuild` to avoid cyclical crate dependencies.
206///
207/// You can either call only `wheel()` to build the wheel directly, call only `metadata()` to get
208/// the metadata without performing the actual or first call `metadata()` and then `wheel()`.
209pub trait SourceBuildTrait {
210    /// A wrapper for `uv_build::SourceBuild::get_metadata_without_build`.
211    ///
212    /// For PEP 517 builds, this calls `prepare_metadata_for_build_wheel`
213    ///
214    /// Returns the metadata directory if we're having a PEP 517 build and the
215    /// `prepare_metadata_for_build_wheel` hook exists
216    fn metadata(&mut self) -> impl Future<Output = Result<Option<PathBuf>, AnyErrorBuild>>;
217
218    /// A wrapper for `uv_build::SourceBuild::build`.
219    ///
220    /// For PEP 517 builds, this calls `build_wheel`.
221    ///
222    /// Returns the filename of the built wheel inside the given `wheel_dir`. The filename is a
223    /// string and not a `WheelFilename` because the on disk filename might not be normalized in the
224    /// same way as uv would.
225    fn wheel<'a>(
226        &'a self,
227        wheel_dir: &'a Path,
228    ) -> impl Future<Output = Result<String, AnyErrorBuild>> + 'a;
229}
230
231/// Provides access to installed distributions during resolution.
232pub trait InstalledPackagesProvider: Clone + Send + Sync + 'static {
233    fn iter(&self) -> impl Iterator<Item = &InstalledDist>;
234    fn get_packages(&self, name: &PackageName) -> Vec<&InstalledDist>;
235}
236
237impl<Provider: InstalledPackagesProvider> InstalledPackagesProvider for Option<Provider> {
238    fn iter(&self) -> impl Iterator<Item = &InstalledDist> {
239        self.as_ref().into_iter().flat_map(Provider::iter)
240    }
241
242    fn get_packages(&self, name: &PackageName) -> Vec<&InstalledDist> {
243        self.as_ref()
244            .map_or_else(Vec::new, |provider| provider.get_packages(name))
245    }
246}
247
248/// An [`InstalledPackagesProvider`] with no packages in it.
249#[derive(Clone)]
250pub struct EmptyInstalledPackages;
251
252impl InstalledPackagesProvider for EmptyInstalledPackages {
253    fn iter(&self) -> impl Iterator<Item = &InstalledDist> {
254        std::iter::empty()
255    }
256
257    fn get_packages(&self, _name: &PackageName) -> Vec<&InstalledDist> {
258        Vec::new()
259    }
260}
261
262/// [`anyhow::Error`]-like wrapper type for [`BuildDispatch`] method return values, that also makes
263/// [`IsBuildBackendError`] work as [`thiserror`] `#[source]`.
264///
265/// The errors types have the same problem as [`BuildDispatch`] generally: The `uv-resolver`,
266/// `uv-installer` and `uv-build-frontend` error types all reference each other:
267/// Resolution and installation may need to build packages, while the build frontend needs to
268/// resolve and install for the PEP 517 build environment.
269///
270/// Usually, [`anyhow::Error`] is opaque error type of choice. In this case though, we error type
271/// that we can inspect on whether it's a build backend error with [`IsBuildBackendError`], and
272/// [`anyhow::Error`] does not allow attaching more traits. The next choice would be
273/// `Box<dyn std::error::Error + IsBuildFrontendError + Send + Sync + 'static>`, but [`thiserror`]
274/// complains about the internal `AsDynError` not being implemented when being used as `#[source]`.
275/// This struct is an otherwise transparent error wrapper that thiserror recognizes.
276pub struct AnyErrorBuild(Box<dyn IsBuildBackendError>);
277
278impl Debug for AnyErrorBuild {
279    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
280        Debug::fmt(&self.0, f)
281    }
282}
283
284impl Display for AnyErrorBuild {
285    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
286        Display::fmt(&self.0, f)
287    }
288}
289
290impl std::error::Error for AnyErrorBuild {
291    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
292        self.0.source()
293    }
294
295    #[allow(deprecated)]
296    fn description(&self) -> &str {
297        self.0.description()
298    }
299
300    #[allow(deprecated)]
301    fn cause(&self) -> Option<&dyn std::error::Error> {
302        self.0.cause()
303    }
304}
305
306impl uv_errors::Hint for AnyErrorBuild {
307    fn hints(&self) -> uv_errors::Hints<'_> {
308        self.0.hints()
309    }
310}
311
312impl<T: IsBuildBackendError> From<T> for AnyErrorBuild {
313    fn from(err: T) -> Self {
314        Self(Box::new(err))
315    }
316}
317
318impl Deref for AnyErrorBuild {
319    type Target = dyn IsBuildBackendError;
320
321    fn deref(&self) -> &Self::Target {
322        &*self.0
323    }
324}
325
326/// The stack of packages being built.
327#[derive(Debug, Clone, Default)]
328pub struct BuildStack(FxHashSet<DistributionId>);
329
330impl BuildStack {
331    pub fn contains(&self, id: &DistributionId) -> bool {
332        self.0.contains(id)
333    }
334
335    /// Push a package onto the stack.
336    pub fn insert(&mut self, id: DistributionId) -> bool {
337        self.0.insert(id)
338    }
339}