Add a required version setting to uv (#10248)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86_64 (push) Blocked by required conditions
CI / check system | python3.10 on windows (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

## 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-key",
"uv-normalize",
"uv-pep440",
"uv-pep508",
"uv-platform-tags",
"uv-pypi-types",

View file

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

View file

@ -16,6 +16,7 @@ pub use package_options::*;
pub use preview::*;
pub use project_build_backend::*;
pub use rayon::*;
pub use required_version::*;
pub use sources::*;
pub use target_triple::*;
pub use trusted_host::*;
@ -40,6 +41,7 @@ mod package_options;
mod preview;
mod project_build_backend;
mod rayon;
mod required_version;
mod sources;
mod target_triple;
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 uv_configuration::{
ConfigSettings, IndexStrategy, KeyringProviderType, TargetTriple, TrustedPublishing,
ConfigSettings, IndexStrategy, KeyringProviderType, RequiredVersion, TargetTriple,
TrustedPublishing,
};
use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex};
use uv_install_wheel::linker::LinkMode;
@ -73,12 +74,12 @@ macro_rules! impl_combine_or {
impl_combine_or!(AnnotationStyle);
impl_combine_or!(ExcludeNewer);
impl_combine_or!(ForkStrategy);
impl_combine_or!(Index);
impl_combine_or!(IndexStrategy);
impl_combine_or!(IndexUrl);
impl_combine_or!(KeyringProviderType);
impl_combine_or!(LinkMode);
impl_combine_or!(ForkStrategy);
impl_combine_or!(NonZeroUsize);
impl_combine_or!(PathBuf);
impl_combine_or!(PipExtraIndex);
@ -88,10 +89,11 @@ impl_combine_or!(PrereleaseMode);
impl_combine_or!(PythonDownloads);
impl_combine_or!(PythonPreference);
impl_combine_or!(PythonVersion);
impl_combine_or!(RequiredVersion);
impl_combine_or!(ResolutionMode);
impl_combine_or!(SchemaConflicts);
impl_combine_or!(String);
impl_combine_or!(SupportedEnvironments);
impl_combine_or!(SchemaConflicts);
impl_combine_or!(TargetTriple);
impl_combine_or!(TrustedPublishing);
impl_combine_or!(Url);

View file

@ -3,8 +3,8 @@ use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf};
use url::Url;
use uv_cache_info::CacheKey;
use uv_configuration::{
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, TargetTriple,
TrustedHost, TrustedPublishing,
ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier, RequiredVersion,
TargetTriple, TrustedHost, TrustedPublishing,
};
use uv_distribution_types::{
Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata,
@ -149,6 +149,20 @@ impl Options {
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
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.
///
/// By default, uv loads certificates from the bundled `webpki-roots` crate. The
@ -1623,6 +1637,7 @@ impl From<ToolOptions> for ResolverInstallerOptions {
pub struct OptionsWire {
// #[serde(flatten)]
// globals: GlobalOptions
required_version: Option<RequiredVersion>,
native_tls: Option<bool>,
offline: Option<bool>,
no_cache: Option<bool>,
@ -1704,6 +1719,7 @@ pub struct OptionsWire {
impl From<OptionsWire> for Options {
fn from(value: OptionsWire) -> Self {
let OptionsWire {
required_version,
native_tls,
offline,
no_cache,
@ -1764,6 +1780,7 @@ impl From<OptionsWire> for Options {
Self {
globals: GlobalOptions {
required_version,
native_tls,
offline,
no_cache,

View file

@ -4,6 +4,7 @@ use std::fmt::Write;
use std::io::stdout;
use std::path::Path;
use std::process::ExitCode;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use anstream::eprintln;
@ -208,6 +209,16 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
// Resolve the cache settings.
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.
#[cfg(feature = "tracing-durations-export")]
let (duration_layer, _duration_guard) = logging::setup_duration()?;

View file

@ -23,8 +23,8 @@ use uv_client::Connectivity;
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, DevGroupsSpecification, EditableMode, ExportFormat,
ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType,
NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall, SourceStrategy, TargetTriple,
TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem,
NoBinary, NoBuild, PreviewMode, ProjectBuildBackend, Reinstall, RequiredVersion,
SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem,
};
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl};
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)]
#[derive(Debug, Clone)]
pub(crate) struct GlobalSettings {
pub(crate) required_version: Option<RequiredVersion>,
pub(crate) quiet: bool,
pub(crate) verbose: u8,
pub(crate) color: ColorChoice,
@ -72,6 +73,8 @@ impl GlobalSettings {
/// Resolve the [`GlobalSettings`] from the CLI and filesystem configuration.
pub(crate) fn resolve(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self {
Self {
required_version: workspace
.and_then(|workspace| workspace.globals.required_version.clone()),
quiet: args.quiet,
verbose: args.verbose,
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();
filters.push((
"expected one of `native-tls`, `offline`, .*",
"expected one of `native-tls`, `offline`, [...]",
"expected one of `required-version`, `native-tls`, .*",
"expected one of `required-version`, `native-tls`, [...]",
));
uv_snapshot!(filters, context.pip_install()
@ -235,7 +235,7 @@ fn invalid_pyproject_toml_option_unknown_field() -> Result<()> {
|
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]
Audited in [TIME]

View file

@ -63,6 +63,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -215,6 +216,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -368,6 +370,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -553,6 +556,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -707,6 +711,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -840,6 +845,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -1017,6 +1023,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -1200,6 +1207,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -1437,6 +1445,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -1613,6 +1622,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -1752,6 +1762,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -1933,6 +1944,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -2138,6 +2150,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -2267,6 +2280,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -2396,6 +2410,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -2527,6 +2542,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -2677,6 +2693,7 @@ fn resolve_tool() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -2835,6 +2852,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -2992,6 +3010,7 @@ fn resolve_both() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -3267,6 +3286,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -3438,7 +3458,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
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
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -3652,6 +3673,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -3792,6 +3814,7 @@ fn allow_insecure_host() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -3946,6 +3969,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -4129,6 +4153,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -4318,6 +4343,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -4502,6 +4528,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -4693,6 +4720,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -4877,6 +4905,7 @@ fn index_priority() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -5074,6 +5103,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -5197,6 +5227,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -5318,6 +5349,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -5441,6 +5473,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -5562,6 +5595,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
color: Auto,
@ -5684,6 +5718,7 @@ fn verify_hashes() -> anyhow::Result<()> {
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: false,
verbose: 0,
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 }
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"
}
},
"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": {
"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": [
@ -1409,6 +1420,10 @@
"type": "string",
"pattern": "^3\\.\\d+(\\.\\d+)?$"
},
"RequiredVersion": {
"description": "A version specifier, e.g. `>=0.5.0` or `==0.5.0`.",
"type": "string"
},
"Requirement": {
"description": "A PEP 508 dependency specifier, e.g., `ruff >= 0.6.0`",
"type": "string"