Skip to main content

uv_settings/
settings.rs

1#[cfg(feature = "schemars")]
2use std::borrow::Cow;
3use std::{
4    fmt::{self, Debug},
5    num::NonZeroUsize,
6    path::Path,
7    path::PathBuf,
8};
9
10use serde::de::{Error, SeqAccess, Visitor, value::SeqAccessDeserializer};
11use serde::{Deserialize, Deserializer, Serialize};
12
13use uv_cache_info::CacheKey;
14use uv_configuration::{
15    AddBoundsKind, AnnotationStyle, BuildIsolation, ExcludeDependency, ExcludeNewerPackage,
16    ForkStrategy, IndexStrategy, KeyringProviderType, PackageNameSpecifier, PrereleaseMode,
17    PrereleasePackage, ProxyUrl, Reinstall, RequiredVersion, ResolutionMode, TargetTriple,
18    TrustedHost, TrustedPublishing, Upgrade, serialize_exclude_newer_package_with_spans,
19};
20use uv_distribution_types::{
21    ConfigSettings, ExcludeNewerOverride, ExcludeNewerSpan, ExcludeNewerValue, ExtraBuildVariables,
22    Index, IndexLocations, IndexUrl, IndexUrlError, MinimumLibcVersion, Origin,
23    PackageConfigSettings, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata,
24};
25use uv_install_wheel::LinkMode;
26use uv_macros::{CombineOptions, OptionsMetadata};
27use uv_normalize::{ExtraName, PackageName, PipGroupName};
28use uv_pep508::Requirement;
29use uv_preview::{MaybePreviewFeature, Preview};
30use uv_pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
31use uv_python_types::{PythonDownloadMirrors, PythonDownloads, PythonPreference, PythonVersion};
32use uv_redacted::DisplaySafeUrl;
33use uv_torch::TorchMode;
34use uv_workspace::pyproject::{
35    BuildConstraintDependency, ExtraBuildDependencies, OverrideDependency,
36};
37
38use crate::{EnvironmentOptions, FilesystemOptions};
39
40/// A `pyproject.toml` with an (optional) `[tool.uv]` section.
41#[allow(dead_code)]
42#[derive(Debug, Clone, Default, Deserialize)]
43pub(crate) struct PyProjectToml {
44    pub(crate) tool: Option<Tools>,
45}
46
47/// A `[tool]` section.
48#[allow(dead_code)]
49#[derive(Debug, Clone, Default, Deserialize)]
50pub(crate) struct Tools {
51    pub(crate) uv: Option<Options>,
52}
53
54/// A `pyproject.toml` with an (optional) `[tool.uv.required-version]`.
55#[derive(Debug, Clone, Default, Deserialize)]
56pub(crate) struct PyProjectRequiredVersionToml {
57    pub(crate) tool: Option<RequiredVersionTools>,
58}
59
60/// A `[tool]` section containing only the fields required for `required-version` discovery.
61#[derive(Debug, Clone, Default, Deserialize)]
62pub(crate) struct RequiredVersionTools {
63    pub(crate) uv: Option<RequiredVersionOptions>,
64}
65
66/// The minimal `[tool.uv]` subset required to enforce `required-version` before full parsing.
67#[derive(Debug, Clone, Default, Deserialize)]
68#[serde(rename_all = "kebab-case")]
69pub(crate) struct RequiredVersionOptions {
70    pub(crate) required_version: Option<RequiredVersion>,
71}
72
73/// A `uv.toml` containing only the fields required for `required-version` discovery.
74#[derive(Debug, Clone, Default, Deserialize)]
75#[serde(rename_all = "kebab-case")]
76pub(crate) struct UvRequiredVersionToml {
77    pub(crate) required_version: Option<RequiredVersion>,
78}
79
80/// A `[tool.uv]` section.
81#[allow(dead_code)]
82#[derive(Debug, Clone, Default, Deserialize, CombineOptions, OptionsMetadata)]
83#[serde(try_from = "OptionsWire", rename_all = "kebab-case")]
84#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
85#[cfg_attr(feature = "schemars", schemars(!try_from))]
86pub struct Options {
87    #[serde(flatten)]
88    pub globals: GlobalOptions,
89
90    #[serde(flatten)]
91    pub top_level: ResolverInstallerSchema,
92
93    #[serde(flatten)]
94    pub install_mirrors: PythonInstallMirrors,
95
96    #[serde(flatten)]
97    pub publish: PublishOptions,
98
99    #[serde(flatten)]
100    pub add: AddOptions,
101
102    #[option_group]
103    pub audit: Option<AuditOptions>,
104
105    #[option_group]
106    pub pip: Option<PipOptions>,
107
108    /// The keys to consider when caching builds for the project.
109    ///
110    /// Cache keys enable you to specify the files or directories that should trigger a rebuild when
111    /// modified. By default, uv will rebuild a project whenever the `pyproject.toml`, `setup.py`,
112    /// or `setup.cfg` files in the project directory are modified, or if a `src` directory is
113    /// added or removed, i.e.:
114    ///
115    /// ```toml
116    /// cache-keys = [{ file = "pyproject.toml" }, { file = "setup.py" }, { file = "setup.cfg" }, { dir = "src" }]
117    /// ```
118    ///
119    /// As an example: if a project uses dynamic metadata to read its dependencies from a
120    /// `requirements.txt` file, you can specify `cache-keys = [{ file = "requirements.txt" }, { file = "pyproject.toml" }]`
121    /// to ensure that the project is rebuilt whenever the `requirements.txt` file is modified (in
122    /// addition to watching the `pyproject.toml`).
123    ///
124    /// Globs are supported, following the syntax of the [`glob`](https://docs.rs/glob/0.3.1/glob/struct.Pattern.html)
125    /// crate. For example, to invalidate the cache whenever a `.toml` file in the project directory
126    /// or any of its subdirectories is modified, you can specify `cache-keys = [{ file = "**/*.toml" }]`.
127    /// Note that the use of globs can be expensive, as uv may need to walk the filesystem to
128    /// determine whether any files have changed.
129    ///
130    /// Cache keys can also include version control information. For example, if a project uses
131    /// `setuptools_scm` to read its version from a Git commit, you can specify `cache-keys = [{ git = { commit = true }, { file = "pyproject.toml" }]`
132    /// to include the current Git commit hash in the cache key (in addition to the
133    /// `pyproject.toml`). Git tags are also supported via `cache-keys = [{ git = { commit = true, tags = true } }]`.
134    ///
135    /// Cache keys can also include environment variables. For example, if a project relies on
136    /// `MACOSX_DEPLOYMENT_TARGET` or other environment variables to determine its behavior, you can
137    /// specify `cache-keys = [{ env = "MACOSX_DEPLOYMENT_TARGET" }]` to invalidate the cache
138    /// whenever the environment variable changes.
139    ///
140    /// Cache keys only affect the project defined by the `pyproject.toml` in which they're
141    /// specified (as opposed to, e.g., affecting all members in a workspace), and all paths and
142    /// globs are interpreted as relative to the project directory.
143    #[option(
144        default = r#"[{ file = "pyproject.toml" }, { file = "setup.py" }, { file = "setup.cfg" }]"#,
145        value_type = "list[dict]",
146        example = r#"
147            cache-keys = [{ file = "pyproject.toml" }, { file = "requirements.txt" }, { git = { commit = true } }]
148        "#
149    )]
150    pub cache_keys: Option<Vec<CacheKey>>,
151
152    // NOTE(charlie): These fields are shared with `ToolUv` in
153    // `crates/uv-workspace/src/pyproject.rs`. The documentation lives on that struct.
154    // They're respected in both `pyproject.toml` and `uv.toml` files.
155    #[cfg_attr(feature = "schemars", schemars(skip))]
156    pub override_dependencies: Option<Vec<OverrideDependency>>,
157
158    #[cfg_attr(feature = "schemars", schemars(skip))]
159    pub exclude_dependencies: Option<Vec<ExcludeDependency>>,
160
161    #[cfg_attr(feature = "schemars", schemars(skip))]
162    pub constraint_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
163
164    #[cfg_attr(feature = "schemars", schemars(skip))]
165    pub build_constraint_dependencies: Option<Vec<BuildConstraintDependency>>,
166
167    #[cfg_attr(feature = "schemars", schemars(skip))]
168    pub environments: Option<SupportedEnvironments>,
169
170    #[cfg_attr(feature = "schemars", schemars(skip))]
171    pub required_environments: Option<SupportedEnvironments>,
172
173    #[cfg_attr(feature = "schemars", schemars(skip))]
174    pub minimum_libc_version: Option<MinimumLibcVersion>,
175
176    // NOTE(charlie): These fields should be kept in-sync with `ToolUv` in
177    // `crates/uv-workspace/src/pyproject.rs`. The documentation lives on that struct.
178    // They're only respected in `pyproject.toml` files, and should be rejected in `uv.toml` files.
179    #[cfg_attr(feature = "schemars", schemars(skip))]
180    pub(crate) conflicts: Option<serde::de::IgnoredAny>,
181
182    #[cfg_attr(feature = "schemars", schemars(skip))]
183    pub(crate) workspace: Option<serde::de::IgnoredAny>,
184
185    #[cfg_attr(feature = "schemars", schemars(skip))]
186    pub(crate) sources: Option<serde::de::IgnoredAny>,
187
188    #[cfg_attr(feature = "schemars", schemars(skip))]
189    pub(crate) dev_dependencies: Option<serde::de::IgnoredAny>,
190
191    #[cfg_attr(feature = "schemars", schemars(skip))]
192    pub(crate) default_groups: Option<serde::de::IgnoredAny>,
193
194    #[cfg_attr(feature = "schemars", schemars(skip))]
195    pub(crate) dependency_groups: Option<serde::de::IgnoredAny>,
196
197    #[cfg_attr(feature = "schemars", schemars(skip))]
198    pub(crate) managed: Option<serde::de::IgnoredAny>,
199
200    #[cfg_attr(feature = "schemars", schemars(skip))]
201    pub(crate) r#package: Option<serde::de::IgnoredAny>,
202
203    #[cfg_attr(feature = "schemars", schemars(skip))]
204    pub(crate) build_backend: Option<serde::de::IgnoredAny>,
205}
206
207impl Options {
208    /// Construct an [`Options`] with the given global and top-level settings.
209    pub fn simple(globals: GlobalOptions, top_level: ResolverInstallerSchema) -> Self {
210        Self {
211            globals,
212            top_level,
213            ..Default::default()
214        }
215    }
216
217    /// Set the [`Origin`] on all indexes without an existing origin.
218    #[must_use]
219    pub(crate) fn with_origin(mut self, origin: Origin) -> Self {
220        if let Some(indexes) = &mut self.top_level.index {
221            for index in indexes {
222                index.origin.get_or_insert(origin);
223            }
224        }
225        if let Some(index_url) = &mut self.top_level.index_url {
226            index_url.try_set_origin(origin);
227        }
228        if let Some(extra_index_urls) = &mut self.top_level.extra_index_url {
229            for index_url in extra_index_urls {
230                index_url.try_set_origin(origin);
231            }
232        }
233        if let Some(pip) = &mut self.pip {
234            if let Some(indexes) = &mut pip.index {
235                for index in indexes {
236                    index.origin.get_or_insert(origin);
237                }
238            }
239            if let Some(index_url) = &mut pip.index_url {
240                index_url.try_set_origin(origin);
241            }
242            if let Some(extra_index_urls) = &mut pip.extra_index_url {
243                for index_url in extra_index_urls {
244                    index_url.try_set_origin(origin);
245                }
246            }
247        }
248        self
249    }
250
251    /// Resolve the [`Options`] relative to the given root directory.
252    pub(crate) fn relative_to(self, root_dir: &Path) -> Result<Self, IndexUrlError> {
253        Ok(Self {
254            top_level: self.top_level.relative_to(root_dir)?,
255            pip: self.pip.map(|pip| pip.relative_to(root_dir)).transpose()?,
256            ..self
257        })
258    }
259}
260
261/// Global settings, relevant to all invocations.
262#[derive(Debug, Clone, Default, Deserialize, CombineOptions, OptionsMetadata)]
263#[serde(try_from = "GlobalOptionsWire", rename_all = "kebab-case")]
264#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
265#[cfg_attr(feature = "schemars", schemars(!try_from))]
266pub struct GlobalOptions {
267    /// Enforce a requirement on the version of uv.
268    ///
269    /// If the version of uv does not meet the requirement at runtime, uv will exit
270    /// with an error.
271    ///
272    /// Accepts a [PEP 440](https://peps.python.org/pep-0440/) specifier, like `==0.5.0` or `>=0.5.0`.
273    #[option(
274        default = "null",
275        value_type = "str",
276        example = r#"
277            required-version = ">=0.5.0"
278        "#
279    )]
280    pub required_version: Option<RequiredVersion>,
281    /// Whether to load TLS certificates from the platform's native certificate store.
282    ///
283    /// By default, uv uses bundled Mozilla root certificates. When enabled, this loads
284    /// certificates from the platform's native certificate store instead.
285    #[option(
286        default = "false",
287        value_type = "bool",
288        uv_toml_only = true,
289        example = r#"
290            system-certs = true
291        "#
292    )]
293    pub system_certs: Option<bool>,
294    /// Whether to load TLS certificates from the platform's native certificate store.
295    ///
296    /// By default, uv uses bundled Mozilla root certificates. When enabled, this loads
297    /// certificates from the platform's native certificate store instead.
298    ///
299    /// (Deprecated: use `system-certs` instead.)
300    #[deprecated(note = "use `system-certs` instead")]
301    #[option(
302        default = "false",
303        value_type = "bool",
304        uv_toml_only = true,
305        example = r#"
306            native-tls = true
307        "#
308    )]
309    pub native_tls: Option<bool>,
310    /// Disable network access, relying only on locally cached data and locally available files.
311    #[option(
312        default = "false",
313        value_type = "bool",
314        example = r#"
315            offline = true
316        "#
317    )]
318    pub offline: Option<bool>,
319    /// Avoid reading from or writing to the cache, instead using a temporary directory for the
320    /// duration of the operation.
321    #[option(
322        default = "false",
323        value_type = "bool",
324        example = r#"
325            no-cache = true
326        "#
327    )]
328    pub no_cache: Option<bool>,
329    /// Path to the cache directory.
330    ///
331    /// Defaults to `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Linux and macOS, and
332    /// `%LOCALAPPDATA%\uv\cache` on Windows.
333    #[option(
334        default = "None",
335        value_type = "str",
336        uv_toml_only = true,
337        example = r#"
338            cache-dir = "./.uv_cache"
339        "#
340    )]
341    pub cache_dir: Option<PathBuf>,
342
343    /// The user's preview configuration.
344    #[serde(flatten)]
345    pub preview: Option<PreviewOption>,
346
347    /// Whether to prefer using Python installations that are already present on the system, or
348    /// those that are downloaded and installed by uv.
349    #[option(
350        default = "\"managed\"",
351        value_type = "str",
352        example = r#"
353            python-preference = "managed"
354        "#,
355        possible_values = true
356    )]
357    pub python_preference: Option<PythonPreference>,
358    /// Whether to allow Python downloads.
359    #[option(
360        default = "\"automatic\"",
361        value_type = "str",
362        example = r#"
363            python-downloads = "manual"
364        "#,
365        possible_values = true
366    )]
367    pub python_downloads: Option<PythonDownloads>,
368    /// The maximum number of in-flight concurrent downloads that uv will perform at any given
369    /// time.
370    #[option(
371        default = "50",
372        value_type = "int",
373        example = r#"
374            concurrent-downloads = 4
375        "#
376    )]
377    pub concurrent_downloads: Option<NonZeroUsize>,
378    /// The maximum number of source distributions that uv will build concurrently at any given
379    /// time.
380    ///
381    /// Defaults to the number of available CPU cores.
382    #[option(
383        default = "None",
384        value_type = "int",
385        example = r#"
386            concurrent-builds = 4
387        "#
388    )]
389    pub concurrent_builds: Option<NonZeroUsize>,
390    /// The number of threads used when installing and unzipping packages.
391    ///
392    /// Defaults to the number of available CPU cores.
393    #[option(
394        default = "None",
395        value_type = "int",
396        example = r#"
397            concurrent-installs = 4
398        "#
399    )]
400    pub concurrent_installs: Option<NonZeroUsize>,
401    /// The URL of the HTTP proxy to use.
402    #[option(
403        default = "None",
404        value_type = "str",
405        uv_toml_only = true,
406        example = r#"
407            http-proxy = "http://proxy.example.com"
408        "#
409    )]
410    pub http_proxy: Option<ProxyUrl>,
411    /// The URL of the HTTPS proxy to use.
412    #[option(
413        default = "None",
414        value_type = "str",
415        uv_toml_only = true,
416        example = r#"
417            https-proxy = "https://proxy.example.com"
418        "#
419    )]
420    pub https_proxy: Option<ProxyUrl>,
421    /// A list of hosts to exclude from proxying.
422    #[option(
423        default = "None",
424        value_type = "list[str]",
425        uv_toml_only = true,
426        example = r#"
427            no-proxy = ["localhost", "127.0.0.1"]
428        "#
429    )]
430    pub no_proxy: Option<Vec<String>>,
431    /// Allow insecure connections to host.
432    ///
433    /// Expects to receive either a hostname (e.g., `localhost`), a host-port pair (e.g.,
434    /// `localhost:8080`), or a URL (e.g., `https://localhost`).
435    ///
436    /// WARNING: Hosts included in this list will not be verified against the system's certificate
437    /// store. Only use `--allow-insecure-host` in a secure network with verified sources, as it
438    /// bypasses SSL verification and could expose you to MITM attacks.
439    #[option(
440        default = "[]",
441        value_type = "list[str]",
442        example = r#"
443            allow-insecure-host = ["localhost:8080"]
444        "#
445    )]
446    pub allow_insecure_host: Option<Vec<TrustedHost>>,
447}
448
449/// Like [`GlobalOptions`], but with any `#[serde(flatten)]` fields inlined.
450/// This improves line/column information in error messages.
451#[derive(Debug, Clone, Default, Deserialize)]
452#[serde(rename_all = "kebab-case")]
453struct GlobalOptionsWire {
454    required_version: Option<RequiredVersion>,
455    system_certs: Option<bool>,
456    native_tls: Option<bool>,
457    offline: Option<bool>,
458    no_cache: Option<bool>,
459    cache_dir: Option<PathBuf>,
460
461    preview: Option<bool>,
462    preview_features: Option<PreviewFeaturesOption>,
463
464    python_preference: Option<PythonPreference>,
465    python_downloads: Option<PythonDownloads>,
466    concurrent_downloads: Option<NonZeroUsize>,
467    concurrent_builds: Option<NonZeroUsize>,
468    concurrent_installs: Option<NonZeroUsize>,
469    http_proxy: Option<ProxyUrl>,
470    https_proxy: Option<ProxyUrl>,
471    no_proxy: Option<Vec<String>>,
472    allow_insecure_host: Option<Vec<TrustedHost>>,
473}
474
475impl TryFrom<GlobalOptionsWire> for GlobalOptions {
476    type Error = &'static str;
477
478    #[allow(deprecated)]
479    fn try_from(value: GlobalOptionsWire) -> Result<Self, Self::Error> {
480        let GlobalOptionsWire {
481            required_version,
482            system_certs,
483            native_tls,
484            offline,
485            no_cache,
486            cache_dir,
487            preview,
488            preview_features,
489            python_preference,
490            python_downloads,
491            concurrent_downloads,
492            concurrent_builds,
493            concurrent_installs,
494            http_proxy,
495            https_proxy,
496            no_proxy,
497            allow_insecure_host,
498        } = value;
499
500        Ok(Self {
501            required_version,
502            system_certs,
503            native_tls,
504            offline,
505            no_cache,
506            cache_dir,
507            preview: PreviewOption::try_from(preview, preview_features)?,
508            python_preference,
509            python_downloads,
510            concurrent_downloads,
511            concurrent_builds,
512            concurrent_installs,
513            http_proxy,
514            https_proxy,
515            no_proxy,
516            allow_insecure_host,
517        })
518    }
519}
520
521/// Resolve registry indexes and find-links relative to the given root directory.
522fn rebase_indexes(
523    root_dir: &Path,
524    indexes: &mut Option<Vec<Index>>,
525    index_url: &mut Option<PipIndex>,
526    extra_index_urls: &mut Option<Vec<PipExtraIndex>>,
527    find_links: &mut Option<Vec<PipFindLinks>>,
528) -> Result<(), IndexUrlError> {
529    *indexes = indexes
530        .take()
531        .map(|indexes| {
532            indexes
533                .into_iter()
534                .map(|index| index.relative_to(root_dir))
535                .collect::<Result<Vec<_>, _>>()
536        })
537        .transpose()?;
538    *index_url = index_url
539        .take()
540        .map(|index| index.relative_to(root_dir))
541        .transpose()?;
542    *extra_index_urls = extra_index_urls
543        .take()
544        .map(|indexes| {
545            indexes
546                .into_iter()
547                .map(|index| index.relative_to(root_dir))
548                .collect::<Result<Vec<_>, _>>()
549        })
550        .transpose()?;
551    *find_links = find_links
552        .take()
553        .map(|find_links| {
554            find_links
555                .into_iter()
556                .map(|find_link| find_link.relative_to(root_dir))
557                .collect::<Result<Vec<_>, _>>()
558        })
559        .transpose()?;
560
561    Ok(())
562}
563
564/// Settings relevant to all installer operations.
565#[derive(Debug, Clone, Default, CombineOptions)]
566pub struct InstallerOptions {
567    index: Option<Vec<Index>>,
568    index_url: Option<PipIndex>,
569    extra_index_url: Option<Vec<PipExtraIndex>>,
570    no_index: Option<bool>,
571    find_links: Option<Vec<PipFindLinks>>,
572    index_strategy: Option<IndexStrategy>,
573    keyring_provider: Option<KeyringProviderType>,
574    config_settings: Option<ConfigSettings>,
575    exclude_newer: Option<ExcludeNewerOverride>,
576    link_mode: Option<LinkMode>,
577    compile_bytecode: Option<bool>,
578    reinstall: Option<Reinstall>,
579    build_isolation: Option<BuildIsolation>,
580    no_build: Option<bool>,
581    no_build_package: Option<Vec<PackageName>>,
582    no_binary: Option<bool>,
583    no_binary_package: Option<Vec<PackageName>>,
584    no_sources: Option<bool>,
585    no_sources_package: Option<Vec<PackageName>>,
586}
587
588/// Settings shared by all operations that use package indexes.
589#[derive(Debug, Clone, Default, CombineOptions)]
590pub struct IndexOptions {
591    pub index: Option<Vec<Index>>,
592    pub index_url: Option<PipIndex>,
593    pub extra_index_url: Option<Vec<PipExtraIndex>>,
594    pub no_index: Option<bool>,
595    pub find_links: Option<Vec<PipFindLinks>>,
596}
597
598impl IndexOptions {
599    /// Resolve the [`IndexOptions`] relative to the given root directory.
600    pub fn relative_to(mut self, root_dir: &Path) -> Result<Self, IndexUrlError> {
601        rebase_indexes(
602            root_dir,
603            &mut self.index,
604            &mut self.index_url,
605            &mut self.extra_index_url,
606            &mut self.find_links,
607        )?;
608
609        Ok(self)
610    }
611}
612
613impl From<IndexOptions> for IndexLocations {
614    fn from(value: IndexOptions) -> Self {
615        let IndexOptions {
616            index,
617            index_url,
618            extra_index_url,
619            no_index,
620            find_links,
621        } = value;
622
623        Self::new(
624            index
625                .into_iter()
626                .flatten()
627                .chain(extra_index_url.into_iter().flatten().map(Index::from))
628                .chain(index_url.into_iter().map(Index::from))
629                .collect(),
630            find_links.into_iter().flatten().map(Index::from).collect(),
631            no_index.unwrap_or_default(),
632        )
633    }
634}
635
636impl From<IndexOptions> for PipOptions {
637    fn from(value: IndexOptions) -> Self {
638        let IndexOptions {
639            index,
640            index_url,
641            extra_index_url,
642            no_index,
643            find_links,
644        } = value;
645
646        Self {
647            index,
648            index_url,
649            extra_index_url,
650            no_index,
651            find_links,
652            ..Self::default()
653        }
654    }
655}
656
657/// Settings relevant to all resolver operations.
658#[derive(Debug, Clone, Default, CombineOptions)]
659pub struct ResolverOptions {
660    pub indexes: IndexOptions,
661    pub index_strategy: Option<IndexStrategy>,
662    pub keyring_provider: Option<KeyringProviderType>,
663    pub resolution: Option<ResolutionMode>,
664    pub prerelease: Option<PrereleaseMode>,
665    pub prerelease_package: Option<PrereleasePackage>,
666    pub fork_strategy: Option<ForkStrategy>,
667    pub dependency_metadata: Option<Vec<StaticMetadata>>,
668    pub config_settings: Option<ConfigSettings>,
669    pub config_settings_package: Option<PackageConfigSettings>,
670    pub exclude_newer: Option<ExcludeNewerOverride>,
671    pub exclude_newer_package: Option<ExcludeNewerPackage>,
672    pub link_mode: Option<LinkMode>,
673    pub torch_backend: Option<TorchMode>,
674    pub upgrade: Option<Upgrade>,
675    pub build_isolation: Option<BuildIsolation>,
676    pub no_build: Option<bool>,
677    pub no_build_package: Option<Vec<PackageName>>,
678    pub no_binary: Option<bool>,
679    pub no_binary_package: Option<Vec<PackageName>>,
680    pub extra_build_dependencies: Option<ExtraBuildDependencies>,
681    pub extra_build_variables: Option<ExtraBuildVariables>,
682    pub no_sources: Option<bool>,
683    pub no_sources_package: Option<Vec<PackageName>>,
684}
685
686impl ResolverOptions {
687    /// Resolve the [`ResolverOptions`] relative to the given root directory.
688    pub fn relative_to(mut self, root_dir: &Path) -> Result<Self, IndexUrlError> {
689        self.indexes = self.indexes.relative_to(root_dir)?;
690        Ok(self)
691    }
692}
693
694/// Shared settings, relevant to all operations that must resolve and install dependencies. The
695/// union of [`InstallerOptions`] and [`ResolverOptions`].
696#[derive(Debug, Clone, Default, CombineOptions)]
697pub struct ResolverInstallerOptions {
698    pub indexes: IndexOptions,
699    pub index_strategy: Option<IndexStrategy>,
700    pub keyring_provider: Option<KeyringProviderType>,
701    pub resolution: Option<ResolutionMode>,
702    pub prerelease: Option<PrereleaseMode>,
703    pub prerelease_package: Option<PrereleasePackage>,
704    pub fork_strategy: Option<ForkStrategy>,
705    pub dependency_metadata: Option<Vec<StaticMetadata>>,
706    pub config_settings: Option<ConfigSettings>,
707    pub config_settings_package: Option<PackageConfigSettings>,
708    pub build_isolation: Option<BuildIsolation>,
709    pub extra_build_dependencies: Option<ExtraBuildDependencies>,
710    pub extra_build_variables: Option<ExtraBuildVariables>,
711    pub exclude_newer: Option<ExcludeNewerOverride>,
712    pub exclude_newer_package: Option<ExcludeNewerPackage>,
713    pub link_mode: Option<LinkMode>,
714    pub torch_backend: Option<TorchMode>,
715    pub compile_bytecode: Option<bool>,
716    pub no_sources: Option<bool>,
717    pub no_sources_package: Option<Vec<PackageName>>,
718    pub upgrade: Option<Upgrade>,
719    pub reinstall: Option<Reinstall>,
720    pub no_build: Option<bool>,
721    pub no_build_package: Option<Vec<PackageName>>,
722    pub no_binary: Option<bool>,
723    pub no_binary_package: Option<Vec<PackageName>>,
724}
725
726impl ResolverInstallerOptions {
727    /// Resolve the [`ResolverInstallerOptions`] relative to the given root directory.
728    pub fn relative_to(mut self, root_dir: &Path) -> Result<Self, IndexUrlError> {
729        self.indexes = self.indexes.relative_to(root_dir)?;
730        Ok(self)
731    }
732}
733
734impl From<ResolverInstallerSchema> for ResolverInstallerOptions {
735    fn from(value: ResolverInstallerSchema) -> Self {
736        let ResolverInstallerSchema {
737            index,
738            index_url,
739            extra_index_url,
740            no_index,
741            find_links,
742            index_strategy,
743            keyring_provider,
744            resolution,
745            prerelease,
746            prerelease_package,
747            fork_strategy,
748            dependency_metadata,
749            config_settings,
750            config_settings_package,
751            no_build_isolation,
752            no_build_isolation_package,
753            extra_build_dependencies,
754            extra_build_variables,
755            exclude_newer,
756            exclude_newer_package,
757            link_mode,
758            torch_backend,
759            compile_bytecode,
760            no_sources,
761            no_sources_package,
762            upgrade,
763            upgrade_package,
764            reinstall,
765            reinstall_package,
766            no_build,
767            no_build_package,
768            no_binary,
769            no_binary_package,
770        } = value;
771        Self {
772            indexes: IndexOptions {
773                index,
774                index_url,
775                extra_index_url,
776                no_index,
777                find_links,
778            },
779            index_strategy,
780            keyring_provider,
781            resolution,
782            prerelease,
783            prerelease_package,
784            fork_strategy,
785            dependency_metadata,
786            config_settings,
787            config_settings_package,
788            build_isolation: BuildIsolation::from_args(
789                no_build_isolation,
790                no_build_isolation_package.into_iter().flatten().collect(),
791            ),
792            extra_build_dependencies,
793            extra_build_variables,
794            exclude_newer,
795            exclude_newer_package,
796            link_mode,
797            torch_backend,
798            compile_bytecode,
799            no_sources,
800            no_sources_package,
801            upgrade: Upgrade::from_args(
802                upgrade,
803                upgrade_package
804                    .into_iter()
805                    .flatten()
806                    .map(Into::into)
807                    .collect(),
808                Vec::new(),
809            ),
810            reinstall: Reinstall::from_args(reinstall, reinstall_package.unwrap_or_default()),
811            no_build,
812            no_build_package,
813            no_binary,
814            no_binary_package,
815        }
816    }
817}
818
819impl ResolverInstallerSchema {
820    /// Resolve the [`ResolverInstallerSchema`] relative to the given root directory.
821    fn relative_to(mut self, root_dir: &Path) -> Result<Self, IndexUrlError> {
822        rebase_indexes(
823            root_dir,
824            &mut self.index,
825            &mut self.index_url,
826            &mut self.extra_index_url,
827            &mut self.find_links,
828        )?;
829
830        Ok(self)
831    }
832}
833
834/// The JSON schema for the `[tool.uv]` section of a `pyproject.toml` file.
835#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)]
836#[serde(rename_all = "kebab-case")]
837#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
838pub struct ResolverInstallerSchema {
839    /// The package indexes to use when resolving dependencies.
840    ///
841    /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
842    /// (the simple repository API), or a local directory laid out in the same format.
843    ///
844    /// Indexes are considered in the order in which they're defined, such that the first-defined
845    /// index has the highest priority. Further, the indexes provided by this setting are given
846    /// higher priority than any indexes specified via [`index_url`](#index-url) or
847    /// [`extra_index_url`](#extra-index-url). uv will only consider the first index that contains
848    /// a given package, unless an alternative [index strategy](#index-strategy) is specified.
849    ///
850    /// If an index is marked as `explicit = true`, it will be used exclusively for those
851    /// dependencies that select it explicitly via `[tool.uv.sources]`, as in:
852    ///
853    /// ```toml
854    /// [[tool.uv.index]]
855    /// name = "pytorch"
856    /// url = "https://download.pytorch.org/whl/cu130"
857    /// explicit = true
858    ///
859    /// [tool.uv.sources]
860    /// torch = { index = "pytorch" }
861    /// ```
862    ///
863    /// If an index is marked as `default = true`, it will be moved to the end of the prioritized list, such that it is
864    /// given the lowest priority when resolving packages. Additionally, marking an index as default will disable the
865    /// PyPI default index.
866    #[option(
867        default = "\"[]\"",
868        value_type = "dict",
869        example = r#"
870            [[tool.uv.index]]
871            name = "pytorch"
872            url = "https://download.pytorch.org/whl/cu130"
873        "#
874    )]
875    pub index: Option<Vec<Index>>,
876    /// The URL of the Python package index (by default: <https://pypi.org/simple>).
877    ///
878    /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
879    /// (the simple repository API), or a local directory laid out in the same format.
880    ///
881    /// The index provided by this setting is given lower priority than any indexes specified via
882    /// [`extra_index_url`](#extra-index-url) or [`index`](#index).
883    ///
884    /// (Deprecated: use `index` instead.)
885    #[option(
886        default = "\"https://pypi.org/simple\"",
887        value_type = "str",
888        example = r#"
889            index-url = "https://test.pypi.org/simple"
890        "#
891    )]
892    pub index_url: Option<PipIndex>,
893    /// Extra URLs of package indexes to use, in addition to `--index-url`.
894    ///
895    /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
896    /// (the simple repository API), or a local directory laid out in the same format.
897    ///
898    /// All indexes provided via this flag take priority over the index specified by
899    /// [`index_url`](#index-url) or [`index`](#index) with `default = true`. When multiple indexes
900    /// are provided, earlier values take priority.
901    ///
902    /// To control uv's resolution strategy when multiple indexes are present, see
903    /// [`index_strategy`](#index-strategy).
904    ///
905    /// (Deprecated: use `index` instead.)
906    #[option(
907        default = "[]",
908        value_type = "list[str]",
909        example = r#"
910            extra-index-url = ["https://download.pytorch.org/whl/cpu"]
911        "#
912    )]
913    pub extra_index_url: Option<Vec<PipExtraIndex>>,
914    /// Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and
915    /// those provided via `--find-links`.
916    #[option(
917        default = "false",
918        value_type = "bool",
919        example = r#"
920            no-index = true
921        "#
922    )]
923    pub no_index: Option<bool>,
924    /// Locations to search for candidate distributions, in addition to those found in the registry
925    /// indexes.
926    ///
927    /// If a path, the target must be a directory that contains packages as wheel files (`.whl`) or
928    /// source distributions (e.g., `.tar.gz` or `.zip`) at the top level.
929    ///
930    /// If a URL, the page must contain a flat list of links to package files adhering to the
931    /// formats described above.
932    #[option(
933        default = "[]",
934        value_type = "list[str]",
935        example = r#"
936            find-links = ["https://download.pytorch.org/whl/torch_stable.html"]
937        "#
938    )]
939    pub find_links: Option<Vec<PipFindLinks>>,
940    /// The strategy to use when resolving against multiple index URLs.
941    ///
942    /// By default, uv will stop at the first index on which a given package is available, and
943    /// limit resolutions to those present on that first index (`first-index`). This prevents
944    /// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the
945    /// same name to an alternate index.
946    #[option(
947        default = "\"first-index\"",
948        value_type = "str",
949        example = r#"
950            index-strategy = "unsafe-best-match"
951        "#,
952        possible_values = true
953    )]
954    pub index_strategy: Option<IndexStrategy>,
955    /// Attempt to use `keyring` for authentication for index URLs.
956    ///
957    /// At present, only `--keyring-provider subprocess` is supported, which configures uv to
958    /// use the `keyring` CLI to handle authentication.
959    #[option(
960        default = "\"disabled\"",
961        value_type = "str",
962        example = r#"
963            keyring-provider = "subprocess"
964        "#
965    )]
966    pub keyring_provider: Option<KeyringProviderType>,
967    /// The strategy to use when selecting between the different compatible versions for a given
968    /// package requirement.
969    ///
970    /// By default, uv will use the latest compatible version of each package (`highest`).
971    #[option(
972        default = "\"highest\"",
973        value_type = "str",
974        example = r#"
975            resolution = "lowest-direct"
976        "#,
977        possible_values = true
978    )]
979    pub resolution: Option<ResolutionMode>,
980    /// The strategy to use when considering pre-release versions.
981    ///
982    /// By default, uv will prefer stable candidates, falling back to pre-releases only after every
983    /// stable candidate that satisfies the active constraints is rejected
984    /// (`if-necessary`).
985    #[option(
986        default = "\"if-necessary\"",
987        value_type = "str",
988        example = r#"
989            prerelease = "allow"
990        "#,
991        possible_values = true
992    )]
993    pub prerelease: Option<PrereleaseMode>,
994    /// The strategy to use when considering pre-release versions for specific packages.
995    ///
996    /// Package-specific modes take precedence over the global [`prerelease`](#prerelease) mode.
997    /// Accepts a dictionary mapping package names to any supported pre-release mode.
998    #[option(
999        default = "{}",
1000        value_type = "dict",
1001        example = r#"
1002            prerelease-package = { numpy = "allow", scipy = "disallow" }
1003        "#
1004    )]
1005    pub prerelease_package: Option<PrereleasePackage>,
1006    /// The strategy to use when selecting multiple versions of a given package across Python
1007    /// versions and platforms.
1008    ///
1009    /// By default, uv will optimize for selecting the latest version of each package for each
1010    /// supported Python version (`requires-python`), while minimizing the number of selected
1011    /// versions across platforms.
1012    ///
1013    /// Under `fewest`, uv will minimize the number of selected versions for each package,
1014    /// preferring older versions that are compatible with a wider range of supported Python
1015    /// versions or platforms.
1016    #[option(
1017        default = "\"requires-python\"",
1018        value_type = "str",
1019        example = r#"
1020            fork-strategy = "fewest"
1021        "#,
1022        possible_values = true
1023    )]
1024    pub fork_strategy: Option<ForkStrategy>,
1025    /// Pre-defined static metadata for dependencies of the project (direct or transitive). When
1026    /// provided, enables the resolver to use the specified metadata instead of querying the
1027    /// registry or building the relevant package from source.
1028    ///
1029    /// Metadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)
1030    /// standard, though only the following fields are respected:
1031    ///
1032    /// - `name`: The name of the package.
1033    /// - (Optional) `version`: The version of the package. If omitted, the metadata will be applied
1034    ///   to all versions of the package.
1035    /// - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).
1036    /// - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).
1037    /// - (Optional) `provides-extra`: The extras provided by the package.
1038    #[option(
1039        default = r#"[]"#,
1040        value_type = "list[dict]",
1041        example = r#"
1042            dependency-metadata = [
1043                { name = "flask", version = "1.0.0", requires-dist = ["werkzeug"], requires-python = ">=3.6" },
1044            ]
1045        "#
1046    )]
1047    pub dependency_metadata: Option<Vec<StaticMetadata>>,
1048    /// Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend,
1049    /// specified as `KEY=VALUE` pairs.
1050    #[option(
1051        default = "{}",
1052        value_type = "dict",
1053        example = r#"
1054            config-settings = { editable_mode = "compat" }
1055        "#
1056    )]
1057    pub config_settings: Option<ConfigSettings>,
1058    /// Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,
1059    /// specified as `KEY=VALUE` pairs.
1060    ///
1061    /// Accepts a map from package names to string key-value pairs.
1062    #[option(
1063        default = "{}",
1064        value_type = "dict",
1065        example = r#"
1066            config-settings-package = { numpy = { editable_mode = "compat" } }
1067        "#
1068    )]
1069    pub config_settings_package: Option<PackageConfigSettings>,
1070    /// Disable isolation when building source distributions.
1071    ///
1072    /// Assumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)
1073    /// are already installed.
1074    #[option(
1075        default = "false",
1076        value_type = "bool",
1077        example = r#"
1078            no-build-isolation = true
1079        "#
1080    )]
1081    pub no_build_isolation: Option<bool>,
1082    /// Disable isolation when building source distributions for a specific package.
1083    ///
1084    /// Assumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)
1085    /// are already installed.
1086    #[option(
1087        default = "[]",
1088        value_type = "list[str]",
1089        example = r#"
1090        no-build-isolation-package = ["package1", "package2"]
1091    "#
1092    )]
1093    pub no_build_isolation_package: Option<Vec<PackageName>>,
1094    /// Additional build dependencies for packages.
1095    ///
1096    /// This allows extending the PEP 517 build environment for the project's dependencies with
1097    /// additional packages. This is useful for packages that assume the presence of packages like
1098    /// `pip`, and do not declare them as build dependencies.
1099    #[option(
1100        default = "[]",
1101        value_type = "dict",
1102        example = r#"
1103            extra-build-dependencies = { pytest = ["setuptools"] }
1104        "#
1105    )]
1106    pub extra_build_dependencies: Option<ExtraBuildDependencies>,
1107    /// Extra environment variables to set when building certain packages.
1108    ///
1109    /// Environment variables will be added to the environment when building the
1110    /// specified packages.
1111    #[option(
1112        default = r#"{}"#,
1113        value_type = r#"dict[str, dict[str, str]]"#,
1114        example = r#"
1115            extra-build-variables = { flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" } }
1116        "#
1117    )]
1118    pub extra_build_variables: Option<ExtraBuildVariables>,
1119    /// Limit candidate packages to those that were uploaded prior to the given date.
1120    ///
1121    /// The date is compared against the upload time of each individual distribution artifact
1122    /// (i.e., when each file was uploaded to the package index), not the release date of the
1123    /// package version.
1124    ///
1125    /// Accepts RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`), a "friendly" duration (e.g.,
1126    /// `24 hours`, `1 week`, `30 days`), or an ISO 8601 duration (e.g., `PT24H`, `P7D`, `P30D`).
1127    ///
1128    /// Durations do not respect semantics of the local time zone and are always resolved to a fixed
1129    /// number of seconds assuming that a day is 24 hours (e.g., DST transitions are ignored).
1130    /// Calendar units such as months and years are not allowed.
1131    ///
1132    /// Set to `false` to disable `exclude-newer`.
1133    #[option(
1134        default = "None",
1135        value_type = "str | false",
1136        example = r#"
1137            exclude-newer = "2006-12-02T02:07:43Z"
1138        "#
1139    )]
1140    pub exclude_newer: Option<ExcludeNewerOverride>,
1141    /// Limit candidate packages for specific packages to those that were uploaded prior to the
1142    /// given date.
1143    ///
1144    /// Accepts a dictionary format of `PACKAGE = "DATE"` pairs, where `DATE` is an RFC 3339
1145    /// timestamp (e.g., `2006-12-02T02:07:43Z`), a "friendly" duration (e.g., `24 hours`, `1 week`,
1146    /// `30 days`), or a ISO 8601 duration (e.g., `PT24H`, `P7D`, `P30D`).
1147    ///
1148    /// Durations do not respect semantics of the local time zone and are always resolved to a fixed
1149    /// number of seconds assuming that a day is 24 hours (e.g., DST transitions are ignored).
1150    /// Calendar units such as months and years are not allowed.
1151    ///
1152    /// Set a package to `false` to exempt it from the global [`exclude-newer`](#exclude-newer)
1153    /// constraint entirely.
1154    #[option(
1155        default = "None",
1156        value_type = "dict",
1157        example = r#"
1158            exclude-newer-package = { tqdm = "2022-04-04T00:00:00Z", markupsafe = false }
1159        "#
1160    )]
1161    pub exclude_newer_package: Option<ExcludeNewerPackage>,
1162    /// The method to use when installing packages from the global cache.
1163    ///
1164    /// Defaults to `clone` (also known as Copy-on-Write) on macOS and Linux, and `hardlink` on
1165    /// Windows.
1166    ///
1167    /// WARNING: The use of symlink link mode is discouraged, as they create tight coupling between
1168    /// the cache and the target environment. For example, clearing the cache (`uv cache clean`)
1169    /// will break all installed packages by way of removing the underlying source files. Use
1170    /// symlinks with caution.
1171    #[option(
1172        default = "\"clone\" (macOS, Linux) or \"hardlink\" (Windows)",
1173        value_type = "str",
1174        example = r#"
1175            link-mode = "copy"
1176        "#,
1177        possible_values = true
1178    )]
1179    pub link_mode: Option<LinkMode>,
1180    /// Compile Python files to bytecode after installation.
1181    ///
1182    /// By default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`);
1183    /// instead, compilation is performed lazily the first time a module is imported. For use-cases
1184    /// in which start time is critical, such as CLI applications and Docker containers, this option
1185    /// can be enabled to trade longer installation times for faster start times.
1186    ///
1187    /// When enabled, uv will process the entire site-packages directory (including packages that
1188    /// are not being modified by the current operation) for consistency. Like pip, it will also
1189    /// ignore errors.
1190    #[option(
1191        default = "false",
1192        value_type = "bool",
1193        example = r#"
1194            compile-bytecode = true
1195        "#
1196    )]
1197    pub compile_bytecode: Option<bool>,
1198    /// Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the
1199    /// standards-compliant, publishable package metadata, as opposed to using any local or Git
1200    /// sources.
1201    #[option(
1202        default = "false",
1203        value_type = "bool",
1204        example = r#"
1205            no-sources = true
1206        "#
1207    )]
1208    pub no_sources: Option<bool>,
1209    /// Ignore `tool.uv.sources` for the specified packages.
1210    #[option(
1211        default = "[]",
1212        value_type = "list[str]",
1213        example = r#"
1214            no-sources-package = ["ruff"]
1215        "#
1216    )]
1217    pub no_sources_package: Option<Vec<PackageName>>,
1218    /// Allow package upgrades, ignoring pinned versions in any existing output file.
1219    #[option(
1220        default = "false",
1221        value_type = "bool",
1222        example = r#"
1223            upgrade = true
1224        "#
1225    )]
1226    pub upgrade: Option<bool>,
1227    /// Allow upgrades for a specific package, ignoring pinned versions in any existing output
1228    /// file.
1229    ///
1230    /// Accepts both standalone package names (`ruff`) and version specifiers (`ruff<0.5.0`).
1231    #[option(
1232        default = "[]",
1233        value_type = "list[str]",
1234        example = r#"
1235            upgrade-package = ["ruff"]
1236        "#
1237    )]
1238    pub upgrade_package: Option<Vec<Requirement<VerbatimParsedUrl>>>,
1239    /// Reinstall all packages, regardless of whether they're already installed. Implies `refresh`.
1240    #[option(
1241        default = "false",
1242        value_type = "bool",
1243        example = r#"
1244            reinstall = true
1245        "#
1246    )]
1247    pub reinstall: Option<bool>,
1248    /// Reinstall a specific package, regardless of whether it's already installed. Implies
1249    /// `refresh-package`.
1250    #[option(
1251        default = "[]",
1252        value_type = "list[str]",
1253        example = r#"
1254            reinstall-package = ["ruff"]
1255        "#
1256    )]
1257    pub reinstall_package: Option<Vec<PackageName>>,
1258    /// Don't build source distributions.
1259    ///
1260    /// When enabled, uv will reuse cached wheels from previously built source distributions, but
1261    /// operations that require building a source distribution will exit with an error. First-party
1262    /// packages, such as projects in the workspace, will still be built. uv will also still build
1263    /// editable requirements, and their build backends may run arbitrary Python code.
1264    #[option(
1265        default = "false",
1266        value_type = "bool",
1267        example = r#"
1268            no-build = true
1269        "#
1270    )]
1271    pub no_build: Option<bool>,
1272    /// Don't build source distributions for a specific package.
1273    ///
1274    /// First-party packages, such as projects in the workspace, will still be built.
1275    #[option(
1276        default = "[]",
1277        value_type = "list[str]",
1278        example = r#"
1279            no-build-package = ["ruff"]
1280        "#
1281    )]
1282    pub no_build_package: Option<Vec<PackageName>>,
1283    /// Don't install pre-built wheels.
1284    ///
1285    /// The given packages will be built and installed from source. The resolver will still use
1286    /// pre-built wheels to extract package metadata, if available.
1287    #[option(
1288        default = "false",
1289        value_type = "bool",
1290        example = r#"
1291            no-binary = true
1292        "#
1293    )]
1294    pub no_binary: Option<bool>,
1295    /// Don't install pre-built wheels for a specific package.
1296    #[option(
1297        default = "[]",
1298        value_type = "list[str]",
1299        example = r#"
1300            no-binary-package = ["ruff"]
1301        "#
1302    )]
1303    pub no_binary_package: Option<Vec<PackageName>>,
1304    /// The backend to use when fetching packages in the PyTorch ecosystem.
1305    ///
1306    /// When set, uv will ignore the configured index URLs for packages in the PyTorch ecosystem,
1307    /// and will instead use the defined backend.
1308    ///
1309    /// For example, when set to `cpu`, uv will use the CPU-only PyTorch index; when set to `cu126`,
1310    /// uv will use the PyTorch index for CUDA 12.6.
1311    ///
1312    /// The `auto` mode will attempt to detect the appropriate PyTorch index based on the currently
1313    /// installed CUDA drivers.
1314    ///
1315    /// This setting is only respected by `uv pip` commands.
1316    ///
1317    /// This option is in preview and may change in any future release.
1318    #[option(
1319        default = "null",
1320        value_type = "str",
1321        example = r#"
1322            torch-backend = "auto"
1323        "#
1324    )]
1325    pub torch_backend: Option<TorchMode>,
1326}
1327
1328/// Shared settings, relevant to all operations that might create managed python installations.
1329#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)]
1330#[serde(rename_all = "kebab-case")]
1331#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1332pub struct PythonInstallMirrors {
1333    /// Mirror URL for downloading managed Python installations.
1334    ///
1335    /// By default, managed Python installations are downloaded from [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone).
1336    /// This variable can be set to a mirror URL to use a different source for Python installations.
1337    /// The provided URL will replace `https://github.com/astral-sh/python-build-standalone/releases/download` in, e.g., `https://github.com/astral-sh/python-build-standalone/releases/download/20240713/cpython-3.12.4%2B20240713-aarch64-apple-darwin-install_only.tar.gz`.
1338    ///
1339    /// Distributions can be read from a local directory by using the `file://` URL scheme.
1340    #[option(
1341        default = "None",
1342        value_type = "str",
1343        uv_toml_only = true,
1344        example = r#"
1345            python-install-mirror = "https://github.com/astral-sh/python-build-standalone/releases/download"
1346        "#
1347    )]
1348    pub python_install_mirror: Option<String>,
1349    /// Mirror URL to use for downloading managed PyPy installations.
1350    ///
1351    /// By default, managed PyPy installations are downloaded from [downloads.python.org](https://downloads.python.org/).
1352    /// This variable can be set to a mirror URL to use a different source for PyPy installations.
1353    /// The provided URL will replace `https://downloads.python.org/pypy` in, e.g., `https://downloads.python.org/pypy/pypy3.8-v7.3.7-osx64.tar.bz2`.
1354    ///
1355    /// Distributions can be read from a
1356    /// local directory by using the `file://` URL scheme.
1357    #[option(
1358        default = "None",
1359        value_type = "str",
1360        uv_toml_only = true,
1361        example = r#"
1362            pypy-install-mirror = "https://downloads.python.org/pypy"
1363        "#
1364    )]
1365    pub pypy_install_mirror: Option<String>,
1366    /// Mirror URL to use for downloading managed GraalPy installations.
1367    ///
1368    /// By default, managed GraalPy installations are downloaded from [GitHub](https://github.com/oracle/graalpython/releases).
1369    /// This variable can be set to a mirror URL to use a different source for GraalPy installations.
1370    /// The provided URL will replace `https://github.com/oracle/graalpython/releases/download` in, e.g., `https://github.com/oracle/graalpython/releases/download/graal-24.2.2/graalpy-24.2.2-macos-aarch64.tar.gz`.
1371    ///
1372    /// Distributions can be read from a local directory by using the `file://` URL scheme.
1373    #[option(
1374        default = "None",
1375        value_type = "str",
1376        uv_toml_only = true,
1377        example = r#"
1378            graalpy-install-mirror = "https://github.com/oracle/graalpython/releases/download"
1379        "#
1380    )]
1381    pub graalpy_install_mirror: Option<String>,
1382    /// Mirror URL to use for downloading managed Pyodide installations.
1383    ///
1384    /// By default, managed Pyodide installations are downloaded from [GitHub](https://github.com/pyodide/pyodide/releases).
1385    /// This variable can be set to a mirror URL to use a different source for Pyodide installations.
1386    /// The provided URL will replace `https://github.com/pyodide/pyodide/releases/download` in, e.g., `https://github.com/pyodide/pyodide/releases/download/0.29.5/xbuildenv-0.29.5.tar.gz`.
1387    ///
1388    /// Distributions can be read from a local directory by using the `file://` URL scheme.
1389    #[option(
1390        default = "None",
1391        value_type = "str",
1392        uv_toml_only = true,
1393        example = r#"
1394            pyodide-install-mirror = "https://github.com/pyodide/pyodide/releases/download"
1395        "#
1396    )]
1397    pub pyodide_install_mirror: Option<String>,
1398
1399    /// URL pointing to JSON of custom Python installations.
1400    #[option(
1401        default = "None",
1402        value_type = "str",
1403        uv_toml_only = true,
1404        example = r#"
1405            python-downloads-json-url = "/etc/uv/python-downloads.json"
1406        "#
1407    )]
1408    pub python_downloads_json_url: Option<String>,
1409}
1410
1411impl PythonInstallMirrors {
1412    /// Return the mirrors to use for managed Python downloads.
1413    pub fn mirrors(&self) -> PythonDownloadMirrors<'_> {
1414        PythonDownloadMirrors {
1415            cpython: self.python_install_mirror.as_deref(),
1416            pypy: self.pypy_install_mirror.as_deref(),
1417            graalpy: self.graalpy_install_mirror.as_deref(),
1418            pyodide: self.pyodide_install_mirror.as_deref(),
1419        }
1420    }
1421
1422    #[must_use]
1423    pub fn combine(self, other: Self) -> Self {
1424        Self {
1425            python_install_mirror: self.python_install_mirror.or(other.python_install_mirror),
1426            pypy_install_mirror: self.pypy_install_mirror.or(other.pypy_install_mirror),
1427            graalpy_install_mirror: self.graalpy_install_mirror.or(other.graalpy_install_mirror),
1428            pyodide_install_mirror: self.pyodide_install_mirror.or(other.pyodide_install_mirror),
1429            python_downloads_json_url: self
1430                .python_downloads_json_url
1431                .or(other.python_downloads_json_url),
1432        }
1433    }
1434}
1435
1436/// Settings that are specific to the `uv pip` command-line interface.
1437///
1438/// These values will be ignored when running commands outside the `uv pip` namespace (e.g.,
1439/// `uv lock`, `uvx`).
1440#[derive(Debug, Clone, Default, Deserialize, CombineOptions, OptionsMetadata)]
1441#[serde(deny_unknown_fields, rename_all = "kebab-case")]
1442#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
1443pub struct PipOptions {
1444    /// The Python interpreter into which packages should be installed.
1445    ///
1446    /// By default, uv installs into the virtual environment in the current working directory or
1447    /// any parent directory. The `--python` option allows you to specify a different interpreter,
1448    /// which is intended for use in continuous integration (CI) environments or other automated
1449    /// workflows.
1450    ///
1451    /// Supported formats:
1452    /// - `3.10` looks for an installed Python 3.10 in the registry on Windows (see
1453    ///   `py --list-paths`), or `python3.10` on Linux and macOS.
1454    /// - `python3.10` or `python.exe` looks for a binary with the given name in `PATH`.
1455    /// - `/home/ferris/.local/bin/python3.10` uses the exact Python at the given path.
1456    #[option(
1457        default = "None",
1458        value_type = "str",
1459        example = r#"
1460            python = "3.10"
1461        "#
1462    )]
1463    pub python: Option<String>,
1464    /// Install packages into the system Python environment.
1465    ///
1466    /// By default, uv installs into the virtual environment in the current working directory or
1467    /// any parent directory. The `--system` option instructs uv to instead use the first Python
1468    /// found in the system `PATH`.
1469    ///
1470    /// WARNING: `--system` is intended for use in continuous integration (CI) environments and
1471    /// should be used with caution, as it can modify the system Python installation.
1472    #[option(
1473        default = "false",
1474        value_type = "bool",
1475        example = r#"
1476            system = true
1477        "#
1478    )]
1479    pub system: Option<bool>,
1480    /// Allow uv to modify an `EXTERNALLY-MANAGED` Python installation.
1481    ///
1482    /// WARNING: `--break-system-packages` is intended for use in continuous integration (CI)
1483    /// environments, when installing into Python installations that are managed by an external
1484    /// package manager, like `apt`. It should be used with caution, as such Python installations
1485    /// explicitly recommend against modifications by other package managers (like uv or pip).
1486    #[option(
1487        default = "false",
1488        value_type = "bool",
1489        example = r#"
1490            break-system-packages = true
1491        "#
1492    )]
1493    pub break_system_packages: Option<bool>,
1494    /// Install packages into the specified directory, rather than into the virtual or system Python
1495    /// environment. The packages will be installed at the top-level of the directory.
1496    #[option(
1497        default = "None",
1498        value_type = "str",
1499        example = r#"
1500            target = "./target"
1501        "#
1502    )]
1503    pub target: Option<PathBuf>,
1504    /// Install packages into `lib`, `bin`, and other top-level folders under the specified
1505    /// directory, as if a virtual environment were present at that location.
1506    ///
1507    /// In general, prefer the use of `--python` to install into an alternate environment, as
1508    /// scripts and other artifacts installed via `--prefix` will reference the installing
1509    /// interpreter, rather than any interpreter added to the `--prefix` directory, rendering them
1510    /// non-portable.
1511    #[option(
1512        default = "None",
1513        value_type = "str",
1514        example = r#"
1515            prefix = "./prefix"
1516        "#
1517    )]
1518    pub prefix: Option<PathBuf>,
1519    #[serde(skip)]
1520    #[cfg_attr(feature = "schemars", schemars(skip))]
1521    pub index: Option<Vec<Index>>,
1522    /// The URL of the Python package index (by default: <https://pypi.org/simple>).
1523    ///
1524    /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
1525    /// (the simple repository API), or a local directory laid out in the same format.
1526    ///
1527    /// The index provided by this setting is given lower priority than any indexes specified via
1528    /// [`extra_index_url`](#extra-index-url).
1529    #[option(
1530        default = "\"https://pypi.org/simple\"",
1531        value_type = "str",
1532        example = r#"
1533            index-url = "https://test.pypi.org/simple"
1534        "#
1535    )]
1536    pub index_url: Option<PipIndex>,
1537    /// Extra URLs of package indexes to use, in addition to `--index-url`.
1538    ///
1539    /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
1540    /// (the simple repository API), or a local directory laid out in the same format.
1541    ///
1542    /// All indexes provided via this flag take priority over the index specified by
1543    /// [`index_url`](#index-url). When multiple indexes are provided, earlier values take priority.
1544    ///
1545    /// To control uv's resolution strategy when multiple indexes are present, see
1546    /// [`index_strategy`](#index-strategy).
1547    #[option(
1548        default = "[]",
1549        value_type = "list[str]",
1550        example = r#"
1551            extra-index-url = ["https://download.pytorch.org/whl/cpu"]
1552        "#
1553    )]
1554    pub extra_index_url: Option<Vec<PipExtraIndex>>,
1555    /// Ignore all registry indexes (e.g., PyPI), instead relying on direct URL dependencies and
1556    /// those provided via `--find-links`.
1557    #[option(
1558        default = "false",
1559        value_type = "bool",
1560        example = r#"
1561            no-index = true
1562        "#
1563    )]
1564    pub no_index: Option<bool>,
1565    /// Locations to search for candidate distributions, in addition to those found in the registry
1566    /// indexes.
1567    ///
1568    /// If a path, the target must be a directory that contains packages as wheel files (`.whl`) or
1569    /// source distributions (e.g., `.tar.gz` or `.zip`) at the top level.
1570    ///
1571    /// If a URL, the page must contain a flat list of links to package files adhering to the
1572    /// formats described above.
1573    #[option(
1574        default = "[]",
1575        value_type = "list[str]",
1576        example = r#"
1577            find-links = ["https://download.pytorch.org/whl/torch_stable.html"]
1578        "#
1579    )]
1580    pub find_links: Option<Vec<PipFindLinks>>,
1581    /// The strategy to use when resolving against multiple index URLs.
1582    ///
1583    /// By default, uv will stop at the first index on which a given package is available, and
1584    /// limit resolutions to those present on that first index (`first-index`). This prevents
1585    /// "dependency confusion" attacks, whereby an attacker can upload a malicious package under the
1586    /// same name to an alternate index.
1587    #[option(
1588        default = "\"first-index\"",
1589        value_type = "str",
1590        example = r#"
1591            index-strategy = "unsafe-best-match"
1592        "#,
1593        possible_values = true
1594    )]
1595    pub index_strategy: Option<IndexStrategy>,
1596    /// Attempt to use `keyring` for authentication for index URLs.
1597    ///
1598    /// At present, only `--keyring-provider subprocess` is supported, which configures uv to
1599    /// use the `keyring` CLI to handle authentication.
1600    #[option(
1601        default = "disabled",
1602        value_type = "str",
1603        example = r#"
1604            keyring-provider = "subprocess"
1605        "#
1606    )]
1607    pub keyring_provider: Option<KeyringProviderType>,
1608    /// Don't build source distributions.
1609    ///
1610    /// When enabled, uv will reuse cached wheels from previously built source distributions, but
1611    /// operations that require building a source distribution will exit with an error. uv may
1612    /// still build editable requirements, and their build backends may run arbitrary Python code.
1613    ///
1614    /// Alias for `--only-binary :all:`.
1615    #[option(
1616        default = "false",
1617        value_type = "bool",
1618        example = r#"
1619            no-build = true
1620        "#
1621    )]
1622    pub no_build: Option<bool>,
1623    /// Don't install pre-built wheels.
1624    ///
1625    /// The given packages will be built and installed from source. The resolver will still use
1626    /// pre-built wheels to extract package metadata, if available.
1627    ///
1628    /// Multiple packages may be provided. Disable binaries for all packages with `:all:`.
1629    /// Clear previously specified packages with `:none:`.
1630    #[option(
1631        default = "[]",
1632        value_type = "list[str]",
1633        example = r#"
1634            no-binary = ["ruff"]
1635        "#
1636    )]
1637    pub no_binary: Option<Vec<PackageNameSpecifier>>,
1638    /// Only use pre-built wheels; don't build source distributions.
1639    ///
1640    /// When enabled, uv will reuse cached wheels from previously built source distributions, but
1641    /// operations that require building a source distribution for the given packages will exit
1642    /// with an error. uv may still build editable requirements, and their build backends may run
1643    /// arbitrary Python code.
1644    ///
1645    /// Multiple packages may be provided. Disable binaries for all packages with `:all:`.
1646    /// Clear previously specified packages with `:none:`.
1647    #[option(
1648        default = "[]",
1649        value_type = "list[str]",
1650        example = r#"
1651            only-binary = ["ruff"]
1652        "#
1653    )]
1654    pub only_binary: Option<Vec<PackageNameSpecifier>>,
1655    /// Disable isolation when building source distributions.
1656    ///
1657    /// Assumes that build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)
1658    /// are already installed.
1659    #[option(
1660        default = "false",
1661        value_type = "bool",
1662        example = r#"
1663            no-build-isolation = true
1664        "#
1665    )]
1666    pub no_build_isolation: Option<bool>,
1667    /// Disable isolation when building source distributions for a specific package.
1668    ///
1669    /// Assumes that the packages' build dependencies specified by [PEP 518](https://peps.python.org/pep-0518/)
1670    /// are already installed.
1671    #[option(
1672        default = "[]",
1673        value_type = "list[str]",
1674        example = r#"
1675            no-build-isolation-package = ["package1", "package2"]
1676        "#
1677    )]
1678    pub no_build_isolation_package: Option<Vec<PackageName>>,
1679    /// Additional build dependencies for packages.
1680    ///
1681    /// This allows extending the PEP 517 build environment for the project's dependencies with
1682    /// additional packages. This is useful for packages that assume the presence of packages like
1683    /// `pip`, and do not declare them as build dependencies.
1684    #[option(
1685        default = "[]",
1686        value_type = "dict",
1687        example = r#"
1688            extra-build-dependencies = { pytest = ["setuptools"] }
1689        "#
1690    )]
1691    pub extra_build_dependencies: Option<ExtraBuildDependencies>,
1692    /// Extra environment variables to set when building certain packages.
1693    ///
1694    /// Environment variables will be added to the environment when building the
1695    /// specified packages.
1696    #[option(
1697        default = r#"{}"#,
1698        value_type = r#"dict[str, dict[str, str]]"#,
1699        example = r#"
1700            extra-build-variables = { flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" } }
1701        "#
1702    )]
1703    pub extra_build_variables: Option<ExtraBuildVariables>,
1704    /// Validate the Python environment, to detect packages with missing dependencies and other
1705    /// issues.
1706    #[option(
1707        default = "false",
1708        value_type = "bool",
1709        example = r#"
1710            strict = true
1711        "#
1712    )]
1713    pub strict: Option<bool>,
1714    /// Include optional dependencies from the specified extra; may be provided more than once.
1715    ///
1716    /// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources.
1717    #[option(
1718        default = "[]",
1719        value_type = "list[str]",
1720        example = r#"
1721            extra = ["dev", "docs"]
1722        "#
1723    )]
1724    pub extra: Option<Vec<ExtraName>>,
1725    /// Include all optional dependencies.
1726    ///
1727    /// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources.
1728    #[option(
1729        default = "false",
1730        value_type = "bool",
1731        example = r#"
1732            all-extras = true
1733        "#
1734    )]
1735    pub all_extras: Option<bool>,
1736    /// Exclude the specified optional dependencies if `all-extras` is supplied.
1737    #[option(
1738        default = "[]",
1739        value_type = "list[str]",
1740        example = r#"
1741            all-extras = true
1742            no-extra = ["dev", "docs"]
1743        "#
1744    )]
1745    pub no_extra: Option<Vec<ExtraName>>,
1746    /// Ignore package dependencies, instead only add those packages explicitly listed
1747    /// on the command line to the resulting requirements file.
1748    #[option(
1749        default = "false",
1750        value_type = "bool",
1751        example = r#"
1752            no-deps = true
1753        "#
1754    )]
1755    pub no_deps: Option<bool>,
1756    /// Include the following dependency groups.
1757    #[option(
1758        default = "None",
1759        value_type = "list[str]",
1760        example = r#"
1761            group = ["dev", "docs"]
1762        "#
1763    )]
1764    pub group: Option<Vec<PipGroupName>>,
1765    /// Allow `uv pip sync` with empty requirements, which will clear the environment of all
1766    /// packages.
1767    #[option(
1768        default = "false",
1769        value_type = "bool",
1770        example = r#"
1771            allow-empty-requirements = true
1772        "#
1773    )]
1774    pub allow_empty_requirements: Option<bool>,
1775    /// The strategy to use when selecting between the different compatible versions for a given
1776    /// package requirement.
1777    ///
1778    /// By default, uv will use the latest compatible version of each package (`highest`).
1779    #[option(
1780        default = "\"highest\"",
1781        value_type = "str",
1782        example = r#"
1783            resolution = "lowest-direct"
1784        "#,
1785        possible_values = true
1786    )]
1787    pub resolution: Option<ResolutionMode>,
1788    /// The strategy to use when considering pre-release versions.
1789    ///
1790    /// By default, uv will prefer stable candidates, falling back to pre-releases only after every
1791    /// stable candidate that satisfies the active constraints is rejected
1792    /// (`if-necessary`).
1793    #[option(
1794        default = "\"if-necessary\"",
1795        value_type = "str",
1796        example = r#"
1797            prerelease = "allow"
1798        "#,
1799        possible_values = true
1800    )]
1801    pub prerelease: Option<PrereleaseMode>,
1802    #[serde(skip)]
1803    #[cfg_attr(feature = "schemars", schemars(skip))]
1804    pub prerelease_package: Option<PrereleasePackage>,
1805    /// The strategy to use when selecting multiple versions of a given package across Python
1806    /// versions and platforms.
1807    ///
1808    /// By default, uv will optimize for selecting the latest version of each package for each
1809    /// supported Python version (`requires-python`), while minimizing the number of selected
1810    /// versions across platforms.
1811    ///
1812    /// Under `fewest`, uv will minimize the number of selected versions for each package,
1813    /// preferring older versions that are compatible with a wider range of supported Python
1814    /// versions or platforms.
1815    #[option(
1816        default = "\"requires-python\"",
1817        value_type = "str",
1818        example = r#"
1819            fork-strategy = "fewest"
1820        "#,
1821        possible_values = true
1822    )]
1823    pub fork_strategy: Option<ForkStrategy>,
1824    /// Pre-defined static metadata for dependencies of the project (direct or transitive). When
1825    /// provided, enables the resolver to use the specified metadata instead of querying the
1826    /// registry or building the relevant package from source.
1827    ///
1828    /// Metadata should be provided in adherence with the [Metadata 2.3](https://packaging.python.org/en/latest/specifications/core-metadata/)
1829    /// standard, though only the following fields are respected:
1830    ///
1831    /// - `name`: The name of the package.
1832    /// - (Optional) `version`: The version of the package. If omitted, the metadata will be applied
1833    ///   to all versions of the package.
1834    /// - (Optional) `requires-dist`: The dependencies of the package (e.g., `werkzeug>=0.14`).
1835    /// - (Optional) `requires-python`: The Python version required by the package (e.g., `>=3.10`).
1836    /// - (Optional) `provides-extra`: The extras provided by the package.
1837    #[option(
1838        default = r#"[]"#,
1839        value_type = "list[dict]",
1840        example = r#"
1841            dependency-metadata = [
1842                { name = "flask", version = "1.0.0", requires-dist = ["werkzeug"], requires-python = ">=3.6" },
1843            ]
1844        "#
1845    )]
1846    pub dependency_metadata: Option<Vec<StaticMetadata>>,
1847    /// Write the requirements generated by `uv pip compile` to the given `requirements.txt` file.
1848    ///
1849    /// If the file already exists, the existing versions will be preferred when resolving
1850    /// dependencies, unless `--upgrade` is also specified.
1851    #[option(
1852        default = "None",
1853        value_type = "str",
1854        example = r#"
1855            output-file = "requirements.txt"
1856        "#
1857    )]
1858    pub output_file: Option<PathBuf>,
1859    /// Include extras in the output file.
1860    ///
1861    /// By default, uv strips extras, as any packages pulled in by the extras are already included
1862    /// as dependencies in the output file directly. Further, output files generated with
1863    /// `--no-strip-extras` cannot be used as constraints files in `install` and `sync` invocations.
1864    #[option(
1865        default = "false",
1866        value_type = "bool",
1867        example = r#"
1868            no-strip-extras = true
1869        "#
1870    )]
1871    pub no_strip_extras: Option<bool>,
1872    /// Include environment markers in the output file generated by `uv pip compile`.
1873    ///
1874    /// By default, uv strips environment markers, as the resolution generated by `compile` is
1875    /// only guaranteed to be correct for the target environment.
1876    #[option(
1877        default = "false",
1878        value_type = "bool",
1879        example = r#"
1880            no-strip-markers = true
1881        "#
1882    )]
1883    pub no_strip_markers: Option<bool>,
1884    /// Exclude comment annotations indicating the source of each package from the output file
1885    /// generated by `uv pip compile`.
1886    #[option(
1887        default = "false",
1888        value_type = "bool",
1889        example = r#"
1890            no-annotate = true
1891        "#
1892    )]
1893    pub no_annotate: Option<bool>,
1894    /// Exclude the comment header at the top of output file generated by `uv pip compile`.
1895    #[option(
1896        default = r#"false"#,
1897        value_type = "bool",
1898        example = r#"
1899            no-header = true
1900        "#
1901    )]
1902    pub no_header: Option<bool>,
1903    /// The header comment to include at the top of the output file generated by `uv pip compile`.
1904    ///
1905    /// Used to reflect custom build scripts and commands that wrap `uv pip compile`.
1906    #[option(
1907        default = "None",
1908        value_type = "str",
1909        example = r#"
1910            custom-compile-command = "./custom-uv-compile.sh"
1911        "#
1912    )]
1913    pub custom_compile_command: Option<String>,
1914    /// Include distribution hashes in the output file.
1915    #[option(
1916        default = "false",
1917        value_type = "bool",
1918        example = r#"
1919            generate-hashes = true
1920        "#
1921    )]
1922    pub generate_hashes: Option<bool>,
1923    /// Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend,
1924    /// specified as `KEY=VALUE` pairs.
1925    #[option(
1926        default = "{}",
1927        value_type = "dict",
1928        example = r#"
1929            config-settings = { editable_mode = "compat" }
1930        "#
1931    )]
1932    pub config_settings: Option<ConfigSettings>,
1933    /// Settings to pass to the [PEP 517](https://peps.python.org/pep-0517/) build backend for specific packages,
1934    /// specified as `KEY=VALUE` pairs.
1935    #[option(
1936        default = "{}",
1937        value_type = "dict",
1938        example = r#"
1939            config-settings-package = { numpy = { editable_mode = "compat" } }
1940        "#
1941    )]
1942    pub config_settings_package: Option<PackageConfigSettings>,
1943    /// The minimum Python version that should be supported by the resolved requirements (e.g.,
1944    /// `3.8` or `3.8.17`).
1945    ///
1946    /// If a patch version is omitted, the minimum patch version is assumed. For example, `3.8` is
1947    /// mapped to `3.8.0`.
1948    #[option(
1949        default = "None",
1950        value_type = "str",
1951        example = r#"
1952            python-version = "3.8"
1953        "#
1954    )]
1955    pub python_version: Option<PythonVersion>,
1956    /// The platform for which requirements should be resolved.
1957    ///
1958    /// Represented as a "target triple", a string that describes the target platform in terms of
1959    /// its CPU, vendor, and operating system name, like `x86_64-unknown-linux-gnu` or
1960    /// `aarch64-apple-darwin`.
1961    #[option(
1962        default = "None",
1963        value_type = "str",
1964        example = r#"
1965            python-platform = "x86_64-unknown-linux-gnu"
1966        "#
1967    )]
1968    pub python_platform: Option<TargetTriple>,
1969    /// Perform a universal resolution, attempting to generate a single `requirements.txt` output
1970    /// file that is compatible with all operating systems, architectures, and Python
1971    /// implementations.
1972    ///
1973    /// In universal mode, the current Python version (or user-provided `--python-version`) will be
1974    /// treated as a lower bound. For example, `--universal --python-version 3.7` would produce a
1975    /// universal resolution for Python 3.7 and later.
1976    #[option(
1977        default = "false",
1978        value_type = "bool",
1979        example = r#"
1980            universal = true
1981        "#
1982    )]
1983    pub universal: Option<bool>,
1984    /// Limit candidate packages to those that were uploaded prior to a given point in time.
1985    ///
1986    /// The date is compared against the upload time of each individual distribution artifact
1987    /// (i.e., when each file was uploaded to the package index), not the release date of the
1988    /// package version.
1989    ///
1990    /// Accepts RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`), a "friendly" duration (e.g.,
1991    /// `24 hours`, `1 week`, `30 days`), or an ISO 8601 duration (e.g., `PT24H`, `P7D`, `P30D`).
1992    ///
1993    /// Durations do not respect semantics of the local time zone and are always resolved to a fixed
1994    /// number of seconds assuming that a day is 24 hours (e.g., DST transitions are ignored).
1995    /// Calendar units such as months and years are not allowed.
1996    ///
1997    /// Set to `false` to disable `exclude-newer`.
1998    #[option(
1999        default = "None",
2000        value_type = "str | false",
2001        example = r#"
2002            exclude-newer = "2006-12-02T02:07:43Z"
2003        "#
2004    )]
2005    pub exclude_newer: Option<ExcludeNewerOverride>,
2006    /// Limit candidate packages for specific packages to those that were uploaded prior to the given date.
2007    ///
2008    /// Accepts a dictionary format of `PACKAGE = "DATE"` pairs, where `DATE` is an RFC 3339
2009    /// timestamp (e.g., `2006-12-02T02:07:43Z`), a "friendly" duration (e.g., `24 hours`, `1 week`,
2010    /// `30 days`), or a ISO 8601 duration (e.g., `PT24H`, `P7D`, `P30D`).
2011    ///
2012    /// Durations do not respect semantics of the local time zone and are always resolved to a fixed
2013    /// number of seconds assuming that a day is 24 hours (e.g., DST transitions are ignored).
2014    /// Calendar units such as months and years are not allowed.
2015    ///
2016    /// Set a package to `false` to exempt it from the global [`exclude-newer`](#exclude-newer)
2017    /// constraint entirely.
2018    #[option(
2019        default = "None",
2020        value_type = "dict",
2021        example = r#"
2022            exclude-newer-package = { tqdm = "2022-04-04T00:00:00Z", markupsafe = false }
2023        "#
2024    )]
2025    pub exclude_newer_package: Option<ExcludeNewerPackage>,
2026    /// Specify a package to omit from the output resolution. Its dependencies will still be
2027    /// included in the resolution. Equivalent to pip-compile's `--unsafe-package` option.
2028    #[option(
2029        default = "[]",
2030        value_type = "list[str]",
2031        example = r#"
2032            no-emit-package = ["ruff"]
2033        "#
2034    )]
2035    pub no_emit_package: Option<Vec<PackageName>>,
2036    /// Include `--index-url` and `--extra-index-url` entries in the output file generated by `uv pip compile`.
2037    #[option(
2038        default = "false",
2039        value_type = "bool",
2040        example = r#"
2041            emit-index-url = true
2042        "#
2043    )]
2044    pub emit_index_url: Option<bool>,
2045    /// Include `--find-links` entries in the output file generated by `uv pip compile`.
2046    #[option(
2047        default = "false",
2048        value_type = "bool",
2049        example = r#"
2050            emit-find-links = true
2051        "#
2052    )]
2053    pub emit_find_links: Option<bool>,
2054    /// Include `--no-binary` and `--only-binary` entries in the output file generated by `uv pip compile`.
2055    #[option(
2056        default = "false",
2057        value_type = "bool",
2058        example = r#"
2059            emit-build-options = true
2060        "#
2061    )]
2062    pub emit_build_options: Option<bool>,
2063    /// Whether to emit a marker string indicating the conditions under which the set of pinned
2064    /// dependencies is valid.
2065    ///
2066    /// The pinned dependencies may be valid even when the marker expression is
2067    /// false, but when the expression is true, the requirements are known to
2068    /// be correct.
2069    #[option(
2070        default = "false",
2071        value_type = "bool",
2072        example = r#"
2073            emit-marker-expression = true
2074        "#
2075    )]
2076    pub emit_marker_expression: Option<bool>,
2077    /// Include comment annotations indicating the index used to resolve each package (e.g.,
2078    /// `# from https://pypi.org/simple`).
2079    #[option(
2080        default = "false",
2081        value_type = "bool",
2082        example = r#"
2083            emit-index-annotation = true
2084        "#
2085    )]
2086    pub emit_index_annotation: Option<bool>,
2087    /// The style of the annotation comments included in the output file, used to indicate the
2088    /// source of each package.
2089    #[option(
2090        default = "\"split\"",
2091        value_type = "str",
2092        example = r#"
2093            annotation-style = "line"
2094        "#,
2095        possible_values = true
2096    )]
2097    pub annotation_style: Option<AnnotationStyle>,
2098    /// The method to use when installing packages from the global cache.
2099    ///
2100    /// Defaults to `clone` (also known as Copy-on-Write) on macOS and Linux, and `hardlink` on
2101    /// Windows.
2102    ///
2103    /// WARNING: The use of symlink link mode is discouraged, as they create tight coupling between
2104    /// the cache and the target environment. For example, clearing the cache (`uv cache clean`)
2105    /// will break all installed packages by way of removing the underlying source files. Use
2106    /// symlinks with caution.
2107    #[option(
2108        default = "\"clone\" (macOS, Linux) or \"hardlink\" (Windows)",
2109        value_type = "str",
2110        example = r#"
2111            link-mode = "copy"
2112        "#,
2113        possible_values = true
2114    )]
2115    pub link_mode: Option<LinkMode>,
2116    /// Compile Python files to bytecode after installation.
2117    ///
2118    /// By default, uv does not compile Python (`.py`) files to bytecode (`__pycache__/*.pyc`);
2119    /// instead, compilation is performed lazily the first time a module is imported. For use-cases
2120    /// in which start time is critical, such as CLI applications and Docker containers, this option
2121    /// can be enabled to trade longer installation times for faster start times.
2122    ///
2123    /// When enabled, uv will process the entire site-packages directory (including packages that
2124    /// are not being modified by the current operation) for consistency. Like pip, it will also
2125    /// ignore errors.
2126    #[option(
2127        default = "false",
2128        value_type = "bool",
2129        example = r#"
2130            compile-bytecode = true
2131        "#
2132    )]
2133    pub compile_bytecode: Option<bool>,
2134    /// Require a matching hash for each requirement.
2135    ///
2136    /// Hash-checking mode is all or nothing. If enabled, _all_ requirements must be provided
2137    /// with a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements
2138    /// must either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL.
2139    ///
2140    /// Hash-checking mode introduces a number of additional constraints:
2141    ///
2142    /// - Git dependencies are not supported.
2143    /// - Editable installations are not supported.
2144    /// - Local dependencies are not supported, unless they point to a specific wheel (`.whl`) or
2145    ///   source archive (`.zip`, `.tar.gz`), as opposed to a directory.
2146    #[option(
2147        default = "false",
2148        value_type = "bool",
2149        example = r#"
2150            require-hashes = true
2151        "#
2152    )]
2153    pub require_hashes: Option<bool>,
2154    /// Validate any hashes provided in the requirements file.
2155    ///
2156    /// Unlike `--require-hashes`, `--verify-hashes` does not require that all requirements have
2157    /// hashes; instead, it will limit itself to verifying the hashes of those requirements that do
2158    /// include them.
2159    #[option(
2160        default = "true",
2161        value_type = "bool",
2162        example = r#"
2163            verify-hashes = true
2164        "#
2165    )]
2166    pub verify_hashes: Option<bool>,
2167    /// Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the
2168    /// standards-compliant, publishable package metadata, as opposed to using any local or Git
2169    /// sources.
2170    #[option(
2171        default = "false",
2172        value_type = "bool",
2173        example = r#"
2174            no-sources = true
2175        "#
2176    )]
2177    pub no_sources: Option<bool>,
2178    /// Ignore `tool.uv.sources` for the specified packages.
2179    #[option(
2180        default = "[]",
2181        value_type = "list[str]",
2182        example = r#"
2183            no-sources-package = ["ruff"]
2184        "#
2185    )]
2186    pub no_sources_package: Option<Vec<PackageName>>,
2187    /// Allow package upgrades, ignoring pinned versions in any existing output file.
2188    #[option(
2189        default = "false",
2190        value_type = "bool",
2191        example = r#"
2192            upgrade = true
2193        "#
2194    )]
2195    pub upgrade: Option<bool>,
2196    /// Allow upgrades for a specific package, ignoring pinned versions in any existing output
2197    /// file.
2198    ///
2199    /// Accepts both standalone package names (`ruff`) and version specifiers (`ruff<0.5.0`).
2200    #[option(
2201        default = "[]",
2202        value_type = "list[str]",
2203        example = r#"
2204            upgrade-package = ["ruff"]
2205        "#
2206    )]
2207    pub upgrade_package: Option<Vec<Requirement<VerbatimParsedUrl>>>,
2208    /// Reinstall all packages, regardless of whether they're already installed. Implies `refresh`.
2209    #[option(
2210        default = "false",
2211        value_type = "bool",
2212        example = r#"
2213            reinstall = true
2214        "#
2215    )]
2216    pub reinstall: Option<bool>,
2217    /// Reinstall a specific package, regardless of whether it's already installed. Implies
2218    /// `refresh-package`.
2219    #[option(
2220        default = "[]",
2221        value_type = "list[str]",
2222        example = r#"
2223            reinstall-package = ["ruff"]
2224        "#
2225    )]
2226    pub reinstall_package: Option<Vec<PackageName>>,
2227    /// The backend to use when fetching packages in the PyTorch ecosystem.
2228    ///
2229    /// When set, uv will ignore the configured index URLs for packages in the PyTorch ecosystem,
2230    /// and will instead use the defined backend.
2231    ///
2232    /// For example, when set to `cpu`, uv will use the CPU-only PyTorch index; when set to `cu126`,
2233    /// uv will use the PyTorch index for CUDA 12.6.
2234    ///
2235    /// The `auto` mode will attempt to detect the appropriate PyTorch index based on the currently
2236    /// installed CUDA drivers.
2237    ///
2238    /// This setting is only respected by `uv pip` commands.
2239    ///
2240    /// This option is in preview and may change in any future release.
2241    #[option(
2242        default = "null",
2243        value_type = "str",
2244        example = r#"
2245            torch-backend = "auto"
2246        "#
2247    )]
2248    pub torch_backend: Option<TorchMode>,
2249}
2250
2251impl PipOptions {
2252    /// Resolve the [`PipOptions`] relative to the given root directory.
2253    fn relative_to(mut self, root_dir: &Path) -> Result<Self, IndexUrlError> {
2254        rebase_indexes(
2255            root_dir,
2256            &mut self.index,
2257            &mut self.index_url,
2258            &mut self.extra_index_url,
2259            &mut self.find_links,
2260        )?;
2261
2262        Ok(self)
2263    }
2264}
2265
2266impl From<ResolverInstallerSchema> for ResolverOptions {
2267    fn from(value: ResolverInstallerSchema) -> Self {
2268        Self {
2269            indexes: IndexOptions {
2270                index: value.index,
2271                index_url: value.index_url,
2272                extra_index_url: value.extra_index_url,
2273                no_index: value.no_index,
2274                find_links: value.find_links,
2275            },
2276            index_strategy: value.index_strategy,
2277            keyring_provider: value.keyring_provider,
2278            resolution: value.resolution,
2279            prerelease: value.prerelease,
2280            prerelease_package: value.prerelease_package,
2281            fork_strategy: value.fork_strategy,
2282            dependency_metadata: value.dependency_metadata,
2283            config_settings: value.config_settings,
2284            config_settings_package: value.config_settings_package,
2285            exclude_newer: value.exclude_newer,
2286            exclude_newer_package: value.exclude_newer_package,
2287            link_mode: value.link_mode,
2288            upgrade: Upgrade::from_args(
2289                value.upgrade,
2290                value
2291                    .upgrade_package
2292                    .into_iter()
2293                    .flatten()
2294                    .map(Into::into)
2295                    .collect(),
2296                Vec::new(),
2297            ),
2298            no_build: value.no_build,
2299            no_build_package: value.no_build_package,
2300            no_binary: value.no_binary,
2301            no_binary_package: value.no_binary_package,
2302            build_isolation: BuildIsolation::from_args(
2303                value.no_build_isolation,
2304                value.no_build_isolation_package.unwrap_or_default(),
2305            ),
2306            extra_build_dependencies: value.extra_build_dependencies,
2307            extra_build_variables: value.extra_build_variables,
2308            no_sources: value.no_sources,
2309            no_sources_package: value.no_sources_package,
2310            torch_backend: value.torch_backend,
2311        }
2312    }
2313}
2314
2315impl From<ResolverInstallerSchema> for InstallerOptions {
2316    fn from(value: ResolverInstallerSchema) -> Self {
2317        Self {
2318            index: value.index,
2319            index_url: value.index_url,
2320            extra_index_url: value.extra_index_url,
2321            no_index: value.no_index,
2322            find_links: value.find_links,
2323            index_strategy: value.index_strategy,
2324            keyring_provider: value.keyring_provider,
2325            config_settings: value.config_settings,
2326            exclude_newer: value.exclude_newer,
2327            link_mode: value.link_mode,
2328            compile_bytecode: value.compile_bytecode,
2329            reinstall: Reinstall::from_args(
2330                value.reinstall,
2331                value.reinstall_package.unwrap_or_default(),
2332            ),
2333            build_isolation: BuildIsolation::from_args(
2334                value.no_build_isolation,
2335                value.no_build_isolation_package.unwrap_or_default(),
2336            ),
2337            no_build: value.no_build,
2338            no_build_package: value.no_build_package,
2339            no_binary: value.no_binary,
2340            no_binary_package: value.no_binary_package,
2341            no_sources: value.no_sources,
2342            no_sources_package: value.no_sources_package,
2343        }
2344    }
2345}
2346
2347/// The options persisted alongside an installed tool.
2348///
2349/// A mirror of [`ResolverInstallerSchema`], without upgrades and reinstalls, which shouldn't be
2350/// persisted in a tool receipt.
2351#[derive(
2352    Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, CombineOptions, OptionsMetadata,
2353)]
2354#[serde(deny_unknown_fields, rename_all = "kebab-case")]
2355#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2356pub struct ToolOptions {
2357    index: Option<Vec<Index>>,
2358    index_url: Option<PipIndex>,
2359    extra_index_url: Option<Vec<PipExtraIndex>>,
2360    no_index: Option<bool>,
2361    find_links: Option<Vec<PipFindLinks>>,
2362    index_strategy: Option<IndexStrategy>,
2363    keyring_provider: Option<KeyringProviderType>,
2364    resolution: Option<ResolutionMode>,
2365    prerelease: Option<PrereleaseMode>,
2366    prerelease_package: Option<PrereleasePackage>,
2367    fork_strategy: Option<ForkStrategy>,
2368    dependency_metadata: Option<Vec<StaticMetadata>>,
2369    config_settings: Option<ConfigSettings>,
2370    config_settings_package: Option<PackageConfigSettings>,
2371    build_isolation: Option<BuildIsolation>,
2372    extra_build_dependencies: Option<ExtraBuildDependencies>,
2373    extra_build_variables: Option<ExtraBuildVariables>,
2374    exclude_newer: Option<ExcludeNewerOverride>,
2375    exclude_newer_package: Option<ExcludeNewerPackage>,
2376    link_mode: Option<LinkMode>,
2377    compile_bytecode: Option<bool>,
2378    no_sources: Option<bool>,
2379    no_sources_package: Option<Vec<PackageName>>,
2380    no_build: Option<bool>,
2381    no_build_package: Option<Vec<PackageName>>,
2382    no_binary: Option<bool>,
2383    no_binary_package: Option<Vec<PackageName>>,
2384    torch_backend: Option<TorchMode>,
2385}
2386
2387/// The on-disk representation of [`ToolOptions`] in a tool receipt.
2388#[derive(Debug, Clone, Default, Serialize, Deserialize)]
2389#[serde(deny_unknown_fields, rename_all = "kebab-case")]
2390pub struct ToolOptionsWire {
2391    index: Option<Vec<Index>>,
2392    index_url: Option<PipIndex>,
2393    extra_index_url: Option<Vec<PipExtraIndex>>,
2394    no_index: Option<bool>,
2395    find_links: Option<Vec<PipFindLinks>>,
2396    index_strategy: Option<IndexStrategy>,
2397    keyring_provider: Option<KeyringProviderType>,
2398    resolution: Option<ResolutionMode>,
2399    prerelease: Option<PrereleaseMode>,
2400    prerelease_package: Option<PrereleasePackage>,
2401    fork_strategy: Option<ForkStrategy>,
2402    dependency_metadata: Option<Vec<StaticMetadata>>,
2403    config_settings: Option<ConfigSettings>,
2404    config_settings_package: Option<PackageConfigSettings>,
2405    build_isolation: Option<BuildIsolation>,
2406    extra_build_dependencies: Option<ExtraBuildDependencies>,
2407    extra_build_variables: Option<ExtraBuildVariables>,
2408    exclude_newer: Option<ExcludeNewerOverride>,
2409    exclude_newer_span: Option<ExcludeNewerSpan>,
2410    #[serde(serialize_with = "serialize_exclude_newer_package_with_spans")]
2411    exclude_newer_package: Option<ExcludeNewerPackage>,
2412    link_mode: Option<LinkMode>,
2413    compile_bytecode: Option<bool>,
2414    no_sources: Option<bool>,
2415    no_sources_package: Option<Vec<PackageName>>,
2416    no_build: Option<bool>,
2417    no_build_package: Option<Vec<PackageName>>,
2418    no_binary: Option<bool>,
2419    no_binary_package: Option<Vec<PackageName>>,
2420    torch_backend: Option<TorchMode>,
2421}
2422
2423impl From<ResolverInstallerOptions> for ToolOptions {
2424    fn from(value: ResolverInstallerOptions) -> Self {
2425        Self {
2426            index: value.indexes.index.map(|indexes| {
2427                indexes
2428                    .into_iter()
2429                    .map(Index::with_promoted_auth_policy)
2430                    .collect()
2431            }),
2432            index_url: value.indexes.index_url,
2433            extra_index_url: value.indexes.extra_index_url,
2434            no_index: value.indexes.no_index,
2435            find_links: value.indexes.find_links,
2436            index_strategy: value.index_strategy,
2437            keyring_provider: value.keyring_provider,
2438            resolution: value.resolution,
2439            prerelease: value.prerelease,
2440            prerelease_package: value.prerelease_package,
2441            fork_strategy: value.fork_strategy,
2442            dependency_metadata: value.dependency_metadata,
2443            config_settings: value.config_settings,
2444            config_settings_package: value.config_settings_package,
2445            build_isolation: value.build_isolation,
2446            extra_build_dependencies: value.extra_build_dependencies,
2447            extra_build_variables: value.extra_build_variables,
2448            exclude_newer: value.exclude_newer,
2449            exclude_newer_package: value.exclude_newer_package,
2450            link_mode: value.link_mode,
2451            compile_bytecode: value.compile_bytecode,
2452            no_sources: value.no_sources,
2453            no_sources_package: value.no_sources_package,
2454            no_build: value.no_build,
2455            no_build_package: value.no_build_package,
2456            no_binary: value.no_binary,
2457            no_binary_package: value.no_binary_package,
2458            torch_backend: value.torch_backend,
2459        }
2460    }
2461}
2462
2463impl From<ToolOptionsWire> for ToolOptions {
2464    fn from(value: ToolOptionsWire) -> Self {
2465        let exclude_newer = value
2466            .exclude_newer
2467            .map(|exclude_newer| match exclude_newer {
2468                ExcludeNewerOverride::Disabled => ExcludeNewerOverride::Disabled,
2469                ExcludeNewerOverride::Enabled(exclude_newer) => {
2470                    let exclude_newer = *exclude_newer;
2471                    if let Some(span) = value.exclude_newer_span
2472                        && exclude_newer.span().is_none()
2473                    {
2474                        ExcludeNewerValue::relative(span).into()
2475                    } else {
2476                        exclude_newer.into()
2477                    }
2478                }
2479            });
2480
2481        Self {
2482            index: value.index,
2483            index_url: value.index_url,
2484            extra_index_url: value.extra_index_url,
2485            no_index: value.no_index,
2486            find_links: value.find_links,
2487            index_strategy: value.index_strategy,
2488            keyring_provider: value.keyring_provider,
2489            resolution: value.resolution,
2490            prerelease: value.prerelease,
2491            prerelease_package: value.prerelease_package,
2492            fork_strategy: value.fork_strategy,
2493            dependency_metadata: value.dependency_metadata,
2494            config_settings: value.config_settings,
2495            config_settings_package: value.config_settings_package,
2496            build_isolation: value.build_isolation,
2497            extra_build_dependencies: value.extra_build_dependencies,
2498            extra_build_variables: value.extra_build_variables,
2499            exclude_newer,
2500            exclude_newer_package: value.exclude_newer_package,
2501            link_mode: value.link_mode,
2502            compile_bytecode: value.compile_bytecode,
2503            no_sources: value.no_sources,
2504            no_sources_package: value.no_sources_package,
2505            no_build: value.no_build,
2506            no_build_package: value.no_build_package,
2507            no_binary: value.no_binary,
2508            no_binary_package: value.no_binary_package,
2509            torch_backend: value.torch_backend,
2510        }
2511    }
2512}
2513
2514impl From<ToolOptions> for ToolOptionsWire {
2515    fn from(value: ToolOptions) -> Self {
2516        let (exclude_newer, exclude_newer_span) = match &value.exclude_newer {
2517            Some(ExcludeNewerOverride::Disabled) => (Some(ExcludeNewerOverride::Disabled), None),
2518            Some(ExcludeNewerOverride::Enabled(value)) => match value.as_ref() {
2519                ExcludeNewerValue::Absolute(_) => {
2520                    (Some(ExcludeNewerOverride::Enabled(value.clone())), None)
2521                }
2522                ExcludeNewerValue::Relative(span) => (
2523                    Some(ExcludeNewerValue::absolute(value.timestamp()).into()),
2524                    Some(*span),
2525                ),
2526            },
2527            None => (None, None),
2528        };
2529
2530        Self {
2531            index: value.index,
2532            index_url: value.index_url,
2533            extra_index_url: value.extra_index_url,
2534            no_index: value.no_index,
2535            find_links: value.find_links,
2536            index_strategy: value.index_strategy,
2537            keyring_provider: value.keyring_provider,
2538            resolution: value.resolution,
2539            prerelease: value.prerelease,
2540            prerelease_package: value.prerelease_package,
2541            fork_strategy: value.fork_strategy,
2542            dependency_metadata: value.dependency_metadata,
2543            config_settings: value.config_settings,
2544            config_settings_package: value.config_settings_package,
2545            build_isolation: value.build_isolation,
2546            extra_build_dependencies: value.extra_build_dependencies,
2547            extra_build_variables: value.extra_build_variables,
2548            exclude_newer,
2549            exclude_newer_span,
2550            exclude_newer_package: value.exclude_newer_package,
2551            link_mode: value.link_mode,
2552            compile_bytecode: value.compile_bytecode,
2553            no_sources: value.no_sources,
2554            no_sources_package: value.no_sources_package,
2555            no_build: value.no_build,
2556            no_build_package: value.no_build_package,
2557            no_binary: value.no_binary,
2558            no_binary_package: value.no_binary_package,
2559            torch_backend: value.torch_backend,
2560        }
2561    }
2562}
2563
2564impl From<ToolOptions> for ResolverInstallerOptions {
2565    fn from(value: ToolOptions) -> Self {
2566        Self {
2567            indexes: IndexOptions {
2568                index: value.index,
2569                index_url: value.index_url,
2570                extra_index_url: value.extra_index_url,
2571                no_index: value.no_index,
2572                find_links: value.find_links,
2573            },
2574            index_strategy: value.index_strategy,
2575            keyring_provider: value.keyring_provider,
2576            resolution: value.resolution,
2577            prerelease: value.prerelease,
2578            prerelease_package: value.prerelease_package,
2579            fork_strategy: value.fork_strategy,
2580            dependency_metadata: value.dependency_metadata,
2581            config_settings: value.config_settings,
2582            config_settings_package: value.config_settings_package,
2583            build_isolation: value.build_isolation,
2584            extra_build_dependencies: value.extra_build_dependencies,
2585            extra_build_variables: value.extra_build_variables,
2586            exclude_newer: value.exclude_newer,
2587            exclude_newer_package: value.exclude_newer_package,
2588            link_mode: value.link_mode,
2589            compile_bytecode: value.compile_bytecode,
2590            no_sources: value.no_sources,
2591            no_sources_package: value.no_sources_package,
2592            upgrade: None,
2593            reinstall: None,
2594            no_build: value.no_build,
2595            no_build_package: value.no_build_package,
2596            no_binary: value.no_binary,
2597            no_binary_package: value.no_binary_package,
2598            torch_backend: value.torch_backend,
2599        }
2600    }
2601}
2602
2603/// Like [`Options]`, but with any `#[serde(flatten)]` fields inlined. This leads to far, far
2604/// better error messages when deserializing.
2605#[derive(Debug, Clone, Default, Deserialize)]
2606#[serde(rename_all = "kebab-case", deny_unknown_fields)]
2607struct OptionsWire {
2608    // #[serde(flatten)]
2609    // globals: GlobalOptions
2610    required_version: Option<RequiredVersion>,
2611    system_certs: Option<bool>,
2612    native_tls: Option<bool>,
2613    offline: Option<bool>,
2614    no_cache: Option<bool>,
2615    cache_dir: Option<PathBuf>,
2616    preview: Option<bool>,
2617    preview_features: Option<PreviewFeaturesOption>,
2618    python_preference: Option<PythonPreference>,
2619    python_downloads: Option<PythonDownloads>,
2620    concurrent_downloads: Option<NonZeroUsize>,
2621    concurrent_builds: Option<NonZeroUsize>,
2622    concurrent_installs: Option<NonZeroUsize>,
2623
2624    // #[serde(flatten)]
2625    // top_level: ResolverInstallerOptions
2626    index: Option<Vec<Index>>,
2627    index_url: Option<PipIndex>,
2628    extra_index_url: Option<Vec<PipExtraIndex>>,
2629    no_index: Option<bool>,
2630    find_links: Option<Vec<PipFindLinks>>,
2631    index_strategy: Option<IndexStrategy>,
2632    keyring_provider: Option<KeyringProviderType>,
2633    http_proxy: Option<ProxyUrl>,
2634    https_proxy: Option<ProxyUrl>,
2635    no_proxy: Option<Vec<String>>,
2636    allow_insecure_host: Option<Vec<TrustedHost>>,
2637    resolution: Option<ResolutionMode>,
2638    prerelease: Option<PrereleaseMode>,
2639    prerelease_package: Option<PrereleasePackage>,
2640    fork_strategy: Option<ForkStrategy>,
2641    dependency_metadata: Option<Vec<StaticMetadata>>,
2642    config_settings: Option<ConfigSettings>,
2643    config_settings_package: Option<PackageConfigSettings>,
2644    no_build_isolation: Option<bool>,
2645    no_build_isolation_package: Option<Vec<PackageName>>,
2646    extra_build_dependencies: Option<ExtraBuildDependencies>,
2647    extra_build_variables: Option<ExtraBuildVariables>,
2648    exclude_newer: Option<ExcludeNewerOverride>,
2649    exclude_newer_package: Option<ExcludeNewerPackage>,
2650    link_mode: Option<LinkMode>,
2651    compile_bytecode: Option<bool>,
2652    no_sources: Option<bool>,
2653    no_sources_package: Option<Vec<PackageName>>,
2654    upgrade: Option<bool>,
2655    upgrade_package: Option<Vec<Requirement<VerbatimParsedUrl>>>,
2656    reinstall: Option<bool>,
2657    reinstall_package: Option<Vec<PackageName>>,
2658    no_build: Option<bool>,
2659    no_build_package: Option<Vec<PackageName>>,
2660    no_binary: Option<bool>,
2661    no_binary_package: Option<Vec<PackageName>>,
2662    torch_backend: Option<TorchMode>,
2663
2664    // #[serde(flatten)]
2665    // install_mirror: PythonInstallMirrors,
2666    python_install_mirror: Option<String>,
2667    pypy_install_mirror: Option<String>,
2668    graalpy_install_mirror: Option<String>,
2669    pyodide_install_mirror: Option<String>,
2670    python_downloads_json_url: Option<String>,
2671
2672    // #[serde(flatten)]
2673    // publish: PublishOptions
2674    publish_url: Option<DisplaySafeUrl>,
2675    trusted_publishing: Option<TrustedPublishing>,
2676    check_url: Option<IndexUrl>,
2677
2678    // #[serde(flatten)]
2679    // add: AddOptions
2680    add_bounds: Option<AddBoundsKind>,
2681
2682    audit: Option<AuditOptions>,
2683    pip: Option<PipOptions>,
2684    cache_keys: Option<Vec<CacheKey>>,
2685
2686    // NOTE(charlie): These fields are shared with `ToolUv` in
2687    // `crates/uv-workspace/src/pyproject.rs`. The documentation lives on that struct.
2688    // They're respected in both `pyproject.toml` and `uv.toml` files.
2689    override_dependencies: Option<Vec<OverrideDependency>>,
2690    exclude_dependencies: Option<Vec<ExcludeDependency>>,
2691    constraint_dependencies: Option<Vec<Requirement<VerbatimParsedUrl>>>,
2692    build_constraint_dependencies: Option<Vec<BuildConstraintDependency>>,
2693    environments: Option<SupportedEnvironments>,
2694    required_environments: Option<SupportedEnvironments>,
2695    minimum_libc_version: Option<MinimumLibcVersion>,
2696
2697    // NOTE(charlie): These fields should be kept in-sync with `ToolUv` in
2698    // `crates/uv-workspace/src/pyproject.rs`. The documentation lives on that struct.
2699    // They're only respected in `pyproject.toml` files, and should be rejected in `uv.toml` files.
2700    conflicts: Option<serde::de::IgnoredAny>,
2701    workspace: Option<serde::de::IgnoredAny>,
2702    sources: Option<serde::de::IgnoredAny>,
2703    managed: Option<serde::de::IgnoredAny>,
2704    r#package: Option<serde::de::IgnoredAny>,
2705    default_groups: Option<serde::de::IgnoredAny>,
2706    dependency_groups: Option<serde::de::IgnoredAny>,
2707    dev_dependencies: Option<serde::de::IgnoredAny>,
2708
2709    // Build backend
2710    build_backend: Option<serde::de::IgnoredAny>,
2711}
2712
2713impl TryFrom<OptionsWire> for Options {
2714    type Error = &'static str;
2715
2716    #[allow(deprecated)]
2717    fn try_from(value: OptionsWire) -> Result<Self, Self::Error> {
2718        let OptionsWire {
2719            required_version,
2720            system_certs,
2721            native_tls,
2722            offline,
2723            no_cache,
2724            cache_dir,
2725            preview,
2726            preview_features,
2727            python_preference,
2728            python_downloads,
2729            python_install_mirror,
2730            pypy_install_mirror,
2731            graalpy_install_mirror,
2732            pyodide_install_mirror,
2733            python_downloads_json_url,
2734            concurrent_downloads,
2735            concurrent_builds,
2736            concurrent_installs,
2737            index,
2738            index_url,
2739            extra_index_url,
2740            no_index,
2741            find_links,
2742            index_strategy,
2743            keyring_provider,
2744            http_proxy,
2745            https_proxy,
2746            no_proxy,
2747            allow_insecure_host,
2748            resolution,
2749            prerelease,
2750            prerelease_package,
2751            fork_strategy,
2752            dependency_metadata,
2753            config_settings,
2754            config_settings_package,
2755            no_build_isolation,
2756            no_build_isolation_package,
2757            exclude_newer,
2758            exclude_newer_package,
2759            link_mode,
2760            compile_bytecode,
2761            no_sources,
2762            no_sources_package,
2763            upgrade,
2764            upgrade_package,
2765            reinstall,
2766            reinstall_package,
2767            no_build,
2768            no_build_package,
2769            no_binary,
2770            no_binary_package,
2771            torch_backend,
2772            audit,
2773            pip,
2774            cache_keys,
2775            override_dependencies,
2776            exclude_dependencies,
2777            constraint_dependencies,
2778            build_constraint_dependencies,
2779            environments,
2780            required_environments,
2781            minimum_libc_version,
2782            conflicts,
2783            publish_url,
2784            trusted_publishing,
2785            check_url,
2786            workspace,
2787            sources,
2788            default_groups,
2789            dependency_groups,
2790            extra_build_dependencies,
2791            extra_build_variables,
2792            dev_dependencies,
2793            managed,
2794            package,
2795            add_bounds: bounds,
2796            // Used by the build backend
2797            build_backend,
2798        } = value;
2799
2800        Ok(Self {
2801            globals: GlobalOptions {
2802                required_version,
2803                system_certs,
2804                native_tls,
2805                offline,
2806                no_cache,
2807                cache_dir,
2808                preview: PreviewOption::try_from(preview, preview_features)?,
2809                python_preference,
2810                python_downloads,
2811                concurrent_downloads,
2812                concurrent_builds,
2813                concurrent_installs,
2814                http_proxy,
2815                https_proxy,
2816                no_proxy,
2817                // Used twice for backwards compatibility
2818                allow_insecure_host: allow_insecure_host.clone(),
2819            },
2820            top_level: ResolverInstallerSchema {
2821                index,
2822                index_url,
2823                extra_index_url,
2824                no_index,
2825                find_links,
2826                index_strategy,
2827                keyring_provider,
2828                resolution,
2829                prerelease,
2830                prerelease_package,
2831                fork_strategy,
2832                dependency_metadata,
2833                config_settings,
2834                config_settings_package,
2835                no_build_isolation,
2836                no_build_isolation_package,
2837                extra_build_dependencies,
2838                extra_build_variables,
2839                exclude_newer,
2840                exclude_newer_package,
2841                link_mode,
2842                compile_bytecode,
2843                no_sources,
2844                no_sources_package,
2845                upgrade,
2846                upgrade_package,
2847                reinstall,
2848                reinstall_package,
2849                no_build,
2850                no_build_package,
2851                no_binary,
2852                no_binary_package,
2853                torch_backend,
2854            },
2855            pip,
2856            cache_keys,
2857            build_backend,
2858            override_dependencies,
2859            exclude_dependencies,
2860            constraint_dependencies,
2861            build_constraint_dependencies,
2862            environments,
2863            required_environments,
2864            minimum_libc_version,
2865            install_mirrors: PythonInstallMirrors {
2866                python_install_mirror,
2867                pypy_install_mirror,
2868                graalpy_install_mirror,
2869                pyodide_install_mirror,
2870                python_downloads_json_url,
2871            },
2872            conflicts,
2873            publish: PublishOptions {
2874                publish_url,
2875                trusted_publishing,
2876                check_url,
2877            },
2878            add: AddOptions { add_bounds: bounds },
2879            audit,
2880            workspace,
2881            sources,
2882            dev_dependencies,
2883            default_groups,
2884            dependency_groups,
2885            managed,
2886            package,
2887        })
2888    }
2889}
2890
2891#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)]
2892#[serde(rename_all = "kebab-case")]
2893#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2894pub struct PublishOptions {
2895    /// The URL for publishing packages to the Python package index (by default:
2896    /// <https://upload.pypi.org/legacy/>).
2897    #[option(
2898        default = "\"https://upload.pypi.org/legacy/\"",
2899        value_type = "str",
2900        example = r#"
2901            publish-url = "https://test.pypi.org/legacy/"
2902        "#
2903    )]
2904    pub publish_url: Option<DisplaySafeUrl>,
2905
2906    /// Configure trusted publishing.
2907    ///
2908    /// By default, uv checks for trusted publishing when running in a supported environment, but
2909    /// ignores it if it isn't configured.
2910    ///
2911    /// uv's supported environments for trusted publishing include GitHub Actions and GitLab CI/CD.
2912    #[option(
2913        default = "automatic",
2914        value_type = "str",
2915        example = r#"
2916            trusted-publishing = "always"
2917        "#
2918    )]
2919    pub trusted_publishing: Option<TrustedPublishing>,
2920
2921    /// Check an index URL for existing files to skip duplicate uploads.
2922    ///
2923    /// This option allows retrying publishing that failed after only some, but not all files have
2924    /// been uploaded, and handles error due to parallel uploads of the same file.
2925    ///
2926    /// Before uploading, the index is checked. If the exact same file already exists in the index,
2927    /// the file will not be uploaded. If an error occurred during the upload, the index is checked
2928    /// again, to handle cases where the identical file was uploaded twice in parallel.
2929    ///
2930    /// The exact behavior will vary based on the index. When uploading to PyPI, uploading the same
2931    /// file succeeds even without `--check-url`, while most other indexes error.
2932    ///
2933    /// The index must provide one of the supported hashes (SHA-256, SHA-384, or SHA-512).
2934    #[option(
2935        default = "None",
2936        value_type = "str",
2937        example = r#"
2938            check-url = "https://test.pypi.org/simple"
2939        "#
2940    )]
2941    pub check_url: Option<IndexUrl>,
2942}
2943
2944#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)]
2945#[serde(rename_all = "kebab-case")]
2946#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2947pub struct AddOptions {
2948    /// The default version specifier when adding a dependency.
2949    ///
2950    /// When adding a dependency to the project, if no constraint or URL is provided, a constraint
2951    /// is added based on the latest compatible version of the package. By default, a lower bound
2952    /// constraint is used, e.g., `>=1.2.3`.
2953    ///
2954    /// When `--frozen` is provided, no resolution is performed, and dependencies are always added
2955    /// without constraints.
2956    ///
2957    /// This option is in preview and may change in any future release.
2958    #[option(
2959        default = "\"lower\"",
2960        value_type = "str",
2961        example = r#"
2962            add-bounds = "major"
2963        "#,
2964        possible_values = true
2965    )]
2966    pub add_bounds: Option<AddBoundsKind>,
2967}
2968
2969#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)]
2970#[serde(rename_all = "kebab-case")]
2971#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2972pub struct AuditOptions {
2973    /// Whether to run the automatic malware check during sync operations.
2974    #[option(
2975        default = "false",
2976        value_type = "bool",
2977        example = r#"
2978            malware-check = true
2979        "#
2980    )]
2981    pub malware_check: Option<bool>,
2982
2983    /// The vulnerability service URL to use for automatic malware checks.
2984    #[option(
2985        default = "\"https://api.osv.dev/\"",
2986        value_type = "str",
2987        example = r#"
2988            malware-check-url = "https://example.com"
2989        "#
2990    )]
2991    pub malware_check_url: Option<DisplaySafeUrl>,
2992
2993    /// A list of vulnerability IDs to ignore during auditing.
2994    ///
2995    /// Vulnerabilities matching any of the provided IDs (including aliases) will be excluded from
2996    /// the audit results.
2997    #[option(
2998        default = "[]",
2999        value_type = "list[str]",
3000        example = r#"
3001            ignore = ["PYSEC-2022-43017", "GHSA-5239-wwwm-4pmq"]
3002        "#
3003    )]
3004    pub ignore: Option<Vec<String>>,
3005
3006    /// A list of vulnerability IDs to ignore during auditing, but only while no fix is available.
3007    ///
3008    /// Vulnerabilities matching any of the provided IDs (including aliases) will be excluded from
3009    /// the audit results as long as they have no known fix versions. Once a fix version becomes
3010    /// available, the vulnerability will be reported again.
3011    #[option(
3012        default = "[]",
3013        value_type = "list[str]",
3014        example = r#"
3015            ignore-until-fixed = ["PYSEC-2022-43017"]
3016        "#
3017    )]
3018    pub ignore_until_fixed: Option<Vec<String>>,
3019}
3020
3021#[derive(Debug, Clone)]
3022pub struct MalwareCheckSettings {
3023    /// Whether the malware check is enabled.
3024    pub enabled: bool,
3025    /// The OSV-shaped service URL to use for malware checks.
3026    pub malware_check_url: Option<DisplaySafeUrl>,
3027}
3028
3029impl MalwareCheckSettings {
3030    pub fn resolve(
3031        filesystem: Option<&FilesystemOptions>,
3032        environment: &EnvironmentOptions,
3033    ) -> Self {
3034        let audit = filesystem.and_then(|options| options.audit.as_ref());
3035
3036        Self {
3037            enabled: environment
3038                .malware_check
3039                .value
3040                .or(audit.and_then(|audit| audit.malware_check))
3041                .unwrap_or_default(),
3042            malware_check_url: environment
3043                .malware_check_url
3044                .clone()
3045                .or_else(|| audit.and_then(|audit| audit.malware_check_url.clone())),
3046        }
3047    }
3048}
3049
3050/// Represents the `preview-features` configuration option.
3051#[derive(Debug, Clone)]
3052#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3053#[cfg_attr(feature = "schemars", schemars(untagged))]
3054pub enum PreviewFeaturesOption {
3055    Toggle(bool),
3056    Features(Vec<MaybePreviewFeature>),
3057}
3058
3059// Dispatch by input type so invalid list elements retain their detailed diagnostics.
3060struct PreviewFeaturesVisitor;
3061
3062impl<'de> Visitor<'de> for PreviewFeaturesVisitor {
3063    type Value = PreviewFeaturesOption;
3064
3065    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3066        formatter.write_str("a boolean or a list of preview feature names")
3067    }
3068
3069    fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
3070    where
3071        E: Error,
3072    {
3073        Ok(PreviewFeaturesOption::Toggle(value))
3074    }
3075
3076    fn visit_seq<S>(self, sequence: S) -> Result<Self::Value, S::Error>
3077    where
3078        S: SeqAccess<'de>,
3079    {
3080        Vec::deserialize(SeqAccessDeserializer::new(sequence)).map(PreviewFeaturesOption::Features)
3081    }
3082}
3083
3084impl<'de> Deserialize<'de> for PreviewFeaturesOption {
3085    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3086    where
3087        D: Deserializer<'de>,
3088    {
3089        deserializer.deserialize_any(PreviewFeaturesVisitor)
3090    }
3091}
3092
3093#[expect(
3094    dead_code,
3095    reason = "Fields are only used by the OptionsMetadata and JsonSchema derives"
3096)]
3097#[derive(OptionsMetadata)]
3098#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
3099#[cfg_attr(feature = "schemars", schemars(rename_all = "kebab-case"))]
3100struct PreviewOptionsDefinition {
3101    // This legacy setting remains supported and included in the JSON schema, but is omitted from
3102    // option metadata so the generated settings reference documents only `preview-features`.
3103    /// Whether to enable all experimental, preview features.
3104    ///
3105    /// Use `preview-features` instead.
3106    #[deprecated(note = "use `preview-features` instead")]
3107    preview: Option<bool>,
3108    /// Whether to enable specific or all experimental preview features.
3109    ///
3110    /// Unknown feature names are ignored with a warning.
3111    #[option(
3112        default = "false",
3113        value_type = "bool | list[str]",
3114        example = r#"
3115            preview-features = true
3116            # or
3117            preview-features = ["json-output"]
3118        "#
3119    )]
3120    preview_features: Option<PreviewFeaturesOption>,
3121}
3122
3123/// Represents the user's preview configuration from either `preview` or `preview-features`.
3124#[derive(Debug, Clone)]
3125pub enum PreviewOption {
3126    /// Whether to enable all experimental, preview features.
3127    Preview(bool),
3128    /// Whether to enable specific or all experimental preview features.
3129    PreviewFeatures(PreviewFeaturesOption),
3130}
3131
3132impl uv_options_metadata::OptionsMetadata for PreviewOption {
3133    fn record(visit: &mut dyn uv_options_metadata::Visit) {
3134        <PreviewOptionsDefinition as uv_options_metadata::OptionsMetadata>::record(visit);
3135    }
3136}
3137
3138#[cfg(feature = "schemars")]
3139struct ConflictingPreviewOptions;
3140
3141#[cfg(feature = "schemars")]
3142impl schemars::JsonSchema for ConflictingPreviewOptions {
3143    fn schema_name() -> Cow<'static, str> {
3144        Cow::Borrowed("ConflictingPreviewOptions")
3145    }
3146
3147    fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
3148        schemars::json_schema!({
3149            "type": "object",
3150            "properties": {
3151                "preview": {},
3152                "preview-features": {},
3153            },
3154            "required": ["preview", "preview-features"],
3155        })
3156    }
3157}
3158
3159#[cfg(feature = "schemars")]
3160impl schemars::JsonSchema for PreviewOption {
3161    fn schema_name() -> Cow<'static, str> {
3162        Cow::Borrowed("PreviewOption")
3163    }
3164
3165    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
3166        let mut schema = <PreviewOptionsDefinition as schemars::JsonSchema>::json_schema(generator);
3167        // Keep this constraint in a referenced schema to avoid a fastjsonschema code-generation
3168        // bug. See: https://github.com/astral-sh/uv/pull/20547.
3169        schema.insert(
3170            "not".to_string(),
3171            generator
3172                .subschema_for::<ConflictingPreviewOptions>()
3173                .into(),
3174        );
3175        schema
3176    }
3177}
3178
3179impl PreviewOption {
3180    fn try_from(
3181        preview: Option<bool>,
3182        preview_features: Option<PreviewFeaturesOption>,
3183    ) -> Result<Option<Self>, &'static str> {
3184        match (preview, preview_features) {
3185            (Some(_), Some(_)) => Err("cannot specify both `preview` and `preview-features`"),
3186            (Some(b), None) => Ok(Some(Self::Preview(b))),
3187            (None, Some(features)) => Ok(Some(Self::PreviewFeatures(features))),
3188            (None, None) => Ok(None),
3189        }
3190    }
3191
3192    /// Resolve the preview configuration, warning and ignoring unknown feature names.
3193    pub fn resolve(&self) -> Preview {
3194        use PreviewFeaturesOption::{Features, Toggle};
3195
3196        match self {
3197            Self::Preview(false) | Self::PreviewFeatures(Toggle(false)) => Preview::default(),
3198            Self::Preview(true) | Self::PreviewFeatures(Toggle(true)) => Preview::all(),
3199            Self::PreviewFeatures(Features(features)) => Preview::from_feature_names(features),
3200        }
3201    }
3202}