Skip to main content

uv_settings/
combine.rs

1use std::path::PathBuf;
2use std::{collections::BTreeMap, num::NonZeroUsize};
3
4use url::Url;
5
6use uv_configuration::{
7    BuildIsolation, ExportFormat, IndexStrategy, KeyringProviderType, NoSources, ProxyUrl,
8    Reinstall, RequiredVersion, TargetTriple, TrustedPublishing, Upgrade,
9};
10use uv_distribution_types::{
11    ConfigSettings, ExtraBuildVariables, Index, IndexUrl, PackageConfigSettings, PipExtraIndex,
12    PipFindLinks, PipIndex,
13};
14use uv_install_wheel::LinkMode;
15use uv_pypi_types::{SchemaConflicts, SupportedEnvironments};
16use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
17use uv_redacted::DisplaySafeUrl;
18use uv_resolver::{
19    AnnotationStyle, ExcludeNewer, ExcludeNewerPackage, ExcludeNewerValue, ForkStrategy,
20    PrereleaseMode, ResolutionMode,
21};
22use uv_torch::TorchMode;
23use uv_workspace::pyproject::ExtraBuildDependencies;
24use uv_workspace::pyproject_mut::AddBoundsKind;
25
26use crate::{FilesystemOptions, Options, PipOptions};
27
28pub trait Combine {
29    /// Combine two values, preferring the values in `self`.
30    ///
31    /// The logic should follow that of Cargo's `config.toml`:
32    ///
33    /// > If a key is specified in multiple config files, the values will get merged together.
34    /// > Numbers, strings, and booleans will use the value in the deeper config directory taking
35    /// > precedence over ancestor directories, where the home directory is the lowest priority.
36    /// > Arrays will be joined together with higher precedence items being placed later in the
37    /// > merged array.
38    ///
39    /// ...with one exception: we place items with higher precedence earlier in the merged array.
40    #[must_use]
41    fn combine(self, other: Self) -> Self;
42}
43
44impl Combine for Option<FilesystemOptions> {
45    /// Combine the options used in two [`FilesystemOptions`]s. Retains the root of `self`.
46    fn combine(self, other: Self) -> Self {
47        match (self, other) {
48            (Some(a), Some(b)) => Some(FilesystemOptions(
49                a.into_options().combine(b.into_options()),
50            )),
51            (a, b) => a.or(b),
52        }
53    }
54}
55
56impl Combine for Option<Options> {
57    /// Combine the options used in two [`Options`]s. Retains the root of `self`.
58    fn combine(self, other: Self) -> Self {
59        match (self, other) {
60            (Some(a), Some(b)) => Some(a.combine(b)),
61            (a, b) => a.or(b),
62        }
63    }
64}
65
66impl Combine for Option<PipOptions> {
67    fn combine(self, other: Self) -> Self {
68        match (self, other) {
69            (Some(a), Some(b)) => Some(a.combine(b)),
70            (a, b) => a.or(b),
71        }
72    }
73}
74
75macro_rules! impl_combine_or {
76    ($name:ident) => {
77        impl Combine for Option<$name> {
78            fn combine(self, other: Option<$name>) -> Option<$name> {
79                self.or(other)
80            }
81        }
82    };
83}
84
85impl_combine_or!(AddBoundsKind);
86impl_combine_or!(AnnotationStyle);
87impl_combine_or!(ExcludeNewer);
88impl_combine_or!(ExcludeNewerValue);
89impl_combine_or!(ExportFormat);
90impl_combine_or!(ForkStrategy);
91impl_combine_or!(Index);
92impl_combine_or!(IndexStrategy);
93impl_combine_or!(IndexUrl);
94impl_combine_or!(KeyringProviderType);
95impl_combine_or!(LinkMode);
96impl_combine_or!(DisplaySafeUrl);
97impl_combine_or!(NonZeroUsize);
98impl_combine_or!(PathBuf);
99impl_combine_or!(PipExtraIndex);
100impl_combine_or!(PipFindLinks);
101impl_combine_or!(PipIndex);
102impl_combine_or!(PrereleaseMode);
103impl_combine_or!(ProxyUrl);
104impl_combine_or!(PythonDownloads);
105impl_combine_or!(PythonPreference);
106impl_combine_or!(PythonVersion);
107impl_combine_or!(RequiredVersion);
108impl_combine_or!(ResolutionMode);
109impl_combine_or!(SchemaConflicts);
110impl_combine_or!(String);
111impl_combine_or!(SupportedEnvironments);
112impl_combine_or!(TargetTriple);
113impl_combine_or!(TorchMode);
114impl_combine_or!(TrustedPublishing);
115impl_combine_or!(Url);
116impl_combine_or!(bool);
117
118impl<T> Combine for Option<Vec<T>> {
119    /// Combine two vectors by extending the vector in `self` with the vector in `other`, if they're
120    /// both `Some`.
121    fn combine(self, other: Self) -> Self {
122        match (self, other) {
123            (Some(mut a), Some(b)) => {
124                a.extend(b);
125                Some(a)
126            }
127            (a, b) => a.or(b),
128        }
129    }
130}
131
132impl<K: Ord, T> Combine for Option<BTreeMap<K, Vec<T>>> {
133    /// Combine two maps of vecs by combining their vecs
134    fn combine(self, other: Self) -> Self {
135        match (self, other) {
136            (Some(mut a), Some(b)) => {
137                for (key, value) in b {
138                    a.entry(key).or_default().extend(value);
139                }
140                Some(a)
141            }
142            (a, b) => a.or(b),
143        }
144    }
145}
146
147impl Combine for Option<ExcludeNewerPackage> {
148    /// Combine two [`ExcludeNewerPackage`] instances by merging them, with the values in `self` taking precedence.
149    fn combine(self, other: Self) -> Self {
150        match (self, other) {
151            (Some(mut a), Some(b)) => {
152                // Extend with values from b, but a takes precedence (we don't overwrite existing keys)
153                for (key, value) in b {
154                    a.entry(key).or_insert(value);
155                }
156                Some(a)
157            }
158            (a, b) => a.or(b),
159        }
160    }
161}
162
163impl Combine for Option<ConfigSettings> {
164    /// Combine two maps by merging the map in `self` with the map in `other`, if they're both
165    /// `Some`.
166    fn combine(self, other: Self) -> Self {
167        match (self, other) {
168            (Some(a), Some(b)) => Some(a.merge(b)),
169            (a, b) => a.or(b),
170        }
171    }
172}
173
174impl Combine for Option<PackageConfigSettings> {
175    /// Combine two maps by merging the map in `self` with the map in `other`, if they're both
176    /// `Some`.
177    fn combine(self, other: Self) -> Self {
178        match (self, other) {
179            (Some(a), Some(b)) => Some(a.merge(b)),
180            (a, b) => a.or(b),
181        }
182    }
183}
184
185impl Combine for Option<NoSources> {
186    /// Combine two source strategies by using the `combine` method if they're both `Some`.
187    fn combine(self, other: Self) -> Self {
188        match (self, other) {
189            (Some(a), Some(b)) => Some(a.combine(b)),
190            (a, b) => a.or(b),
191        }
192    }
193}
194
195impl Combine for Option<Upgrade> {
196    fn combine(self, other: Self) -> Self {
197        match (self, other) {
198            (Some(a), Some(b)) => Some(a.combine(b)),
199            (a, b) => a.or(b),
200        }
201    }
202}
203
204impl Combine for Option<Reinstall> {
205    fn combine(self, other: Self) -> Self {
206        match (self, other) {
207            (Some(a), Some(b)) => Some(a.combine(b)),
208            (a, b) => a.or(b),
209        }
210    }
211}
212
213impl Combine for Option<BuildIsolation> {
214    fn combine(self, other: Self) -> Self {
215        match (self, other) {
216            (Some(a), Some(b)) => Some(a.combine(b)),
217            (a, b) => a.or(b),
218        }
219    }
220}
221
222impl Combine for serde::de::IgnoredAny {
223    fn combine(self, _other: Self) -> Self {
224        self
225    }
226}
227
228impl Combine for Option<serde::de::IgnoredAny> {
229    fn combine(self, _other: Self) -> Self {
230        self
231    }
232}
233
234impl Combine for ExcludeNewer {
235    fn combine(mut self, other: Self) -> Self {
236        self.global = self.global.combine(other.global);
237
238        if !other.package.is_empty() {
239            if self.package.is_empty() {
240                self.package = other.package;
241            } else {
242                // Merge package-specific settings, with self taking precedence
243                for (pkg, setting) in &other.package {
244                    self.package
245                        .entry(pkg.clone())
246                        .or_insert_with(|| setting.clone());
247                }
248            }
249        }
250
251        self
252    }
253}
254
255impl Combine for ExtraBuildDependencies {
256    fn combine(mut self, other: Self) -> Self {
257        for (key, value) in other {
258            match self.entry(key) {
259                std::collections::btree_map::Entry::Occupied(mut entry) => {
260                    // Combine the vecs, with self taking precedence
261                    let existing = entry.get_mut();
262                    existing.extend(value);
263                }
264                std::collections::btree_map::Entry::Vacant(entry) => {
265                    entry.insert(value);
266                }
267            }
268        }
269        self
270    }
271}
272
273impl Combine for Option<ExtraBuildDependencies> {
274    fn combine(self, other: Self) -> Self {
275        match (self, other) {
276            (Some(a), Some(b)) => Some(a.combine(b)),
277            (a, b) => a.or(b),
278        }
279    }
280}
281
282impl Combine for ExtraBuildVariables {
283    fn combine(mut self, other: Self) -> Self {
284        for (key, value) in other {
285            match self.entry(key) {
286                std::collections::btree_map::Entry::Occupied(mut entry) => {
287                    // Combine the maps, with self taking precedence
288                    let existing = entry.get_mut();
289                    for (k, v) in value {
290                        existing.entry(k).or_insert(v);
291                    }
292                }
293                std::collections::btree_map::Entry::Vacant(entry) => {
294                    entry.insert(value);
295                }
296            }
297        }
298        self
299    }
300}
301
302impl Combine for Option<ExtraBuildVariables> {
303    fn combine(self, other: Self) -> Self {
304        match (self, other) {
305            (Some(a), Some(b)) => Some(a.combine(b)),
306            (a, b) => a.or(b),
307        }
308    }
309}