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, 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/// 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 source distribution building or pre-built wheels is disabled.
115 ///
116 /// This [`BuildContext::setup_build`] calls will fail if builds are disabled.
117 /// This method exists to avoid fetching source distributions if we know we can't build them.
118 fn build_options(&self) -> &BuildOptions;
119
120 /// The isolation mode used for building source distributions.
121 fn build_isolation(&self) -> BuildIsolation<'_>;
122
123 /// The [`ConfigSettings`] used to build distributions.
124 fn config_settings(&self) -> &ConfigSettings;
125
126 /// The [`ConfigSettings`] used to build a specific package.
127 fn config_settings_package(&self) -> &PackageConfigSettings;
128
129 /// Whether to incorporate `tool.uv.sources` when resolving requirements.
130 fn sources(&self) -> &NoSources;
131
132 /// How source tree requirements should influence workspace-member editability.
133 fn source_tree_editable_policy(&self) -> SourceTreeEditablePolicy {
134 SourceTreeEditablePolicy::Project
135 }
136
137 /// The index locations being searched.
138 fn locations(&self) -> &IndexLocations;
139
140 /// Workspace discovery caching.
141 fn workspace_cache(&self) -> &WorkspaceCache;
142
143 /// Get the extra build requirements.
144 fn extra_build_requires(&self) -> &ExtraBuildRequires;
145
146 /// Get the extra build variables.
147 fn extra_build_variables(&self) -> &ExtraBuildVariables;
148
149 /// Resolve the given requirements into a ready-to-install set of package versions.
150 fn resolve<'a>(
151 &'a self,
152 requirements: &'a [Requirement],
153 build_stack: &'a BuildStack,
154 ) -> impl Future<Output = Result<Resolution, impl IsBuildBackendError>> + 'a;
155
156 /// Install the given set of package versions into the virtual environment. The environment must
157 /// use the same base Python as [`BuildContext::interpreter`]
158 fn install<'a>(
159 &'a self,
160 resolution: &'a Resolution,
161 venv: &'a PythonEnvironment,
162 build_stack: &'a BuildStack,
163 ) -> impl Future<Output = Result<Vec<CachedDist>, impl IsBuildBackendError>> + 'a;
164
165 /// Set up a source distribution build by installing the required dependencies. A wrapper for
166 /// `uv_build::SourceBuild::setup`.
167 ///
168 /// For PEP 517 builds, this calls `get_requires_for_build_wheel`.
169 ///
170 /// `version_id` is for error reporting only.
171 /// `dist` is for safety checks and may be null for editable builds.
172 fn setup_build<'a>(
173 &'a self,
174 source: &'a Path,
175 subdirectory: Option<&'a Path>,
176 install_path: &'a Path,
177 version_id: Option<&'a str>,
178 dist: Option<&'a SourceDist>,
179 sources: &'a NoSources,
180 build_kind: BuildKind,
181 build_output: BuildOutput,
182 build_stack: BuildStack,
183 ) -> impl Future<Output = Result<Self::SourceDistBuilder, impl IsBuildBackendError>> + 'a;
184
185 /// Build by calling directly into the uv build backend without PEP 517, if possible.
186 ///
187 /// Checks if the source tree uses uv as build backend. If not, it returns `Ok(None)`, otherwise
188 /// it builds and returns the name of the built file.
189 ///
190 /// `version_id` is for error reporting only.
191 fn direct_build<'a>(
192 &'a self,
193 source: &'a Path,
194 subdirectory: Option<&'a Path>,
195 output_dir: &'a Path,
196 sources: NoSources,
197 build_kind: BuildKind,
198 version_id: Option<&'a str>,
199 ) -> impl Future<Output = Result<Option<DistFilename>, impl IsBuildBackendError>> + 'a;
200}
201
202/// A wrapper for `uv_build::SourceBuild` to avoid cyclical crate dependencies.
203///
204/// You can either call only `wheel()` to build the wheel directly, call only `metadata()` to get
205/// the metadata without performing the actual or first call `metadata()` and then `wheel()`.
206pub trait SourceBuildTrait {
207 /// A wrapper for `uv_build::SourceBuild::get_metadata_without_build`.
208 ///
209 /// For PEP 517 builds, this calls `prepare_metadata_for_build_wheel`
210 ///
211 /// Returns the metadata directory if we're having a PEP 517 build and the
212 /// `prepare_metadata_for_build_wheel` hook exists
213 fn metadata(&mut self) -> impl Future<Output = Result<Option<PathBuf>, AnyErrorBuild>>;
214
215 /// A wrapper for `uv_build::SourceBuild::build`.
216 ///
217 /// For PEP 517 builds, this calls `build_wheel`.
218 ///
219 /// Returns the filename of the built wheel inside the given `wheel_dir`. The filename is a
220 /// string and not a `WheelFilename` because the on disk filename might not be normalized in the
221 /// same way as uv would.
222 fn wheel<'a>(
223 &'a self,
224 wheel_dir: &'a Path,
225 ) -> impl Future<Output = Result<String, AnyErrorBuild>> + 'a;
226}
227
228/// A wrapper for [`uv_installer::SitePackages`]
229pub trait InstalledPackagesProvider: Clone + Send + Sync + 'static {
230 fn iter(&self) -> impl Iterator<Item = &InstalledDist>;
231 fn get_packages(&self, name: &PackageName) -> Vec<&InstalledDist>;
232}
233
234/// An [`InstalledPackagesProvider`] with no packages in it.
235#[derive(Clone)]
236pub struct EmptyInstalledPackages;
237
238impl InstalledPackagesProvider for EmptyInstalledPackages {
239 fn iter(&self) -> impl Iterator<Item = &InstalledDist> {
240 std::iter::empty()
241 }
242
243 fn get_packages(&self, _name: &PackageName) -> Vec<&InstalledDist> {
244 Vec::new()
245 }
246}
247
248/// [`anyhow::Error`]-like wrapper type for [`BuildDispatch`] method return values, that also makes
249/// [`IsBuildBackendError`] work as [`thiserror`] `#[source]`.
250///
251/// The errors types have the same problem as [`BuildDispatch`] generally: The `uv-resolver`,
252/// `uv-installer` and `uv-build-frontend` error types all reference each other:
253/// Resolution and installation may need to build packages, while the build frontend needs to
254/// resolve and install for the PEP 517 build environment.
255///
256/// Usually, [`anyhow::Error`] is opaque error type of choice. In this case though, we error type
257/// that we can inspect on whether it's a build backend error with [`IsBuildBackendError`], and
258/// [`anyhow::Error`] does not allow attaching more traits. The next choice would be
259/// `Box<dyn std::error::Error + IsBuildFrontendError + Send + Sync + 'static>`, but [`thiserror`]
260/// complains about the internal `AsDynError` not being implemented when being used as `#[source]`.
261/// This struct is an otherwise transparent error wrapper that thiserror recognizes.
262pub struct AnyErrorBuild(Box<dyn IsBuildBackendError>);
263
264impl Debug for AnyErrorBuild {
265 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
266 Debug::fmt(&self.0, f)
267 }
268}
269
270impl Display for AnyErrorBuild {
271 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
272 Display::fmt(&self.0, f)
273 }
274}
275
276impl std::error::Error for AnyErrorBuild {
277 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
278 self.0.source()
279 }
280
281 #[allow(deprecated)]
282 fn description(&self) -> &str {
283 self.0.description()
284 }
285
286 #[allow(deprecated)]
287 fn cause(&self) -> Option<&dyn std::error::Error> {
288 self.0.cause()
289 }
290}
291
292impl<T: IsBuildBackendError> From<T> for AnyErrorBuild {
293 fn from(err: T) -> Self {
294 Self(Box::new(err))
295 }
296}
297
298impl Deref for AnyErrorBuild {
299 type Target = dyn IsBuildBackendError;
300
301 fn deref(&self) -> &Self::Target {
302 &*self.0
303 }
304}
305
306/// The stack of packages being built.
307#[derive(Debug, Clone, Default)]
308pub struct BuildStack(FxHashSet<DistributionId>);
309
310impl BuildStack {
311 /// Return an empty stack.
312 pub fn empty() -> Self {
313 Self(FxHashSet::default())
314 }
315
316 pub fn contains(&self, id: &DistributionId) -> bool {
317 self.0.contains(id)
318 }
319
320 /// Push a package onto the stack.
321 pub fn insert(&mut self, id: DistributionId) -> bool {
322 self.0.insert(id)
323 }
324}