diff --git a/Cargo.lock b/Cargo.lock
index ff72f1418..5b2fd66b8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4732,6 +4732,7 @@ dependencies = [
"uv-pep440",
"uv-pep508",
"uv-performance-memory-allocator",
+ "uv-platform",
"uv-platform-tags",
"uv-publish",
"uv-pypi-types",
@@ -5553,6 +5554,23 @@ dependencies = [
"tikv-jemallocator",
]
+[[package]]
+name = "uv-platform"
+version = "0.0.1"
+dependencies = [
+ "fs-err",
+ "goblin",
+ "indoc",
+ "procfs",
+ "regex",
+ "target-lexicon",
+ "thiserror 2.0.12",
+ "tracing",
+ "uv-fs",
+ "uv-platform-tags",
+ "uv-static",
+]
+
[[package]]
name = "uv-platform-tags"
version = "0.0.1"
@@ -5646,14 +5664,12 @@ dependencies = [
"dunce",
"fs-err",
"futures",
- "goblin",
"indexmap",
"indoc",
"insta",
"itertools 0.14.0",
"once_cell",
"owo-colors",
- "procfs",
"ref-cast",
"regex",
"reqwest",
@@ -5687,6 +5703,7 @@ dependencies = [
"uv-install-wheel",
"uv-pep440",
"uv-pep508",
+ "uv-platform",
"uv-platform-tags",
"uv-pypi-types",
"uv-redacted",
diff --git a/Cargo.toml b/Cargo.toml
index f11b91556..6e9742bc8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -49,6 +49,7 @@ uv-once-map = { path = "crates/uv-once-map" }
uv-options-metadata = { path = "crates/uv-options-metadata" }
uv-pep440 = { path = "crates/uv-pep440", features = ["tracing", "rkyv", "version-ranges"] }
uv-pep508 = { path = "crates/uv-pep508", features = ["non-pep508-extensions"] }
+uv-platform = { path = "crates/uv-platform" }
uv-platform-tags = { path = "crates/uv-platform-tags" }
uv-publish = { path = "crates/uv-publish" }
uv-pypi-types = { path = "crates/uv-pypi-types" }
diff --git a/crates/uv-bench/benches/uv.rs b/crates/uv-bench/benches/uv.rs
index 2ba7a550c..0ccd536b6 100644
--- a/crates/uv-bench/benches/uv.rs
+++ b/crates/uv-bench/benches/uv.rs
@@ -99,8 +99,8 @@ mod resolver {
use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment};
use uv_python::Interpreter;
use uv_resolver::{
- FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, Preferences, PythonRequirement,
- Resolver, ResolverEnvironment, ResolverOutput,
+ ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, Preferences,
+ PythonRequirement, Resolver, ResolverEnvironment, ResolverOutput,
};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_workspace::WorkspaceCache;
@@ -145,7 +145,7 @@ mod resolver {
let concurrency = Concurrency::default();
let config_settings = ConfigSettings::default();
let config_settings_package = PackageConfigSettings::default();
- let exclude_newer = Some(
+ let exclude_newer = ExcludeNewer::global(
jiff::civil::date(2024, 9, 1)
.to_zoned(jiff::tz::TimeZone::UTC)
.unwrap()
@@ -159,7 +159,9 @@ mod resolver {
let index = InMemoryIndex::default();
let index_locations = IndexLocations::default();
let installed_packages = EmptyInstalledPackages;
- let options = OptionsBuilder::new().exclude_newer(exclude_newer).build();
+ let options = OptionsBuilder::new()
+ .exclude_newer(exclude_newer.clone())
+ .build();
let sources = SourceStrategy::default();
let dependency_metadata = DependencyMetadata::default();
let conflicts = Conflicts::empty();
diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs
index 7cd743bc6..e5bd3b0e2 100644
--- a/crates/uv-cli/src/lib.rs
+++ b/crates/uv-cli/src/lib.rs
@@ -20,7 +20,10 @@ use uv_pep508::{MarkerTree, Requirement};
use uv_pypi_types::VerbatimParsedUrl;
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
use uv_redacted::DisplaySafeUrl;
-use uv_resolver::{AnnotationStyle, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode};
+use uv_resolver::{
+ AnnotationStyle, ExcludeNewerPackageEntry, ExcludeNewerTimestamp, ForkStrategy, PrereleaseMode,
+ ResolutionMode,
+};
use uv_static::EnvVars;
use uv_torch::TorchMode;
use uv_workspace::pyproject_mut::AddBoundsKind;
@@ -2749,7 +2752,16 @@ pub struct VenvArgs {
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`) in your system's configured time zone.
#[arg(long, env = EnvVars::UV_EXCLUDE_NEWER)]
- pub exclude_newer: Option,
+ pub exclude_newer: Option,
+
+ /// Limit candidate packages for a specific package to those that were uploaded prior to the given date.
+ ///
+ /// Accepts package-date pairs in the format `PACKAGE=DATE`, where `DATE` is an RFC 3339 timestamp
+ /// (e.g., `2006-12-02T02:07:43Z`) or local date (e.g., `2006-12-02`) in your system's configured time zone.
+ ///
+ /// Can be provided multiple times for different packages.
+ #[arg(long)]
+ pub exclude_newer_package: Option>,
/// The method to use when installing packages from the global cache.
///
@@ -4777,7 +4789,16 @@ pub struct ToolUpgradeArgs {
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`) in your system's configured time zone.
#[arg(long, env = EnvVars::UV_EXCLUDE_NEWER, help_heading = "Resolver options")]
- pub exclude_newer: Option,
+ pub exclude_newer: Option,
+
+ /// Limit candidate packages for specific packages to those that were uploaded prior to the given date.
+ ///
+ /// Accepts package-date pairs in the format `PACKAGE=DATE`, where `DATE` is an RFC 3339 timestamp
+ /// (e.g., `2006-12-02T02:07:43Z`) or local date (e.g., `2006-12-02`) in your system's configured time zone.
+ ///
+ /// Can be provided multiple times for different packages.
+ #[arg(long, help_heading = "Resolver options")]
+ pub exclude_newer_package: Option>,
/// The method to use when installing packages from the global cache.
///
@@ -5572,7 +5593,16 @@ pub struct InstallerArgs {
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`) in your system's configured time zone.
#[arg(long, env = EnvVars::UV_EXCLUDE_NEWER, help_heading = "Resolver options")]
- pub exclude_newer: Option,
+ pub exclude_newer: Option,
+
+ /// Limit candidate packages for specific packages to those that were uploaded prior to the given date.
+ ///
+ /// Accepts package-date pairs in the format `PACKAGE=DATE`, where `DATE` is an RFC 3339 timestamp
+ /// (e.g., `2006-12-02T02:07:43Z`) or local date (e.g., `2006-12-02`) in your system's configured time zone.
+ ///
+ /// Can be provided multiple times for different packages.
+ #[arg(long, help_heading = "Resolver options")]
+ pub exclude_newer_package: Option>,
/// The method to use when installing packages from the global cache.
///
@@ -5773,7 +5803,16 @@ pub struct ResolverArgs {
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`) in your system's configured time zone.
#[arg(long, env = EnvVars::UV_EXCLUDE_NEWER, help_heading = "Resolver options")]
- pub exclude_newer: Option,
+ pub exclude_newer: Option,
+
+ /// Limit candidate packages for a specific package to those that were uploaded prior to the given date.
+ ///
+ /// Accepts package-date pairs in the format `PACKAGE=DATE`, where `DATE` is an RFC 3339 timestamp
+ /// (e.g., `2006-12-02T02:07:43Z`) or local date (e.g., `2006-12-02`) in your system's configured time zone.
+ ///
+ /// Can be provided multiple times for different packages.
+ #[arg(long, help_heading = "Resolver options")]
+ pub exclude_newer_package: Option>,
/// The method to use when installing packages from the global cache.
///
@@ -5970,7 +6009,16 @@ pub struct ResolverInstallerArgs {
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`) in your system's configured time zone.
#[arg(long, env = EnvVars::UV_EXCLUDE_NEWER, help_heading = "Resolver options")]
- pub exclude_newer: Option,
+ pub exclude_newer: Option,
+
+ /// Limit candidate packages for specific packages to those that were uploaded prior to the given date.
+ ///
+ /// Accepts package-date pairs in the format `PACKAGE=DATE`, where `DATE` is an RFC 3339 timestamp
+ /// (e.g., `2006-12-02T02:07:43Z`) or local date (e.g., `2006-12-02`) in your system's configured time zone.
+ ///
+ /// Can be provided multiple times for different packages.
+ #[arg(long, help_heading = "Resolver options")]
+ pub exclude_newer_package: Option>,
/// The method to use when installing packages from the global cache.
///
@@ -6059,7 +6107,7 @@ pub struct FetchArgs {
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`) in your system's configured time zone.
#[arg(long, env = EnvVars::UV_EXCLUDE_NEWER, help_heading = "Resolver options")]
- pub exclude_newer: Option,
+ pub exclude_newer: Option,
}
#[derive(Args)]
diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs
index ce3d8621a..24ffd9093 100644
--- a/crates/uv-cli/src/options.rs
+++ b/crates/uv-cli/src/options.rs
@@ -2,7 +2,7 @@ use anstream::eprintln;
use uv_cache::Refresh;
use uv_configuration::{ConfigSettings, PackageConfigSettings};
-use uv_resolver::PrereleaseMode;
+use uv_resolver::{ExcludeNewer, ExcludeNewerPackage, PrereleaseMode};
use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};
use uv_warnings::owo_colors::OwoColorize;
@@ -69,6 +69,7 @@ impl From for PipOptions {
exclude_newer,
link_mode,
no_sources,
+ exclude_newer_package,
} = args;
Self {
@@ -93,6 +94,7 @@ impl From for PipOptions {
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: Some(no_build_isolation_package),
exclude_newer,
+ exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
link_mode,
no_sources: if no_sources { Some(true) } else { None },
..PipOptions::from(index_args)
@@ -118,6 +120,7 @@ impl From for PipOptions {
compile_bytecode,
no_compile_bytecode,
no_sources,
+ exclude_newer_package,
} = args;
Self {
@@ -134,6 +137,7 @@ impl From for PipOptions {
}),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
exclude_newer,
+ exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
no_sources: if no_sources { Some(true) } else { None },
@@ -168,6 +172,7 @@ impl From for PipOptions {
compile_bytecode,
no_compile_bytecode,
no_sources,
+ exclude_newer_package,
} = args;
Self {
@@ -194,6 +199,7 @@ impl From for PipOptions {
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: Some(no_build_isolation_package),
exclude_newer,
+ exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
no_sources: if no_sources { Some(true) } else { None },
@@ -285,6 +291,7 @@ pub fn resolver_options(
exclude_newer,
link_mode,
no_sources,
+ exclude_newer_package,
} = resolver_args;
let BuildOptionsArgs {
@@ -347,7 +354,10 @@ pub fn resolver_options(
}),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: Some(no_build_isolation_package),
- exclude_newer,
+ exclude_newer: ExcludeNewer::from_args(
+ exclude_newer,
+ exclude_newer_package.unwrap_or_default(),
+ ),
link_mode,
no_build: flag(no_build, build, "build"),
no_build_package: Some(no_build_package),
@@ -383,6 +393,7 @@ pub fn resolver_installer_options(
no_build_isolation_package,
build_isolation,
exclude_newer,
+ exclude_newer_package,
link_mode,
compile_bytecode,
no_compile_bytecode,
@@ -466,6 +477,7 @@ pub fn resolver_installer_options(
Some(no_build_isolation_package)
},
exclude_newer,
+ exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter),
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
no_build: flag(no_build, build, "build"),
diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs
index 5474e8af8..4c548cf79 100644
--- a/crates/uv-dispatch/src/lib.rs
+++ b/crates/uv-dispatch/src/lib.rs
@@ -93,7 +93,7 @@ pub struct BuildDispatch<'a> {
config_settings: &'a ConfigSettings,
config_settings_package: &'a PackageConfigSettings,
hasher: &'a HashStrategy,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
source_build_context: SourceBuildContext,
build_extra_env_vars: FxHashMap,
sources: SourceStrategy,
@@ -120,7 +120,7 @@ impl<'a> BuildDispatch<'a> {
link_mode: uv_install_wheel::LinkMode,
build_options: &'a BuildOptions,
hasher: &'a HashStrategy,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
sources: SourceStrategy,
workspace_cache: WorkspaceCache,
concurrency: Concurrency,
@@ -236,7 +236,7 @@ impl BuildContext for BuildDispatch<'_> {
.with_constraints(self.constraints.clone())
.with_preferences(self.preferences.clone()),
OptionsBuilder::new()
- .exclude_newer(self.exclude_newer)
+ .exclude_newer(self.exclude_newer.clone())
.index_strategy(self.index_strategy)
.build_options(self.build_options.clone())
.flexibility(Flexibility::Fixed)
diff --git a/crates/uv-platform/Cargo.toml b/crates/uv-platform/Cargo.toml
new file mode 100644
index 000000000..0bb891ed9
--- /dev/null
+++ b/crates/uv-platform/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "uv-platform"
+version = "0.0.1"
+edition = { workspace = true }
+rust-version = { workspace = true }
+homepage = { workspace = true }
+documentation = { workspace = true }
+repository = { workspace = true }
+authors = { workspace = true }
+license = { workspace = true }
+
+[lib]
+doctest = false
+
+[lints]
+workspace = true
+
+[dependencies]
+uv-static = { workspace = true }
+uv-fs = { workspace = true }
+uv-platform-tags = { workspace = true }
+
+fs-err = { workspace = true }
+goblin = { workspace = true }
+regex = { workspace = true }
+target-lexicon = { workspace = true }
+thiserror = { workspace = true }
+tracing = { workspace = true }
+
+[target.'cfg(target_os = "linux")'.dependencies]
+procfs = { workspace = true }
+
+[dev-dependencies]
+indoc = { workspace = true }
diff --git a/crates/uv-platform/src/arch.rs b/crates/uv-platform/src/arch.rs
new file mode 100644
index 000000000..f64312489
--- /dev/null
+++ b/crates/uv-platform/src/arch.rs
@@ -0,0 +1,249 @@
+use crate::Error;
+use std::fmt::Display;
+use std::str::FromStr;
+use std::{cmp, fmt};
+
+/// Architecture variants, e.g., with support for different instruction sets
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd)]
+pub enum ArchVariant {
+ /// Targets 64-bit Intel/AMD CPUs newer than Nehalem (2008).
+ /// Includes SSE3, SSE4 and other post-2003 CPU instructions.
+ V2,
+ /// Targets 64-bit Intel/AMD CPUs newer than Haswell (2013) and Excavator (2015).
+ /// Includes AVX, AVX2, MOVBE and other newer CPU instructions.
+ V3,
+ /// Targets 64-bit Intel/AMD CPUs with AVX-512 instructions (post-2017 Intel CPUs).
+ /// Many post-2017 Intel CPUs do not support AVX-512.
+ V4,
+}
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
+pub struct Arch {
+ pub(crate) family: target_lexicon::Architecture,
+ pub(crate) variant: Option,
+}
+
+impl Ord for Arch {
+ fn cmp(&self, other: &Self) -> cmp::Ordering {
+ if self.family == other.family {
+ return self.variant.cmp(&other.variant);
+ }
+
+ // For the time being, manually make aarch64 windows disfavored
+ // on its own host platform, because most packages don't have wheels for
+ // aarch64 windows, making emulation more useful than native execution!
+ //
+ // The reason we do this in "sorting" and not "supports" is so that we don't
+ // *refuse* to use an aarch64 windows pythons if they happen to be installed
+ // and nothing else is available.
+ //
+ // Similarly if someone manually requests an aarch64 windows install, we
+ // should respect that request (this is the way users should "override"
+ // this behaviour).
+ let preferred = if cfg!(all(windows, target_arch = "aarch64")) {
+ Arch {
+ family: target_lexicon::Architecture::X86_64,
+ variant: None,
+ }
+ } else {
+ // Prefer native architectures
+ Arch::from_env()
+ };
+
+ match (
+ self.family == preferred.family,
+ other.family == preferred.family,
+ ) {
+ (true, true) => unreachable!(),
+ (true, false) => cmp::Ordering::Less,
+ (false, true) => cmp::Ordering::Greater,
+ (false, false) => {
+ // Both non-preferred, fallback to lexicographic order
+ self.family.to_string().cmp(&other.family.to_string())
+ }
+ }
+ }
+}
+
+impl PartialOrd for Arch {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ Some(self.cmp(other))
+ }
+}
+
+impl Arch {
+ pub fn new(family: target_lexicon::Architecture, variant: Option) -> Self {
+ Self { family, variant }
+ }
+
+ pub fn from_env() -> Self {
+ Self {
+ family: target_lexicon::HOST.architecture,
+ variant: None,
+ }
+ }
+
+ /// Does the current architecture support running the other?
+ ///
+ /// When the architecture is equal, this is always true. Otherwise, this is true if the
+ /// architecture is transparently emulated or is a microarchitecture with worse performance
+ /// characteristics.
+ pub fn supports(self, other: Self) -> bool {
+ if self == other {
+ return true;
+ }
+
+ // TODO: Implement `variant` support checks
+
+ // Windows ARM64 runs emulated x86_64 binaries transparently
+ // Similarly, macOS aarch64 runs emulated x86_64 binaries transparently if you have Rosetta
+ // installed. We don't try to be clever and check if that's the case here, we just assume
+ // that if x86_64 distributions are available, they're usable.
+ if (cfg!(windows) || cfg!(target_os = "macos"))
+ && matches!(self.family, target_lexicon::Architecture::Aarch64(_))
+ {
+ return other.family == target_lexicon::Architecture::X86_64;
+ }
+
+ false
+ }
+
+ pub fn family(&self) -> target_lexicon::Architecture {
+ self.family
+ }
+
+ pub fn is_arm(&self) -> bool {
+ matches!(self.family, target_lexicon::Architecture::Arm(_))
+ }
+}
+
+impl Display for Arch {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self.family {
+ target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686) => {
+ write!(f, "x86")?;
+ }
+ inner => write!(f, "{inner}")?,
+ }
+ if let Some(variant) = self.variant {
+ write!(f, "_{variant}")?;
+ }
+ Ok(())
+ }
+}
+
+impl FromStr for Arch {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result {
+ fn parse_family(s: &str) -> Result {
+ let inner = match s {
+ // Allow users to specify "x86" as a shorthand for the "i686" variant, they should not need
+ // to specify the exact architecture and this variant is what we have downloads for.
+ "x86" => {
+ target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)
+ }
+ _ => target_lexicon::Architecture::from_str(s)
+ .map_err(|()| Error::UnknownArch(s.to_string()))?,
+ };
+ if matches!(inner, target_lexicon::Architecture::Unknown) {
+ return Err(Error::UnknownArch(s.to_string()));
+ }
+ Ok(inner)
+ }
+
+ // First check for a variant
+ if let Some((Ok(family), Ok(variant))) = s
+ .rsplit_once('_')
+ .map(|(family, variant)| (parse_family(family), ArchVariant::from_str(variant)))
+ {
+ // We only support variants for `x86_64` right now
+ if !matches!(family, target_lexicon::Architecture::X86_64) {
+ return Err(Error::UnsupportedVariant(
+ variant.to_string(),
+ family.to_string(),
+ ));
+ }
+ return Ok(Self {
+ family,
+ variant: Some(variant),
+ });
+ }
+
+ let family = parse_family(s)?;
+
+ Ok(Self {
+ family,
+ variant: None,
+ })
+ }
+}
+
+impl FromStr for ArchVariant {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ "v2" => Ok(Self::V2),
+ "v3" => Ok(Self::V3),
+ "v4" => Ok(Self::V4),
+ _ => Err(()),
+ }
+ }
+}
+
+impl Display for ArchVariant {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::V2 => write!(f, "v2"),
+ Self::V3 => write!(f, "v3"),
+ Self::V4 => write!(f, "v4"),
+ }
+ }
+}
+
+impl From<&uv_platform_tags::Arch> for Arch {
+ fn from(value: &uv_platform_tags::Arch) -> Self {
+ match value {
+ uv_platform_tags::Arch::Aarch64 => Arch::new(
+ target_lexicon::Architecture::Aarch64(target_lexicon::Aarch64Architecture::Aarch64),
+ None,
+ ),
+ uv_platform_tags::Arch::Armv5TEL => Arch::new(
+ target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv5te),
+ None,
+ ),
+ uv_platform_tags::Arch::Armv6L => Arch::new(
+ target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv6),
+ None,
+ ),
+ uv_platform_tags::Arch::Armv7L => Arch::new(
+ target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7),
+ None,
+ ),
+ uv_platform_tags::Arch::S390X => Arch::new(target_lexicon::Architecture::S390x, None),
+ uv_platform_tags::Arch::Powerpc => {
+ Arch::new(target_lexicon::Architecture::Powerpc, None)
+ }
+ uv_platform_tags::Arch::Powerpc64 => {
+ Arch::new(target_lexicon::Architecture::Powerpc64, None)
+ }
+ uv_platform_tags::Arch::Powerpc64Le => {
+ Arch::new(target_lexicon::Architecture::Powerpc64le, None)
+ }
+ uv_platform_tags::Arch::X86 => Arch::new(
+ target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686),
+ None,
+ ),
+ uv_platform_tags::Arch::X86_64 => Arch::new(target_lexicon::Architecture::X86_64, None),
+ uv_platform_tags::Arch::LoongArch64 => {
+ Arch::new(target_lexicon::Architecture::LoongArch64, None)
+ }
+ uv_platform_tags::Arch::Riscv64 => Arch::new(
+ target_lexicon::Architecture::Riscv64(target_lexicon::Riscv64Architecture::Riscv64),
+ None,
+ ),
+ uv_platform_tags::Arch::Wasm32 => Arch::new(target_lexicon::Architecture::Wasm32, None),
+ }
+ }
+}
diff --git a/crates/uv-python/src/cpuinfo.rs b/crates/uv-platform/src/cpuinfo.rs
similarity index 94%
rename from crates/uv-python/src/cpuinfo.rs
rename to crates/uv-platform/src/cpuinfo.rs
index f0827886b..89a4f89e9 100644
--- a/crates/uv-python/src/cpuinfo.rs
+++ b/crates/uv-platform/src/cpuinfo.rs
@@ -1,6 +1,6 @@
//! Fetches CPU information.
-use anyhow::Error;
+use std::io::Error;
#[cfg(target_os = "linux")]
use procfs::{CpuInfo, Current};
@@ -14,7 +14,7 @@ use procfs::{CpuInfo, Current};
/// More information on this can be found in the [Debian ARM Hard Float Port documentation](https://wiki.debian.org/ArmHardFloatPort#VFP).
#[cfg(target_os = "linux")]
pub(crate) fn detect_hardware_floating_point_support() -> Result {
- let cpu_info = CpuInfo::current()?;
+ let cpu_info = CpuInfo::current().map_err(Error::other)?;
if let Some(features) = cpu_info.fields.get("Features") {
if features.contains("vfp") {
return Ok(true); // "vfp" found: hard-float (gnueabihf) detected
diff --git a/crates/uv-platform/src/lib.rs b/crates/uv-platform/src/lib.rs
new file mode 100644
index 000000000..7eb23875a
--- /dev/null
+++ b/crates/uv-platform/src/lib.rs
@@ -0,0 +1,26 @@
+//! Platform detection for operating system, architecture, and libc.
+
+use thiserror::Error;
+
+pub use crate::arch::{Arch, ArchVariant};
+pub use crate::libc::{Libc, LibcDetectionError, LibcVersion};
+pub use crate::os::Os;
+
+mod arch;
+mod cpuinfo;
+mod libc;
+mod os;
+
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("Unknown operating system: {0}")]
+ UnknownOs(String),
+ #[error("Unknown architecture: {0}")]
+ UnknownArch(String),
+ #[error("Unknown libc environment: {0}")]
+ UnknownLibc(String),
+ #[error("Unsupported variant `{0}` for architecture `{1}`")]
+ UnsupportedVariant(String, String),
+ #[error(transparent)]
+ LibcDetectionError(#[from] crate::libc::LibcDetectionError),
+}
diff --git a/crates/uv-python/src/libc.rs b/crates/uv-platform/src/libc.rs
similarity index 77%
rename from crates/uv-python/src/libc.rs
rename to crates/uv-platform/src/libc.rs
index 40950ae08..184f0487c 100644
--- a/crates/uv-python/src/libc.rs
+++ b/crates/uv-platform/src/libc.rs
@@ -3,18 +3,22 @@
//! Taken from `glibc_version` (),
//! which used the Apache 2.0 license (but not the MIT license)
+use crate::cpuinfo::detect_hardware_floating_point_support;
use fs_err as fs;
use goblin::elf::Elf;
use regex::Regex;
+use std::fmt::Display;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
+use std::str::FromStr;
use std::sync::LazyLock;
-use thiserror::Error;
+use std::{env, fmt};
use tracing::trace;
use uv_fs::Simplified;
+use uv_static::EnvVars;
-#[derive(Debug, Error)]
+#[derive(Debug, thiserror::Error)]
pub enum LibcDetectionError {
#[error(
"Could not detect either glibc version nor musl libc version, at least one of which is required"
@@ -45,11 +49,89 @@ pub enum LibcDetectionError {
/// We support glibc (manylinux) and musl (musllinux) on linux.
#[derive(Debug, PartialEq, Eq)]
-pub(crate) enum LibcVersion {
+pub enum LibcVersion {
Manylinux { major: u32, minor: u32 },
Musllinux { major: u32, minor: u32 },
}
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
+pub enum Libc {
+ Some(target_lexicon::Environment),
+ None,
+}
+
+impl Libc {
+ pub fn from_env() -> Result {
+ match env::consts::OS {
+ "linux" => {
+ if let Ok(libc) = env::var(EnvVars::UV_LIBC) {
+ if !libc.is_empty() {
+ return Self::from_str(&libc);
+ }
+ }
+
+ Ok(Self::Some(match detect_linux_libc()? {
+ LibcVersion::Manylinux { .. } => match env::consts::ARCH {
+ // Checks if the CPU supports hardware floating-point operations.
+ // Depending on the result, it selects either the `gnueabihf` (hard-float) or `gnueabi` (soft-float) environment.
+ // download-metadata.json only includes armv7.
+ "arm" | "armv5te" | "armv7" => {
+ match detect_hardware_floating_point_support() {
+ Ok(true) => target_lexicon::Environment::Gnueabihf,
+ Ok(false) => target_lexicon::Environment::Gnueabi,
+ Err(_) => target_lexicon::Environment::Gnu,
+ }
+ }
+ _ => target_lexicon::Environment::Gnu,
+ },
+ LibcVersion::Musllinux { .. } => target_lexicon::Environment::Musl,
+ }))
+ }
+ "windows" | "macos" => Ok(Self::None),
+ // Use `None` on platforms without explicit support.
+ _ => Ok(Self::None),
+ }
+ }
+
+ pub fn is_musl(&self) -> bool {
+ matches!(self, Self::Some(target_lexicon::Environment::Musl))
+ }
+}
+
+impl FromStr for Libc {
+ type Err = crate::Error;
+
+ fn from_str(s: &str) -> Result {
+ match s {
+ "gnu" => Ok(Self::Some(target_lexicon::Environment::Gnu)),
+ "gnueabi" => Ok(Self::Some(target_lexicon::Environment::Gnueabi)),
+ "gnueabihf" => Ok(Self::Some(target_lexicon::Environment::Gnueabihf)),
+ "musl" => Ok(Self::Some(target_lexicon::Environment::Musl)),
+ "none" => Ok(Self::None),
+ _ => Err(crate::Error::UnknownLibc(s.to_string())),
+ }
+ }
+}
+
+impl Display for Libc {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Some(env) => write!(f, "{env}"),
+ Self::None => write!(f, "none"),
+ }
+ }
+}
+
+impl From<&uv_platform_tags::Os> for Libc {
+ fn from(value: &uv_platform_tags::Os) -> Self {
+ match value {
+ uv_platform_tags::Os::Manylinux { .. } => Libc::Some(target_lexicon::Environment::Gnu),
+ uv_platform_tags::Os::Musllinux { .. } => Libc::Some(target_lexicon::Environment::Musl),
+ _ => Libc::None,
+ }
+ }
+}
+
/// Determine whether we're running glibc or musl and in which version, given we are on linux.
///
/// Normally, we determine this from the python interpreter, which is more accurate, but when
diff --git a/crates/uv-platform/src/os.rs b/crates/uv-platform/src/os.rs
new file mode 100644
index 000000000..01f896f3f
--- /dev/null
+++ b/crates/uv-platform/src/os.rs
@@ -0,0 +1,88 @@
+use crate::Error;
+use std::fmt;
+use std::fmt::Display;
+use std::ops::Deref;
+use std::str::FromStr;
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
+pub struct Os(pub(crate) target_lexicon::OperatingSystem);
+
+impl Os {
+ pub fn new(os: target_lexicon::OperatingSystem) -> Self {
+ Self(os)
+ }
+
+ pub fn from_env() -> Self {
+ Self(target_lexicon::HOST.operating_system)
+ }
+
+ pub fn is_windows(&self) -> bool {
+ matches!(self.0, target_lexicon::OperatingSystem::Windows)
+ }
+}
+
+impl Display for Os {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match &**self {
+ target_lexicon::OperatingSystem::Darwin(_) => write!(f, "macos"),
+ inner => write!(f, "{inner}"),
+ }
+ }
+}
+
+impl FromStr for Os {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result {
+ let inner = match s {
+ "macos" => target_lexicon::OperatingSystem::Darwin(None),
+ _ => target_lexicon::OperatingSystem::from_str(s)
+ .map_err(|()| Error::UnknownOs(s.to_string()))?,
+ };
+ if matches!(inner, target_lexicon::OperatingSystem::Unknown) {
+ return Err(Error::UnknownOs(s.to_string()));
+ }
+ Ok(Self(inner))
+ }
+}
+
+impl Deref for Os {
+ type Target = target_lexicon::OperatingSystem;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl From<&uv_platform_tags::Os> for Os {
+ fn from(value: &uv_platform_tags::Os) -> Self {
+ match value {
+ uv_platform_tags::Os::Dragonfly { .. } => {
+ Os::new(target_lexicon::OperatingSystem::Dragonfly)
+ }
+ uv_platform_tags::Os::FreeBsd { .. } => {
+ Os::new(target_lexicon::OperatingSystem::Freebsd)
+ }
+ uv_platform_tags::Os::Haiku { .. } => Os::new(target_lexicon::OperatingSystem::Haiku),
+ uv_platform_tags::Os::Illumos { .. } => {
+ Os::new(target_lexicon::OperatingSystem::Illumos)
+ }
+ uv_platform_tags::Os::Macos { .. } => {
+ Os::new(target_lexicon::OperatingSystem::Darwin(None))
+ }
+ uv_platform_tags::Os::Manylinux { .. }
+ | uv_platform_tags::Os::Musllinux { .. }
+ | uv_platform_tags::Os::Android { .. } => {
+ Os::new(target_lexicon::OperatingSystem::Linux)
+ }
+ uv_platform_tags::Os::NetBsd { .. } => Os::new(target_lexicon::OperatingSystem::Netbsd),
+ uv_platform_tags::Os::OpenBsd { .. } => {
+ Os::new(target_lexicon::OperatingSystem::Openbsd)
+ }
+ uv_platform_tags::Os::Windows => Os::new(target_lexicon::OperatingSystem::Windows),
+ uv_platform_tags::Os::Pyodide { .. } => {
+ Os::new(target_lexicon::OperatingSystem::Emscripten)
+ }
+ }
+ }
+}
diff --git a/crates/uv-python/Cargo.toml b/crates/uv-python/Cargo.toml
index 53c70ba5f..1c6f09b15 100644
--- a/crates/uv-python/Cargo.toml
+++ b/crates/uv-python/Cargo.toml
@@ -28,6 +28,7 @@ uv-fs = { workspace = true }
uv-install-wheel = { workspace = true }
uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true }
+uv-platform = { workspace = true }
uv-platform-tags = { workspace = true }
uv-pypi-types = { workspace = true }
uv-redacted = { workspace = true }
@@ -42,7 +43,6 @@ configparser = { workspace = true }
dunce = { workspace = true }
fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true }
-goblin = { workspace = true, default-features = false }
indexmap = { workspace = true }
itertools = { workspace = true }
owo-colors = { workspace = true }
@@ -68,9 +68,6 @@ url = { workspace = true }
which = { workspace = true }
once_cell = { workspace = true }
-[target.'cfg(target_os = "linux")'.dependencies]
-procfs = { workspace = true }
-
[target.'cfg(target_os = "windows")'.dependencies]
windows-registry = { workspace = true }
windows-result = { workspace = true }
diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs
index 466ea4b0f..496191818 100644
--- a/crates/uv-python/src/discovery.rs
+++ b/crates/uv-python/src/discovery.rs
@@ -3066,8 +3066,8 @@ mod tests {
discovery::{PythonRequest, VersionRequest},
downloads::{ArchRequest, PythonDownloadRequest},
implementation::ImplementationName,
- platform::{Arch, Libc, Os},
};
+ use uv_platform::{Arch, Libc, Os};
use super::{Error, PythonVariant};
@@ -3154,11 +3154,11 @@ mod tests {
PythonVariant::Default
)),
implementation: Some(ImplementationName::CPython),
- arch: Some(ArchRequest::Explicit(Arch {
- family: Architecture::Aarch64(Aarch64Architecture::Aarch64),
- variant: None
- })),
- os: Some(Os(target_lexicon::OperatingSystem::Darwin(None))),
+ arch: Some(ArchRequest::Explicit(Arch::new(
+ Architecture::Aarch64(Aarch64Architecture::Aarch64),
+ None
+ ))),
+ os: Some(Os::new(target_lexicon::OperatingSystem::Darwin(None))),
libc: Some(Libc::None),
prereleases: None
})
@@ -3189,10 +3189,10 @@ mod tests {
PythonVariant::Default
)),
implementation: None,
- arch: Some(ArchRequest::Explicit(Arch {
- family: Architecture::Aarch64(Aarch64Architecture::Aarch64),
- variant: None
- })),
+ arch: Some(ArchRequest::Explicit(Arch::new(
+ Architecture::Aarch64(Aarch64Architecture::Aarch64),
+ None
+ ))),
os: None,
libc: None,
prereleases: None
diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs
index 05bf17cd1..9e1e03c91 100644
--- a/crates/uv-python/src/downloads.rs
+++ b/crates/uv-python/src/downloads.rs
@@ -25,6 +25,7 @@ use uv_client::{BaseClient, WrappedReqwestError, is_extended_transient_error};
use uv_distribution_filename::{ExtensionError, SourceDistExtension};
use uv_extract::hash::Hasher;
use uv_fs::{Simplified, rename_with_retry};
+use uv_platform::{self as platform, Arch, Libc, Os};
use uv_pypi_types::{HashAlgorithm, HashDigest};
use uv_redacted::DisplaySafeUrl;
use uv_static::EnvVars;
@@ -34,9 +35,7 @@ use crate::implementation::{
Error as ImplementationError, ImplementationName, LenientImplementationName,
};
use crate::installation::PythonInstallationKey;
-use crate::libc::LibcDetectionError;
use crate::managed::ManagedPythonInstallation;
-use crate::platform::{self, Arch, Libc, Os};
use crate::{Interpreter, PythonRequest, PythonVersion, VersionRequest};
#[derive(Error, Debug)]
@@ -98,7 +97,7 @@ pub enum Error {
#[error("A mirror was provided via `{0}`, but the URL does not match the expected format: {0}")]
Mirror(&'static str, &'static str),
#[error("Failed to determine the libc used on the current platform")]
- LibcDetection(#[from] LibcDetectionError),
+ LibcDetection(#[from] platform::LibcDetectionError),
#[error("Remote Python downloads JSON is not yet supported, please use a local path")]
RemoteJSONNotSupported,
#[error("The JSON of the python downloads is invalid: {0}")]
diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs
index 3f5b506a6..8cdc33106 100644
--- a/crates/uv-python/src/installation.rs
+++ b/crates/uv-python/src/installation.rs
@@ -10,6 +10,7 @@ use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_configuration::Preview;
use uv_pep440::{Prerelease, Version};
+use uv_platform::{Arch, Libc, Os};
use crate::discovery::{
EnvironmentPreference, PythonRequest, find_best_python_installation, find_python_installation,
@@ -17,7 +18,6 @@ use crate::discovery::{
use crate::downloads::{DownloadResult, ManagedPythonDownload, PythonDownloadRequest, Reporter};
use crate::implementation::LenientImplementationName;
use crate::managed::{ManagedPythonInstallation, ManagedPythonInstallations};
-use crate::platform::{Arch, Libc, Os};
use crate::{
Error, ImplementationName, Interpreter, PythonDownloads, PythonPreference, PythonSource,
PythonVariant, PythonVersion, downloads,
diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs
index dd9dd1cb4..3a7cce3f0 100644
--- a/crates/uv-python/src/interpreter.rs
+++ b/crates/uv-python/src/interpreter.rs
@@ -21,13 +21,13 @@ use uv_fs::{LockedFile, PythonExt, Simplified, write_atomic_sync};
use uv_install_wheel::Layout;
use uv_pep440::Version;
use uv_pep508::{MarkerEnvironment, StringVersion};
+use uv_platform::{Arch, Libc, Os};
use uv_platform_tags::Platform;
use uv_platform_tags::{Tags, TagsError};
use uv_pypi_types::{ResolverMarkerEnvironment, Scheme};
use crate::implementation::LenientImplementationName;
use crate::managed::ManagedPythonInstallations;
-use crate::platform::{Arch, Libc, Os};
use crate::pointer_size::PointerSize;
use crate::{
Prefix, PythonInstallationKey, PythonVariant, PythonVersion, Target, VersionRequest,
diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs
index 8b8e9c129..f08198d97 100644
--- a/crates/uv-python/src/lib.rs
+++ b/crates/uv-python/src/lib.rs
@@ -29,19 +29,16 @@ pub use crate::version_files::{
};
pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment};
-mod cpuinfo;
mod discovery;
pub mod downloads;
mod environment;
mod implementation;
mod installation;
mod interpreter;
-mod libc;
pub mod macos_dylib;
pub mod managed;
#[cfg(windows)]
mod microsoft_store;
-pub mod platform;
mod pointer_size;
mod prefix;
mod python_version;
diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs
index d9b96e5ed..69d12a0a3 100644
--- a/crates/uv-python/src/managed.rs
+++ b/crates/uv-python/src/managed.rs
@@ -17,6 +17,8 @@ use uv_configuration::{Preview, PreviewFeatures};
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
use uv_fs::{LockedFile, Simplified, replace_symlink, symlink_or_copy_file};
+use uv_platform::Error as PlatformError;
+use uv_platform::{Arch, Libc, LibcDetectionError, Os};
use uv_state::{StateBucket, StateStore};
use uv_static::EnvVars;
use uv_trampoline_builder::{Launcher, windows_python_launcher};
@@ -26,9 +28,6 @@ use crate::implementation::{
Error as ImplementationError, ImplementationName, LenientImplementationName,
};
use crate::installation::{self, PythonInstallationKey};
-use crate::libc::LibcDetectionError;
-use crate::platform::Error as PlatformError;
-use crate::platform::{Arch, Libc, Os};
use crate::python_version::PythonVersion;
use crate::{
PythonInstallationMinorVersionKey, PythonRequest, PythonVariant, macos_dylib, sysconfig,
@@ -271,7 +270,7 @@ impl ManagedPythonInstallations {
&& (arch.supports(installation.key.arch)
// TODO(zanieb): Allow inequal variants, as `Arch::supports` does not
// implement this yet. See https://github.com/astral-sh/uv/pull/9788
- || arch.family == installation.key.arch.family)
+ || arch.family() == installation.key.arch.family())
&& installation.key.libc == libc
});
@@ -545,7 +544,7 @@ impl ManagedPythonInstallation {
/// standard `EXTERNALLY-MANAGED` file.
pub fn ensure_externally_managed(&self) -> Result<(), Error> {
// Construct the path to the `stdlib` directory.
- let stdlib = if matches!(self.key.os, Os(target_lexicon::OperatingSystem::Windows)) {
+ let stdlib = if self.key.os.is_windows() {
self.python_dir().join("Lib")
} else {
let lib_suffix = self.key.variant.suffix();
diff --git a/crates/uv-python/src/platform.rs b/crates/uv-python/src/platform.rs
deleted file mode 100644
index 606e05e28..000000000
--- a/crates/uv-python/src/platform.rs
+++ /dev/null
@@ -1,427 +0,0 @@
-use crate::cpuinfo::detect_hardware_floating_point_support;
-use crate::libc::{LibcDetectionError, LibcVersion, detect_linux_libc};
-use std::fmt::Display;
-use std::ops::Deref;
-use std::{fmt, str::FromStr};
-use thiserror::Error;
-
-use uv_static::EnvVars;
-
-#[derive(Error, Debug)]
-pub enum Error {
- #[error("Unknown operating system: {0}")]
- UnknownOs(String),
- #[error("Unknown architecture: {0}")]
- UnknownArch(String),
- #[error("Unknown libc environment: {0}")]
- UnknownLibc(String),
- #[error("Unsupported variant `{0}` for architecture `{1}`")]
- UnsupportedVariant(String, String),
- #[error(transparent)]
- LibcDetectionError(#[from] LibcDetectionError),
-}
-
-/// Architecture variants, e.g., with support for different instruction sets
-#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Ord, PartialOrd)]
-pub enum ArchVariant {
- /// Targets 64-bit Intel/AMD CPUs newer than Nehalem (2008).
- /// Includes SSE3, SSE4 and other post-2003 CPU instructions.
- V2,
- /// Targets 64-bit Intel/AMD CPUs newer than Haswell (2013) and Excavator (2015).
- /// Includes AVX, AVX2, MOVBE and other newer CPU instructions.
- V3,
- /// Targets 64-bit Intel/AMD CPUs with AVX-512 instructions (post-2017 Intel CPUs).
- /// Many post-2017 Intel CPUs do not support AVX-512.
- V4,
-}
-
-#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
-pub struct Arch {
- pub(crate) family: target_lexicon::Architecture,
- pub(crate) variant: Option,
-}
-
-impl Ord for Arch {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- if self.family == other.family {
- return self.variant.cmp(&other.variant);
- }
-
- // For the time being, manually make aarch64 windows disfavored
- // on its own host platform, because most packages don't have wheels for
- // aarch64 windows, making emulation more useful than native execution!
- //
- // The reason we do this in "sorting" and not "supports" is so that we don't
- // *refuse* to use an aarch64 windows pythons if they happen to be installed
- // and nothing else is available.
- //
- // Similarly if someone manually requests an aarch64 windows install, we
- // should respect that request (this is the way users should "override"
- // this behaviour).
- let preferred = if cfg!(all(windows, target_arch = "aarch64")) {
- Arch {
- family: target_lexicon::Architecture::X86_64,
- variant: None,
- }
- } else {
- // Prefer native architectures
- Arch::from_env()
- };
-
- match (
- self.family == preferred.family,
- other.family == preferred.family,
- ) {
- (true, true) => unreachable!(),
- (true, false) => std::cmp::Ordering::Less,
- (false, true) => std::cmp::Ordering::Greater,
- (false, false) => {
- // Both non-preferred, fallback to lexicographic order
- self.family.to_string().cmp(&other.family.to_string())
- }
- }
- }
-}
-
-impl PartialOrd for Arch {
- fn partial_cmp(&self, other: &Self) -> Option {
- Some(self.cmp(other))
- }
-}
-
-#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
-pub struct Os(pub(crate) target_lexicon::OperatingSystem);
-
-#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)]
-pub enum Libc {
- Some(target_lexicon::Environment),
- None,
-}
-
-impl Libc {
- pub(crate) fn from_env() -> Result {
- match std::env::consts::OS {
- "linux" => {
- if let Ok(libc) = std::env::var(EnvVars::UV_LIBC) {
- if !libc.is_empty() {
- return Self::from_str(&libc);
- }
- }
-
- Ok(Self::Some(match detect_linux_libc()? {
- LibcVersion::Manylinux { .. } => match std::env::consts::ARCH {
- // Checks if the CPU supports hardware floating-point operations.
- // Depending on the result, it selects either the `gnueabihf` (hard-float) or `gnueabi` (soft-float) environment.
- // download-metadata.json only includes armv7.
- "arm" | "armv5te" | "armv7" => {
- match detect_hardware_floating_point_support() {
- Ok(true) => target_lexicon::Environment::Gnueabihf,
- Ok(false) => target_lexicon::Environment::Gnueabi,
- Err(_) => target_lexicon::Environment::Gnu,
- }
- }
- _ => target_lexicon::Environment::Gnu,
- },
- LibcVersion::Musllinux { .. } => target_lexicon::Environment::Musl,
- }))
- }
- "windows" | "macos" => Ok(Self::None),
- // Use `None` on platforms without explicit support.
- _ => Ok(Self::None),
- }
- }
-
- pub fn is_musl(&self) -> bool {
- matches!(self, Self::Some(target_lexicon::Environment::Musl))
- }
-}
-
-impl FromStr for Libc {
- type Err = Error;
-
- fn from_str(s: &str) -> Result {
- match s {
- "gnu" => Ok(Self::Some(target_lexicon::Environment::Gnu)),
- "gnueabi" => Ok(Self::Some(target_lexicon::Environment::Gnueabi)),
- "gnueabihf" => Ok(Self::Some(target_lexicon::Environment::Gnueabihf)),
- "musl" => Ok(Self::Some(target_lexicon::Environment::Musl)),
- "none" => Ok(Self::None),
- _ => Err(Error::UnknownLibc(s.to_string())),
- }
- }
-}
-
-impl Os {
- pub fn from_env() -> Self {
- Self(target_lexicon::HOST.operating_system)
- }
-}
-
-impl Arch {
- pub fn from_env() -> Self {
- Self {
- family: target_lexicon::HOST.architecture,
- variant: None,
- }
- }
-
- /// Does the current architecture support running the other?
- ///
- /// When the architecture is equal, this is always true. Otherwise, this is true if the
- /// architecture is transparently emulated or is a microarchitecture with worse performance
- /// characteristics.
- pub(crate) fn supports(self, other: Self) -> bool {
- if self == other {
- return true;
- }
-
- // TODO: Implement `variant` support checks
-
- // Windows ARM64 runs emulated x86_64 binaries transparently
- // Similarly, macOS aarch64 runs emulated x86_64 binaries transparently if you have Rosetta
- // installed. We don't try to be clever and check if that's the case here, we just assume
- // that if x86_64 distributions are available, they're usable.
- if (cfg!(windows) || cfg!(target_os = "macos"))
- && matches!(self.family, target_lexicon::Architecture::Aarch64(_))
- {
- return other.family == target_lexicon::Architecture::X86_64;
- }
-
- false
- }
-
- pub fn family(&self) -> target_lexicon::Architecture {
- self.family
- }
-
- pub fn is_arm(&self) -> bool {
- matches!(self.family, target_lexicon::Architecture::Arm(_))
- }
-}
-
-impl Display for Libc {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Some(env) => write!(f, "{env}"),
- Self::None => write!(f, "none"),
- }
- }
-}
-
-impl Display for Os {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match &**self {
- target_lexicon::OperatingSystem::Darwin(_) => write!(f, "macos"),
- inner => write!(f, "{inner}"),
- }
- }
-}
-
-impl Display for Arch {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self.family {
- target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686) => {
- write!(f, "x86")?;
- }
- inner => write!(f, "{inner}")?,
- }
- if let Some(variant) = self.variant {
- write!(f, "_{variant}")?;
- }
- Ok(())
- }
-}
-
-impl FromStr for Os {
- type Err = Error;
-
- fn from_str(s: &str) -> Result {
- let inner = match s {
- "macos" => target_lexicon::OperatingSystem::Darwin(None),
- _ => target_lexicon::OperatingSystem::from_str(s)
- .map_err(|()| Error::UnknownOs(s.to_string()))?,
- };
- if matches!(inner, target_lexicon::OperatingSystem::Unknown) {
- return Err(Error::UnknownOs(s.to_string()));
- }
- Ok(Self(inner))
- }
-}
-
-impl FromStr for Arch {
- type Err = Error;
-
- fn from_str(s: &str) -> Result {
- fn parse_family(s: &str) -> Result {
- let inner = match s {
- // Allow users to specify "x86" as a shorthand for the "i686" variant, they should not need
- // to specify the exact architecture and this variant is what we have downloads for.
- "x86" => {
- target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686)
- }
- _ => target_lexicon::Architecture::from_str(s)
- .map_err(|()| Error::UnknownArch(s.to_string()))?,
- };
- if matches!(inner, target_lexicon::Architecture::Unknown) {
- return Err(Error::UnknownArch(s.to_string()));
- }
- Ok(inner)
- }
-
- // First check for a variant
- if let Some((Ok(family), Ok(variant))) = s
- .rsplit_once('_')
- .map(|(family, variant)| (parse_family(family), ArchVariant::from_str(variant)))
- {
- // We only support variants for `x86_64` right now
- if !matches!(family, target_lexicon::Architecture::X86_64) {
- return Err(Error::UnsupportedVariant(
- variant.to_string(),
- family.to_string(),
- ));
- }
- return Ok(Self {
- family,
- variant: Some(variant),
- });
- }
-
- let family = parse_family(s)?;
-
- Ok(Self {
- family,
- variant: None,
- })
- }
-}
-
-impl FromStr for ArchVariant {
- type Err = ();
-
- fn from_str(s: &str) -> Result {
- match s {
- "v2" => Ok(Self::V2),
- "v3" => Ok(Self::V3),
- "v4" => Ok(Self::V4),
- _ => Err(()),
- }
- }
-}
-
-impl Display for ArchVariant {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::V2 => write!(f, "v2"),
- Self::V3 => write!(f, "v3"),
- Self::V4 => write!(f, "v4"),
- }
- }
-}
-
-impl Deref for Os {
- type Target = target_lexicon::OperatingSystem;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl From<&uv_platform_tags::Arch> for Arch {
- fn from(value: &uv_platform_tags::Arch) -> Self {
- match value {
- uv_platform_tags::Arch::Aarch64 => Self {
- family: target_lexicon::Architecture::Aarch64(
- target_lexicon::Aarch64Architecture::Aarch64,
- ),
- variant: None,
- },
- uv_platform_tags::Arch::Armv5TEL => Self {
- family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv5te),
- variant: None,
- },
- uv_platform_tags::Arch::Armv6L => Self {
- family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv6),
- variant: None,
- },
- uv_platform_tags::Arch::Armv7L => Self {
- family: target_lexicon::Architecture::Arm(target_lexicon::ArmArchitecture::Armv7),
- variant: None,
- },
- uv_platform_tags::Arch::S390X => Self {
- family: target_lexicon::Architecture::S390x,
- variant: None,
- },
- uv_platform_tags::Arch::Powerpc => Self {
- family: target_lexicon::Architecture::Powerpc,
- variant: None,
- },
- uv_platform_tags::Arch::Powerpc64 => Self {
- family: target_lexicon::Architecture::Powerpc64,
- variant: None,
- },
- uv_platform_tags::Arch::Powerpc64Le => Self {
- family: target_lexicon::Architecture::Powerpc64le,
- variant: None,
- },
- uv_platform_tags::Arch::X86 => Self {
- family: target_lexicon::Architecture::X86_32(
- target_lexicon::X86_32Architecture::I686,
- ),
- variant: None,
- },
- uv_platform_tags::Arch::X86_64 => Self {
- family: target_lexicon::Architecture::X86_64,
- variant: None,
- },
- uv_platform_tags::Arch::LoongArch64 => Self {
- family: target_lexicon::Architecture::LoongArch64,
- variant: None,
- },
- uv_platform_tags::Arch::Riscv64 => Self {
- family: target_lexicon::Architecture::Riscv64(
- target_lexicon::Riscv64Architecture::Riscv64,
- ),
- variant: None,
- },
- uv_platform_tags::Arch::Wasm32 => Self {
- family: target_lexicon::Architecture::Wasm32,
- variant: None,
- },
- }
- }
-}
-
-impl From<&uv_platform_tags::Os> for Libc {
- fn from(value: &uv_platform_tags::Os) -> Self {
- match value {
- uv_platform_tags::Os::Manylinux { .. } => Self::Some(target_lexicon::Environment::Gnu),
- uv_platform_tags::Os::Musllinux { .. } => Self::Some(target_lexicon::Environment::Musl),
- _ => Self::None,
- }
- }
-}
-
-impl From<&uv_platform_tags::Os> for Os {
- fn from(value: &uv_platform_tags::Os) -> Self {
- match value {
- uv_platform_tags::Os::Dragonfly { .. } => {
- Self(target_lexicon::OperatingSystem::Dragonfly)
- }
- uv_platform_tags::Os::FreeBsd { .. } => Self(target_lexicon::OperatingSystem::Freebsd),
- uv_platform_tags::Os::Haiku { .. } => Self(target_lexicon::OperatingSystem::Haiku),
- uv_platform_tags::Os::Illumos { .. } => Self(target_lexicon::OperatingSystem::Illumos),
- uv_platform_tags::Os::Macos { .. } => {
- Self(target_lexicon::OperatingSystem::Darwin(None))
- }
- uv_platform_tags::Os::Manylinux { .. }
- | uv_platform_tags::Os::Musllinux { .. }
- | uv_platform_tags::Os::Android { .. } => Self(target_lexicon::OperatingSystem::Linux),
- uv_platform_tags::Os::NetBsd { .. } => Self(target_lexicon::OperatingSystem::Netbsd),
- uv_platform_tags::Os::OpenBsd { .. } => Self(target_lexicon::OperatingSystem::Openbsd),
- uv_platform_tags::Os::Windows => Self(target_lexicon::OperatingSystem::Windows),
- uv_platform_tags::Os::Pyodide { .. } => {
- Self(target_lexicon::OperatingSystem::Emscripten)
- }
- }
- }
-}
diff --git a/crates/uv-python/src/windows_registry.rs b/crates/uv-python/src/windows_registry.rs
index 0020f95e9..cd6393aec 100644
--- a/crates/uv-python/src/windows_registry.rs
+++ b/crates/uv-python/src/windows_registry.rs
@@ -1,7 +1,6 @@
//! PEP 514 interactions with the Windows registry.
use crate::managed::ManagedPythonInstallation;
-use crate::platform::Arch;
use crate::{COMPANY_DISPLAY_NAME, COMPANY_KEY, PythonInstallationKey, PythonVersion};
use anyhow::anyhow;
use std::cmp::Ordering;
@@ -11,6 +10,7 @@ use std::str::FromStr;
use target_lexicon::PointerWidth;
use thiserror::Error;
use tracing::debug;
+use uv_platform::Arch;
use uv_warnings::{warn_user, warn_user_once};
use windows_registry::{CURRENT_USER, HSTRING, Key, LOCAL_MACHINE, Value};
use windows_result::HRESULT;
diff --git a/crates/uv-resolver/src/exclude_newer.rs b/crates/uv-resolver/src/exclude_newer.rs
index 65fa55cfe..7f4166f98 100644
--- a/crates/uv-resolver/src/exclude_newer.rs
+++ b/crates/uv-resolver/src/exclude_newer.rs
@@ -1,30 +1,35 @@
#[cfg(feature = "schemars")]
use std::borrow::Cow;
-use std::str::FromStr;
+use std::{
+ ops::{Deref, DerefMut},
+ str::FromStr,
+};
use jiff::{Timestamp, ToSpan, tz::TimeZone};
+use rustc_hash::FxHashMap;
+use uv_normalize::PackageName;
/// A timestamp that excludes files newer than it.
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
-pub struct ExcludeNewer(Timestamp);
+pub struct ExcludeNewerTimestamp(Timestamp);
-impl ExcludeNewer {
+impl ExcludeNewerTimestamp {
/// Returns the timestamp in milliseconds.
pub fn timestamp_millis(&self) -> i64 {
self.0.as_millisecond()
}
}
-impl From for ExcludeNewer {
+impl From for ExcludeNewerTimestamp {
fn from(timestamp: Timestamp) -> Self {
Self(timestamp)
}
}
-impl FromStr for ExcludeNewer {
+impl FromStr for ExcludeNewerTimestamp {
type Err = String;
- /// Parse an [`ExcludeNewer`] from a string.
+ /// Parse an [`ExcludeNewerTimestamp`] from a string.
///
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same
/// format (e.g., `2006-12-02`).
@@ -61,16 +66,174 @@ impl FromStr for ExcludeNewer {
}
}
-impl std::fmt::Display for ExcludeNewer {
+impl std::fmt::Display for ExcludeNewerTimestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
+/// A package-specific exclude-newer entry.
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+pub struct ExcludeNewerPackageEntry {
+ pub package: PackageName,
+ pub timestamp: ExcludeNewerTimestamp,
+}
+
+impl FromStr for ExcludeNewerPackageEntry {
+ type Err = String;
+
+ /// Parses a [`ExcludeNewerPackageEntry`] from a string in the format `PACKAGE=DATE`.
+ fn from_str(s: &str) -> Result {
+ let Some((package, date)) = s.split_once('=') else {
+ return Err(format!(
+ "Invalid `exclude-newer-package` value `{s}`: expected format `PACKAGE=DATE`"
+ ));
+ };
+
+ let package = PackageName::from_str(package).map_err(|err| {
+ format!("Invalid `exclude-newer-package` package name `{package}`: {err}")
+ })?;
+ let timestamp = ExcludeNewerTimestamp::from_str(date)
+ .map_err(|err| format!("Invalid `exclude-newer-package` timestamp `{date}`: {err}"))?;
+
+ Ok(Self { package, timestamp })
+ }
+}
+
+impl From<(PackageName, ExcludeNewerTimestamp)> for ExcludeNewerPackageEntry {
+ fn from((package, timestamp): (PackageName, ExcludeNewerTimestamp)) -> Self {
+ Self { package, timestamp }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
+#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+pub struct ExcludeNewerPackage(FxHashMap);
+
+impl Deref for ExcludeNewerPackage {
+ type Target = FxHashMap;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for ExcludeNewerPackage {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl FromIterator for ExcludeNewerPackage {
+ fn from_iter>(iter: T) -> Self {
+ Self(
+ iter.into_iter()
+ .map(|entry| (entry.package, entry.timestamp))
+ .collect(),
+ )
+ }
+}
+
+impl IntoIterator for ExcludeNewerPackage {
+ type Item = (PackageName, ExcludeNewerTimestamp);
+ type IntoIter = std::collections::hash_map::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.0.into_iter()
+ }
+}
+
+impl<'a> IntoIterator for &'a ExcludeNewerPackage {
+ type Item = (&'a PackageName, &'a ExcludeNewerTimestamp);
+ type IntoIter = std::collections::hash_map::Iter<'a, PackageName, ExcludeNewerTimestamp>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.0.iter()
+ }
+}
+
+impl ExcludeNewerPackage {
+ /// Convert to the inner `HashMap`.
+ pub fn into_inner(self) -> FxHashMap {
+ self.0
+ }
+}
+
+/// A setting that excludes files newer than a timestamp, at a global level or per-package.
+#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
+#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+pub struct ExcludeNewer {
+ /// Global timestamp that applies to all packages if no package-specific timestamp is set.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub global: Option,
+ /// Per-package timestamps that override the global timestamp.
+ #[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
+ pub package: ExcludeNewerPackage,
+}
+
+impl ExcludeNewer {
+ /// Create a new exclude newer configuration with just a global timestamp.
+ pub fn global(global: ExcludeNewerTimestamp) -> Self {
+ Self {
+ global: Some(global),
+ package: ExcludeNewerPackage::default(),
+ }
+ }
+
+ /// Create a new exclude newer configuration.
+ pub fn new(global: Option, package: ExcludeNewerPackage) -> Self {
+ Self { global, package }
+ }
+
+ /// Create from CLI arguments.
+ pub fn from_args(
+ global: Option,
+ package: Vec,
+ ) -> Self {
+ let package: ExcludeNewerPackage = package.into_iter().collect();
+
+ Self { global, package }
+ }
+
+ /// Returns the timestamp for a specific package, falling back to the global timestamp if set.
+ pub fn exclude_newer_package(
+ &self,
+ package_name: &PackageName,
+ ) -> Option {
+ self.package.get(package_name).copied().or(self.global)
+ }
+
+ /// Returns true if this has any configuration (global or per-package).
+ pub fn is_empty(&self) -> bool {
+ self.global.is_none() && self.package.is_empty()
+ }
+}
+
+impl std::fmt::Display for ExcludeNewer {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if let Some(global) = self.global {
+ write!(f, "global: {global}")?;
+ if !self.package.is_empty() {
+ write!(f, ", ")?;
+ }
+ }
+ let mut first = true;
+ for (name, timestamp) in &self.package {
+ if !first {
+ write!(f, ", ")?;
+ }
+ write!(f, "{name}: {timestamp}")?;
+ first = false;
+ }
+ Ok(())
+ }
+}
+
#[cfg(feature = "schemars")]
-impl schemars::JsonSchema for ExcludeNewer {
+impl schemars::JsonSchema for ExcludeNewerTimestamp {
fn schema_name() -> Cow<'static, str> {
- Cow::Borrowed("ExcludeNewer")
+ Cow::Borrowed("ExcludeNewerTimestamp")
}
fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs
index e91df3a7e..00cb9732e 100644
--- a/crates/uv-resolver/src/lib.rs
+++ b/crates/uv-resolver/src/lib.rs
@@ -1,6 +1,8 @@
pub use dependency_mode::DependencyMode;
pub use error::{ErrorTree, NoSolutionError, NoSolutionHeader, ResolveError, SentinelRange};
-pub use exclude_newer::ExcludeNewer;
+pub use exclude_newer::{
+ ExcludeNewer, ExcludeNewerPackage, ExcludeNewerPackageEntry, ExcludeNewerTimestamp,
+};
pub use exclusions::Exclusions;
pub use flat_index::{FlatDistributions, FlatIndex};
pub use fork_strategy::ForkStrategy;
diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs
index 2e3ee56d4..44aa915e6 100644
--- a/crates/uv-resolver/src/lock/mod.rs
+++ b/crates/uv-resolver/src/lock/mod.rs
@@ -60,7 +60,8 @@ pub use crate::lock::tree::TreeDisplay;
use crate::resolution::{AnnotatedDist, ResolutionGraphNode};
use crate::universal_marker::{ConflictMarker, UniversalMarker};
use crate::{
- ExcludeNewer, InMemoryIndex, MetadataResponse, PrereleaseMode, ResolutionMode, ResolverOutput,
+ ExcludeNewer, ExcludeNewerTimestamp, InMemoryIndex, MetadataResponse, PrereleaseMode,
+ ResolutionMode, ResolverOutput,
};
mod export;
@@ -72,7 +73,7 @@ mod tree;
pub const VERSION: u32 = 1;
/// The current revision of the lockfile format.
-const REVISION: u32 = 2;
+const REVISION: u32 = 3;
static LINUX_MARKERS: LazyLock = LazyLock::new(|| {
let pep508 = MarkerTree::from_str("os_name == 'posix' and sys_platform == 'linux'").unwrap();
@@ -278,11 +279,23 @@ impl Lock {
}
let packages = packages.into_values().collect();
+ let (exclude_newer, exclude_newer_package) = {
+ let exclude_newer = &resolution.options.exclude_newer;
+ let global_exclude_newer = exclude_newer.global;
+ let package_exclude_newer = if exclude_newer.package.is_empty() {
+ None
+ } else {
+ Some(exclude_newer.package.clone().into_inner())
+ };
+ (global_exclude_newer, package_exclude_newer)
+ };
+
let options = ResolverOptions {
resolution_mode: resolution.options.resolution_mode,
prerelease_mode: resolution.options.prerelease_mode,
fork_strategy: resolution.options.fork_strategy,
- exclude_newer: resolution.options.exclude_newer,
+ exclude_newer,
+ exclude_newer_package,
};
let lock = Self::new(
VERSION,
@@ -643,8 +656,8 @@ impl Lock {
}
/// Returns the exclude newer setting used to generate this lock.
- pub fn exclude_newer(&self) -> Option {
- self.options.exclude_newer
+ pub fn exclude_newer(&self) -> ExcludeNewer {
+ self.options.exclude_newer()
}
/// Returns the conflicting groups that were used to generate this lock.
@@ -890,8 +903,21 @@ impl Lock {
value(self.options.fork_strategy.to_string()),
);
}
- if let Some(exclude_newer) = self.options.exclude_newer {
- options_table.insert("exclude-newer", value(exclude_newer.to_string()));
+ let exclude_newer = &self.options.exclude_newer();
+ if !exclude_newer.is_empty() {
+ // Always serialize global exclude-newer as a string
+ if let Some(global) = exclude_newer.global {
+ options_table.insert("exclude-newer", value(global.to_string()));
+ }
+
+ // Serialize package-specific exclusions as a separate field
+ if !exclude_newer.package.is_empty() {
+ let mut package_table = toml_edit::Table::new();
+ for (name, timestamp) in &exclude_newer.package {
+ package_table.insert(name.as_ref(), value(timestamp.to_string()));
+ }
+ options_table.insert("exclude-newer-package", Item::Table(package_table));
+ }
}
if !options_table.is_empty() {
@@ -1870,8 +1896,25 @@ struct ResolverOptions {
/// The [`ForkStrategy`] used to generate this lock.
#[serde(default)]
fork_strategy: ForkStrategy,
- /// The [`ExcludeNewer`] used to generate this lock.
- exclude_newer: Option,
+ /// The global [`ExcludeNewer`] timestamp.
+ exclude_newer: Option,
+ /// Package-specific [`ExcludeNewer`] timestamps.
+ exclude_newer_package: Option>,
+}
+
+impl ResolverOptions {
+ /// Get the combined exclude-newer configuration.
+ fn exclude_newer(&self) -> ExcludeNewer {
+ ExcludeNewer::from_args(
+ self.exclude_newer,
+ self.exclude_newer_package
+ .clone()
+ .unwrap_or_default()
+ .into_iter()
+ .map(Into::into)
+ .collect(),
+ )
+ }
}
#[derive(Clone, Debug, Default, serde::Deserialize, PartialEq, Eq)]
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap
index dc113609c..1e0f3ef0f 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap
index 203aebdb5..123c3521b 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap
index 14ddaa43f..3c7d13be1 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap
index 811ac8260..c6fe9c4af 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap
index 811ac8260..c6fe9c4af 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_dynamic.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_dynamic.snap
index 0393168f6..b023d8238 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_dynamic.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_dynamic.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap
index 811ac8260..c6fe9c4af 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap
index df411251c..59786dddf 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_has_subdir.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap
index a0519d53a..337b3fea5 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_direct_no_subdir.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap
index 4ac13fff9..2db372e3e 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_directory.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap
index 0244bfc40..4c7642f83 100644
--- a/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap
+++ b/crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__source_editable.snap
@@ -37,6 +37,7 @@ Ok(
prerelease_mode: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
exclude_newer: None,
+ exclude_newer_package: None,
},
packages: [
Package {
diff --git a/crates/uv-resolver/src/options.rs b/crates/uv-resolver/src/options.rs
index 176b32910..f7baa4ef2 100644
--- a/crates/uv-resolver/src/options.rs
+++ b/crates/uv-resolver/src/options.rs
@@ -12,7 +12,7 @@ pub struct Options {
pub prerelease_mode: PrereleaseMode,
pub dependency_mode: DependencyMode,
pub fork_strategy: ForkStrategy,
- pub exclude_newer: Option,
+ pub exclude_newer: ExcludeNewer,
pub index_strategy: IndexStrategy,
pub required_environments: SupportedEnvironments,
pub flexibility: Flexibility,
@@ -27,7 +27,7 @@ pub struct OptionsBuilder {
prerelease_mode: PrereleaseMode,
dependency_mode: DependencyMode,
fork_strategy: ForkStrategy,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
index_strategy: IndexStrategy,
required_environments: SupportedEnvironments,
flexibility: Flexibility,
@@ -71,7 +71,7 @@ impl OptionsBuilder {
/// Sets the exclusion date.
#[must_use]
- pub fn exclude_newer(mut self, exclude_newer: Option) -> Self {
+ pub fn exclude_newer(mut self, exclude_newer: ExcludeNewer) -> Self {
self.exclude_newer = exclude_newer;
self
}
diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs
index 14eff5a9d..769a748db 100644
--- a/crates/uv-resolver/src/resolver/mod.rs
+++ b/crates/uv-resolver/src/resolver/mod.rs
@@ -182,7 +182,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
python_requirement.target(),
AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
hasher,
- options.exclude_newer,
+ options.exclude_newer.clone(),
build_context.build_options(),
build_context.capabilities(),
);
@@ -366,7 +366,7 @@ impl ResolverState ResolverState,
+ exclude_newer: Option<&ExcludeNewer>,
visited: &FxHashSet,
) -> ResolveError {
err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
@@ -2596,7 +2596,9 @@ impl ResolverState {
requires_python: RequiresPython,
allowed_yanks: AllowedYanks,
hasher: HashStrategy,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
build_options: &'a BuildOptions,
capabilities: &'a IndexCapabilities,
}
@@ -130,7 +130,7 @@ impl<'a, Context: BuildContext> DefaultResolverProvider<'a, Context> {
requires_python: &'a RequiresPython,
allowed_yanks: AllowedYanks,
hasher: &'a HashStrategy,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
build_options: &'a BuildOptions,
capabilities: &'a IndexCapabilities,
) -> Self {
@@ -184,7 +184,7 @@ impl ResolverProvider for DefaultResolverProvider<'_, Con
&self.requires_python,
&self.allowed_yanks,
&self.hasher,
- self.exclude_newer.as_ref(),
+ Some(&self.exclude_newer),
flat_index
.and_then(|flat_index| flat_index.get(package_name))
.cloned(),
diff --git a/crates/uv-resolver/src/version_map.rs b/crates/uv-resolver/src/version_map.rs
index 63132ad0d..4b0e07ad8 100644
--- a/crates/uv-resolver/src/version_map.rs
+++ b/crates/uv-resolver/src/version_map.rs
@@ -22,7 +22,7 @@ use uv_types::HashStrategy;
use uv_warnings::warn_user_once;
use crate::flat_index::FlatDistributions;
-use crate::{ExcludeNewer, yanks::AllowedYanks};
+use crate::{ExcludeNewer, ExcludeNewerTimestamp, yanks::AllowedYanks};
/// A map from versions to distributions.
#[derive(Debug)]
@@ -112,7 +112,7 @@ impl VersionMap {
allowed_yanks: allowed_yanks.clone(),
hasher: hasher.clone(),
requires_python: requires_python.clone(),
- exclude_newer: exclude_newer.copied(),
+ exclude_newer: exclude_newer.and_then(|en| en.exclude_newer_package(package_name)),
}),
}
}
@@ -365,7 +365,7 @@ struct VersionMapLazy {
/// in the current environment.
tags: Option,
/// Whether files newer than this timestamp should be excluded or not.
- exclude_newer: Option,
+ exclude_newer: Option,
/// Which yanked versions are allowed
allowed_yanks: AllowedYanks,
/// The hashes of allowed distributions.
@@ -420,7 +420,7 @@ impl VersionMapLazy {
for (filename, file) in files.all() {
// Support resolving as if it were an earlier timestamp, at least as long files have
// upload time information.
- let (excluded, upload_time) = if let Some(exclude_newer) = self.exclude_newer {
+ let (excluded, upload_time) = if let Some(exclude_newer) = &self.exclude_newer {
match file.upload_time_utc_ms.as_ref() {
Some(&upload_time) if upload_time >= exclude_newer.timestamp_millis() => {
(true, Some(upload_time))
diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs
index b8d2e3c28..ce74bd63a 100644
--- a/crates/uv-settings/src/combine.rs
+++ b/crates/uv-settings/src/combine.rs
@@ -12,7 +12,10 @@ use uv_install_wheel::LinkMode;
use uv_pypi_types::{SchemaConflicts, SupportedEnvironments};
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
use uv_redacted::DisplaySafeUrl;
-use uv_resolver::{AnnotationStyle, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode};
+use uv_resolver::{
+ AnnotationStyle, ExcludeNewer, ExcludeNewerPackage, ExcludeNewerTimestamp, ForkStrategy,
+ PrereleaseMode, ResolutionMode,
+};
use uv_torch::TorchMode;
use uv_workspace::pyproject_mut::AddBoundsKind;
@@ -79,6 +82,7 @@ impl_combine_or!(AddBoundsKind);
impl_combine_or!(AnnotationStyle);
impl_combine_or!(BuildDependencyStrategy);
impl_combine_or!(ExcludeNewer);
+impl_combine_or!(ExcludeNewerTimestamp);
impl_combine_or!(ExportFormat);
impl_combine_or!(ForkStrategy);
impl_combine_or!(Index);
@@ -121,6 +125,22 @@ impl Combine for Option> {
}
}
+impl Combine for Option {
+ /// Combine two [`ExcludeNewerPackage`] instances by merging them, with the values in `self` taking precedence.
+ fn combine(self, other: Option) -> Option {
+ match (self, other) {
+ (Some(mut a), Some(b)) => {
+ // Extend with values from b, but a takes precedence (we don't overwrite existing keys)
+ for (key, value) in b {
+ a.entry(key).or_insert(value);
+ }
+ Some(a)
+ }
+ (a, b) => a.or(b),
+ }
+ }
+}
+
impl Combine for Option {
/// Combine two maps by merging the map in `self` with the map in `other`, if they're both
/// `Some`.
@@ -154,3 +174,22 @@ impl Combine for Option {
self
}
}
+
+impl Combine for ExcludeNewer {
+ fn combine(mut self, other: Self) -> Self {
+ self.global = self.global.combine(other.global);
+
+ if !other.package.is_empty() {
+ if self.package.is_empty() {
+ self.package = other.package;
+ } else {
+ // Merge package-specific timestamps, with self taking precedence
+ for (pkg, timestamp) in &other.package {
+ self.package.entry(pkg.clone()).or_insert(*timestamp);
+ }
+ }
+ }
+
+ self
+ }
+}
diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs
index d56d512e0..1ebe802fc 100644
--- a/crates/uv-settings/src/lib.rs
+++ b/crates/uv-settings/src/lib.rs
@@ -318,6 +318,7 @@ fn warn_uv_toml_masked_fields(options: &Options) {
no_build_isolation,
no_build_isolation_package,
exclude_newer,
+ exclude_newer_package,
link_mode,
compile_bytecode,
no_sources,
@@ -448,6 +449,9 @@ fn warn_uv_toml_masked_fields(options: &Options) {
if exclude_newer.is_some() {
masked_fields.push("exclude-newer");
}
+ if exclude_newer_package.is_some() {
+ masked_fields.push("exclude-newer-package");
+ }
if link_mode.is_some() {
masked_fields.push("link-mode");
}
diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs
index dbd5fd324..da1dc193f 100644
--- a/crates/uv-settings/src/settings.rs
+++ b/crates/uv-settings/src/settings.rs
@@ -13,12 +13,16 @@ use uv_distribution_types::{
};
use uv_install_wheel::LinkMode;
use uv_macros::{CombineOptions, OptionsMetadata};
+
use uv_normalize::{ExtraName, PackageName, PipGroupName};
use uv_pep508::Requirement;
use uv_pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
use uv_redacted::DisplaySafeUrl;
-use uv_resolver::{AnnotationStyle, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode};
+use uv_resolver::{
+ AnnotationStyle, ExcludeNewer, ExcludeNewerPackage, ExcludeNewerTimestamp, ForkStrategy,
+ PrereleaseMode, ResolutionMode,
+};
use uv_static::EnvVars;
use uv_torch::TorchMode;
use uv_workspace::pyproject_mut::AddBoundsKind;
@@ -334,7 +338,7 @@ pub struct InstallerOptions {
pub index_strategy: Option,
pub keyring_provider: Option,
pub config_settings: Option,
- pub exclude_newer: Option,
+ pub exclude_newer: Option,
pub link_mode: Option,
pub compile_bytecode: Option,
pub reinstall: Option,
@@ -363,7 +367,7 @@ pub struct ResolverOptions {
pub dependency_metadata: Option>,
pub config_settings: Option,
pub config_settings_package: Option,
- pub exclude_newer: Option,
+ pub exclude_newer: ExcludeNewer,
pub link_mode: Option,
pub upgrade: Option,
pub upgrade_package: Option>>,
@@ -657,7 +661,18 @@ pub struct ResolverInstallerOptions {
exclude-newer = "2006-12-02T02:07:43Z"
"#
)]
- pub exclude_newer: Option,
+ pub exclude_newer: Option,
+ /// Limit candidate packages for specific packages to those that were uploaded prior to the given date.
+ ///
+ /// Accepts package-date pairs in a dictionary format.
+ #[option(
+ default = "None",
+ value_type = "dict",
+ example = r#"
+ exclude-newer-package = { tqdm = "2022-04-04T00:00:00Z" }
+ "#
+ )]
+ pub exclude_newer_package: Option,
/// The method to use when installing packages from the global cache.
///
/// Defaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and
@@ -1430,7 +1445,18 @@ pub struct PipOptions {
exclude-newer = "2006-12-02T02:07:43Z"
"#
)]
- pub exclude_newer: Option,
+ pub exclude_newer: Option,
+ /// Limit candidate packages for specific packages to those that were uploaded prior to the given date.
+ ///
+ /// Accepts package-date pairs in a dictionary format.
+ #[option(
+ default = "None",
+ value_type = "dict",
+ example = r#"
+ exclude-newer-package = { tqdm = "2022-04-04T00:00:00Z" }
+ "#
+ )]
+ pub exclude_newer_package: Option,
/// Specify a package to omit from the output resolution. Its dependencies will still be
/// included in the resolution. Equivalent to pip-compile's `--unsafe-package` option.
#[option(
@@ -1696,7 +1722,15 @@ impl From for ResolverOptions {
dependency_metadata: value.dependency_metadata,
config_settings: value.config_settings,
config_settings_package: value.config_settings_package,
- exclude_newer: value.exclude_newer,
+ exclude_newer: ExcludeNewer::from_args(
+ value.exclude_newer,
+ value
+ .exclude_newer_package
+ .unwrap_or_default()
+ .into_iter()
+ .map(Into::into)
+ .collect(),
+ ),
link_mode: value.link_mode,
upgrade: value.upgrade,
upgrade_package: value.upgrade_package,
@@ -1723,7 +1757,16 @@ impl From for InstallerOptions {
index_strategy: value.index_strategy,
keyring_provider: value.keyring_provider,
config_settings: value.config_settings,
- exclude_newer: value.exclude_newer,
+ exclude_newer: ExcludeNewer::from_args(
+ value.exclude_newer,
+ value
+ .exclude_newer_package
+ .unwrap_or_default()
+ .into_iter()
+ .map(Into::into)
+ .collect(),
+ )
+ .global,
link_mode: value.link_mode,
compile_bytecode: value.compile_bytecode,
reinstall: value.reinstall,
@@ -1763,7 +1806,8 @@ pub struct ToolOptions {
pub config_settings_package: Option,
pub no_build_isolation: Option,
pub no_build_isolation_package: Option>,
- pub exclude_newer: Option,
+ pub exclude_newer: Option,
+ pub exclude_newer_package: Option,
pub link_mode: Option,
pub compile_bytecode: Option,
pub no_sources: Option,
@@ -1792,6 +1836,7 @@ impl From for ToolOptions {
no_build_isolation: value.no_build_isolation,
no_build_isolation_package: value.no_build_isolation_package,
exclude_newer: value.exclude_newer,
+ exclude_newer_package: value.exclude_newer_package,
link_mode: value.link_mode,
compile_bytecode: value.compile_bytecode,
no_sources: value.no_sources,
@@ -1822,6 +1867,7 @@ impl From for ResolverInstallerOptions {
no_build_isolation: value.no_build_isolation,
no_build_isolation_package: value.no_build_isolation_package,
exclude_newer: value.exclude_newer,
+ exclude_newer_package: value.exclude_newer_package,
link_mode: value.link_mode,
compile_bytecode: value.compile_bytecode,
no_sources: value.no_sources,
@@ -1875,7 +1921,8 @@ pub struct OptionsWire {
config_settings_package: Option,
no_build_isolation: Option,
no_build_isolation_package: Option>,
- exclude_newer: Option,
+ exclude_newer: Option,
+ exclude_newer_package: Option,
link_mode: Option,
compile_bytecode: Option,
no_sources: Option,
@@ -1967,6 +2014,7 @@ impl From for Options {
no_build_isolation,
no_build_isolation_package,
exclude_newer,
+ exclude_newer_package,
link_mode,
compile_bytecode,
no_sources,
@@ -2036,6 +2084,7 @@ impl From for Options {
no_build_isolation,
no_build_isolation_package,
exclude_newer,
+ exclude_newer_package,
link_mode,
compile_bytecode,
no_sources,
diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml
index 5acbb7f20..f37e8c2f0 100644
--- a/crates/uv/Cargo.toml
+++ b/crates/uv/Cargo.toml
@@ -38,6 +38,7 @@ uv-normalize = { workspace = true }
uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true }
uv-performance-memory-allocator = { path = "../uv-performance-memory-allocator", optional = true }
+uv-platform = { workspace = true }
uv-platform-tags = { workspace = true }
uv-publish = { workspace = true }
uv-pypi-types = { workspace = true }
diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs
index c817bc3e5..38cf665db 100644
--- a/crates/uv/src/commands/build_frontend.rs
+++ b/crates/uv/src/commands/build_frontend.rs
@@ -349,7 +349,7 @@ async fn build_impl(
no_build_isolation_package,
*index_strategy,
*keyring_provider,
- *exclude_newer,
+ exclude_newer.clone(),
*sources,
concurrency,
build_options,
@@ -427,7 +427,7 @@ async fn build_package(
no_build_isolation_package: &[PackageName],
index_strategy: IndexStrategy,
keyring_provider: KeyringProviderType,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
sources: SourceStrategy,
concurrency: Concurrency,
build_options: &BuildOptions,
diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs
index 156905ff1..29538f31a 100644
--- a/crates/uv/src/commands/pip/compile.rs
+++ b/crates/uv/src/commands/pip/compile.rs
@@ -99,7 +99,7 @@ pub(crate) async fn pip_compile(
mut python_version: Option,
python_platform: Option,
universal: bool,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
sources: SourceStrategy,
annotation_style: AnnotationStyle,
link_mode: LinkMode,
@@ -485,7 +485,7 @@ pub(crate) async fn pip_compile(
link_mode,
&build_options,
&build_hashes,
- exclude_newer,
+ exclude_newer.clone(),
sources,
WorkspaceCache::default(),
concurrency,
@@ -498,7 +498,7 @@ pub(crate) async fn pip_compile(
.prerelease_mode(prerelease_mode)
.fork_strategy(fork_strategy)
.dependency_mode(dependency_mode)
- .exclude_newer(exclude_newer)
+ .exclude_newer(exclude_newer.clone())
.index_strategy(index_strategy)
.torch_backend(torch_backend)
.build_options(build_options.clone())
diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs
index 9b7f85c9d..3db324d82 100644
--- a/crates/uv/src/commands/pip/install.rs
+++ b/crates/uv/src/commands/pip/install.rs
@@ -83,7 +83,7 @@ pub(crate) async fn pip_install(
python_version: Option,
python_platform: Option,
strict: bool,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
sources: SourceStrategy,
python: Option,
system: bool,
@@ -429,7 +429,7 @@ pub(crate) async fn pip_install(
link_mode,
&build_options,
&build_hasher,
- exclude_newer,
+ exclude_newer.clone(),
sources,
WorkspaceCache::default(),
concurrency,
diff --git a/crates/uv/src/commands/pip/latest.rs b/crates/uv/src/commands/pip/latest.rs
index 25da8466c..87e26f3f5 100644
--- a/crates/uv/src/commands/pip/latest.rs
+++ b/crates/uv/src/commands/pip/latest.rs
@@ -13,12 +13,12 @@ use uv_warnings::warn_user_once;
///
/// The returned distribution is guaranteed to be compatible with the provided tags and Python
/// requirement.
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Clone)]
pub(crate) struct LatestClient<'env> {
pub(crate) client: &'env RegistryClient,
pub(crate) capabilities: &'env IndexCapabilities,
pub(crate) prerelease: PrereleaseMode,
- pub(crate) exclude_newer: Option,
+ pub(crate) exclude_newer: ExcludeNewer,
pub(crate) tags: Option<&'env Tags>,
pub(crate) requires_python: &'env RequiresPython,
}
@@ -70,7 +70,7 @@ impl LatestClient<'_> {
for (filename, file) in files.all() {
// Skip distributions uploaded after the cutoff.
- if let Some(exclude_newer) = self.exclude_newer {
+ if let Some(exclude_newer) = self.exclude_newer.exclude_newer_package(package) {
match file.upload_time_utc_ms.as_ref() {
Some(&upload_time)
if upload_time >= exclude_newer.timestamp_millis() =>
@@ -79,8 +79,9 @@ impl LatestClient<'_> {
}
None => {
warn_user_once!(
- "{} is missing an upload date, but user provided: {exclude_newer}",
+ "{} is missing an upload date, but user provided: {}",
file.filename,
+ self.exclude_newer
);
}
_ => {}
diff --git a/crates/uv/src/commands/pip/list.rs b/crates/uv/src/commands/pip/list.rs
index 9205268ba..fb7f69011 100644
--- a/crates/uv/src/commands/pip/list.rs
+++ b/crates/uv/src/commands/pip/list.rs
@@ -49,7 +49,7 @@ pub(crate) async fn pip_list(
network_settings: &NetworkSettings,
concurrency: Concurrency,
strict: bool,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
python: Option<&str>,
system: bool,
cache: &Cache,
diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs
index a7bd36652..67ded42a2 100644
--- a/crates/uv/src/commands/pip/sync.rs
+++ b/crates/uv/src/commands/pip/sync.rs
@@ -71,7 +71,7 @@ pub(crate) async fn pip_sync(
python_version: Option,
python_platform: Option,
strict: bool,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
python: Option,
system: bool,
break_system_packages: bool,
@@ -364,7 +364,7 @@ pub(crate) async fn pip_sync(
link_mode,
&build_options,
&build_hasher,
- exclude_newer,
+ exclude_newer.clone(),
sources,
WorkspaceCache::default(),
concurrency,
diff --git a/crates/uv/src/commands/pip/tree.rs b/crates/uv/src/commands/pip/tree.rs
index 8b0aa0c3a..2d6c8a4f7 100644
--- a/crates/uv/src/commands/pip/tree.rs
+++ b/crates/uv/src/commands/pip/tree.rs
@@ -47,7 +47,7 @@ pub(crate) async fn pip_tree(
network_settings: NetworkSettings,
concurrency: Concurrency,
strict: bool,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
python: Option<&str>,
system: bool,
cache: &Cache,
diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs
index a66d2b517..0943bba16 100644
--- a/crates/uv/src/commands/project/add.rs
+++ b/crates/uv/src/commands/project/add.rs
@@ -473,7 +473,7 @@ pub(crate) async fn add(
settings.resolver.link_mode,
&settings.resolver.build_options,
&build_hasher,
- settings.resolver.exclude_newer,
+ settings.resolver.exclude_newer.clone(),
sources,
// No workspace caching since `uv add` changes the workspace definition.
WorkspaceCache::default(),
@@ -1312,6 +1312,7 @@ impl PythonTarget {
/// Represents the destination where dependencies are added, either to a project or a script.
#[derive(Debug, Clone)]
+#[allow(clippy::large_enum_variant)]
pub(super) enum AddTarget {
/// A PEP 723 script, with inline metadata.
Script(Pep723Script, Box),
@@ -1420,6 +1421,7 @@ impl AddTarget {
}
#[derive(Debug, Clone)]
+#[allow(clippy::large_enum_variant)]
enum AddTargetSnapshot {
Script(Pep723Script, Option>),
Project(VirtualProject, Option>),
diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs
index 6b06e493a..df839be08 100644
--- a/crates/uv/src/commands/project/export.rs
+++ b/crates/uv/src/commands/project/export.rs
@@ -32,6 +32,7 @@ use crate::printer::Printer;
use crate::settings::{NetworkSettings, ResolverSettings};
#[derive(Debug, Clone)]
+#[allow(clippy::large_enum_variant)]
enum ExportTarget {
/// A PEP 723 script, with inline metadata.
Script(Pep723Script),
diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs
index e4848daba..6d3214a25 100644
--- a/crates/uv/src/commands/project/lock.rs
+++ b/crates/uv/src/commands/project/lock.rs
@@ -652,7 +652,7 @@ async fn do_lock(
.resolution_mode(*resolution)
.prerelease_mode(*prerelease)
.fork_strategy(*fork_strategy)
- .exclude_newer(*exclude_newer)
+ .exclude_newer(exclude_newer.clone())
.index_strategy(*index_strategy)
.build_options(build_options.clone())
.required_environments(required_environments.cloned().unwrap_or_default())
@@ -711,7 +711,7 @@ async fn do_lock(
*link_mode,
build_options,
&build_hasher,
- *exclude_newer,
+ exclude_newer.clone(),
*sources,
workspace_cache.clone(),
concurrency,
@@ -990,31 +990,37 @@ impl ValidatedLock {
);
return Ok(Self::Unusable(lock));
}
- match (lock.exclude_newer(), options.exclude_newer) {
- (None, None) => (),
- (Some(existing), Some(provided)) if existing == provided => (),
- (Some(existing), Some(provided)) => {
+ let lock_exclude_newer = lock.exclude_newer();
+ let options_exclude_newer = &options.exclude_newer;
+
+ match (
+ lock_exclude_newer.is_empty(),
+ options_exclude_newer.is_empty(),
+ ) {
+ (true, true) => (),
+ (false, false) if lock_exclude_newer == *options_exclude_newer => (),
+ (false, false) => {
let _ = writeln!(
printer.stderr(),
"Ignoring existing lockfile due to change in timestamp cutoff: `{}` vs. `{}`",
- existing.cyan(),
- provided.cyan()
+ lock_exclude_newer.cyan(),
+ options_exclude_newer.cyan()
);
return Ok(Self::Unusable(lock));
}
- (Some(existing), None) => {
+ (false, true) => {
let _ = writeln!(
printer.stderr(),
"Ignoring existing lockfile due to removal of timestamp cutoff: `{}`",
- existing.cyan(),
+ lock_exclude_newer.cyan(),
);
return Ok(Self::Unusable(lock));
}
- (None, Some(provided)) => {
+ (true, false) => {
let _ = writeln!(
printer.stderr(),
"Ignoring existing lockfile due to addition of timestamp cutoff: `{}`",
- provided.cyan()
+ options_exclude_newer.cyan()
);
return Ok(Self::Unusable(lock));
}
diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs
index 2579030ee..095711a53 100644
--- a/crates/uv/src/commands/project/mod.rs
+++ b/crates/uv/src/commands/project/mod.rs
@@ -1757,7 +1757,7 @@ pub(crate) async fn resolve_names(
*link_mode,
build_options,
&build_hasher,
- *exclude_newer,
+ exclude_newer.clone(),
*sources,
workspace_cache.clone(),
concurrency,
@@ -1904,7 +1904,7 @@ pub(crate) async fn resolve_environment(
.resolution_mode(*resolution)
.prerelease_mode(*prerelease)
.fork_strategy(*fork_strategy)
- .exclude_newer(*exclude_newer)
+ .exclude_newer(exclude_newer.clone())
.index_strategy(*index_strategy)
.build_options(build_options.clone())
.build();
@@ -1967,7 +1967,7 @@ pub(crate) async fn resolve_environment(
*link_mode,
build_options,
&build_hasher,
- *exclude_newer,
+ exclude_newer.clone(),
*sources,
workspace_cache,
concurrency,
@@ -2290,7 +2290,7 @@ pub(crate) async fn update_environment(
.resolution_mode(*resolution)
.prerelease_mode(*prerelease)
.fork_strategy(*fork_strategy)
- .exclude_newer(*exclude_newer)
+ .exclude_newer(exclude_newer.clone())
.index_strategy(*index_strategy)
.build_options(build_options.clone())
.build();
@@ -2333,7 +2333,7 @@ pub(crate) async fn update_environment(
*link_mode,
build_options,
&build_hasher,
- *exclude_newer,
+ exclude_newer.clone(),
*sources,
workspace_cache,
concurrency,
diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs
index 1af4c818f..50c498833 100644
--- a/crates/uv/src/commands/project/remove.rs
+++ b/crates/uv/src/commands/project/remove.rs
@@ -386,6 +386,7 @@ pub(crate) async fn remove(
/// Represents the destination where dependencies are added, either to a project or a script.
#[derive(Debug)]
+#[allow(clippy::large_enum_variant)]
enum RemoveTarget {
/// A PEP 723 script, with inline metadata.
Project(VirtualProject),
diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs
index 9794c109e..e83021351 100644
--- a/crates/uv/src/commands/project/sync.rs
+++ b/crates/uv/src/commands/project/sync.rs
@@ -495,6 +495,7 @@ fn identify_installation_target<'a>(
}
#[derive(Debug, Clone)]
+#[allow(clippy::large_enum_variant)]
enum SyncTarget {
/// Sync a project environment.
Project(VirtualProject),
diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs
index 02e4c27e5..e3b1ef797 100644
--- a/crates/uv/src/commands/python/install.rs
+++ b/crates/uv/src/commands/python/install.rs
@@ -16,6 +16,7 @@ use tracing::{debug, trace};
use uv_configuration::{Preview, PreviewFeatures};
use uv_fs::Simplified;
+use uv_platform::{Arch, Libc};
use uv_python::downloads::{
self, ArchRequest, DownloadResult, ManagedPythonDownload, PythonDownloadRequest,
};
@@ -23,7 +24,6 @@ use uv_python::managed::{
ManagedPythonInstallation, ManagedPythonInstallations, PythonMinorVersionLink,
create_link_to_executable, python_executable_dir,
};
-use uv_python::platform::{Arch, Libc};
use uv_python::{
PythonDownloads, PythonInstallationKey, PythonInstallationMinorVersionKey, PythonRequest,
PythonVersionFile, VersionFileDiscoveryOptions, VersionFilePreference, VersionRequest,
diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs
index 14225bd99..f673779ed 100644
--- a/crates/uv/src/commands/venv.rs
+++ b/crates/uv/src/commands/venv.rs
@@ -75,7 +75,7 @@ pub(crate) async fn venv(
system_site_packages: bool,
seed: bool,
on_existing: OnExisting,
- exclude_newer: Option,
+ exclude_newer: ExcludeNewer,
concurrency: Concurrency,
no_config: bool,
no_project: bool,
diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs
index 8036f3c28..60b6542e9 100644
--- a/crates/uv/src/settings.rs
+++ b/crates/uv/src/settings.rs
@@ -35,7 +35,8 @@ use uv_pypi_types::SupportedEnvironments;
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
use uv_redacted::DisplaySafeUrl;
use uv_resolver::{
- AnnotationStyle, DependencyMode, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode,
+ AnnotationStyle, DependencyMode, ExcludeNewer, ExcludeNewerPackage, ForkStrategy,
+ PrereleaseMode, ResolutionMode,
};
use uv_settings::{
Combine, EnvironmentOptions, FilesystemOptions, Options, PipOptions, PublishOptions,
@@ -723,6 +724,7 @@ impl ToolUpgradeSettings {
compile_bytecode,
no_compile_bytecode,
no_sources,
+ exclude_newer_package,
build,
} = args;
@@ -754,6 +756,7 @@ impl ToolUpgradeSettings {
no_build_isolation_package,
build_isolation,
exclude_newer,
+ exclude_newer_package,
link_mode,
compile_bytecode,
no_compile_bytecode,
@@ -2666,6 +2669,7 @@ impl VenvSettings {
link_mode,
refresh,
compat_args: _,
+ exclude_newer_package,
} = args;
Self {
@@ -2685,6 +2689,8 @@ impl VenvSettings {
index_strategy,
keyring_provider,
exclude_newer,
+ exclude_newer_package: exclude_newer_package
+ .map(ExcludeNewerPackage::from_iter),
link_mode,
..PipOptions::from(index_args)
},
@@ -2708,7 +2714,7 @@ pub(crate) struct InstallerSettingsRef<'a> {
pub(crate) config_settings_package: &'a PackageConfigSettings,
pub(crate) no_build_isolation: bool,
pub(crate) no_build_isolation_package: &'a [PackageName],
- pub(crate) exclude_newer: Option,
+ pub(crate) exclude_newer: ExcludeNewer,
pub(crate) link_mode: LinkMode,
pub(crate) compile_bytecode: bool,
pub(crate) reinstall: &'a Reinstall,
@@ -2727,7 +2733,7 @@ pub(crate) struct ResolverSettings {
pub(crate) config_setting: ConfigSettings,
pub(crate) config_settings_package: PackageConfigSettings,
pub(crate) dependency_metadata: DependencyMetadata,
- pub(crate) exclude_newer: Option,
+ pub(crate) exclude_newer: ExcludeNewer,
pub(crate) fork_strategy: ForkStrategy,
pub(crate) index_locations: IndexLocations,
pub(crate) index_strategy: IndexStrategy,
@@ -2870,7 +2876,15 @@ impl From for ResolverInstallerSettings {
dependency_metadata: DependencyMetadata::from_entries(
value.dependency_metadata.into_iter().flatten(),
),
- exclude_newer: value.exclude_newer,
+ exclude_newer: ExcludeNewer::from_args(
+ value.exclude_newer,
+ value
+ .exclude_newer_package
+ .unwrap_or_default()
+ .into_iter()
+ .map(Into::into)
+ .collect(),
+ ),
fork_strategy: value.fork_strategy.unwrap_or_default(),
index_locations,
index_strategy: value.index_strategy.unwrap_or_default(),
@@ -2941,7 +2955,7 @@ pub(crate) struct PipSettings {
pub(crate) python_version: Option,
pub(crate) python_platform: Option,
pub(crate) universal: bool,
- pub(crate) exclude_newer: Option,
+ pub(crate) exclude_newer: ExcludeNewer,
pub(crate) no_emit_package: Vec,
pub(crate) emit_index_url: bool,
pub(crate) emit_find_links: bool,
@@ -3028,6 +3042,7 @@ impl PipSettings {
upgrade_package,
reinstall,
reinstall_package,
+ exclude_newer_package,
} = pip.unwrap_or_default();
let ResolverInstallerOptions {
@@ -3059,6 +3074,7 @@ impl PipSettings {
no_binary: top_level_no_binary,
no_binary_package: top_level_no_binary_package,
build_dependency_strategy: _,
+ exclude_newer_package: top_level_exclude_newer_package,
} = top_level;
// Merge the top-level options (`tool.uv`) with the pip-specific options (`tool.uv.pip`),
@@ -3082,7 +3098,15 @@ impl PipSettings {
let no_build_isolation = no_build_isolation.combine(top_level_no_build_isolation);
let no_build_isolation_package =
no_build_isolation_package.combine(top_level_no_build_isolation_package);
- let exclude_newer = exclude_newer.combine(top_level_exclude_newer);
+ let exclude_newer = args
+ .exclude_newer
+ .combine(exclude_newer)
+ .combine(top_level_exclude_newer);
+ let exclude_newer_package = args
+ .exclude_newer_package
+ .combine(exclude_newer_package)
+ .combine(top_level_exclude_newer_package)
+ .unwrap_or_default();
let link_mode = link_mode.combine(top_level_link_mode);
let compile_bytecode = compile_bytecode.combine(top_level_compile_bytecode);
let no_sources = no_sources.combine(top_level_no_sources);
@@ -3189,7 +3213,10 @@ impl PipSettings {
python_version: args.python_version.combine(python_version),
python_platform: args.python_platform.combine(python_platform),
universal: args.universal.combine(universal).unwrap_or_default(),
- exclude_newer: args.exclude_newer.combine(exclude_newer),
+ exclude_newer: ExcludeNewer::from_args(
+ exclude_newer,
+ exclude_newer_package.into_iter().map(Into::into).collect(),
+ ),
no_emit_package: args
.no_emit_package
.combine(no_emit_package)
@@ -3281,7 +3308,7 @@ impl<'a> From<&'a ResolverInstallerSettings> for InstallerSettingsRef<'a> {
config_settings_package: &settings.resolver.config_settings_package,
no_build_isolation: settings.resolver.no_build_isolation,
no_build_isolation_package: &settings.resolver.no_build_isolation_package,
- exclude_newer: settings.resolver.exclude_newer,
+ exclude_newer: settings.resolver.exclude_newer.clone(),
link_mode: settings.resolver.link_mode,
compile_bytecode: settings.compile_bytecode,
reinstall: &settings.reinstall,
diff --git a/crates/uv/tests/it/branching_urls.rs b/crates/uv/tests/it/branching_urls.rs
index aa6edd090..29493ef14 100644
--- a/crates/uv/tests/it/branching_urls.rs
+++ b/crates/uv/tests/it/branching_urls.rs
@@ -212,7 +212,7 @@ fn root_package_splits_transitive_too() -> Result<()> {
assert_snapshot!(context.read("uv.lock"), @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11, <3.13"
resolution-markers = [
"python_full_version >= '3.12'",
@@ -409,7 +409,7 @@ fn root_package_splits_other_dependencies_too() -> Result<()> {
assert_snapshot!(context.read("uv.lock"), @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11, <3.13"
resolution-markers = [
"python_full_version >= '3.12'",
@@ -572,7 +572,7 @@ fn branching_between_registry_and_direct_url() -> Result<()> {
// We have source dist and wheel for the registry, but only the wheel for the direct URL.
assert_snapshot!(context.read("uv.lock"), @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11, <3.13"
resolution-markers = [
"python_full_version >= '3.12'",
@@ -659,7 +659,7 @@ fn branching_urls_of_different_sources_disjoint() -> Result<()> {
// We have source dist and wheel for the registry, but only the wheel for the direct URL.
assert_snapshot!(context.read("uv.lock"), @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11, <3.13"
resolution-markers = [
"python_full_version >= '3.12'",
@@ -789,7 +789,7 @@ fn dont_pre_visit_url_packages() -> Result<()> {
assert_snapshot!(context.read("uv.lock"), @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11, <3.13"
[options]
diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs
index 65a9dd023..9337f6ad0 100644
--- a/crates/uv/tests/it/edit.rs
+++ b/crates/uv/tests/it/edit.rs
@@ -76,7 +76,7 @@ fn add_registry() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -230,7 +230,7 @@ fn add_git() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -361,7 +361,7 @@ fn add_git_private_source() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -456,7 +456,7 @@ fn add_git_private_raw() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -739,7 +739,7 @@ fn add_git_raw() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1033,7 +1033,7 @@ fn add_unnamed() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1129,7 +1129,7 @@ fn add_remove_dev() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1246,7 +1246,7 @@ fn add_remove_dev() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1336,7 +1336,7 @@ fn add_remove_optional() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1453,7 +1453,7 @@ fn add_remove_optional() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1689,7 +1689,7 @@ fn add_remove_workspace() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1773,7 +1773,7 @@ fn add_remove_workspace() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2315,7 +2315,7 @@ fn add_workspace_editable() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2447,7 +2447,7 @@ fn add_workspace_path() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2575,7 +2575,7 @@ fn add_path_implicit_workspace() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2697,7 +2697,7 @@ fn add_path_no_workspace() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2811,7 +2811,7 @@ fn add_path_adjacent_directory() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2986,7 +2986,7 @@ fn update() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -3754,7 +3754,7 @@ fn add_inexact() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -3882,7 +3882,7 @@ fn remove_registry() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -4518,7 +4518,7 @@ fn add_lower_bound_optional() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -4631,7 +4631,7 @@ fn add_lower_bound_local() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[[package]]
@@ -4733,7 +4733,7 @@ fn add_non_project() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -5131,7 +5131,7 @@ fn add_requirements_file_constraints() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -6069,7 +6069,7 @@ fn add_script_settings() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -6612,7 +6612,7 @@ fn add_remove_script_lock() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -6795,7 +6795,7 @@ fn add_remove_script_lock() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -7000,7 +7000,7 @@ fn add_remove_script_lock() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -8629,7 +8629,7 @@ fn add_warn_index_url() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8731,7 +8731,7 @@ fn add_no_warn_index_url() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8824,7 +8824,7 @@ fn add_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8910,7 +8910,7 @@ fn add_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9022,7 +9022,7 @@ fn add_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9142,7 +9142,7 @@ fn add_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9271,7 +9271,7 @@ fn add_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9407,7 +9407,7 @@ fn add_default_index_url() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9480,7 +9480,7 @@ fn add_default_index_url() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9581,7 +9581,7 @@ fn add_index_credentials() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9677,7 +9677,7 @@ fn existing_index_credentials() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9770,7 +9770,7 @@ fn add_index_with_trailing_slash() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9866,7 +9866,7 @@ fn add_index_without_trailing_slash() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10142,7 +10142,7 @@ fn add_group_comment() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -10276,7 +10276,7 @@ fn add_index_comments() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10596,7 +10596,7 @@ fn add_direct_url_subdirectory() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10726,7 +10726,7 @@ fn add_direct_url_subdirectory_raw() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11374,7 +11374,7 @@ fn multiple_index_cli() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11485,7 +11485,7 @@ fn repeated_index_cli_environment_variable() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11591,7 +11591,7 @@ fn repeated_index_cli_environment_variable_newline() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11701,7 +11701,7 @@ fn repeated_index_cli() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11811,7 +11811,7 @@ fn repeated_index_cli_reversed() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs
index b48536f2e..dfa251ab7 100644
--- a/crates/uv/tests/it/export.rs
+++ b/crates/uv/tests/it/export.rs
@@ -583,7 +583,7 @@ fn requirements_txt_dependency_conflicting_markers() -> Result<()> {
insta::assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -1505,7 +1505,7 @@ fn requirements_txt_non_project_fork() -> Result<()> {
insta::assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'win32'",
@@ -2477,7 +2477,7 @@ fn requirements_txt_script() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
resolution-markers = [
"sys_platform == 'win32'",
@@ -2596,7 +2596,7 @@ fn requirements_txt_script() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
resolution-markers = [
"sys_platform == 'win32'",
diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs
index 9f38a168a..5bd4b31b5 100644
--- a/crates/uv/tests/it/lock.rs
+++ b/crates/uv/tests/it/lock.rs
@@ -45,7 +45,7 @@ fn lock_wheel_registry() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -177,7 +177,7 @@ fn lock_sdist_registry() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -275,7 +275,7 @@ fn lock_sdist_git() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -365,7 +365,7 @@ fn lock_sdist_git() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -422,7 +422,7 @@ fn lock_sdist_git() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -479,7 +479,7 @@ fn lock_sdist_git() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -541,7 +541,7 @@ fn lock_sdist_git_subdirectory() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -635,7 +635,7 @@ fn lock_sdist_git_pep508() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -699,7 +699,7 @@ fn lock_sdist_git_pep508() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -753,7 +753,7 @@ fn lock_sdist_git_pep508() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -807,7 +807,7 @@ fn lock_sdist_git_pep508() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -872,7 +872,7 @@ fn lock_sdist_git_short_rev() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -976,7 +976,7 @@ fn lock_wheel_url() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1130,7 +1130,7 @@ fn lock_sdist_url() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1274,7 +1274,7 @@ fn lock_sdist_url_subdirectory() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1408,7 +1408,7 @@ fn lock_sdist_url_subdirectory_pep508() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -1545,7 +1545,7 @@ fn lock_project_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2056,7 +2056,7 @@ fn lock_dependency_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2255,7 +2255,7 @@ fn lock_conditional_dependency_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.7"
resolution-markers = [
"python_full_version >= '3.10'",
@@ -2553,7 +2553,7 @@ fn lock_dependency_non_existent_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2734,7 +2734,7 @@ fn lock_upgrade_log() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2816,7 +2816,7 @@ fn lock_upgrade_log() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2904,7 +2904,7 @@ fn lock_upgrade_log_multi_version() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform != 'win32'",
@@ -2990,7 +2990,7 @@ fn lock_upgrade_log_multi_version() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -3064,7 +3064,7 @@ fn lock_preference() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -3124,7 +3124,7 @@ fn lock_preference() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -3171,7 +3171,7 @@ fn lock_preference() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -3240,7 +3240,7 @@ fn lock_git_plus_prefix() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -3326,7 +3326,7 @@ fn lock_partial_git() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.12'",
@@ -3618,7 +3618,7 @@ fn lock_git_sha() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -3719,7 +3719,7 @@ fn lock_requires_python() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.7"
resolution-markers = [
"python_full_version >= '3.8'",
@@ -4010,7 +4010,7 @@ fn lock_requires_python() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.7.9"
resolution-markers = [
"python_full_version >= '3.8'",
@@ -4230,7 +4230,7 @@ fn lock_requires_python() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -4369,7 +4369,7 @@ fn lock_requires_python_upper() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.11.*"
[options]
@@ -4494,7 +4494,7 @@ fn lock_requires_python_exact() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.13"
[options]
@@ -4636,7 +4636,7 @@ fn lock_requires_python_fork() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.9"
[options]
@@ -4731,7 +4731,7 @@ fn lock_requires_python_wheels() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.12.*"
[options]
@@ -4816,7 +4816,7 @@ fn lock_requires_python_wheels() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.11.*"
[options]
@@ -4910,7 +4910,7 @@ fn lock_requires_python_star() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.11.*"
[options]
@@ -5032,7 +5032,7 @@ fn lock_requires_python_not_equal() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">3.10, !=3.10.9, !=3.10.10, !=3.11.*, <3.13"
[options]
@@ -5111,7 +5111,7 @@ fn lock_requires_python_pre() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -5233,7 +5233,7 @@ fn lock_requires_python_unbounded() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "<=3.12"
resolution-markers = [
"python_full_version >= '3.7'",
@@ -5374,7 +5374,7 @@ fn lock_requires_python_maximum_version() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"python_full_version >= '3.9'",
@@ -5533,7 +5533,7 @@ fn lock_requires_python_fewest_versions() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
[options]
@@ -5650,7 +5650,7 @@ fn lock_python_version_marker_complement() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"python_full_version >= '3.11'",
@@ -5761,7 +5761,7 @@ fn lock_dev() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -5875,7 +5875,7 @@ fn lock_conditional_unconditional() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -5953,7 +5953,7 @@ fn lock_multiple_markers() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -6069,7 +6069,7 @@ fn lock_relative_and_absolute_paths() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11, <3.13"
[options]
@@ -6149,7 +6149,7 @@ fn lock_cycles() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -6352,7 +6352,7 @@ fn lock_new_extras() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -6477,7 +6477,7 @@ fn lock_new_extras() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -6725,7 +6725,7 @@ fn lock_resolution_mode() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -6806,7 +6806,7 @@ fn lock_resolution_mode() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -6968,7 +6968,7 @@ fn lock_same_version_multiple_urls() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -7190,7 +7190,7 @@ fn lock_exclusion() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -7496,7 +7496,7 @@ fn lock_peer_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -7619,7 +7619,7 @@ fn lock_index_workspace_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -7768,7 +7768,7 @@ fn lock_dev_transitive() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -7901,7 +7901,7 @@ fn lock_redact_https() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8084,7 +8084,7 @@ fn lock_redact_git_pep508() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8172,7 +8172,7 @@ fn lock_redact_git_sources() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8258,7 +8258,7 @@ fn lock_redact_git_pep508_non_project() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8342,7 +8342,7 @@ fn lock_redact_index_sources() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8430,7 +8430,7 @@ fn lock_redact_url_sources() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8538,7 +8538,7 @@ fn lock_env_credentials() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8699,7 +8699,7 @@ fn lock_relative_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8811,7 +8811,7 @@ fn lock_no_sources() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -8900,7 +8900,7 @@ fn lock_no_sources() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9070,7 +9070,7 @@ fn lock_migrate() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9169,7 +9169,7 @@ fn lock_upgrade_package() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9265,7 +9265,7 @@ fn lock_upgrade_package() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9349,7 +9349,7 @@ fn lock_upgrade_package() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9539,7 +9539,7 @@ fn lock_find_links_local_wheel() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9660,7 +9660,7 @@ fn lock_find_links_ignore_explicit_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9777,7 +9777,7 @@ fn lock_find_links_relative_url() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9890,7 +9890,7 @@ fn lock_find_links_local_sdist() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9981,7 +9981,7 @@ fn lock_find_links_http_wheel() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10072,7 +10072,7 @@ fn lock_find_links_http_sdist() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10190,7 +10190,7 @@ fn lock_find_links_explicit_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10292,7 +10292,7 @@ fn lock_find_links_higher_priority_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10387,7 +10387,7 @@ fn lock_find_links_lower_priority_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10508,7 +10508,7 @@ fn lock_local_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[[package]]
@@ -10595,7 +10595,7 @@ fn lock_sources_url() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10731,7 +10731,7 @@ fn lock_sources_archive() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10882,7 +10882,7 @@ fn lock_sources_source_tree() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11019,7 +11019,7 @@ fn lock_editable() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11202,7 +11202,7 @@ fn lock_mixed_extras() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11399,7 +11399,7 @@ fn lock_transitive_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11550,7 +11550,7 @@ fn lock_mismatched_sources() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11593,7 +11593,7 @@ fn lock_mismatched_sources() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11661,7 +11661,7 @@ fn lock_mismatched_versions() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11949,7 +11949,7 @@ fn lock_change_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -11996,7 +11996,7 @@ fn lock_change_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -12090,7 +12090,7 @@ fn lock_remove_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -12214,7 +12214,7 @@ fn lock_remove_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -12320,7 +12320,7 @@ fn lock_remove_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -12378,7 +12378,7 @@ fn lock_add_member_with_build_system() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -12489,7 +12489,7 @@ fn lock_add_member_with_build_system() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -12590,7 +12590,7 @@ fn lock_add_member_without_build_system() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -12697,7 +12697,7 @@ fn lock_add_member_without_build_system() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -12816,7 +12816,7 @@ fn lock_add_member_without_build_system() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -12926,7 +12926,7 @@ fn lock_redundant_add_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -13031,7 +13031,7 @@ fn lock_redundant_add_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -13123,7 +13123,7 @@ fn lock_new_constraints() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -13230,7 +13230,7 @@ fn lock_new_constraints() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -13335,7 +13335,7 @@ fn lock_remove_member_non_project() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -13444,7 +13444,7 @@ fn lock_remove_member_non_project() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -13491,7 +13491,7 @@ fn lock_rename_project() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -13572,7 +13572,7 @@ fn lock_rename_project() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -13689,7 +13689,7 @@ fn lock_missing_metadata() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -13841,7 +13841,7 @@ fn lock_dev_dependencies_alias() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -13924,7 +13924,7 @@ fn lock_reorder() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -14074,7 +14074,7 @@ fn lock_narrowed_python_version_upper() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.7, <4"
resolution-markers = [
"python_full_version >= '3.10'",
@@ -14185,7 +14185,7 @@ fn lock_narrowed_python_version() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.7"
resolution-markers = [
"python_full_version >= '3.11'",
@@ -14285,7 +14285,7 @@ fn lock_exclude_unnecessary_python_forks() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -14393,7 +14393,7 @@ fn lock_constrained_environment() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform != 'win32'",
@@ -14571,7 +14571,7 @@ fn lock_constrained_environment() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -14717,7 +14717,7 @@ fn lock_constrained_environment_legacy() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform != 'win32'",
@@ -14906,7 +14906,7 @@ fn lock_non_project_fork() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.11'",
@@ -15099,7 +15099,7 @@ fn lock_non_project_conditional() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -15208,7 +15208,7 @@ fn lock_non_project_group() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10"
[options]
@@ -15349,7 +15349,7 @@ fn lock_non_project_sources() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -15432,7 +15432,7 @@ fn lock_dropped_dev_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -15546,7 +15546,7 @@ fn lock_empty_dev_dependencies() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -15650,7 +15650,7 @@ fn lock_empty_dependency_group() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -15751,7 +15751,7 @@ fn lock_add_empty_dependency_group() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -15833,7 +15833,7 @@ fn lock_add_empty_dependency_group() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -15915,7 +15915,7 @@ fn lock_add_empty_dependency_group() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -15993,7 +15993,7 @@ fn lock_trailing_slash_index_url() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -16169,7 +16169,7 @@ fn lock_explicit_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -16277,7 +16277,7 @@ fn lock_explicit_default_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -16363,7 +16363,7 @@ fn lock_explicit_default_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -16440,7 +16440,7 @@ fn lock_named_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -16509,7 +16509,7 @@ fn lock_default_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -16572,7 +16572,7 @@ fn lock_default_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -16652,7 +16652,7 @@ fn lock_named_index_cli() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -16802,7 +16802,7 @@ fn lock_repeat_named_index_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -16891,7 +16891,7 @@ fn lock_unique_named_index() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -16965,7 +16965,7 @@ fn lock_repeat_named_index_cli() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -17032,7 +17032,7 @@ fn lock_repeat_named_index_cli() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -17131,7 +17131,7 @@ fn lock_named_index_overlap() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'linux'",
@@ -17213,7 +17213,7 @@ fn lock_explicit_virtual_project() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -17431,7 +17431,7 @@ fn lock_implicit_virtual_project() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -17659,7 +17659,7 @@ fn lock_implicit_package_path() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -17843,7 +17843,7 @@ fn lock_split_python_environment() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.7"
resolution-markers = [
"python_full_version < '3.8'",
@@ -17954,7 +17954,7 @@ fn lock_python_upper_bound() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"python_full_version >= '3.9' and python_full_version < '3.13'",
@@ -18326,7 +18326,7 @@ fn lock_simplified_environments() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.11.*"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -18436,7 +18436,7 @@ fn lock_dependency_metadata() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -18678,7 +18678,7 @@ fn lock_dependency_metadata_git() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -18791,7 +18791,7 @@ fn lock_strip_fragment() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -19191,7 +19191,7 @@ fn lock_change_requires_python() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.13'",
@@ -19301,7 +19301,7 @@ fn lock_change_requires_python() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.13'",
@@ -19446,7 +19446,7 @@ fn lock_keyring_credentials() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -19634,7 +19634,7 @@ fn lock_keyring_credentials_always_authenticate_fetches_username() -> Result<()>
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -19761,7 +19761,7 @@ fn lock_multiple_sources() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform != 'win32'",
@@ -19949,7 +19949,7 @@ fn lock_multiple_sources_index_disjoint_markers() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'win32'",
@@ -20080,7 +20080,7 @@ fn lock_multiple_sources_index_mixed() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'win32'",
@@ -20214,7 +20214,7 @@ fn lock_multiple_sources_index_non_total() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'win32'",
@@ -20315,7 +20315,7 @@ fn lock_multiple_sources_index_explicit() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'win32'",
@@ -20461,7 +20461,7 @@ fn lock_multiple_sources_non_total() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -20562,7 +20562,7 @@ fn lock_multiple_sources_respect_marker() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -20644,7 +20644,7 @@ fn lock_multiple_sources_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -21074,7 +21074,7 @@ fn lock_group_include() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -21261,7 +21261,7 @@ fn lock_group_requires_python() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.13'",
@@ -21383,7 +21383,7 @@ fn lock_group_includes_requires_python() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.13.1'",
@@ -21598,7 +21598,7 @@ fn lock_group_includes_requires_python_contradiction() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.13'",
@@ -22063,7 +22063,7 @@ fn lock_group_workspace() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -22254,7 +22254,7 @@ fn lock_transitive_git() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -22417,7 +22417,7 @@ fn lock_dynamic_version() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -22456,7 +22456,7 @@ fn lock_dynamic_version() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -22527,7 +22527,7 @@ fn lock_dynamic_version_dependencies() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -22566,7 +22566,7 @@ fn lock_dynamic_version_dependencies() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -22723,7 +22723,7 @@ fn lock_dynamic_version_workspace_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -22793,7 +22793,7 @@ fn lock_dynamic_version_workspace_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -22911,7 +22911,7 @@ fn lock_dynamic_version_path_dependency() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -22975,7 +22975,7 @@ fn lock_dynamic_version_path_dependency() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -23075,7 +23075,7 @@ fn lock_dynamic_version_self_extra_hatchling() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -23235,7 +23235,7 @@ fn lock_dynamic_version_self_extra_setuptools() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -23387,7 +23387,7 @@ fn lock_dynamic_built_cache() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -23434,7 +23434,7 @@ fn lock_dynamic_built_cache() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -23509,7 +23509,7 @@ fn lock_shared_build_dependency() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"python_full_version >= '3.9'",
@@ -23788,7 +23788,7 @@ fn lock_dynamic_to_static() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -23845,7 +23845,7 @@ fn lock_dynamic_to_static() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -23899,7 +23899,7 @@ fn lock_static_to_dynamic() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -23976,7 +23976,7 @@ fn lock_static_to_dynamic() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -24024,7 +24024,7 @@ fn lock_bump_static_version() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -24078,7 +24078,7 @@ fn lock_bump_static_version() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -24412,7 +24412,7 @@ fn lock_relative_project() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -24514,7 +24514,7 @@ fn lock_recursive_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -24653,7 +24653,7 @@ fn lock_no_build_static_metadata() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -24778,7 +24778,7 @@ fn lock_self_compatible() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -24878,7 +24878,7 @@ fn lock_self_exact() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -25011,7 +25011,7 @@ fn lock_self_extra_to_extra_compatible() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -25182,7 +25182,7 @@ fn lock_self_extra_compatible() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -25316,7 +25316,7 @@ fn lock_self_marker_compatible() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -25451,7 +25451,7 @@ fn lock_split_on_windows() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform != 'win32'",
@@ -25575,7 +25575,7 @@ fn lock_arm() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"platform_machine == 'arm64'",
@@ -25650,7 +25650,7 @@ fn lock_x86_64() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"platform_machine == 'x86_64'",
@@ -25726,7 +25726,7 @@ fn lock_x86() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"platform_machine == 'i686'",
@@ -25798,7 +25798,7 @@ fn lock_script() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -25935,7 +25935,7 @@ fn lock_script_path() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -26051,7 +26051,7 @@ fn lock_script_initialize() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -26159,7 +26159,7 @@ fn lock_pytorch_cpu() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12.[X]"
resolution-markers = [
"(platform_machine != 'aarch64' and extra != 'extra-7-project-cpu' and extra == 'extra-7-project-cu124') or (sys_platform != 'linux' and extra != 'extra-7-project-cpu' and extra == 'extra-7-project-cu124')",
@@ -26812,7 +26812,7 @@ fn lock_pytorch_index_preferences() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10.0"
resolution-markers = [
"sys_platform != 'darwin' and extra != 'extra-7-project-cpu' and extra == 'extra-7-project-cu118'",
@@ -27279,7 +27279,7 @@ fn lock_intel_mac() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
resolution-markers = [
"(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version >= '3.12' and sys_platform != 'darwin')",
@@ -27670,7 +27670,7 @@ fn lock_pytorch_local_preference() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12.[X]"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -28005,7 +28005,7 @@ fn windows_arm() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.12.*"
resolution-markers = [
"platform_machine == 'x86_64' and sys_platform == 'linux'",
@@ -28082,7 +28082,7 @@ fn windows_amd64_required() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.12.*"
required-markers = [
"platform_machine == 'x86' and sys_platform == 'win32'",
@@ -28151,7 +28151,7 @@ fn lock_empty_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -28258,7 +28258,7 @@ fn lock_empty_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -28421,7 +28421,7 @@ fn lock_omit_wheels_exclude_newer() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -28606,7 +28606,7 @@ fn lock_requires_python_empty_lock_file() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.13.0"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -28682,7 +28682,7 @@ fn lock_requires_python_empty_lock_file() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.13.2"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -28871,7 +28871,7 @@ fn lock_trailing_slash_index_url_in_pyproject_not_index_argument() -> Result<()>
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -29240,7 +29240,7 @@ fn lock_trailing_slash_find_links() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -29321,7 +29321,7 @@ fn lock_trailing_slash_find_links() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -29412,6 +29412,148 @@ fn test_tilde_equals_python_version() -> Result<()> {
Ok(())
}
+/// Test that exclude-newer-package is properly serialized in the lockfile.
+#[test]
+fn lock_exclude_newer_package() -> Result<()> {
+ let context = TestContext::new("3.12");
+
+ let pyproject_toml = context.temp_dir.child("pyproject.toml");
+ pyproject_toml.write_str(
+ r#"
+ [project]
+ name = "project"
+ version = "0.1.0"
+ requires-python = ">=3.12"
+ dependencies = ["requests", "tqdm"]
+ "#,
+ )?;
+
+ // Lock with both global exclude-newer and package-specific overrides
+ uv_snapshot!(context.filters(), context
+ .lock()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER)
+ .arg("--exclude-newer")
+ .arg("2022-04-04T12:00:00Z")
+ .arg("--exclude-newer-package")
+ .arg("tqdm=2022-09-04T00:00:00Z"), @r###"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Resolved 8 packages in [TIME]
+ "###);
+
+ let lock = context.read("uv.lock");
+
+ insta::with_settings!({
+ filters => context.filters(),
+ }, {
+ assert_snapshot!(
+ lock, @r#"
+ version = 1
+ revision = 3
+ requires-python = ">=3.12"
+
+ [options]
+ exclude-newer = "2022-04-04T12:00:00Z"
+
+ [options.exclude-newer-package]
+ tqdm = "2022-09-04T00:00:00Z"
+
+ [[package]]
+ name = "certifi"
+ version = "2021.10.8"
+ source = { registry = "https://pypi.org/simple" }
+ sdist = { url = "https://files.pythonhosted.org/packages/6c/ae/d26450834f0acc9e3d1f74508da6df1551ceab6c2ce0766a593362d6d57f/certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", size = 151214, upload-time = "2021-10-08T19:32:15.277Z" }
+ wheels = [
+ { url = "https://files.pythonhosted.org/packages/37/45/946c02767aabb873146011e665728b680884cd8fe70dde973c640e45b775/certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569", size = 149195, upload-time = "2021-10-08T19:32:10.712Z" },
+ ]
+
+ [[package]]
+ name = "charset-normalizer"
+ version = "2.0.12"
+ source = { registry = "https://pypi.org/simple" }
+ sdist = { url = "https://files.pythonhosted.org/packages/56/31/7bcaf657fafb3c6db8c787a865434290b726653c912085fbd371e9b92e1c/charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", size = 79105, upload-time = "2022-02-12T14:33:13.788Z" }
+ wheels = [
+ { url = "https://files.pythonhosted.org/packages/06/b3/24afc8868eba069a7f03650ac750a778862dc34941a4bebeb58706715726/charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df", size = 39623, upload-time = "2022-02-12T14:33:12.294Z" },
+ ]
+
+ [[package]]
+ name = "colorama"
+ version = "0.4.4"
+ source = { registry = "https://pypi.org/simple" }
+ sdist = { url = "https://files.pythonhosted.org/packages/1f/bb/5d3246097ab77fa083a61bd8d3d527b7ae063c7d8e8671b1cf8c4ec10cbe/colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", size = 27813, upload-time = "2020-10-15T18:36:33.372Z" }
+ wheels = [
+ { url = "https://files.pythonhosted.org/packages/44/98/5b86278fbbf250d239ae0ecb724f8572af1c91f4a11edf4d36a206189440/colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2", size = 16028, upload-time = "2020-10-13T02:42:26.463Z" },
+ ]
+
+ [[package]]
+ name = "idna"
+ version = "3.3"
+ source = { registry = "https://pypi.org/simple" }
+ sdist = { url = "https://files.pythonhosted.org/packages/62/08/e3fc7c8161090f742f504f40b1bccbfc544d4a4e09eb774bf40aafce5436/idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d", size = 286689, upload-time = "2021-10-12T23:33:41.312Z" }
+ wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", size = 61160, upload-time = "2021-10-12T23:33:38.02Z" },
+ ]
+
+ [[package]]
+ name = "project"
+ version = "0.1.0"
+ source = { virtual = "." }
+ dependencies = [
+ { name = "requests" },
+ { name = "tqdm" },
+ ]
+
+ [package.metadata]
+ requires-dist = [
+ { name = "requests" },
+ { name = "tqdm" },
+ ]
+
+ [[package]]
+ name = "requests"
+ version = "2.27.1"
+ source = { registry = "https://pypi.org/simple" }
+ dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+ ]
+ sdist = { url = "https://files.pythonhosted.org/packages/60/f3/26ff3767f099b73e0efa138a9998da67890793bfa475d8278f84a30fec77/requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", size = 106758, upload-time = "2022-01-05T15:40:51.698Z" }
+ wheels = [
+ { url = "https://files.pythonhosted.org/packages/2d/61/08076519c80041bc0ffa1a8af0cbd3bf3e2b62af10435d269a9d0f40564d/requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d", size = 63133, upload-time = "2022-01-05T15:40:49.334Z" },
+ ]
+
+ [[package]]
+ name = "tqdm"
+ version = "4.64.1"
+ source = { registry = "https://pypi.org/simple" }
+ dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ ]
+ sdist = { url = "https://files.pythonhosted.org/packages/c1/c2/d8a40e5363fb01806870e444fc1d066282743292ff32a9da54af51ce36a2/tqdm-4.64.1.tar.gz", hash = "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", size = 169599, upload-time = "2022-09-03T11:10:30.943Z" }
+ wheels = [
+ { url = "https://files.pythonhosted.org/packages/47/bb/849011636c4da2e44f1253cd927cfb20ada4374d8b3a4e425416e84900cc/tqdm-4.64.1-py2.py3-none-any.whl", hash = "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1", size = 78468, upload-time = "2022-09-03T11:10:27.148Z" },
+ ]
+
+ [[package]]
+ name = "urllib3"
+ version = "1.26.9"
+ source = { registry = "https://pypi.org/simple" }
+ sdist = { url = "https://files.pythonhosted.org/packages/1b/a5/4eab74853625505725cefdf168f48661b2cd04e7843ab836f3f63abf81da/urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e", size = 295258, upload-time = "2022-03-16T13:28:19.197Z" }
+ wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/03/062e6444ce4baf1eac17a6a0ebfe36bb1ad05e1df0e20b110de59c278498/urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", size = 138990, upload-time = "2022-03-16T13:28:16.026Z" },
+ ]
+ "#
+ );
+ });
+
+ Ok(())
+}
+
/// Test that lockfile validation includes explicit indexes from path dependencies.
///
#[test]
diff --git a/crates/uv/tests/it/lock_conflict.rs b/crates/uv/tests/it/lock_conflict.rs
index 2025dbd8b..868dfb9aa 100644
--- a/crates/uv/tests/it/lock_conflict.rs
+++ b/crates/uv/tests/it/lock_conflict.rs
@@ -91,7 +91,7 @@ fn extra_basic() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "extra1" },
@@ -285,7 +285,7 @@ fn extra_basic_three_extras() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "extra1" },
@@ -760,7 +760,7 @@ fn extra_multiple_independent() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "extra1" },
@@ -910,7 +910,7 @@ fn extra_config_change_ignore_lockfile() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "extra1" },
@@ -1787,7 +1787,7 @@ fn extra_depends_on_conflicting_extra_transitive() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "example", extra = "bar" },
@@ -1972,7 +1972,7 @@ fn group_basic() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", group = "group1" },
@@ -2127,7 +2127,7 @@ fn group_default() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", group = "group1" },
@@ -2339,7 +2339,7 @@ fn mixed() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "extra1" },
@@ -2509,7 +2509,7 @@ fn multiple_sources_index_disjoint_extras() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "cu118" },
@@ -2659,7 +2659,7 @@ fn multiple_sources_index_disjoint_groups() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", group = "cu118" },
@@ -2808,7 +2808,7 @@ fn multiple_sources_index_disjoint_extras_with_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "cu118" },
@@ -2977,7 +2977,7 @@ fn multiple_sources_index_disjoint_extras_with_marker() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"extra != 'extra-7-project-cu118' and extra == 'extra-7-project-cu124'",
@@ -3303,7 +3303,7 @@ fn shared_optional_dependency_extra1() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "bar" },
@@ -3443,7 +3443,7 @@ fn shared_optional_dependency_group1() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", group = "bar" },
@@ -3584,7 +3584,7 @@ fn shared_optional_dependency_mixed1() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "foo" },
@@ -3729,7 +3729,7 @@ fn shared_optional_dependency_extra2() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.11.*"
conflicts = [[
{ package = "project", extra = "bar" },
@@ -3870,7 +3870,7 @@ fn shared_optional_dependency_group2() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.11.*"
conflicts = [[
{ package = "project", group = "bar" },
@@ -4016,7 +4016,7 @@ fn shared_optional_dependency_mixed2() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.11.*"
conflicts = [[
{ package = "project", extra = "foo" },
@@ -4160,7 +4160,7 @@ fn shared_dependency_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "bar" },
@@ -4335,7 +4335,7 @@ fn shared_dependency_group() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", group = "bar" },
@@ -4511,7 +4511,7 @@ fn shared_dependency_mixed() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "foo" },
@@ -4729,7 +4729,7 @@ conflicts = [
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.11.*"
conflicts = [[
{ package = "project", extra = "x1" },
@@ -4915,7 +4915,7 @@ fn jinja_no_conflict_markers1() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", extra = "cu118" },
@@ -5077,7 +5077,7 @@ fn jinja_no_conflict_markers2() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"extra != 'extra-7-project-cu118' and extra == 'extra-7-project-cu124'",
@@ -5238,7 +5238,7 @@ fn collision_extra() -> Result<()> {
lock,
@r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "pkg", extra = "bar" },
@@ -5467,7 +5467,7 @@ fn extra_inferences() -> Result<()> {
lock,
@r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "pkg", extra = "x1" },
@@ -7503,7 +7503,7 @@ fn deduplicate_resolution_markers() -> Result<()> {
lock,
@r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"sys_platform != 'linux' and extra != 'extra-3-pkg-x1' and extra == 'extra-3-pkg-x2'",
@@ -7657,7 +7657,7 @@ fn incorrect_extra_simplification_leads_to_multiple_torch_packages() -> Result<(
lock,
@r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'extra-4-test-chgnet' and extra == 'extra-4-test-m3gnet'",
@@ -10432,7 +10432,7 @@ fn duplicate_torch_and_sympy_because_of_wrong_inferences() -> Result<()> {
lock,
@r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.12' and sys_platform == 'win32' and extra != 'extra-4-test-alignn' and extra == 'extra-4-test-all' and extra == 'extra-4-test-chgnet' and extra != 'extra-4-test-m3gnet'",
@@ -13654,7 +13654,7 @@ fn overlapping_resolution_markers() -> Result<()> {
lock,
@r#"
version = 1
- revision = 2
+ revision = 3
requires-python = "==3.10.*"
resolution-markers = [
"sys_platform == 'linux' and extra != 'extra-14-ads-mega-model-cpu' and extra == 'extra-14-ads-mega-model-cu118'",
@@ -14337,7 +14337,7 @@ fn avoids_exponential_lock_file_growth() -> Result<()> {
lock,
@r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"extra != 'extra-27-resolution-markers-for-days-cpu' and extra == 'extra-27-resolution-markers-for-days-cu124'",
@@ -14752,7 +14752,7 @@ fn avoids_exponential_lock_file_growth() -> Result<()> {
lock,
@r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
resolution-markers = [
"extra != 'extra-27-resolution-markers-for-days-cpu' and extra == 'extra-27-resolution-markers-for-days-cu124'",
diff --git a/crates/uv/tests/it/lock_scenarios.rs b/crates/uv/tests/it/lock_scenarios.rs
index 3be986ad1..f1a72361f 100644
--- a/crates/uv/tests/it/lock_scenarios.rs
+++ b/crates/uv/tests/it/lock_scenarios.rs
@@ -137,7 +137,7 @@ fn wrong_backtracking_basic() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
[[package]]
@@ -319,7 +319,7 @@ fn wrong_backtracking_indirect() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
[[package]]
@@ -466,7 +466,7 @@ fn fork_allows_non_conflicting_non_overlapping_dependencies() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -586,7 +586,7 @@ fn fork_allows_non_conflicting_repeated_dependencies() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
[[package]]
@@ -688,7 +688,7 @@ fn fork_basic() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -991,7 +991,7 @@ fn fork_filter_sibling_dependencies() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'linux'",
@@ -1174,7 +1174,7 @@ fn fork_upgrade() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
[[package]]
@@ -1299,7 +1299,7 @@ fn fork_incomplete_markers() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"python_full_version >= '3.11'",
@@ -1456,7 +1456,7 @@ fn fork_marker_accrue() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
[[package]]
@@ -1667,7 +1667,7 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"implementation_name == 'pypy' and sys_platform == 'darwin'",
@@ -1853,7 +1853,7 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"implementation_name == 'pypy' and sys_platform == 'darwin'",
@@ -2028,7 +2028,7 @@ fn fork_marker_inherit_combined() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"implementation_name == 'pypy' and sys_platform == 'darwin'",
@@ -2194,7 +2194,7 @@ fn fork_marker_inherit_isolated() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -2348,7 +2348,7 @@ fn fork_marker_inherit_transitive() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -2508,7 +2508,7 @@ fn fork_marker_inherit() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -2651,7 +2651,7 @@ fn fork_marker_limited_inherit() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -2811,7 +2811,7 @@ fn fork_marker_selection() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -2974,7 +2974,7 @@ fn fork_marker_track() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'darwin'",
@@ -3131,7 +3131,7 @@ fn fork_non_fork_marker_transitive() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
[[package]]
@@ -3442,7 +3442,7 @@ fn fork_overlapping_markers_basic() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"python_full_version >= '3.11'",
@@ -3626,7 +3626,7 @@ fn preferences_dependent_forking_bistable() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'linux'",
@@ -4038,7 +4038,7 @@ fn preferences_dependent_forking_tristable() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'linux'",
@@ -4332,7 +4332,7 @@ fn preferences_dependent_forking() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"sys_platform == 'linux'",
@@ -4512,7 +4512,7 @@ fn fork_remaining_universe_partitioning() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"os_name == 'darwin' and sys_platform == 'illumos'",
@@ -4665,7 +4665,7 @@ fn fork_requires_python_full_prerelease() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10"
[[package]]
@@ -4750,7 +4750,7 @@ fn fork_requires_python_full() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10"
[[package]]
@@ -4839,7 +4839,7 @@ fn fork_requires_python_patch_overlap() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10.1"
[[package]]
@@ -4933,7 +4933,7 @@ fn fork_requires_python() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10"
[[package]]
@@ -5014,7 +5014,7 @@ fn requires_python_wheels() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.10"
[[package]]
@@ -5113,7 +5113,7 @@ fn unreachable_package() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
[[package]]
@@ -5218,7 +5218,7 @@ fn unreachable_wheels() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
[[package]]
@@ -5352,7 +5352,7 @@ fn marker_variants_have_different_extras() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
resolution-markers = [
"platform_python_implementation != 'PyPy'",
@@ -5494,7 +5494,7 @@ fn virtual_package_extra_priorities() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[[package]]
@@ -5618,7 +5618,7 @@ fn specific_architecture() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.8"
[[package]]
diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs
index 34af0ed08..a453b3b57 100644
--- a/crates/uv/tests/it/pip_compile.rs
+++ b/crates/uv/tests/it/pip_compile.rs
@@ -3362,7 +3362,7 @@ fn compile_exclude_newer() -> Result<()> {
.arg("--exclude-newer")
// 4.64.0: 2022-04-04T01:48:46.194635Z1
// 4.64.1: 2022-09-03T11:10:27.148080Z
- .arg("2022-04-04T12:00:00Z"), @r###"
+ .arg("2022-04-04T12:00:00Z"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -3373,7 +3373,7 @@ fn compile_exclude_newer() -> Result<()> {
----- stderr -----
Resolved 1 package in [TIME]
- "###
+ "
);
// Use a date as input instead.
@@ -3383,7 +3383,7 @@ fn compile_exclude_newer() -> Result<()> {
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
.arg("requirements.in")
.arg("--exclude-newer")
- .arg("2022-04-04"), @r###"
+ .arg("2022-04-04"), @r"
success: true
exit_code: 0
----- stdout -----
@@ -3394,7 +3394,7 @@ fn compile_exclude_newer() -> Result<()> {
----- stderr -----
Resolved 1 package in [TIME]
- "###
+ "
);
// Check the error message for invalid datetime
@@ -3438,6 +3438,190 @@ fn compile_exclude_newer() -> Result<()> {
Ok(())
}
+/// Test per-package exclude-newer functionality
+#[test]
+fn compile_exclude_newer_package() -> Result<()> {
+ let context = TestContext::new("3.12");
+ let requirements_in = context.temp_dir.child("requirements.in");
+ requirements_in.write_str("tqdm\nrequests")?;
+
+ // First, establish baseline with global exclude-newer
+ // tqdm 4.64.0 was released on 2022-04-04, 4.64.1 on 2022-09-03
+ // requests 2.27.1 was released on 2022-01-05, 2.28.0 on 2022-05-29
+ uv_snapshot!(context
+ .pip_compile()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER)
+ .arg("requirements.in")
+ .arg("--exclude-newer")
+ .arg("2022-04-04T12:00:00Z"), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ # This file was autogenerated by uv via the following command:
+ # uv pip compile --cache-dir [CACHE_DIR] requirements.in --exclude-newer 2022-04-04T12:00:00Z
+ certifi==2021.10.8
+ # via requests
+ charset-normalizer==2.0.12
+ # via requests
+ idna==3.3
+ # via requests
+ requests==2.27.1
+ # via -r requirements.in
+ tqdm==4.64.0
+ # via -r requirements.in
+ urllib3==1.26.9
+ # via requests
+
+ ----- stderr -----
+ Resolved 6 packages in [TIME]
+ "
+ );
+
+ // Test override: allow tqdm to use newer versions while keeping requests pinned
+ uv_snapshot!(context
+ .pip_compile()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER)
+ .arg("requirements.in")
+ .arg("--exclude-newer")
+ .arg("2022-04-04T12:00:00Z")
+ .arg("--exclude-newer-package")
+ .arg("tqdm=2022-09-04T00:00:00Z"), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ # This file was autogenerated by uv via the following command:
+ # uv pip compile --cache-dir [CACHE_DIR] requirements.in --exclude-newer 2022-04-04T12:00:00Z --exclude-newer-package tqdm=2022-09-04T00:00:00Z
+ certifi==2021.10.8
+ # via requests
+ charset-normalizer==2.0.12
+ # via requests
+ idna==3.3
+ # via requests
+ requests==2.27.1
+ # via -r requirements.in
+ tqdm==4.64.1
+ # via -r requirements.in
+ urllib3==1.26.9
+ # via requests
+
+ ----- stderr -----
+ Resolved 6 packages in [TIME]
+ "
+ );
+
+ // Test multiple package overrides
+ uv_snapshot!(context
+ .pip_compile()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER)
+ .arg("requirements.in")
+ .arg("--exclude-newer")
+ .arg("2022-01-01T00:00:00Z")
+ .arg("--exclude-newer-package")
+ .arg("tqdm=2022-09-04T00:00:00Z")
+ .arg("--exclude-newer-package")
+ .arg("requests=2022-06-01T00:00:00Z"), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ # This file was autogenerated by uv via the following command:
+ # uv pip compile --cache-dir [CACHE_DIR] requirements.in --exclude-newer 2022-01-01T00:00:00Z --exclude-newer-package tqdm=2022-09-04T00:00:00Z --exclude-newer-package requests=2022-06-01T00:00:00Z
+ certifi==2021.10.8
+ # via requests
+ charset-normalizer==2.0.9
+ # via requests
+ idna==3.3
+ # via requests
+ requests==2.27.1
+ # via -r requirements.in
+ tqdm==4.64.1
+ # via -r requirements.in
+ urllib3==1.26.7
+ # via requests
+
+ ----- stderr -----
+ Resolved 6 packages in [TIME]
+ "
+ );
+
+ // Test exclude-newer-package without global exclude-newer
+ uv_snapshot!(context
+ .pip_compile()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER)
+ .arg("requirements.in")
+ .arg("--exclude-newer-package")
+ .arg("tqdm=2022-04-04T12:00:00Z"), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+ # This file was autogenerated by uv via the following command:
+ # uv pip compile --cache-dir [CACHE_DIR] requirements.in --exclude-newer-package tqdm=2022-04-04T12:00:00Z
+ certifi==2025.7.14
+ # via requests
+ charset-normalizer==3.4.2
+ # via requests
+ idna==3.10
+ # via requests
+ requests==2.32.4
+ # via -r requirements.in
+ tqdm==4.64.0
+ # via -r requirements.in
+ urllib3==2.5.0
+ # via requests
+
+ ----- stderr -----
+ Resolved 6 packages in [TIME]
+ "
+ );
+
+ Ok(())
+}
+
+/// Test error handling for malformed --exclude-newer-package
+#[test]
+fn compile_exclude_newer_package_errors() -> Result<()> {
+ let context = TestContext::new("3.12");
+ let requirements_in = context.temp_dir.child("requirements.in");
+ requirements_in.write_str("tqdm")?;
+
+ // Test invalid format (missing =)
+ uv_snapshot!(context
+ .pip_compile()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER)
+ .arg("requirements.in")
+ .arg("--exclude-newer-package")
+ .arg("tqdm"), @r"
+ success: false
+ exit_code: 2
+ ----- stdout -----
+
+ ----- stderr -----
+ error: invalid value 'tqdm' for '--exclude-newer-package ': Invalid `exclude-newer-package` value `tqdm`: expected format `PACKAGE=DATE`
+
+ For more information, try '--help'.
+ "
+ );
+
+ // Test invalid date format
+ uv_snapshot!(context
+ .pip_compile()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER)
+ .arg("requirements.in")
+ .arg("--exclude-newer-package")
+ .arg("tqdm=invalid-date"), @r#"
+ success: false
+ exit_code: 2
+ ----- stdout -----
+
+ ----- stderr -----
+ error: invalid value 'tqdm=invalid-date' for '--exclude-newer-package ': Invalid `exclude-newer-package` timestamp `invalid-date`: `invalid-date` could not be parsed as a valid date: failed to parse year in date "invalid-date": failed to parse "inva" as year (a four digit integer): invalid digit, expected 0-9 but got i
+
+ For more information, try '--help'.
+ "#
+ );
+
+ Ok(())
+}
+
/// Resolve a local path dependency on a specific wheel.
#[test]
fn compile_wheel_path_dependency() -> Result<()> {
diff --git a/crates/uv/tests/it/python_find.rs b/crates/uv/tests/it/python_find.rs
index d2ae51e38..5086fe2df 100644
--- a/crates/uv/tests/it/python_find.rs
+++ b/crates/uv/tests/it/python_find.rs
@@ -2,7 +2,7 @@ use assert_fs::prelude::{FileTouch, PathChild};
use assert_fs::{fixture::FileWriteStr, prelude::PathCreateDir};
use indoc::indoc;
-use uv_python::platform::{Arch, Os};
+use uv_platform::{Arch, Os};
use uv_static::EnvVars;
use crate::common::{TestContext, uv_snapshot, venv_bin_path};
diff --git a/crates/uv/tests/it/python_list.rs b/crates/uv/tests/it/python_list.rs
index 11472baec..5c23a3e93 100644
--- a/crates/uv/tests/it/python_list.rs
+++ b/crates/uv/tests/it/python_list.rs
@@ -1,4 +1,4 @@
-use uv_python::platform::{Arch, Os};
+use uv_platform::{Arch, Os};
use uv_static::EnvVars;
use crate::common::{TestContext, uv_snapshot};
diff --git a/crates/uv/tests/it/python_pin.rs b/crates/uv/tests/it/python_pin.rs
index 97093831c..0f01a0011 100644
--- a/crates/uv/tests/it/python_pin.rs
+++ b/crates/uv/tests/it/python_pin.rs
@@ -5,10 +5,8 @@ use anyhow::Result;
use assert_cmd::assert::OutputAssertExt;
use assert_fs::fixture::{FileWriteStr, PathChild, PathCreateDir};
use insta::assert_snapshot;
-use uv_python::{
- PYTHON_VERSION_FILENAME, PYTHON_VERSIONS_FILENAME,
- platform::{Arch, Os},
-};
+use uv_platform::{Arch, Os};
+use uv_python::{PYTHON_VERSION_FILENAME, PYTHON_VERSIONS_FILENAME};
#[test]
fn python_pin() {
diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs
index 300a87f06..acccbc43b 100644
--- a/crates/uv/tests/it/run.rs
+++ b/crates/uv/tests/it/run.rs
@@ -888,7 +888,7 @@ fn run_pep723_script_lock() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -997,7 +997,7 @@ fn run_pep723_script_lock() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -2121,7 +2121,7 @@ fn run_locked() -> Result<()> {
assert_snapshot!(
existing, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -5513,7 +5513,7 @@ fn run_pep723_script_with_constraints_lock() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs
index d0a90780e..1543ac774 100644
--- a/crates/uv/tests/it/show_settings.rs
+++ b/crates/uv/tests/it/show_settings.rs
@@ -213,7 +213,12 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -402,7 +407,12 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -592,7 +602,12 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -814,7 +829,12 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -971,7 +991,12 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -1174,7 +1199,12 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
X8664UnknownLinuxGnu,
),
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -1421,7 +1451,12 @@ fn resolve_index_url() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -1680,7 +1715,12 @@ fn resolve_index_url() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -1894,7 +1934,12 @@ fn resolve_find_links() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -2073,7 +2118,12 @@ fn resolve_top_level() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -2312,7 +2362,12 @@ fn resolve_top_level() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -2534,7 +2589,12 @@ fn resolve_top_level() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -2712,7 +2772,12 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -2874,7 +2939,12 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -3036,7 +3106,12 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -3200,7 +3275,12 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -3329,6 +3409,7 @@ fn resolve_tool() -> anyhow::Result<()> {
no_build_isolation: None,
no_build_isolation_package: None,
exclude_newer: None,
+ exclude_newer_package: None,
link_mode: Some(
Clone,
),
@@ -3358,7 +3439,12 @@ fn resolve_tool() -> anyhow::Result<()> {
dependency_metadata: DependencyMetadata(
{},
),
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
fork_strategy: RequiresPython,
index_locations: IndexLocations {
indexes: [],
@@ -3558,7 +3644,12 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -3788,7 +3879,12 @@ fn resolve_both() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -4022,7 +4118,12 @@ fn resolve_both_special_fields() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -4335,7 +4436,12 @@ fn resolve_config_file() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -4386,7 +4492,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
1 | [project]
| ^^^^^^^
- 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`, `config-settings-package`, `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`, `build-dependency-strategy`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-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`, `config-settings-package`, `no-build-isolation`, `no-build-isolation-package`, `exclude-newer`, `exclude-newer-package`, `link-mode`, `compile-bytecode`, `no-sources`, `upgrade`, `upgrade-package`, `reinstall`, `reinstall-package`, `no-build`, `no-build-package`, `no-binary`, `no-binary-package`, `build-dependency-strategy`, `python-install-mirror`, `pypy-install-mirror`, `python-downloads-json-url`, `publish-url`, `trusted-publishing`, `check-url`, `add-bounds`, `pip`, `cache-keys`, `override-dependencies`, `constraint-dependencies`, `build-constraint-dependencies`, `environments`, `required-environments`, `conflicts`, `workspace`, `sources`, `managed`, `package`, `default-groups`, `dependency-groups`, `dev-dependencies`, `build-backend`
"
);
@@ -4590,7 +4696,12 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -4755,7 +4866,12 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -4939,7 +5055,12 @@ fn allow_insecure_host() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -5184,7 +5305,12 @@ fn index_priority() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -5408,7 +5534,12 @@ fn index_priority() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -5638,7 +5769,12 @@ fn index_priority() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -5863,7 +5999,12 @@ fn index_priority() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -6095,7 +6236,12 @@ fn index_priority() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -6320,7 +6466,12 @@ fn index_priority() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -6489,7 +6640,12 @@ fn verify_hashes() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -6644,7 +6800,12 @@ fn verify_hashes() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -6797,7 +6958,12 @@ fn verify_hashes() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -6952,7 +7118,12 @@ fn verify_hashes() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -7105,7 +7276,12 @@ fn verify_hashes() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -7259,7 +7435,12 @@ fn verify_hashes() -> anyhow::Result<()> {
python_version: None,
python_platform: None,
universal: false,
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
@@ -7376,7 +7557,12 @@ fn preview_features() {
dependency_metadata: DependencyMetadata(
{},
),
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
fork_strategy: RequiresPython,
index_locations: IndexLocations {
indexes: [],
@@ -7479,7 +7665,12 @@ fn preview_features() {
dependency_metadata: DependencyMetadata(
{},
),
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
fork_strategy: RequiresPython,
index_locations: IndexLocations {
indexes: [],
@@ -7582,7 +7773,12 @@ fn preview_features() {
dependency_metadata: DependencyMetadata(
{},
),
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
fork_strategy: RequiresPython,
index_locations: IndexLocations {
indexes: [],
@@ -7685,7 +7881,12 @@ fn preview_features() {
dependency_metadata: DependencyMetadata(
{},
),
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
fork_strategy: RequiresPython,
index_locations: IndexLocations {
indexes: [],
@@ -7788,7 +7989,12 @@ fn preview_features() {
dependency_metadata: DependencyMetadata(
{},
),
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
fork_strategy: RequiresPython,
index_locations: IndexLocations {
indexes: [],
@@ -7893,7 +8099,12 @@ fn preview_features() {
dependency_metadata: DependencyMetadata(
{},
),
- exclude_newer: None,
+ exclude_newer: ExcludeNewer {
+ global: None,
+ package: ExcludeNewerPackage(
+ {},
+ ),
+ },
fork_strategy: RequiresPython,
index_locations: IndexLocations {
indexes: [],
diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__black-lock-file.snap b/crates/uv/tests/it/snapshots/it__ecosystem__black-lock-file.snap
index 68c1d6bb4..082fef710 100644
--- a/crates/uv/tests/it/snapshots/it__ecosystem__black-lock-file.snap
+++ b/crates/uv/tests/it/snapshots/it__ecosystem__black-lock-file.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/ecosystem.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.8"
resolution-markers = [
"python_full_version >= '3.10' and implementation_name == 'pypy' and sys_platform == 'win32'",
diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__github-wikidata-bot-lock-file.snap b/crates/uv/tests/it/snapshots/it__ecosystem__github-wikidata-bot-lock-file.snap
index 506f4ca6d..210dbb210 100644
--- a/crates/uv/tests/it/snapshots/it__ecosystem__github-wikidata-bot-lock-file.snap
+++ b/crates/uv/tests/it/snapshots/it__ecosystem__github-wikidata-bot-lock-file.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/ecosystem.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.12"
resolution-markers = [
"python_full_version >= '3.13'",
diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__home-assistant-core-lock-file.snap b/crates/uv/tests/it/snapshots/it__ecosystem__home-assistant-core-lock-file.snap
index f17f8b0c7..e7babf608 100644
--- a/crates/uv/tests/it/snapshots/it__ecosystem__home-assistant-core-lock-file.snap
+++ b/crates/uv/tests/it/snapshots/it__ecosystem__home-assistant-core-lock-file.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/ecosystem.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.12.[X]"
[options]
diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__packse-lock-file.snap b/crates/uv/tests/it/snapshots/it__ecosystem__packse-lock-file.snap
index 29810d89f..e15913e5c 100644
--- a/crates/uv/tests/it/snapshots/it__ecosystem__packse-lock-file.snap
+++ b/crates/uv/tests/it/snapshots/it__ecosystem__packse-lock-file.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/ecosystem.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.12"
[options]
diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__saleor-lock-file.snap b/crates/uv/tests/it/snapshots/it__ecosystem__saleor-lock-file.snap
index 7ec8e0b5b..d757d7668 100644
--- a/crates/uv/tests/it/snapshots/it__ecosystem__saleor-lock-file.snap
+++ b/crates/uv/tests/it/snapshots/it__ecosystem__saleor-lock-file.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/ecosystem.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.12"
[options]
diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap b/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap
index 616fddd6d..1d1a8ee2a 100644
--- a/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap
+++ b/crates/uv/tests/it/snapshots/it__ecosystem__transformers-lock-file.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/ecosystem.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.9.0"
resolution-markers = [
"python_full_version >= '3.13' and sys_platform == 'darwin'",
diff --git a/crates/uv/tests/it/snapshots/it__ecosystem__warehouse-lock-file.snap b/crates/uv/tests/it/snapshots/it__ecosystem__warehouse-lock-file.snap
index bf786a591..624e755ac 100644
--- a/crates/uv/tests/it/snapshots/it__ecosystem__warehouse-lock-file.snap
+++ b/crates/uv/tests/it/snapshots/it__ecosystem__warehouse-lock-file.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/ecosystem.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = "==3.11.*"
[options]
diff --git a/crates/uv/tests/it/snapshots/it__workflow__jax_instability-2.snap b/crates/uv/tests/it/snapshots/it__workflow__jax_instability-2.snap
index ca4a08d1a..47b39f721 100644
--- a/crates/uv/tests/it/snapshots/it__workflow__jax_instability-2.snap
+++ b/crates/uv/tests/it/snapshots/it__workflow__jax_instability-2.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/workflow.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.9.0"
resolution-markers = [
"python_full_version >= '3.12'",
diff --git a/crates/uv/tests/it/snapshots/it__workflow__packse_add_remove_existing_package_noop-2.snap b/crates/uv/tests/it/snapshots/it__workflow__packse_add_remove_existing_package_noop-2.snap
index df7f8e63a..a08aa7d24 100644
--- a/crates/uv/tests/it/snapshots/it__workflow__packse_add_remove_existing_package_noop-2.snap
+++ b/crates/uv/tests/it/snapshots/it__workflow__packse_add_remove_existing_package_noop-2.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/workflow.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.12"
[options]
diff --git a/crates/uv/tests/it/snapshots/it__workflow__packse_add_remove_one_package-2.snap b/crates/uv/tests/it/snapshots/it__workflow__packse_add_remove_one_package-2.snap
index df7f8e63a..a08aa7d24 100644
--- a/crates/uv/tests/it/snapshots/it__workflow__packse_add_remove_one_package-2.snap
+++ b/crates/uv/tests/it/snapshots/it__workflow__packse_add_remove_one_package-2.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/workflow.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.12"
[options]
diff --git a/crates/uv/tests/it/snapshots/it__workflow__packse_promote_transitive_to_direct_then_remove-2.snap b/crates/uv/tests/it/snapshots/it__workflow__packse_promote_transitive_to_direct_then_remove-2.snap
index df7f8e63a..a08aa7d24 100644
--- a/crates/uv/tests/it/snapshots/it__workflow__packse_promote_transitive_to_direct_then_remove-2.snap
+++ b/crates/uv/tests/it/snapshots/it__workflow__packse_promote_transitive_to_direct_then_remove-2.snap
@@ -3,7 +3,7 @@ source: crates/uv/tests/it/workflow.rs
expression: lock
---
version = 1
-revision = 2
+revision = 3
requires-python = ">=3.12"
[options]
diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs
index 5fb59241d..ee7ace93d 100644
--- a/crates/uv/tests/it/sync.rs
+++ b/crates/uv/tests/it/sync.rs
@@ -1691,7 +1691,7 @@ fn sync_relative_wheel() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -3246,7 +3246,7 @@ fn sync_group_legacy_non_project_member() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -3357,7 +3357,7 @@ fn sync_group_self() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -4367,7 +4367,7 @@ fn convert_to_virtual() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -4427,7 +4427,7 @@ fn convert_to_virtual() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -4496,7 +4496,7 @@ fn convert_to_package() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -4561,7 +4561,7 @@ fn convert_to_package() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -6456,7 +6456,7 @@ fn sync_dynamic_extra() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -7879,7 +7879,7 @@ fn sync_stale_egg_info() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.13"
[options]
@@ -7986,7 +7986,7 @@ fn sync_git_repeated_member_static_metadata() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.13"
[options]
@@ -8080,7 +8080,7 @@ fn sync_git_repeated_member_dynamic_metadata() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.13"
[options]
@@ -8198,7 +8198,7 @@ fn sync_git_repeated_member_backwards_path() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.13"
[options]
@@ -8380,7 +8380,7 @@ fn sync_git_path_dependency() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.13"
[options]
@@ -8488,7 +8488,7 @@ fn sync_build_tag() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -9156,7 +9156,7 @@ fn sync_locked_script() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -9261,7 +9261,7 @@ fn sync_locked_script() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -10027,7 +10027,7 @@ fn locked_version_coherence() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10131,7 +10131,7 @@ fn sync_build_constraints() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -10635,6 +10635,157 @@ fn sync_url_with_query_parameters() -> Result<()> {
Ok(())
}
+/// Test uv sync with --exclude-newer-package
+#[test]
+fn sync_exclude_newer_package() -> Result<()> {
+ let context = TestContext::new("3.12").with_filtered_counts();
+
+ let pyproject_toml = context.temp_dir.child("pyproject.toml");
+ pyproject_toml.write_str(
+ r#"
+[project]
+name = "project"
+version = "0.1.0"
+requires-python = ">=3.12"
+dependencies = [
+ "tqdm",
+ "requests",
+]
+"#,
+ )?;
+
+ // First sync with only the global exclude-newer to show the baseline
+ uv_snapshot!(context.filters(), context
+ .sync()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER)
+ .arg("--exclude-newer")
+ .arg("2022-04-04T12:00:00Z"), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Resolved [N] packages in [TIME]
+ Prepared [N] packages in [TIME]
+ Installed [N] packages in [TIME]
+ + certifi==2021.10.8
+ + charset-normalizer==2.0.12
+ + idna==3.3
+ + requests==2.27.1
+ + tqdm==4.64.0
+ + urllib3==1.26.9
+ "
+ );
+
+ // Now sync with --exclude-newer-package to allow tqdm to use a newer version
+ uv_snapshot!(context.filters(), context
+ .sync()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER)
+ .arg("--exclude-newer")
+ .arg("2022-04-04T12:00:00Z")
+ .arg("--exclude-newer-package")
+ .arg("tqdm=2022-09-04T00:00:00Z"), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Ignoring existing lockfile due to change in timestamp cutoff: `global: 2022-04-04T12:00:00Z` vs. `global: 2022-04-04T12:00:00Z, tqdm: 2022-09-04T00:00:00Z`
+ Resolved [N] packages in [TIME]
+ Prepared [N] packages in [TIME]
+ Uninstalled [N] packages in [TIME]
+ Installed [N] packages in [TIME]
+ - tqdm==4.64.0
+ + tqdm==4.64.1
+ "
+ );
+
+ Ok(())
+}
+
+/// Test exclude-newer-package in pyproject.toml configuration
+#[test]
+fn sync_exclude_newer_package_config() -> Result<()> {
+ let context = TestContext::new("3.12").with_filtered_counts();
+
+ let pyproject_toml = context.temp_dir.child("pyproject.toml");
+ pyproject_toml.write_str(
+ r#"
+[project]
+name = "project"
+version = "0.1.0"
+requires-python = ">=3.12"
+dependencies = [
+ "tqdm",
+ "requests",
+]
+
+[tool.uv]
+exclude-newer = "2022-04-04T12:00:00Z"
+"#,
+ )?;
+
+ // First sync with only the global exclude-newer from the config
+ uv_snapshot!(context.filters(), context
+ .sync()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Resolved [N] packages in [TIME]
+ Prepared [N] packages in [TIME]
+ Installed [N] packages in [TIME]
+ + certifi==2021.10.8
+ + charset-normalizer==2.0.12
+ + idna==3.3
+ + requests==2.27.1
+ + tqdm==4.64.0
+ + urllib3==1.26.9
+ "
+ );
+
+ // Now add the package-specific exclude-newer to the config
+ pyproject_toml.write_str(
+ r#"
+[project]
+name = "project"
+version = "0.1.0"
+requires-python = ">=3.12"
+dependencies = [
+ "tqdm",
+ "requests",
+]
+
+[tool.uv]
+exclude-newer = "2022-04-04T12:00:00Z"
+exclude-newer-package = { tqdm = "2022-09-04T00:00:00Z" }
+"#,
+ )?;
+
+ // Sync again with the package-specific override
+ uv_snapshot!(context.filters(), context
+ .sync()
+ .env_remove(EnvVars::UV_EXCLUDE_NEWER), @r"
+ success: true
+ exit_code: 0
+ ----- stdout -----
+
+ ----- stderr -----
+ Ignoring existing lockfile due to change in timestamp cutoff: `global: 2022-04-04T12:00:00Z` vs. `global: 2022-04-04T12:00:00Z, tqdm: 2022-09-04T00:00:00Z`
+ Resolved [N] packages in [TIME]
+ Prepared [N] packages in [TIME]
+ Uninstalled [N] packages in [TIME]
+ Installed [N] packages in [TIME]
+ - tqdm==4.64.0
+ + tqdm==4.64.1
+ "
+ );
+
+ Ok(())
+}
+
#[test]
#[cfg(unix)]
fn read_only() -> Result<()> {
@@ -10803,7 +10954,7 @@ fn conflicting_editable() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", group = "bar" },
@@ -10969,7 +11120,7 @@ fn undeclared_editable() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
conflicts = [[
{ package = "project", group = "bar" },
diff --git a/crates/uv/tests/it/tree.rs b/crates/uv/tests/it/tree.rs
index 66d7cf2bc..5ef2be396 100644
--- a/crates/uv/tests/it/tree.rs
+++ b/crates/uv/tests/it/tree.rs
@@ -1250,7 +1250,7 @@ fn script() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
@@ -1436,7 +1436,7 @@ fn script() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.11"
[options]
diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs
index e2f9f1201..0ac660d97 100644
--- a/crates/uv/tests/it/version.rs
+++ b/crates/uv/tests/it/version.rs
@@ -1962,7 +1962,7 @@ fn version_set_workspace() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2022,7 +2022,7 @@ fn version_set_workspace() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2149,7 +2149,7 @@ fn version_set_workspace() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2295,7 +2295,7 @@ fn version_set_evil_constraints() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
@@ -2381,7 +2381,7 @@ fn version_set_evil_constraints() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
diff --git a/crates/uv/tests/it/workspace.rs b/crates/uv/tests/it/workspace.rs
index 631e4f6c3..1dfe51f74 100644
--- a/crates/uv/tests/it/workspace.rs
+++ b/crates/uv/tests/it/workspace.rs
@@ -1226,7 +1226,7 @@ fn workspace_inherit_sources() -> Result<()> {
assert_snapshot!(
lock, @r#"
version = 1
- revision = 2
+ revision = 3
requires-python = ">=3.12"
[options]
diff --git a/docs/reference/cli.md b/docs/reference/cli.md
index f761a49f0..5d4ffea68 100644
--- a/docs/reference/cli.md
+++ b/docs/reference/cli.md
@@ -97,7 +97,10 @@ uv run [OPTIONS] [COMMAND]
When enabled, uv will remove any extraneous packages from the environment. By default, uv run will make the minimum necessary changes to satisfy the requirements.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
Include optional dependencies from the specified extra name.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for specific packages to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+Include optional dependencies from the specified extra name.
May be provided more than once.
Optional dependencies are defined via project.optional-dependencies in a pyproject.toml.
This option is only available when running in a project.
@@ -459,7 +462,10 @@ uv add [OPTIONS] >
--editableAdd the requirements as editable
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
Extras to enable for the dependency.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for specific packages to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+Extras to enable for the dependency.
May be provided more than once.
To add this dependency to an optional extra instead, see --optional.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
@@ -653,7 +659,10 @@ uv remove [OPTIONS] ...
See --project to only change the project root directory.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for specific packages to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.
May also be set with the UV_EXTRA_INDEX_URL environment variable.
--find-links, -f find-linksLocations to search for candidate distributions, in addition to those found in the registry indexes.
@@ -832,7 +841,10 @@ uv version [OPTIONS] [VALUE]
Instead, the version will be displayed.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for specific packages to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.
May also be set with the UV_EXTRA_INDEX_URL environment variable.
--find-links, -f find-linksLocations to search for candidate distributions, in addition to those found in the registry indexes.
@@ -1017,7 +1029,10 @@ uv sync [OPTIONS]
In dry-run mode, uv will resolve the project's dependencies and report on the resulting changes to both the lockfile and the project environment, but will not modify either.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
Include optional dependencies from the specified extra name.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for specific packages to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+Include optional dependencies from the specified extra name.
May be provided more than once.
When multiple extras or groups are specified that appear in tool.uv.conflicts, uv will report an error.
Note that all optional dependencies are always included in the resolution; this option only affects the selection of packages to install.
@@ -1265,7 +1280,10 @@ uv lock [OPTIONS]
In dry-run mode, uv will resolve the project's dependencies and report on the resulting changes, but will not write the lockfile to disk.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for a specific package to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.
May also be set with the UV_EXTRA_INDEX_URL environment variable.
--find-links, -f find-linksLocations to search for candidate distributions, in addition to those found in the registry indexes.
@@ -1427,7 +1445,10 @@ uv export [OPTIONS]
See --project to only change the project root directory.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
Include optional dependencies from the specified extra name.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for a specific package to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+Include optional dependencies from the specified extra name.
May be provided more than once.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
@@ -1623,7 +1644,10 @@ uv tree [OPTIONS]
See --project to only change the project root directory.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for a specific package to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.
May also be set with the UV_EXTRA_INDEX_URL environment variable.
--find-links, -f find-linksLocations to search for candidate distributions, in addition to those found in the registry indexes.
@@ -1886,7 +1910,10 @@ uv tool run [OPTIONS] [COMMAND]
Can be provided multiple times, with subsequent files overriding values defined in previous files.
May also be set with the UV_ENV_FILE environment variable.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for specific packages to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.
May also be set with the UV_EXTRA_INDEX_URL environment variable.
--find-links, -f find-linksLocations to search for candidate distributions, in addition to those found in the registry indexes.
@@ -2058,7 +2085,10 @@ uv tool install [OPTIONS]
--editable, -eInstall the target package in editable mode, such that changes in the package's source directory are reflected without reinstallation
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for specific packages to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.
May also be set with the UV_EXTRA_INDEX_URL environment variable.
--find-links, -f find-linksLocations to search for candidate distributions, in addition to those found in the registry indexes.
@@ -2222,7 +2252,10 @@ uv tool upgrade [OPTIONS] ...
See --project to only change the project root directory.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for specific packages to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.
May also be set with the UV_EXTRA_INDEX_URL environment variable.
--find-links, -f find-linksLocations to search for candidate distributions, in addition to those found in the registry indexes.
@@ -3377,7 +3410,10 @@ uv pip compile [OPTIONS] >
--emit-index-urlInclude --index-url and --extra-index-url entries in the generated output file
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
Include optional dependencies from the specified extra name; may be provided more than once.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for a specific package to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+Include optional dependencies from the specified extra name; may be provided more than once.
Only applies to pyproject.toml, setup.py, and setup.cfg sources.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
@@ -3680,7 +3716,10 @@ uv pip sync [OPTIONS] ...
--dry-runPerform a dry run, i.e., don't actually install anything but resolve the dependencies and print the resulting plan
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
Include optional dependencies from the specified extra name; may be provided more than once.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for specific packages to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+Include optional dependencies from the specified extra name; may be provided more than once.
Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
@@ -3939,7 +3978,10 @@ uv pip install [OPTIONS] |--editable By default, installing will make the minimum necessary changes to satisfy the requirements. When enabled, uv will update the environment to exactly match the requirements, removing packages that are not included in the requirements.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
Include optional dependencies from the specified extra name; may be provided more than once.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for specific packages to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+Include optional dependencies from the specified extra name; may be provided more than once.
Only applies to pylock.toml, pyproject.toml, setup.py, and setup.cfg sources.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
@@ -4735,7 +4777,10 @@ uv venv [OPTIONS] [PATH]
See --project to only change the project root directory.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for a specific package to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.
May also be set with the UV_EXTRA_INDEX_URL environment variable.
--find-links, -f find-linksLocations to search for candidate distributions, in addition to those found in the registry indexes.
@@ -4878,7 +4923,10 @@ uv build [OPTIONS] [SRC]
See --project to only change the project root directory.
--exclude-newer exclude-newerLimit candidate packages to those that were uploaded prior to the given date.
Accepts both RFC 3339 timestamps (e.g., 2006-12-02T02:07:43Z) and local dates in the same format (e.g., 2006-12-02) in your system's configured time zone.
-May also be set with the UV_EXCLUDE_NEWER environment variable.
(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
+May also be set with the UV_EXCLUDE_NEWER environment variable.
--exclude-newer-package exclude-newer-packageLimit candidate packages for a specific package to those that were uploaded prior to the given date.
+Accepts package-date pairs in the format PACKAGE=DATE, where DATE is an RFC 3339 timestamp (e.g., 2006-12-02T02:07:43Z) or local date (e.g., 2006-12-02) in your system's configured time zone.
+Can be provided multiple times for different packages.
+(Deprecated: use --index instead) Extra URLs of package indexes to use, in addition to --index-url.
Accepts either a repository compliant with PEP 503 (the simple repository API), or a local directory laid out in the same format.
All indexes provided via this flag take priority over the index specified by --index-url (which defaults to PyPI). When multiple --extra-index-url flags are provided, earlier values take priority.
May also be set with the UV_EXTRA_INDEX_URL environment variable.
--find-links, -f find-linksLocations to search for candidate distributions, in addition to those found in the registry indexes.
diff --git a/docs/reference/settings.md b/docs/reference/settings.md
index 7fed1b2b6..c82164409 100644
--- a/docs/reference/settings.md
+++ b/docs/reference/settings.md
@@ -1137,6 +1137,32 @@ behave consistently across timezones.
---
+### [`exclude-newer-package`](#exclude-newer-package) {: #exclude-newer-package }
+
+Limit candidate packages for specific packages to those that were uploaded prior to the given date.
+
+Accepts package-date pairs in a dictionary format.
+
+**Default value**: `None`
+
+**Type**: `dict`
+
+**Example usage**:
+
+=== "pyproject.toml"
+
+ ```toml
+ [tool.uv]
+ exclude-newer-package = { tqdm = "2022-04-04T00:00:00Z" }
+ ```
+=== "uv.toml"
+
+ ```toml
+ exclude-newer-package = { tqdm = "2022-04-04T00:00:00Z" }
+ ```
+
+---
+
### [`extra-index-url`](#extra-index-url) {: #extra-index-url }
Extra URLs of package indexes to use, in addition to `--index-url`.
@@ -2570,6 +2596,34 @@ behave consistently across timezones.
---
+#### [`exclude-newer-package`](#pip_exclude-newer-package) {: #pip_exclude-newer-package }
+
+
+Limit candidate packages for specific packages to those that were uploaded prior to the given date.
+
+Accepts package-date pairs in a dictionary format.
+
+**Default value**: `None`
+
+**Type**: `dict`
+
+**Example usage**:
+
+=== "pyproject.toml"
+
+ ```toml
+ [tool.uv.pip]
+ exclude-newer-package = { tqdm = "2022-04-04T00:00:00Z" }
+ ```
+=== "uv.toml"
+
+ ```toml
+ [pip]
+ exclude-newer-package = { tqdm = "2022-04-04T00:00:00Z" }
+ ```
+
+---
+
#### [`extra`](#pip_extra) {: #pip_extra }
diff --git a/uv.schema.json b/uv.schema.json
index fce59304e..cecd411d1 100644
--- a/uv.schema.json
+++ b/uv.schema.json
@@ -218,7 +218,18 @@
"description": "Limit candidate packages to those that were uploaded prior to a given point in time.\n\nAccepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g.,\n`2006-12-02T02:07:43Z`). A full timestamp is required to ensure that the resolver will\nbehave consistently across timezones.",
"anyOf": [
{
- "$ref": "#/definitions/ExcludeNewer"
+ "$ref": "#/definitions/ExcludeNewerTimestamp"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "exclude-newer-package": {
+ "description": "Limit candidate packages for specific packages to those that were uploaded prior to the given date.\n\nAccepts package-date pairs in a dictionary format.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/ExcludeNewerPackage"
},
{
"type": "null"
@@ -876,7 +887,13 @@
"type": "string",
"format": "uri"
},
- "ExcludeNewer": {
+ "ExcludeNewerPackage": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/ExcludeNewerTimestamp"
+ }
+ },
+ "ExcludeNewerTimestamp": {
"description": "Exclude distributions uploaded after the given timestamp.\n\nAccepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and local dates in the same format (e.g., `2006-12-02`).",
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2}))?$"
@@ -1295,7 +1312,18 @@
"description": "Limit candidate packages to those that were uploaded prior to a given point in time.\n\nAccepts a superset of [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html) (e.g.,\n`2006-12-02T02:07:43Z`). A full timestamp is required to ensure that the resolver will\nbehave consistently across timezones.",
"anyOf": [
{
- "$ref": "#/definitions/ExcludeNewer"
+ "$ref": "#/definitions/ExcludeNewerTimestamp"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "exclude-newer-package": {
+ "description": "Limit candidate packages for specific packages to those that were uploaded prior to the given date.\n\nAccepts package-date pairs in a dictionary format.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/ExcludeNewerPackage"
},
{
"type": "null"