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