Add a required version setting to uv (#10248)

## Summary

This follows Ruff's design exactly: you can provide a version specifier
(like `>=0.5`), and we'll enforce it at runtime.

Closes https://github.com/astral-sh/uv/issues/8605.
This commit is contained in:
Charlie Marsh 2024-12-31 10:37:46 -05:00 committed by GitHub
parent a2f436f79b
commit c77aa5820b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 188 additions and 11 deletions

1
Cargo.lock generated
View File

@ -4772,6 +4772,7 @@ dependencies = [
"uv-cache-info", "uv-cache-info",
"uv-cache-key", "uv-cache-key",
"uv-normalize", "uv-normalize",
"uv-pep440",
"uv-pep508", "uv-pep508",
"uv-platform-tags", "uv-platform-tags",
"uv-pypi-types", "uv-pypi-types",

View File

@ -21,6 +21,7 @@ uv-cache = { workspace = true }
uv-cache-info = { workspace = true } uv-cache-info = { workspace = true }
uv-cache-key = { workspace = true } uv-cache-key = { workspace = true }
uv-normalize = { workspace = true } uv-normalize = { workspace = true }
uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true, features = ["schemars"] } uv-pep508 = { workspace = true, features = ["schemars"] }
uv-platform-tags = { workspace = true } uv-platform-tags = { workspace = true }
uv-pypi-types = { workspace = true } uv-pypi-types = { workspace = true }

View File

@ -16,6 +16,7 @@ pub use package_options::*;
pub use preview::*; pub use preview::*;
pub use project_build_backend::*; pub use project_build_backend::*;
pub use rayon::*; pub use rayon::*;
pub use required_version::*;
pub use sources::*; pub use sources::*;
pub use target_triple::*; pub use target_triple::*;
pub use trusted_host::*; pub use trusted_host::*;
@ -40,6 +41,7 @@ mod package_options;
mod preview; mod preview;
mod project_build_backend; mod project_build_backend;
mod rayon; mod rayon;
mod required_version;
mod sources; mod sources;
mod target_triple; mod target_triple;
mod trusted_host; mod trusted_host;

View File

@ -0,0 +1,61 @@
use std::str::FromStr;
use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers, VersionSpecifiersParseError};
/// A required version of uv, represented as a version specifier (e.g. `>=0.5.0`).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RequiredVersion(VersionSpecifiers);
impl RequiredVersion {
/// Return `true` if the given version is required.
pub fn contains(&self, version: &Version) -> bool {
self.0.contains(version)
}
}
impl FromStr for RequiredVersion {
type Err = VersionSpecifiersParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Treat `0.5.0` as `==0.5.0`, for backwards compatibility.
if let Ok(version) = Version::from_str(s) {
Ok(Self(VersionSpecifiers::from(
VersionSpecifier::equals_version(version),
)))
} else {
Ok(Self(VersionSpecifiers::from_str(s)?))
}
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for RequiredVersion {
fn schema_name() -> String {
String::from("RequiredVersion")
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::String.into()),
metadata: Some(Box::new(schemars::schema::Metadata {
description: Some("A version specifier, e.g. `>=0.5.0` or `==0.5.0`.".to_string()),
..schemars::schema::Metadata::default()
})),
..schemars::schema::SchemaObject::default()
}
.into()
}
}
impl<'de> serde::Deserialize<'de> for RequiredVersion {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(serde::de::Error::custom)
}
}
impl std::fmt::Display for RequiredVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}

View File

