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