Skip to main content

uv_settings/
settings.rs

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