Skip to main content

uv_settings/
settings.rs

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