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}