@ -4,7 +4,8 @@ use std::path::PathBuf;
use url::Url; use url::Url;
use uv_configuration::{ use uv_configuration::{
ConfigSettings, IndexStrategy, KeyringProviderType, TargetTriple, TrustedPublishing, ConfigSettings, IndexStrategy, KeyringProviderType, RequiredVersion, TargetTriple,
TrustedPublishing,
}; };
use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex}; use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex};
use uv_install_wheel::linker::LinkMode; use uv_install_wheel::linker::LinkMode;
@ -73,12 +74,12 @@ macro_rules! impl_combine_or {
impl_combine_or!(AnnotationStyle); impl_combine_or!(AnnotationStyle);
impl_combine_or!(ExcludeNewer); impl_combine_or!(ExcludeNewer);
impl_combine_or!(ForkStrategy);
impl_combine_or!(Index); impl_combine_or!(Index);
impl_combine_or!(IndexStrategy); impl_combine_or!(IndexStrategy);
impl_combine_or!(IndexUrl); impl_combine_or!(IndexUrl);
impl_combine_or!(KeyringProviderType); impl_combine_or!(KeyringProviderType);
impl_combine_or!(LinkMode); impl_combine_or!(LinkMode);
impl_combine_or!(ForkStrategy);
impl_combine_or!(NonZeroUsize); impl_combine_or!(NonZeroUsize);
impl_combine_or!(PathBuf); impl_combine_or!(PathBuf);
impl_combine_or!(PipExtraIndex); impl_combine_or!(PipExtraIndex);
@ -88,10 +89,11 @@ impl_combine_or!(PrereleaseMode);
impl_combine_or!(PythonDownloads); impl_combine_or!(PythonDownloads);
impl_combine_or!(PythonPreference); impl_combine_or!(PythonPreference);
impl_combine_or!(PythonVersion); impl_combine_or!(PythonVersion);
impl_combine_or!(RequiredVersion);
impl_combine_or!(ResolutionMode); impl_combine_or!(ResolutionMode);
impl_combine_or!(SchemaConflicts);
impl_combine_or!(String); impl_combine_or!(String);
impl_combine_or!(SupportedEnvironments); impl_combine_or!(SupportedEnvironments);
impl_combine_or!(SchemaConflicts);
impl_combine_or!(TargetTriple); impl_combine_or!(TargetTriple);
impl_combine_or!(TrustedPublishing); impl_combine_or!(TrustedPublishing);
impl_combine_or!(Url); impl_combine_or!(Url);

View File

@ -3,8 +3,8 @@ use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf};
use url::Url; use url::Url;
use uv_cache_info::CacheKey; use uv_cache_info::CacheKey;
use uv_configuration::{ use uv_configuration::{
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple, ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, RequiredVersion,
TrustedHost, TrustedPublishing, TargetTriple, TrustedHost, TrustedPublishing,
}; };
use uv_distribution_types::{ use uv_distribution_types::{
Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata, Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata,
@ -149,6 +149,20 @@ impl Options {
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GlobalOptions { pub struct GlobalOptions {
/// Enforce a requirement on the version of uv.
///
/// If the version of uv does not meet the requirement at runtime, uv will exit
/// with an error.
///
/// Accepts a [PEP 440](https://peps.python.org/pep-0440/) specifier, like `==0.5.0` or `>=0.5.0`.
#[option(
default = "null",
value_type = "str",
example = r#"
required-version = ">=0.5.0"
"#
)]
pub required_version: Option<RequiredVersion>,
/// Whether to load TLS certificates from the platform's native certificate store. /// Whether to load TLS certificates from the platform's native certificate store.
/// ///
/// By default, uv loads certificates from the bundled `webpki-roots` crate. The /// By default, uv loads certificates from the bundled `webpki-roots` crate. The
@ -1623,6 +1637,7 @@ impl From<ToolOptions> for ResolverInstallerOptions {
pub struct OptionsWire { pub struct OptionsWire {
// #[serde(flatten)] // #[serde(flatten)]
// globals: GlobalOptions // globals: GlobalOptions
required_version: Option<RequiredVersion>,
native_tls: Option<bool>, native_tls: Option<bool>,
offline: Option<bool>, offline: Option<bool>,
no_cache: Option<bool>, no_cache: Option<bool>,
@ -1704,6 +1719,7 @@ pub struct OptionsWire {
impl From<OptionsWire> for Options { impl From<OptionsWire> for Options {
fn from(value: OptionsWire) -> Self { fn from(value: OptionsWire) -> Self {
let OptionsWire { let OptionsWire {
required_version,
native_tls, native_tls,
offline, offline,
no_cache, no_cache,
@ -1764,6 +1780,7 @@ impl From<OptionsWire> for Options {
Self { Self {
globals: GlobalOptions { globals: GlobalOptions {
required_version,
native_tls, native_tls,
offline, offline,
no_cache, no_cache,

View File

@ -4,6 +4,7 @@ use std::fmt::Write;
use std::io::stdout; use std::io::stdout;
use std::path::Path; use std::path::Path;
use std::process::ExitCode; use std::process::ExitCode;
use std::str::FromStr;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use anstream::eprintln; use anstream::eprintln;
@ -208,6 +209,16 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
// Resolve the cache settings. // Resolve the cache settings.
let cache_settings = CacheSettings::resolve(*cli.top_level.cache_args, filesystem.as_ref()); let cache_settings = CacheSettings::resolve(*cli.top_level.cache_args, filesystem.as_ref());
// Enforce the required version.
if let Some(required_version) = globals.required_version.as_ref() {
let package_version = uv_pep440::Version::from_str(uv_version::version())?;
if !required_version.contains(&package_version) {
return Err(anyhow::anyhow!(
"Required version `{required_version}` does not match the running version `{package_version}`",
));
}
}
// Configure the `tracing` crate, which controls internal logging. // Configure the `tracing` crate, which controls internal logging.
#[cfg(feature = "tracing-durations-export")] #[cfg(feature = "tracing-durations-export")]
let (duration_layer, _duration_guard) = logging::setup_duration()?; let (duration_layer, _duration_guard) = logging::setup_duration()?;

View File

@ -23,8 +23,8 @@ use uv_client::Connectivity;
use uv_configuration::{ use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, DevGroupsSpecification, EditableMode, ExportFormat, BuildOptions, Concurrency, ConfigSettings, DevGroupsSpecification, EditableMode, ExportFormat,
ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType,
NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall, SourceStrategy, TargetTriple, NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall, RequiredVersion,
TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem, SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem,
}; };
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl}; use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl};
use uv_install_wheel::linker::LinkMode; use uv_install_wheel::linker::LinkMode;
@ -53,6 +53,7 @@ const PYPI_PUBLISH_URL: &str = "https://upload.pypi.org/legacy/";
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct GlobalSettings { pub(crate) struct GlobalSettings {
pub(crate) required_version: Option<RequiredVersion>,
pub(crate) quiet: bool, pub(crate) quiet: bool,
pub(crate) verbose: u8, pub(crate) verbose: u8,
pub(crate) color: ColorChoice, pub(crate) color: ColorChoice,
@ -72,6 +73,8 @@ impl GlobalSettings {
/// Resolve the [`GlobalSettings`] from the CLI and filesystem configuration. /// Resolve the [`GlobalSettings`] from the CLI and filesystem configuration.
pub(crate) fn resolve(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self { pub(crate) fn resolve(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self {
Self { Self {
required_version: workspace
.and_then(|workspace| workspace.globals.required_version.clone()),
quiet: args.quiet, quiet: args.quiet,
verbose: args.verbose, verbose: args.verbose,
color: if let Some(color_choice) = args.color { color: if let Some(color_choice) = args.color {

View File

@ -218,8 +218,8 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> {
let mut filters = context.filters(); let mut filters = context.filters();
filters.push(( filters.push((
"expected one of `native-tls`, `offline`, .*", "expected one of `required-version`, `native-tls`, .*",
"expected one of `native-tls`, `offline`, [...]", "expected one of `required-version`, `native-tls`, [...]",
)); ));
uv_snapshot!(filters, context.pip_install() uv_snapshot!(filters, context.pip_install()
@ -235,7 +235,7 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> {
| |
2 | unknown = "field" 2 | unknown = "field"
| ^^^^^^^ | ^^^^^^^
unknown field `unknown`, expected one of `native-tls`, `offline`, [...] unknown field `unknown`, expected one of `required-version`, `native-tls`, [...]
Resolved in [TIME] Resolved in [TIME]
Audited in [TIME] Audited in [TIME]

View File

@ -63,6 +63,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -215,6 +216,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -368,6 +370,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -553,6 +556,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -707,6 +711,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -840,6 +845,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -1017,6 +1023,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -1200,6 +1207,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -1437,6 +1445,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -1613,6 +1622,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -1752,6 +1762,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -1933,6 +1944,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -2138,6 +2150,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -2267,6 +2280,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -2396,6 +2410,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -2527,6 +2542,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -2677,6 +2693,7 @@ fn resolve_tool() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -2835,6 +2852,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -2992,6 +3010,7 @@ fn resolve_both() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -3267,6 +3286,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -3438,7 +3458,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
| |
1 | [project] 1 | [project]
| ^^^^^^^ | ^^^^^^^
unknown field `project`, expected one of `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `check-url`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies`, `build-backend` unknown field `project`, expected one of `required-version`, `native-tls`, `offline`, `no-cache`, `cache-dir`, `preview`, `python-preference`, `python-downloads`, `concurrent-downloads`, `concurrent-builds`, `concurrent-installs`, `index`, `index-url`, `extra-index-url`, `no-index`, `find-links`, `index-strategy`, `keyring-provider`, `allow-insecure-host`, `resolution`, `prerelease`, `fork-strategy`, `dependency-metadata`, `config-settings`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `python-install-mirror`, `pypy-install-mirror`, `publish-url`, `trusted-publishing`, `check-url`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dev-dependencies`, `build-backend`
"### "###
); );
@ -3520,6 +3540,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -3652,6 +3673,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -3792,6 +3814,7 @@ fn allow_insecure_host() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -3946,6 +3969,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -4129,6 +4153,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -4318,6 +4343,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -4502,6 +4528,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -4693,6 +4720,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -4877,6 +4905,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -5074,6 +5103,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -5197,6 +5227,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -5318,6 +5349,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -5441,6 +5473,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -5562,6 +5595,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,
@ -5684,6 +5718,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0 exit_code: 0
----- stdout ----- ----- stdout -----
GlobalSettings { GlobalSettings {
required_version: None,
quiet: false, quiet: false,
verbose: 0, verbose: 0,
color: Auto, color: Auto,

View File

@ -1522,6 +1522,35 @@ Reinstall a specific package, regardless of whether it's already installed. Impl
--- ---
### [`required-version`](#required-version) {: #required-version }
Enforce a requirement on the version of uv.
If the version of uv does not meet the requirement at runtime, uv will exit
with an error.
Accepts a [PEP 440](https://peps.python.org/pep-0440/) specifier, like `==0.5.0` or `>=0.5.0`.
**Default value**: `null`
**Type**: `str`
**Example usage**:
=== "pyproject.toml"
```toml
[tool.uv]
required-version = ">=0.5.0"
```
=== "uv.toml"
```toml
required-version = ">=0.5.0"
```
---
### [`resolution`](#resolution) {: #resolution } ### [`resolution`](#resolution) {: #resolution }
The strategy to use when selecting between the different compatible versions for a given The strategy to use when selecting between the different compatible versions for a given

15
uv.schema.json generated
View File

@ -443,6 +443,17 @@
"$ref": "#/definitions/PackageName" "$ref": "#/definitions/PackageName"
} }
}, },
"required-version": {
"description": "Enforce a requirement on the version of uv.\n\nIf the version of uv does not meet the requirement at runtime, uv will exit with an error.\n\nAccepts a [PEP 440](https://peps.python.org/pep-0440/) specifier, like `==0.5.0` or `>=0.5.0`.",
"anyOf": [
{
"$ref": "#/definitions/RequiredVersion"
},
{
"type": "null"
}
]
},
"resolution": { "resolution": {
"description": "The strategy to use when selecting between the different compatible versions for a given package requirement.\n\nBy default, uv will use the latest compatible version of each package (`highest`).", "description": "The strategy to use when selecting between the different compatible versions for a given package requirement.\n\nBy default, uv will use the latest compatible version of each package (`highest`).",
"anyOf": [ "anyOf": [
@ -1409,6 +1420,10 @@
"type": "string", "type": "string",
"pattern": "^3\\.\\d+(\\.\\d+)?$" "pattern": "^3\\.\\d+(\\.\\d+)?$"
}, },
"RequiredVersion": {
"description": "A version specifier, e.g. `>=0.5.0` or `==0.5.0`.",
"type": "string"
},
"Requirement": { "Requirement": {
"description": "A PEP 508 dependency specifier, e.g., `ruff >= 0.6.0`", "description": "A PEP 508 dependency specifier, e.g., `ruff >= 0.6.0`",
"type": "string" "type": "string"