uv_configuration/extras.rs
1use std::{borrow::Cow, sync::Arc};
2
3use uv_normalize::{DefaultExtras, ExtraName};
4
5/// Manager of all extra decisions and settings history.
6///
7/// This is an Arc mostly just to avoid size bloat on things that contain these.
8#[derive(Debug, Default, Clone)]
9pub struct ExtrasSpecification(Arc<ExtrasSpecificationInner>);
10
11/// Manager of all dependency-group decisions and settings history.
12#[derive(Debug, Default, Clone)]
13pub struct ExtrasSpecificationInner {
14 /// Extras to include.
15 include: IncludeExtras,
16 /// Extras to exclude (always wins over include).
17 exclude: Vec<ExtraName>,
18 /// Whether an `--only` flag was passed.
19 ///
20 /// If true, users of this API should refrain from looking at packages
21 /// that *aren't* specified by the extras. This is exposed
22 /// via [`ExtrasSpecificationInner::prod`][].
23 only_extras: bool,
24 /// The "raw" flags/settings we were passed for diagnostics.
25 history: ExtrasSpecificationHistory,
26}
27
28impl ExtrasSpecification {
29 /// Create from history.
30 ///
31 /// This is the "real" constructor, it's basically taking raw CLI flags but in
32 /// a way that's a bit nicer for other constructors to use.
33 fn from_history(history: ExtrasSpecificationHistory) -> Self {
34 let ExtrasSpecificationHistory {
35 mut extra,
36 mut only_extra,
37 no_extra,
38 all_extras,
39 no_default_extras,
40 mut defaults,
41 } = history.clone();
42
43 // `extra` and `only_extra` actually have the same meanings: packages to include.
44 // But if `only_extra` is non-empty then *other* packages should be excluded.
45 // So we just record whether it was and then treat the two lists as equivalent.
46 let only_extras = !only_extra.is_empty();
47 // --only flags imply --no-default-extras
48 let default_extras = !no_default_extras && !only_extras;
49
50 let include = if all_extras {
51 // If this is set we can ignore extra/only_extra/defaults as irrelevant.
52 IncludeExtras::All
53 } else {
54 // Merge all these lists, they're equivalent now
55 extra.append(&mut only_extra);
56 // Resolve default extras potentially also setting All
57 if default_extras {
58 match &mut defaults {
59 DefaultExtras::All => IncludeExtras::All,
60 DefaultExtras::List(defaults) => {
61 extra.append(defaults);
62 IncludeExtras::Some(extra)
63 }
64 }
65 } else {
66 IncludeExtras::Some(extra)
67 }
68 };
69
70 Self(Arc::new(ExtrasSpecificationInner {
71 include,
72 exclude: no_extra,
73 only_extras,
74 history,
75 }))
76 }
77
78 /// Create from raw CLI args
79 pub fn from_args(
80 extra: Vec<ExtraName>,
81 no_extra: Vec<ExtraName>,
82 no_default_extras: bool,
83 only_extra: Vec<ExtraName>,
84 all_extras: bool,
85 ) -> Self {
86 Self::from_history(ExtrasSpecificationHistory {
87 extra,
88 only_extra,
89 no_extra,
90 all_extras,
91 no_default_extras,
92 // This is unknown at CLI-time, use `.with_defaults(...)` to apply this later!
93 defaults: DefaultExtras::default(),
94 })
95 }
96
97 /// Helper to make a spec from just a --extra
98 pub fn from_extra(extra: Vec<ExtraName>) -> Self {
99 Self::from_history(ExtrasSpecificationHistory {
100 extra,
101 ..Default::default()
102 })
103 }
104
105 /// Helper to make a spec from just --all-extras
106 pub fn from_all_extras() -> Self {
107 Self::from_history(ExtrasSpecificationHistory {
108 all_extras: true,
109 ..Default::default()
110 })
111 }
112
113 /// Apply defaults to a base [`ExtrasSpecification`].
114 pub fn with_defaults(&self, defaults: DefaultExtras) -> ExtrasSpecificationWithDefaults {
115 // Explicitly clone the inner history and set the defaults, then remake the result.
116 let mut history = self.0.history.clone();
117 history.defaults = defaults;
118
119 ExtrasSpecificationWithDefaults {
120 cur: Self::from_history(history),
121 prev: self.clone(),
122 }
123 }
124}
125
126impl std::ops::Deref for ExtrasSpecification {
127 type Target = ExtrasSpecificationInner;
128 fn deref(&self) -> &Self::Target {
129 &self.0
130 }
131}
132
133impl ExtrasSpecificationInner {
134 /// Returns `true` if packages other than the ones referenced by these
135 /// extras should be considered.
136 ///
137 /// That is, if I tell you to install a project and this is false,
138 /// you should ignore the project itself and all its dependencies,
139 /// and instead just install the extras.
140 ///
141 /// (This is really just asking if an --only flag was passed.)
142 pub fn prod(&self) -> bool {
143 !self.only_extras
144 }
145
146 /// Returns `true` if the specification includes the given extra.
147 pub fn contains(&self, extra: &ExtraName) -> bool {
148 // exclude always trumps include
149 !self.exclude.contains(extra) && self.include.contains(extra)
150 }
151
152 /// Iterate over all extras that we think should exist.
153 pub fn desugarred_names(&self) -> impl Iterator<Item = &ExtraName> {
154 self.include.names().chain(&self.exclude)
155 }
156
157 /// Returns an iterator over all extras that are included in the specification,
158 /// assuming `all_names` is an iterator over all extras.
159 pub fn extra_names<'a, Names>(
160 &'a self,
161 all_names: Names,
162 ) -> impl Iterator<Item = &'a ExtraName> + 'a
163 where
164 Names: Iterator<Item = &'a ExtraName> + 'a,
165 {
166 all_names.filter(move |name| self.contains(name))
167 }
168
169 /// Iterate over all groups the user explicitly asked for on the CLI
170 pub fn explicit_names(&self) -> impl Iterator<Item = &ExtraName> {
171 let ExtrasSpecificationHistory {
172 extra,
173 only_extra,
174 no_extra,
175 // These reference no extras explicitly
176 all_extras: _,
177 no_default_extras: _,
178 defaults: _,
179 } = self.history();
180
181 extra.iter().chain(no_extra).chain(only_extra)
182 }
183
184 /// Returns `true` if the specification will have no effect.
185 pub fn is_empty(&self) -> bool {
186 self.prod() && self.exclude.is_empty() && self.include.is_empty()
187 }
188
189 /// Get the raw history for diagnostics
190 pub fn history(&self) -> &ExtrasSpecificationHistory {
191 &self.history
192 }
193}
194
195/// Context about a [`ExtrasSpecification`][] that we've preserved for diagnostics
196#[derive(Debug, Default, Clone)]
197pub struct ExtrasSpecificationHistory {
198 pub extra: Vec<ExtraName>,
199 pub only_extra: Vec<ExtraName>,
200 pub no_extra: Vec<ExtraName>,
201 pub all_extras: bool,
202 pub no_default_extras: bool,
203 pub defaults: DefaultExtras,
204}
205
206impl ExtrasSpecificationHistory {
207 /// Returns all the CLI flags that this represents.
208 ///
209 /// If a flag was provided multiple times (e.g. `--extra A --extra B`) this will
210 /// elide the arguments and just show the flag once (e.g. just yield "--extra").
211 ///
212 /// Conceptually this being an empty list should be equivalent to
213 /// [`ExtrasSpecification::is_empty`][] when there aren't any defaults set.
214 /// When there are defaults the two will disagree, and rightfully so!
215 pub fn as_flags_pretty(&self) -> Vec<Cow<'_, str>> {
216 let Self {
217 extra,
218 no_extra,
219 all_extras,
220 only_extra,
221 no_default_extras,
222 // defaults aren't CLI flags!
223 defaults: _,
224 } = self;
225
226 let mut flags = vec![];
227 if *all_extras {
228 flags.push(Cow::Borrowed("--all-extras"));
229 }
230 if *no_default_extras {
231 flags.push(Cow::Borrowed("--no-default-extras"));
232 }
233 match &**extra {
234 [] => {}
235 [extra] => flags.push(Cow::Owned(format!("--extra {extra}"))),
236 [..] => flags.push(Cow::Borrowed("--extra")),
237 }
238 match &**only_extra {
239 [] => {}
240 [extra] => flags.push(Cow::Owned(format!("--only-extra {extra}"))),
241 [..] => flags.push(Cow::Borrowed("--only-extra")),
242 }
243 match &**no_extra {
244 [] => {}
245 [extra] => flags.push(Cow::Owned(format!("--no-extra {extra}"))),
246 [..] => flags.push(Cow::Borrowed("--no-extra")),
247 }
248 flags
249 }
250}
251
252/// A trivial newtype wrapped around [`ExtrasSpecification`][] that signifies "defaults applied"
253///
254/// It includes a copy of the previous semantics to provide info on if
255/// the group being a default actually affected it being enabled, because it's obviously "correct".
256/// (These are Arcs so it's ~free to hold onto the previous semantics)
257#[derive(Debug, Clone)]
258pub struct ExtrasSpecificationWithDefaults {
259 /// The active semantics
260 cur: ExtrasSpecification,
261 /// The semantics before defaults were applied
262 prev: ExtrasSpecification,
263}
264
265impl ExtrasSpecificationWithDefaults {
266 /// Do not enable any extras
267 ///
268 /// Many places in the code need to know what extras are active,
269 /// but various commands or subsystems never enable any extras,
270 /// in which case they want this.
271 pub fn none() -> Self {
272 ExtrasSpecification::default().with_defaults(DefaultExtras::default())
273 }
274 /// Returns `true` if the specification was enabled, and *only* because it was a default
275 pub fn contains_because_default(&self, extra: &ExtraName) -> bool {
276 self.cur.contains(extra) && !self.prev.contains(extra)
277 }
278}
279impl std::ops::Deref for ExtrasSpecificationWithDefaults {
280 type Target = ExtrasSpecification;
281 fn deref(&self) -> &Self::Target {
282 &self.cur
283 }
284}
285
286#[derive(Debug, Clone)]
287pub enum IncludeExtras {
288 /// Include dependencies from the specified extras.
289 Some(Vec<ExtraName>),
290 /// A marker indicates including dependencies from all extras.
291 All,
292}
293
294impl IncludeExtras {
295 /// Returns `true` if the specification includes the given extra.
296 pub fn contains(&self, extra: &ExtraName) -> bool {
297 match self {
298 Self::Some(extras) => extras.contains(extra),
299 Self::All => true,
300 }
301 }
302
303 /// Returns `true` if the specification will have no effect.
304 pub fn is_empty(&self) -> bool {
305 match self {
306 Self::Some(extras) => extras.is_empty(),
307 // Although technically this is a noop if they have no extras,
308 // conceptually they're *trying* to have an effect, so treat it as one.
309 Self::All => false,
310 }
311 }
312
313 /// Iterate over all extras referenced in the [`IncludeExtras`].
314 pub fn names(&self) -> std::slice::Iter<'_, ExtraName> {
315 match self {
316 Self::Some(extras) => extras.iter(),
317 Self::All => [].iter(),
318 }
319 }
320}
321
322impl Default for IncludeExtras {
323 fn default() -> Self {
324 Self::Some(Vec::new())
325 }
326}