1use std::fmt;
2
3use anstream::eprintln;
4
5use uv_cache::Refresh;
6use uv_configuration::{BuildIsolation, Reinstall, Upgrade};
7use uv_distribution_types::{ConfigSettings, PackageConfigSettings, Requirement};
8use uv_resolver::{ExcludeNewer, ExcludeNewerPackage, PrereleaseMode};
9use uv_settings::{Combine, EnvFlag, PipOptions, ResolverInstallerOptions, ResolverOptions};
10use uv_warnings::owo_colors::OwoColorize;
11
12use crate::{
13 BuildOptionsArgs, FetchArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs,
14 ResolverInstallerArgs,
15};
16
17pub fn flag(yes: bool, no: bool, name: &str) -> Option<bool> {
19 match (yes, no) {
20 (true, false) => Some(true),
21 (false, true) => Some(false),
22 (false, false) => None,
23 (..) => {
24 eprintln!(
25 "{}{} `{}` and `{}` cannot be used together. \
26 Boolean flags on different levels are currently not supported \
27 (https://github.com/clap-rs/clap/issues/6049)",
28 "error".bold().red(),
29 ":".bold(),
30 format!("--{name}").green(),
31 format!("--no-{name}").green(),
32 );
33 #[allow(clippy::exit)]
35 {
36 std::process::exit(2);
37 }
38 }
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum FlagSource {
45 Cli,
47 Env(&'static str),
49 Config,
51}
52
53impl fmt::Display for FlagSource {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 Self::Cli => write!(f, "command-line argument"),
57 Self::Env(name) => write!(f, "environment variable `{name}`"),
58 Self::Config => write!(f, "workspace configuration"),
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy)]
65pub enum Flag {
66 Disabled,
68 Enabled {
70 source: FlagSource,
71 name: &'static str,
73 },
74}
75
76impl Flag {
77 pub const fn disabled() -> Self {
79 Self::Disabled
80 }
81
82 pub const fn from_cli(name: &'static str) -> Self {
84 Self::Enabled {
85 source: FlagSource::Cli,
86 name,
87 }
88 }
89
90 pub const fn from_config(name: &'static str) -> Self {
92 Self::Enabled {
93 source: FlagSource::Config,
94 name,
95 }
96 }
97
98 pub fn is_enabled(self) -> bool {
100 matches!(self, Self::Enabled { .. })
101 }
102
103 pub fn source(self) -> Option<FlagSource> {
105 match self {
106 Self::Disabled => None,
107 Self::Enabled { source, .. } => Some(source),
108 }
109 }
110
111 pub fn name(self) -> Option<&'static str> {
113 match self {
114 Self::Disabled => None,
115 Self::Enabled { name, .. } => Some(name),
116 }
117 }
118}
119
120impl From<Flag> for bool {
121 fn from(flag: Flag) -> Self {
122 flag.is_enabled()
123 }
124}
125
126pub fn resolve_flag(cli_flag: bool, name: &'static str, env_flag: EnvFlag) -> Flag {
131 if cli_flag {
132 Flag::Enabled {
133 source: FlagSource::Cli,
134 name,
135 }
136 } else if env_flag.value == Some(true) {
137 Flag::Enabled {
138 source: FlagSource::Env(env_flag.env_var),
139 name,
140 }
141 } else {
142 Flag::Disabled
143 }
144}
145
146pub fn check_conflicts(flag_a: Flag, flag_b: Flag) {
151 if let (
152 Flag::Enabled {
153 source: source_a,
154 name: name_a,
155 },
156 Flag::Enabled {
157 source: source_b,
158 name: name_b,
159 },
160 ) = (flag_a, flag_b)
161 {
162 let display_a = match source_a {
163 FlagSource::Cli => format!("`--{name_a}`"),
164 FlagSource::Env(env) => format!("`{env}` (environment variable)"),
165 FlagSource::Config => format!("`{name_a}` (workspace configuration)"),
166 };
167 let display_b = match source_b {
168 FlagSource::Cli => format!("`--{name_b}`"),
169 FlagSource::Env(env) => format!("`{env}` (environment variable)"),
170 FlagSource::Config => format!("`{name_b}` (workspace configuration)"),
171 };
172 eprintln!(
173 "{}{} the argument {} cannot be used with {}",
174 "error".bold().red(),
175 ":".bold(),
176 display_a.green(),
177 display_b.green(),
178 );
179 #[allow(clippy::exit)]
180 {
181 std::process::exit(2);
182 }
183 }
184}
185
186impl From<RefreshArgs> for Refresh {
187 fn from(value: RefreshArgs) -> Self {
188 let RefreshArgs {
189 refresh,
190 no_refresh,
191 refresh_package,
192 } = value;
193
194 Self::from_args(flag(refresh, no_refresh, "no-refresh"), refresh_package)
195 }
196}
197
198impl From<ResolverArgs> for PipOptions {
199 fn from(args: ResolverArgs) -> Self {
200 let ResolverArgs {
201 index_args,
202 upgrade,
203 no_upgrade,
204 upgrade_package,
205 index_strategy,
206 keyring_provider,
207 resolution,
208 prerelease,
209 pre,
210 fork_strategy,
211 config_setting,
212 config_settings_package,
213 no_build_isolation,
214 no_build_isolation_package,
215 build_isolation,
216 exclude_newer,
217 link_mode,
218 no_sources,
219 exclude_newer_package,
220 } = args;
221
222 Self {
223 upgrade: flag(upgrade, no_upgrade, "no-upgrade"),
224 upgrade_package: Some(upgrade_package),
225 index_strategy,
226 keyring_provider,
227 resolution,
228 fork_strategy,
229 prerelease: if pre {
230 Some(PrereleaseMode::Allow)
231 } else {
232 prerelease
233 },
234 config_settings: config_setting
235 .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
236 config_settings_package: config_settings_package.map(|config_settings| {
237 config_settings
238 .into_iter()
239 .collect::<PackageConfigSettings>()
240 }),
241 no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
242 no_build_isolation_package: Some(no_build_isolation_package),
243 exclude_newer,
244 exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
245 link_mode,
246 no_sources: if no_sources { Some(true) } else { None },
247 ..Self::from(index_args)
248 }
249 }
250}
251
252impl From<InstallerArgs> for PipOptions {
253 fn from(args: InstallerArgs) -> Self {
254 let InstallerArgs {
255 index_args,
256 reinstall,
257 no_reinstall,
258 reinstall_package,
259 index_strategy,
260 keyring_provider,
261 config_setting,
262 config_settings_package,
263 no_build_isolation,
264 build_isolation,
265 exclude_newer,
266 link_mode,
267 compile_bytecode,
268 no_compile_bytecode,
269 no_sources,
270 exclude_newer_package,
271 } = args;
272
273 Self {
274 reinstall: flag(reinstall, no_reinstall, "reinstall"),
275 reinstall_package: Some(reinstall_package),
276 index_strategy,
277 keyring_provider,
278 config_settings: config_setting
279 .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
280 config_settings_package: config_settings_package.map(|config_settings| {
281 config_settings
282 .into_iter()
283 .collect::<PackageConfigSettings>()
284 }),
285 no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
286 exclude_newer,
287 exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
288 link_mode,
289 compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
290 no_sources: if no_sources { Some(true) } else { None },
291 ..Self::from(index_args)
292 }
293 }
294}
295
296impl From<ResolverInstallerArgs> for PipOptions {
297 fn from(args: ResolverInstallerArgs) -> Self {
298 let ResolverInstallerArgs {
299 index_args,
300 upgrade,
301 no_upgrade,
302 upgrade_package,
303 reinstall,
304 no_reinstall,
305 reinstall_package,
306 index_strategy,
307 keyring_provider,
308 resolution,
309 prerelease,
310 pre,
311 fork_strategy,
312 config_setting,
313 config_settings_package,
314 no_build_isolation,
315 no_build_isolation_package,
316 build_isolation,
317 exclude_newer,
318 link_mode,
319 compile_bytecode,
320 no_compile_bytecode,
321 no_sources,
322 exclude_newer_package,
323 } = args;
324
325 Self {
326 upgrade: flag(upgrade, no_upgrade, "upgrade"),
327 upgrade_package: Some(upgrade_package),
328 reinstall: flag(reinstall, no_reinstall, "reinstall"),
329 reinstall_package: Some(reinstall_package),
330 index_strategy,
331 keyring_provider,
332 resolution,
333 prerelease: if pre {
334 Some(PrereleaseMode::Allow)
335 } else {
336 prerelease
337 },
338 fork_strategy,
339 config_settings: config_setting
340 .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
341 config_settings_package: config_settings_package.map(|config_settings| {
342 config_settings
343 .into_iter()
344 .collect::<PackageConfigSettings>()
345 }),
346 no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
347 no_build_isolation_package: Some(no_build_isolation_package),
348 exclude_newer,
349 exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
350 link_mode,
351 compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
352 no_sources: if no_sources { Some(true) } else { None },
353 ..Self::from(index_args)
354 }
355 }
356}
357
358impl From<FetchArgs> for PipOptions {
359 fn from(args: FetchArgs) -> Self {
360 let FetchArgs {
361 index_args,
362 index_strategy,
363 keyring_provider,
364 exclude_newer,
365 } = args;
366
367 Self {
368 index_strategy,
369 keyring_provider,
370 exclude_newer,
371 ..Self::from(index_args)
372 }
373 }
374}
375
376impl From<IndexArgs> for PipOptions {
377 fn from(args: IndexArgs) -> Self {
378 let IndexArgs {
379 default_index,
380 index,
381 index_url,
382 extra_index_url,
383 no_index,
384 find_links,
385 } = args;
386
387 Self {
388 index: default_index
389 .and_then(Maybe::into_option)
390 .map(|default_index| vec![default_index])
391 .combine(index.map(|index| {
392 index
393 .iter()
394 .flat_map(std::clone::Clone::clone)
395 .filter_map(Maybe::into_option)
396 .collect()
397 })),
398 index_url: index_url.and_then(Maybe::into_option),
399 extra_index_url: extra_index_url.map(|extra_index_urls| {
400 extra_index_urls
401 .into_iter()
402 .filter_map(Maybe::into_option)
403 .collect()
404 }),
405 no_index: if no_index { Some(true) } else { None },
406 find_links: find_links.map(|find_links| {
407 find_links
408 .into_iter()
409 .filter_map(Maybe::into_option)
410 .collect()
411 }),
412 ..Self::default()
413 }
414 }
415}
416
417pub fn resolver_options(
419 resolver_args: ResolverArgs,
420 build_args: BuildOptionsArgs,
421) -> ResolverOptions {
422 let ResolverArgs {
423 index_args,
424 upgrade,
425 no_upgrade,
426 upgrade_package,
427 index_strategy,
428 keyring_provider,
429 resolution,
430 prerelease,
431 pre,
432 fork_strategy,
433 config_setting,
434 config_settings_package,
435 no_build_isolation,
436 no_build_isolation_package,
437 build_isolation,
438 exclude_newer,
439 link_mode,
440 no_sources,
441 exclude_newer_package,
442 } = resolver_args;
443
444 let BuildOptionsArgs {
445 no_build,
446 build,
447 no_build_package,
448 no_binary,
449 binary,
450 no_binary_package,
451 } = build_args;
452
453 ResolverOptions {
454 index: index_args
455 .default_index
456 .and_then(Maybe::into_option)
457 .map(|default_index| vec![default_index])
458 .combine(index_args.index.map(|index| {
459 index
460 .into_iter()
461 .flat_map(|v| v.clone())
462 .filter_map(Maybe::into_option)
463 .collect()
464 })),
465 index_url: index_args.index_url.and_then(Maybe::into_option),
466 extra_index_url: index_args.extra_index_url.map(|extra_index_url| {
467 extra_index_url
468 .into_iter()
469 .filter_map(Maybe::into_option)
470 .collect()
471 }),
472 no_index: if index_args.no_index {
473 Some(true)
474 } else {
475 None
476 },
477 find_links: index_args.find_links.map(|find_links| {
478 find_links
479 .into_iter()
480 .filter_map(Maybe::into_option)
481 .collect()
482 }),
483 upgrade: Upgrade::from_args(
484 flag(upgrade, no_upgrade, "no-upgrade"),
485 upgrade_package.into_iter().map(Requirement::from).collect(),
486 ),
487 index_strategy,
488 keyring_provider,
489 resolution,
490 prerelease: if pre {
491 Some(PrereleaseMode::Allow)
492 } else {
493 prerelease
494 },
495 fork_strategy,
496 dependency_metadata: None,
497 config_settings: config_setting
498 .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
499 config_settings_package: config_settings_package.map(|config_settings| {
500 config_settings
501 .into_iter()
502 .collect::<PackageConfigSettings>()
503 }),
504 build_isolation: BuildIsolation::from_args(
505 flag(no_build_isolation, build_isolation, "build-isolation"),
506 no_build_isolation_package,
507 ),
508 extra_build_dependencies: None,
509 extra_build_variables: None,
510 exclude_newer: ExcludeNewer::from_args(
511 exclude_newer,
512 exclude_newer_package.unwrap_or_default(),
513 ),
514 link_mode,
515 torch_backend: None,
516 no_build: flag(no_build, build, "build"),
517 no_build_package: Some(no_build_package),
518 no_binary: flag(no_binary, binary, "binary"),
519 no_binary_package: Some(no_binary_package),
520 no_sources: if no_sources { Some(true) } else { None },
521 }
522}
523
524pub fn resolver_installer_options(
526 resolver_installer_args: ResolverInstallerArgs,
527 build_args: BuildOptionsArgs,
528) -> ResolverInstallerOptions {
529 let ResolverInstallerArgs {
530 index_args,
531 upgrade,
532 no_upgrade,
533 upgrade_package,
534 reinstall,
535 no_reinstall,
536 reinstall_package,
537 index_strategy,
538 keyring_provider,
539 resolution,
540 prerelease,
541 pre,
542 fork_strategy,
543 config_setting,
544 config_settings_package,
545 no_build_isolation,
546 no_build_isolation_package,
547 build_isolation,
548 exclude_newer,
549 exclude_newer_package,
550 link_mode,
551 compile_bytecode,
552 no_compile_bytecode,
553 no_sources,
554 } = resolver_installer_args;
555
556 let BuildOptionsArgs {
557 no_build,
558 build,
559 no_build_package,
560 no_binary,
561 binary,
562 no_binary_package,
563 } = build_args;
564
565 let default_index = index_args
566 .default_index
567 .and_then(Maybe::into_option)
568 .map(|default_index| vec![default_index]);
569 let index = index_args.index.map(|index| {
570 index
571 .into_iter()
572 .flat_map(|v| v.clone())
573 .filter_map(Maybe::into_option)
574 .collect()
575 });
576
577 ResolverInstallerOptions {
578 index: default_index.combine(index),
579 index_url: index_args.index_url.and_then(Maybe::into_option),
580 extra_index_url: index_args.extra_index_url.map(|extra_index_url| {
581 extra_index_url
582 .into_iter()
583 .filter_map(Maybe::into_option)
584 .collect()
585 }),
586 no_index: if index_args.no_index {
587 Some(true)
588 } else {
589 None
590 },
591 find_links: index_args.find_links.map(|find_links| {
592 find_links
593 .into_iter()
594 .filter_map(Maybe::into_option)
595 .collect()
596 }),
597 upgrade: Upgrade::from_args(
598 flag(upgrade, no_upgrade, "upgrade"),
599 upgrade_package.into_iter().map(Requirement::from).collect(),
600 ),
601 reinstall: Reinstall::from_args(
602 flag(reinstall, no_reinstall, "reinstall"),
603 reinstall_package,
604 ),
605 index_strategy,
606 keyring_provider,
607 resolution,
608 prerelease: if pre {
609 Some(PrereleaseMode::Allow)
610 } else {
611 prerelease
612 },
613 fork_strategy,
614 dependency_metadata: None,
615 config_settings: config_setting
616 .map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
617 config_settings_package: config_settings_package.map(|config_settings| {
618 config_settings
619 .into_iter()
620 .collect::<PackageConfigSettings>()
621 }),
622 build_isolation: BuildIsolation::from_args(
623 flag(no_build_isolation, build_isolation, "build-isolation"),
624 no_build_isolation_package,
625 ),
626 extra_build_dependencies: None,
627 extra_build_variables: None,
628 exclude_newer,
629 exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
630 link_mode,
631 compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
632 no_build: flag(no_build, build, "build"),
633 no_build_package: if no_build_package.is_empty() {
634 None
635 } else {
636 Some(no_build_package)
637 },
638 no_binary: flag(no_binary, binary, "binary"),
639 no_binary_package: if no_binary_package.is_empty() {
640 None
641 } else {
642 Some(no_binary_package)
643 },
644 no_sources: if no_sources { Some(true) } else { None },
645 torch_backend: None,
646 }
647}