diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 9912bb2b2a3ef..e1ca6681d6b1c 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -137,7 +137,7 @@ jobs: with: project: 7hd4vdzmw5 # astral-sh/uv context: . - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64,linux/arm64,linux/riscv64 push: ${{ needs.docker-plan.outputs.push }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -170,19 +170,16 @@ jobs: # Mapping of base image followed by a comma followed by one or more base tags (comma separated) # Note, org.opencontainers.image.version label will use the first base tag (use the most specific tag first) image-mapping: - - alpine:3.22,alpine3.22,alpine - - alpine:3.21,alpine3.21 + - alpine:3.23,alpine3.23,alpine + - alpine:3.22,alpine3.22 - debian:trixie-slim,trixie-slim,debian-slim - buildpack-deps:trixie,trixie,debian - - debian:bookworm-slim,bookworm-slim - - buildpack-deps:bookworm,bookworm - python:3.14-alpine3.23,python3.14-alpine3.23,python3.14-alpine - python:3.13-alpine3.23,python3.13-alpine3.23,python3.13-alpine - python:3.12-alpine3.23,python3.12-alpine3.23,python3.12-alpine - python:3.11-alpine3.23,python3.11-alpine3.23,python3.11-alpine - python:3.10-alpine3.23,python3.10-alpine3.23,python3.10-alpine - python:3.9-alpine3.22,python3.9-alpine3.22,python3.9-alpine - - python:3.8-alpine3.20,python3.8-alpine3.20,python3.8-alpine - python:3.14-trixie,python3.14-trixie - python:3.13-trixie,python3.13-trixie - python:3.12-trixie,python3.12-trixie @@ -195,20 +192,6 @@ jobs: - python:3.11-slim-trixie,python3.11-trixie-slim - python:3.10-slim-trixie,python3.10-trixie-slim - python:3.9-slim-trixie,python3.9-trixie-slim - - python:3.14-bookworm,python3.14-bookworm - - python:3.13-bookworm,python3.13-bookworm - - python:3.12-bookworm,python3.12-bookworm - - python:3.11-bookworm,python3.11-bookworm - - python:3.10-bookworm,python3.10-bookworm - - python:3.9-bookworm,python3.9-bookworm - - python:3.8-bookworm,python3.8-bookworm - - python:3.14-slim-bookworm,python3.14-bookworm-slim - - python:3.13-slim-bookworm,python3.13-bookworm-slim - - python:3.12-slim-bookworm,python3.12-bookworm-slim - - python:3.11-slim-bookworm,python3.11-bookworm-slim - - python:3.10-slim-bookworm,python3.10-bookworm-slim - - python:3.9-slim-bookworm,python3.9-bookworm-slim - - python:3.8-slim-bookworm,python3.8-bookworm-slim steps: # Login to DockerHub (when not pushing, it's to avoid rate-limiting) - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 @@ -286,7 +269,7 @@ jobs: with: context: . project: 7hd4vdzmw5 # astral-sh/uv - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64,linux/arm64,linux/riscv64 push: ${{ needs.docker-plan.outputs.push }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/build-release-binaries.yml b/.github/workflows/build-release-binaries.yml index 4b2ac598ba6fc..9983e890fccd6 100644 --- a/.github/workflows/build-release-binaries.yml +++ b/.github/workflows/build-release-binaries.yml @@ -555,7 +555,6 @@ jobs: args: --release --locked --out dist --features self-update --compatibility pypi rust-toolchain: ${{ matrix.platform.toolchain || null }} - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1 - if: matrix.platform.arch != 'ppc64' name: "Test wheel" with: arch: ${{ matrix.platform.arch }} @@ -609,7 +608,6 @@ jobs: docker-options: ${{ matrix.platform.maturin_docker_options }} args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml --compatibility pypi - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1 - if: matrix.platform.arch != 'ppc64' name: "Test wheel uv-build" with: arch: ${{ matrix.platform.arch }} @@ -643,10 +641,6 @@ jobs: arch: ppc64le # see https://github.com/astral-sh/uv/issues/6528 maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 - - target: powerpc64-unknown-linux-gnu - arch: ppc64 - # see https://github.com/astral-sh/uv/issues/6528 - maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -677,7 +671,6 @@ jobs: fi # TODO(charlie): Re-enable testing for PPC wheels. # - uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1 - # if: matrix.platform.arch != 'ppc64' # name: "Test wheel" # with: # arch: ${{ matrix.platform.arch }} diff --git a/Cargo.lock b/Cargo.lock index baf2ecf58f6e2..1b5d2ba89aa1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7188,7 +7188,6 @@ dependencies = [ "uv-python", "uv-shell", "uv-version", - "uv-warnings", ] [[package]] diff --git a/Dockerfile b/Dockerfile index 856fe0f9229f9..34622cdf6d175 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,7 @@ ARG TARGETPLATFORM RUN case "$TARGETPLATFORM" in \ "linux/arm64") echo "aarch64-unknown-linux-musl" > rust_target.txt ;; \ "linux/amd64") echo "x86_64-unknown-linux-musl" > rust_target.txt ;; \ + "linux/riscv64") echo "riscv64gc-unknown-linux-musl" > rust_target.txt ;; \ *) exit 1 ;; \ esac diff --git a/crates/uv-bin-install/src/lib.rs b/crates/uv-bin-install/src/lib.rs index 535ae4fcda8c1..aae31d2050c06 100644 --- a/crates/uv-bin-install/src/lib.rs +++ b/crates/uv-bin-install/src/lib.rs @@ -34,7 +34,7 @@ impl Binary { pub fn default_version(&self) -> Version { match self { // TODO(zanieb): Figure out a nice way to automate updating this - Self::Ruff => Version::new([0, 12, 5]), + Self::Ruff => Version::new([0, 15, 0]), } } diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index d99b645d43aaf..1ac47c9f2f29e 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -51,7 +51,7 @@ impl IndexCacheControl { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[serde(rename_all = "kebab-case")] pub struct Index { @@ -545,6 +545,56 @@ impl<'a> From<&'a IndexUrl> for IndexMetadataRef<'a> { } } +/// Wire type for deserializing an [`Index`] with validation. +#[derive(Deserialize)] +#[serde(rename_all = "kebab-case")] +struct IndexWire { + name: Option, + url: IndexUrl, + #[serde(default)] + explicit: bool, + #[serde(default)] + default: bool, + #[serde(default)] + format: IndexFormat, + publish_url: Option, + #[serde(default)] + authenticate: AuthPolicy, + #[serde(default)] + ignore_error_codes: Option>, + #[serde(default)] + cache_control: Option, +} + +impl<'de> Deserialize<'de> for Index { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let wire = IndexWire::deserialize(deserializer)?; + + if wire.explicit && wire.name.is_none() { + return Err(serde::de::Error::custom(format!( + "An index with `explicit = true` requires a `name`: {}", + wire.url + ))); + } + + Ok(Self { + name: wire.name, + url: wire.url, + explicit: wire.explicit, + default: wire.default, + origin: None, + format: wire.format, + publish_url: wire.publish_url, + authenticate: wire.authenticate, + ignore_error_codes: wire.ignore_error_codes, + cache_control: wire.cache_control, + }) + } +} + /// An error that can occur when parsing an [`Index`]. #[derive(Error, Debug)] pub enum IndexSourceError { diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 8156c216b8d6c..873453d79dc96 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -46,7 +46,7 @@ use crate::{BrokenSymlink, Interpreter, PythonVersion}; /// A request to find a Python installation. /// /// See [`PythonRequest::from_str`]. -#[derive(Debug, Clone, PartialEq, Eq, Default, Hash)] +#[derive(Debug, Clone, Eq, Default)] pub enum PythonRequest { /// An appropriate default Python installation /// @@ -73,6 +73,18 @@ pub enum PythonRequest { Key(PythonDownloadRequest), } +impl PartialEq for PythonRequest { + fn eq(&self, other: &Self) -> bool { + self.to_canonical_string() == other.to_canonical_string() + } +} + +impl std::hash::Hash for PythonRequest { + fn hash(&self, state: &mut H) { + self.to_canonical_string().hash(state); + } +} + impl<'a> serde::Deserialize<'a> for PythonRequest { fn deserialize(deserializer: D) -> Result where diff --git a/crates/uv-python/src/implementation.rs b/crates/uv-python/src/implementation.rs index 2bb0cec9d32c6..8039fef4c1771 100644 --- a/crates/uv-python/src/implementation.rs +++ b/crates/uv-python/src/implementation.rs @@ -49,6 +49,7 @@ impl ImplementationName { } } + /// The executable name used in distributions of this implementation. pub fn executable_name(self) -> &'static str { match self { Self::CPython | Self::Pyodide => "python", @@ -56,6 +57,14 @@ impl ImplementationName { } } + /// The name used when installing this implementation as an executable into the bin directory. + pub fn executable_install_name(self) -> &'static str { + match self { + Self::Pyodide => "pyodide", + _ => self.executable_name(), + } + } + pub fn matches_interpreter(self, interpreter: &Interpreter) -> bool { match self { Self::Pyodide => interpreter.os().is_emscripten(), @@ -80,6 +89,13 @@ impl LenientImplementationName { Self::Unknown(name) => name, } } + + pub fn executable_install_name(&self) -> &str { + match self { + Self::Known(implementation) => implementation.executable_install_name(), + Self::Unknown(name) => name, + } + } } impl From<&ImplementationName> for &'static str { diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs index ea5363fa1b84d..e818d4961a0e9 100644 --- a/crates/uv-python/src/installation.rs +++ b/crates/uv-python/src/installation.rs @@ -585,7 +585,8 @@ impl PythonInstallationKey { /// Return a canonical name for a minor versioned executable. pub fn executable_name_minor(&self) -> String { format!( - "python{maj}.{min}{var}{exe}", + "{name}{maj}.{min}{var}{exe}", + name = self.implementation().executable_install_name(), maj = self.major, min = self.minor, var = self.variant.executable_suffix(), @@ -596,7 +597,8 @@ impl PythonInstallationKey { /// Return a canonical name for a major versioned executable. pub fn executable_name_major(&self) -> String { format!( - "python{maj}{var}{exe}", + "{name}{maj}{var}{exe}", + name = self.implementation().executable_install_name(), maj = self.major, var = self.variant.executable_suffix(), exe = std::env::consts::EXE_SUFFIX @@ -606,7 +608,8 @@ impl PythonInstallationKey { /// Return a canonical name for an un-versioned executable. pub fn executable_name(&self) -> String { format!( - "python{var}{exe}", + "{name}{var}{exe}", + name = self.implementation().executable_install_name(), var = self.variant.executable_suffix(), exe = std::env::consts::EXE_SUFFIX ) diff --git a/crates/uv-virtualenv/Cargo.toml b/crates/uv-virtualenv/Cargo.toml index 5a44903367514..13454b980797b 100644 --- a/crates/uv-virtualenv/Cargo.toml +++ b/crates/uv-virtualenv/Cargo.toml @@ -26,7 +26,6 @@ uv-pypi-types = { workspace = true } uv-python = { workspace = true } uv-shell = { workspace = true } uv-version = { workspace = true } -uv-warnings = { workspace = true } console = { workspace = true } fs-err = { workspace = true } diff --git a/crates/uv-virtualenv/src/lib.rs b/crates/uv-virtualenv/src/lib.rs index a8fdba7925ea6..44a39ead9dbf0 100644 --- a/crates/uv-virtualenv/src/lib.rs +++ b/crates/uv-virtualenv/src/lib.rs @@ -1,5 +1,5 @@ use std::io; -use std::path::Path; +use std::path::{Path, PathBuf}; use thiserror::Error; @@ -20,6 +20,13 @@ pub enum Error { NotFound(String), #[error(transparent)] Python(#[from] uv_python::managed::Error), + #[error("A {name} already exists at `{}`. Use `--clear` to replace it", path.display())] + Exists { + /// The type of environment (e.g., "virtual environment"). + name: &'static str, + /// The path to the existing environment. + path: PathBuf, + }, } /// The value to use for the shell prompt when inside a virtual environment. diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index a83979916a12b..75282f41e5485 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -11,6 +11,7 @@ use itertools::Itertools; use owo_colors::OwoColorize; use tracing::{debug, trace}; +use crate::{Error, Prompt}; use uv_fs::{CWD, Simplified, cachedir}; use uv_platform_tags::Os; use uv_preview::Preview; @@ -19,9 +20,6 @@ use uv_python::managed::{PythonMinorVersionLink, create_link_to_executable}; use uv_python::{Interpreter, VirtualEnvironment}; use uv_shell::escape_posix_for_single_quotes; use uv_version::version; -use uv_warnings::warn_user_once; - -use crate::{Error, Prompt}; /// Activation scripts for the environment, with dependent paths templated out. const ACTIVATE_TEMPLATES: &[(&str, &str)] = &[ @@ -164,13 +162,12 @@ pub(crate) fn create( fs_err::create_dir_all(&location)?; } Some(false) => return err, - // When we don't have a TTY, warn that the behavior will change in the future + // When we don't have a TTY, require `--clear` explicitly. None => { - warn_user_once!( - "A {name} already exists at `{}`. In the future, uv will require `{}` to replace it", - location.user_display(), - "--clear".green(), - ); + return Err(Error::Exists { + name, + path: location.to_path_buf(), + }); } } } @@ -447,6 +444,13 @@ pub(crate) fn create( // Add all the activate scripts for different shells for (name, template) in ACTIVATE_TEMPLATES { + // csh has no way to determine its own script location, so a relocatable + // activate.csh is not possible. Skip it entirely instead of generating a + // non-functional script. + if relocatable && *name == "activate.csh" { + continue; + } + let path_sep = if cfg!(windows) { ";" } else { ":" }; let relative_site_packages = [ @@ -477,9 +481,7 @@ pub(crate) fn create( escape_posix_for_single_quotes(location.simplified().to_str().unwrap()) ) } - // Note: - // * relocatable activate scripts appear not to be possible in csh. - // * `activate.ps1` is already relocatable by default. + // Note: `activate.ps1` is already relocatable by default. _ => escape_posix_for_single_quotes(location.simplified().to_str().unwrap()), }; diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 0de4751330013..8d1d442f39880 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -35,7 +35,6 @@ use uv_pypi_types::{ Conflicts, DependencyGroups, SchemaConflicts, SupportedEnvironments, VerbatimParsedUrl, }; use uv_redacted::DisplaySafeUrl; -use uv_warnings::warn_user_once; #[derive(Error, Debug)] pub enum PyprojectTomlError { @@ -300,11 +299,9 @@ where } if index.default { if seen_default { - warn_user_once!( - "Found multiple indexes with `default = true`; \ - only one index may be marked as default. \ - This will become an error in the future.", - ); + return Err(serde::de::Error::custom( + "found multiple indexes with `default = true`; only one index may be marked as default", + )); } seen_default = true; } diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index f1b1d843c5349..fa9886b4ea334 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -29,7 +29,7 @@ use uv_fs::{CWD, Simplified}; use uv_git::ResolvedRepositoryReference; use uv_install_wheel::LinkMode; use uv_normalize::PackageName; -use uv_preview::{Preview, PreviewFeature}; +use uv_preview::Preview; use uv_pypi_types::{Conflicts, SupportedEnvironments}; use uv_python::{ EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation, @@ -49,7 +49,7 @@ use uv_settings::PythonInstallMirrors; use uv_static::EnvVars; use uv_torch::{TorchMode, TorchSource, TorchStrategy}; use uv_types::{EmptyInstalledPackages, HashStrategy}; -use uv_warnings::{warn_user, warn_user_once}; +use uv_warnings::warn_user; use uv_workspace::WorkspaceCache; use uv_workspace::pyproject::ExtraBuildDependencies; @@ -123,15 +123,6 @@ pub(crate) async fn pip_compile( printer: Printer, preview: Preview, ) -> Result { - if !preview.is_enabled(PreviewFeature::ExtraBuildDependencies) - && !extra_build_dependencies.is_empty() - { - warn_user_once!( - "The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", - PreviewFeature::ExtraBuildDependencies - ); - } - // If the user provides a `pyproject.toml` or other TOML file as the output file, raise an // error. if output_file diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 7cba0f1909ef2..a6a592ee550a3 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -37,7 +37,7 @@ use uv_resolver::{ use uv_settings::PythonInstallMirrors; use uv_torch::{TorchMode, TorchSource, TorchStrategy}; use uv_types::HashStrategy; -use uv_warnings::{warn_user, warn_user_once}; +use uv_warnings::warn_user; use uv_workspace::WorkspaceCache; use uv_workspace::pyproject::ExtraBuildDependencies; @@ -106,15 +106,6 @@ pub(crate) async fn pip_install( ) -> anyhow::Result { let start = std::time::Instant::now(); - if !preview.is_enabled(PreviewFeature::ExtraBuildDependencies) - && !extra_build_dependencies.is_empty() - { - warn_user_once!( - "The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", - PreviewFeature::ExtraBuildDependencies - ); - } - let client_builder = client_builder.clone().keyring(keyring_provider); // Read all requirements from the provided sources. diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index c8fc45e018e12..6f5ddc962281c 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -36,7 +36,7 @@ use uv_resolver::{ use uv_settings::PythonInstallMirrors; use uv_torch::{TorchMode, TorchSource, TorchStrategy}; use uv_types::HashStrategy; -use uv_warnings::{warn_user, warn_user_once}; +use uv_warnings::warn_user; use uv_workspace::WorkspaceCache; use uv_workspace::pyproject::ExtraBuildDependencies; @@ -93,15 +93,6 @@ pub(crate) async fn pip_sync( printer: Printer, preview: Preview, ) -> Result { - if !preview.is_enabled(PreviewFeature::ExtraBuildDependencies) - && !extra_build_dependencies.is_empty() - { - warn_user_once!( - "The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", - PreviewFeature::ExtraBuildDependencies - ); - } - let client_builder = client_builder.clone().keyring(keyring_provider); // Initialize a few defaults. diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 3c0bf54763223..86d45c35a41b7 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -29,7 +29,7 @@ use uv_fs::{LockedFile, LockedFileError, Simplified}; use uv_git::GIT_STORE; use uv_normalize::{DEV_DEPENDENCIES, DefaultExtras, DefaultGroups, ExtraName, PackageName}; use uv_pep508::{MarkerTree, VersionOrUrl}; -use uv_preview::{Preview, PreviewFeature}; +use uv_preview::Preview; use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; use uv_redacted::DisplaySafeUrl; use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification}; @@ -103,22 +103,6 @@ pub(crate) async fn add( printer: Printer, preview: Preview, ) -> Result { - if bounds.is_some() && !preview.is_enabled(PreviewFeature::AddBounds) { - warn_user_once!( - "The `bounds` option is in preview and may change in any future release. Pass `--preview-features {}` to disable this warning.", - PreviewFeature::AddBounds - ); - } - - if !preview.is_enabled(PreviewFeature::ExtraBuildDependencies) - && !settings.resolver.extra_build_dependencies.is_empty() - { - warn_user_once!( - "The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", - PreviewFeature::ExtraBuildDependencies - ); - } - for source in &requirements { match source { RequirementsSource::PyprojectToml(_) => { diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 210a625ddeb56..bd55a73d16b67 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -472,15 +472,6 @@ async fn do_lock( torch_backend: _, } = settings; - if !preview.is_enabled(PreviewFeature::ExtraBuildDependencies) - && !extra_build_dependencies.is_empty() - { - warn_user_once!( - "The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", - PreviewFeature::ExtraBuildDependencies - ); - } - // Collect the requirements, etc. let members = target.members(); let packages = target.packages(); diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 07f674af1d13f..08cec6a4c4b1d 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -32,7 +32,7 @@ use uv_resolver::{FlatIndex, ForkStrategy, Installable, Lock, PrereleaseMode, Re use uv_scripts::Pep723Script; use uv_settings::PythonInstallMirrors; use uv_types::{BuildIsolation, HashStrategy}; -use uv_warnings::{warn_user, warn_user_once}; +use uv_warnings::warn_user; use uv_workspace::pyproject::Source; use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace, WorkspaceCache}; @@ -639,15 +639,6 @@ pub(super) async fn do_sync( sources, } = settings; - if !preview.is_enabled(PreviewFeature::ExtraBuildDependencies) - && !extra_build_dependencies.is_empty() - { - warn_user_once!( - "The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", - PreviewFeature::ExtraBuildDependencies - ); - } - // Lower the extra build dependencies with source resolution. let extra_build_requires = match &target { InstallTarget::Workspace { workspace, .. } diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 645cfa9d21a1c..482a8580c1c5a 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -17,13 +17,15 @@ use uv_distribution_types::{ ExtraBuildRequires, IndexCapabilities, NameRequirementSpecification, Requirement, RequirementSource, UnresolvedRequirementSpecification, }; +use uv_fs::CWD; use uv_installer::{InstallationStrategy, SatisfiesResult, SitePackages}; use uv_normalize::PackageName; use uv_pep440::{VersionSpecifier, VersionSpecifiers}; use uv_pep508::MarkerTree; use uv_preview::Preview; use uv_python::{ - EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest, + EnvironmentPreference, Interpreter, PythonDownloads, PythonEnvironment, PythonInstallation, + PythonPreference, PythonRequest, PythonVersionFile, VersionFileDiscoveryOptions, }; use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_settings::{PythonInstallMirrors, ResolverInstallerOptions, ToolOptions}; @@ -49,6 +51,7 @@ use crate::printer::Printer; use crate::settings::{ResolverInstallerSettings, ResolverSettings}; /// Install a tool. +#[expect(clippy::fn_params_excessive_bools)] pub(crate) async fn install( package: String, editable: bool, @@ -71,6 +74,7 @@ pub(crate) async fn install( python_downloads: PythonDownloads, installer_metadata: bool, concurrency: Concurrency, + no_config: bool, cache: Cache, printer: Printer, preview: Preview, @@ -83,7 +87,24 @@ pub(crate) async fn install( let reporter = PythonDownloadReporter::single(printer); - let python_request = python.as_deref().map(PythonRequest::parse); + let (python_request, explicit_python_request) = if let Some(request) = python.as_deref() { + (Some(PythonRequest::parse(request)), true) + } else { + // Discover a global Python version pin, if no request was made + ( + PythonVersionFile::discover( + // TODO(zanieb): We don't use the directory, should we expose another interface? + // Should `no_local` be implied by `None` here? + &*CWD, + &VersionFileDiscoveryOptions::default() + .with_no_config(no_config) + .with_no_local(true), + ) + .await? + .and_then(PythonVersionFile::into_version), + false, + ) + }; // Pre-emptively identify a Python interpreter. We need an interpreter to resolve any unnamed // requirements, even if we end up using a different interpreter for the tool install itself. @@ -423,26 +444,20 @@ pub(crate) async fn install( } }; - let existing_environment = - installed_tools - .get_environment(package_name, &cache)? - .filter(|environment| { - if environment.environment().uses(&interpreter) { - trace!( - "Existing interpreter matches the requested interpreter for `{}`: {}", - package_name, - environment.environment().interpreter().sys_executable().display() - ); - true - } else { - let _ = writeln!( - printer.stderr(), - "Ignoring existing environment for `{}`: the requested Python interpreter does not match the environment interpreter", - package_name.cyan(), - ); - false - } - }); + let existing_environment = installed_tools + .get_environment(package_name, &cache)? + .filter(|environment| { + existing_environment_usable( + environment.environment(), + &interpreter, + package_name, + python_request.as_ref(), + explicit_python_request, + &settings, + existing_tool_receipt.as_ref(), + printer, + ) + }); // If the requested and receipt requirements are the same... if let Some(environment) = existing_environment.as_ref().filter(|_| { @@ -473,9 +488,19 @@ pub(crate) async fn install( ) .into_inner(); - // Determine the markers and tags to use for the resolution. - let markers = resolution_markers(None, python_platform.as_ref(), &interpreter); - let tags = resolution_tags(None, python_platform.as_ref(), &interpreter)?; + // Determine the markers and tags to use for the resolution. We use the existing + // environment for markers here — above we filter the environment to `None` if + // `existing_environment_usable` is `false`, so we've determined it's valid. + let markers = resolution_markers( + None, + python_platform.as_ref(), + environment.environment().interpreter(), + ); + let tags = resolution_tags( + None, + python_platform.as_ref(), + environment.environment().interpreter(), + )?; // Check if the installed packages meet the requirements. let site_packages = SitePackages::from_environment(environment.environment())?; @@ -720,7 +745,12 @@ pub(crate) async fn install( &installed_tools, &options, force || invalid_tool_receipt, - python_request, + // Only persist the Python request if it was explicitly provided + if explicit_python_request { + python_request + } else { + None + }, requirements, constraints, overrides, @@ -730,3 +760,54 @@ pub(crate) async fn install( Ok(ExitStatus::Success) } + +fn existing_environment_usable( + environment: &PythonEnvironment, + interpreter: &Interpreter, + package_name: &PackageName, + python_request: Option<&PythonRequest>, + explicit_python_request: bool, + settings: &ResolverInstallerSettings, + existing_tool_receipt: Option<&uv_tool::Tool>, + printer: Printer, +) -> bool { + // If the environment matches the interpreter, it's usable + if environment.uses(interpreter) { + trace!( + "Existing interpreter matches the requested interpreter for `{}`: {}", + package_name, + environment.interpreter().sys_executable().display() + ); + return true; + } + + // If there was an explicit Python request that does not match, we'll invalidate the + // environment. + if explicit_python_request { + let _ = writeln!( + printer.stderr(), + "Ignoring existing environment for `{}`: the requested Python interpreter does not match the environment interpreter", + package_name.cyan(), + ); + return false; + } + + // Otherwise, we'll invalidate the environment if all of the following are true: + // - The user requested a reinstall + // - The tool was not previously pinned to a Python version + // - There is _some_ alternative Python request + if let Some(tool_receipt) = existing_tool_receipt + && settings.reinstall.is_all() + && tool_receipt.python().is_none() + && python_request.is_some() + { + let _ = writeln!( + printer.stderr(), + "Ignoring existing environment for `{from}`: the Python interpreter does not match the environment interpreter", + from = package_name.cyan(), + ); + return false; + } + + true +} diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index 14feb8910ec09..0cf66f0868bc5 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -24,12 +24,15 @@ use uv_distribution_types::{ IndexCapabilities, IndexUrl, Name, NameRequirementSpecification, Requirement, RequirementSource, UnresolvedRequirement, UnresolvedRequirementSpecification, }; +use uv_fs::CWD; use uv_fs::Simplified; use uv_installer::{InstallationStrategy, SatisfiesResult, SitePackages}; use uv_normalize::PackageName; use uv_pep440::{VersionSpecifier, VersionSpecifiers}; use uv_pep508::MarkerTree; use uv_preview::Preview; +use uv_python::PythonVersionFile; +use uv_python::VersionFileDiscoveryOptions; use uv_python::{ EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, @@ -740,43 +743,38 @@ async fn get_or_create_environment( ) -> Result<(ToolRequirement, PythonEnvironment), ProjectError> { let reporter = PythonDownloadReporter::single(printer); - // Figure out what Python we're targeting, either explicitly like `uvx python@3`, or via the - // -p/--python flag. - let python_request = match request { - ToolRequest::Python { - request: tool_python_request, - .. - } => { - match python { - None => Some(tool_python_request.clone()), - - // The user is both invoking a python interpreter directly and also supplying the - // -p/--python flag. Cases like `uvx -p pypy python` are allowed, for two reasons: - // 1) Previously this was the only way to invoke e.g. PyPy via `uvx`, and it's nice - // to remain compatible with that. 2) A script might define an alias like `uvx - // --python $MY_PYTHON ...`, and it's nice to be able to run the interpreter - // directly while sticking to that alias. - // - // However, we want to error out if we see conflicting or redundant versions like - // `uvx -p python38 python39`. - // - // Note that a command like `uvx default` doesn't bring us here. ToolRequest::parse - // returns ToolRequest::Package rather than ToolRequest::Python in that case. See - // PythonRequest::try_from_tool_name. - Some(python_flag) => { - if tool_python_request != &PythonRequest::Default { - return Err(anyhow::anyhow!( - "Received multiple Python version requests: `{}` and `{}`", - python_flag.to_string().cyan(), - tool_python_request.to_canonical_string().cyan() - ) - .into()); - } - Some(PythonRequest::parse(python_flag)) - } - } + // Determine explicit Python version requests + let explicit_python_request = python.map(PythonRequest::parse); + let tool_python_request = match request { + ToolRequest::Python { request, .. } => Some(request.clone()), + ToolRequest::Package { .. } => None, + }; + + // Resolve Python request with version file lookup when no explicit request + let python_request = match (explicit_python_request, tool_python_request) { + // e.g., `uvx --python 3.10 python3.12` + (Some(explicit), Some(tool_request)) if tool_request != PythonRequest::Default => { + // Conflict: both --python flag and versioned tool name + return Err(anyhow::anyhow!( + "Received multiple Python version requests: `{}` and `{}`", + explicit.to_canonical_string().cyan(), + tool_request.to_canonical_string().cyan() + ) + .into()); } - ToolRequest::Package { .. } => python.map(PythonRequest::parse), + // e.g, `uvx --python 3.10 ...` + (Some(explicit), _) => Some(explicit), + // e.g., `uvx python` or `uvx ` + (None, Some(PythonRequest::Default) | None) => PythonVersionFile::discover( + &*CWD, + &VersionFileDiscoveryOptions::default() + .with_no_config(false) + .with_no_local(true), + ) + .await? + .and_then(PythonVersionFile::into_version), + // e.g., `uvx python3.12` + (None, Some(tool_request)) => Some(tool_request), }; // Discover an interpreter. diff --git a/crates/uv/src/commands/workspace/dir.rs b/crates/uv/src/commands/workspace/dir.rs index eb3f0527f1d83..016aad0891e6f 100644 --- a/crates/uv/src/commands/workspace/dir.rs +++ b/crates/uv/src/commands/workspace/dir.rs @@ -6,8 +6,6 @@ use anyhow::{Result, bail}; use owo_colors::OwoColorize; use uv_fs::Simplified; use uv_normalize::PackageName; -use uv_preview::{Preview, PreviewFeature}; -use uv_warnings::warn_user; use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache}; use crate::commands::ExitStatus; @@ -17,16 +15,8 @@ use crate::printer::Printer; pub(crate) async fn dir( package_name: Option, project_dir: &Path, - preview: Preview, printer: Printer, ) -> Result { - if !preview.is_enabled(PreviewFeature::WorkspaceDir) { - warn_user!( - "The `uv workspace dir` command is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", - PreviewFeature::WorkspaceDir - ); - } - let workspace_cache = WorkspaceCache::default(); let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default(), &workspace_cache).await?; diff --git a/crates/uv/src/commands/workspace/list.rs b/crates/uv/src/commands/workspace/list.rs index 24c7a63537610..0b1fa27e9c9a5 100644 --- a/crates/uv/src/commands/workspace/list.rs +++ b/crates/uv/src/commands/workspace/list.rs @@ -5,27 +5,13 @@ use anyhow::Result; use owo_colors::OwoColorize; use uv_fs::Simplified; -use uv_preview::{Preview, PreviewFeature}; -use uv_warnings::warn_user; use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache}; use crate::commands::ExitStatus; use crate::printer::Printer; /// List workspace members -pub(crate) async fn list( - project_dir: &Path, - paths: bool, - preview: Preview, - printer: Printer, -) -> Result { - if !preview.is_enabled(PreviewFeature::WorkspaceList) { - warn_user!( - "The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.", - PreviewFeature::WorkspaceList - ); - } - +pub(crate) async fn list(project_dir: &Path, paths: bool, printer: Printer) -> Result { let workspace_cache = WorkspaceCache::default(); let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default(), &workspace_cache).await?; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index cdd8b3f1b494c..d22b30065845e 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1537,6 +1537,7 @@ async fn run(mut cli: Cli) -> Result { globals.python_downloads, globals.installer_metadata, globals.concurrency, + cli.top_level.no_config, cache, printer, globals.preview, @@ -1872,12 +1873,8 @@ async fn run(mut cli: Cli) -> Result { WorkspaceCommand::Metadata(_args) => { commands::metadata(&project_dir, globals.preview, printer).await } - WorkspaceCommand::Dir(args) => { - commands::dir(args.package, &project_dir, globals.preview, printer).await - } - WorkspaceCommand::List(args) => { - commands::list(&project_dir, args.paths, globals.preview, printer).await - } + WorkspaceCommand::Dir(args) => commands::dir(args.package, &project_dir, printer).await, + WorkspaceCommand::List(args) => commands::list(&project_dir, args.paths, printer).await, }, Commands::BuildBackend { command } => spawn_blocking(move || match command { BuildBackendCommand::BuildSdist { sdist_directory } => { diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index 3dc4575162d9d..2a92375e1b038 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -2161,7 +2161,7 @@ fn venv_included_in_sdist() -> Result<()> { .child("pyproject.toml") .write_str(pyproject_toml)?; - context.venv().assert().success(); + context.venv().arg("--clear").assert().success(); // context.filters() uv_snapshot!(context.filters(), context.build(), @" diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index da8eaf5555f55..7adf5a5579097 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -6846,7 +6846,6 @@ fn add_script_bounds() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: The `bounds` option is in preview and may change in any future release. Pass `--preview-features add-bounds` to disable this warning. Resolved 3 packages in [TIME] "); @@ -14098,7 +14097,6 @@ fn add_bounds() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: The `bounds` option is in preview and may change in any future release. Pass `--preview-features add-bounds` to disable this warning. Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] @@ -14138,7 +14136,6 @@ fn add_bounds() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: The `bounds` option is in preview and may change in any future release. Pass `--preview-features add-bounds` to disable this warning. Resolved 4 packages in [TIME] Prepared 2 packages in [TIME] Installed 2 packages in [TIME] @@ -14163,7 +14160,7 @@ fn add_bounds() -> Result<()> { ); // Existing constraints take precedence over the bounds option - uv_snapshot!(context.filters(), context.add().arg("anyio").arg("--bounds").arg("minor").arg("--preview"), @" + uv_snapshot!(context.filters(), context.add().arg("anyio").arg("--bounds").arg("minor"), @" success: true exit_code: 0 ----- stdout ----- @@ -14190,7 +14187,7 @@ fn add_bounds() -> Result<()> { ); // Explicit constraints take precedence over the bounds option - uv_snapshot!(context.filters(), context.add().arg("anyio==4.2").arg("idna").arg("--bounds").arg("minor").arg("--preview"), @" + uv_snapshot!(context.filters(), context.add().arg("anyio==4.2").arg("idna").arg("--bounds").arg("minor"), @" success: true exit_code: 0 ----- stdout ----- @@ -14221,8 +14218,8 @@ fn add_bounds() -> Result<()> { "# ); - // Set bounds on the CLI and use `--preview` to silence the warning. - uv_snapshot!(context.filters(), context.add().arg("sniffio").arg("--bounds").arg("minor").arg("--preview"), @" + // Set bounds on the CLI. + uv_snapshot!(context.filters(), context.add().arg("sniffio").arg("--bounds").arg("minor"), @" success: true exit_code: 0 ----- stdout ----- @@ -14271,7 +14268,7 @@ fn add_bounds_requirement_over_bounds_kind() -> Result<()> { requires-python = ">=3.12" "#})?; - uv_snapshot!(context.filters(), context.add().arg("anyio==4.2").arg("idna").arg("--bounds").arg("minor").arg("--preview"), @" + uv_snapshot!(context.filters(), context.add().arg("anyio==4.2").arg("idna").arg("--bounds").arg("minor"), @" success: true exit_code: 0 ----- stdout ----- diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index aa46e8c1adadb..7f5c0799d01a1 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -18910,6 +18910,50 @@ fn lock_explicit_default_index() -> Result<()> { Ok(()) } +/// Error when an explicit index does not have a name. +#[test] +fn lock_unnamed_explicit_index() -> 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 = ["iniconfig==2.0.0"] + + [[tool.uv.index]] + url = "https://test.pypi.org/simple" + explicit = true + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + warning: Failed to parse `pyproject.toml` during settings discovery: + TOML parse error at line 8, column 9 + | + 8 | [[tool.uv.index]] + | ^^^^^^^^^^^^^^^^^ + An index with `explicit = true` requires a `name`: https://test.pypi.org/simple + + error: Failed to parse: `pyproject.toml` + Caused by: TOML parse error at line 8, column 9 + | + 8 | [[tool.uv.index]] + | ^^^^^^^^^^^^^^^^^ + An index with `explicit = true` requires a `name`: https://test.pypi.org/simple + "#); + + Ok(()) +} + #[test] fn lock_named_index() -> Result<()> { let context = TestContext::new("3.12"); @@ -19276,15 +19320,19 @@ fn lock_multiple_default_indexes() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock(), @" - success: true - exit_code: 0 + uv_snapshot!(context.filters(), context.lock(), @r###" + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- - warning: Found multiple indexes with `default = true`; only one index may be marked as default. This will become an error in the future. - Resolved 2 packages in [TIME] - "); + error: Failed to parse: `pyproject.toml` + Caused by: TOML parse error at line 8, column 9 + | + 8 | [[tool.uv.index]] + | ^^^^^^^^^^^^^^^^^ + found multiple indexes with `default = true`; only one index may be marked as default + "###); Ok(()) } @@ -33308,64 +33356,17 @@ fn lock_check_multiple_default_indexes_explicit_assignment_dependency_group() -> )?; uv_snapshot!(context.filters(), context.lock(), @" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - warning: Found multiple indexes with `default = true`; only one index may be marked as default. This will become an error in the future. - Resolved 2 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 = "2025-01-30T00:00:00Z" - - [[package]] - name = "iniconfig" - version = "2.0.0" - source = { registry = "https://pypi-proxy.fly.dev/basic-auth/simple" } - sdist = { url = "https://pypi-proxy.fly.dev/basic-auth/files/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } - wheels = [ - { url = "https://pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, - ] - - [[package]] - name = "project" - version = "0.1.0" - source = { virtual = "." } - - [package.dev-dependencies] - dev = [ - { name = "iniconfig" }, - ] - - [package.metadata] - - [package.metadata.requires-dev] - dev = [{ name = "iniconfig", index = "https://pypi-proxy.fly.dev/basic-auth/simple" }] - "# - ); - }); - - uv_snapshot!(context.filters(), context.lock().arg("--check"), @" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- - warning: Found multiple indexes with `default = true`; only one index may be marked as default. This will become an error in the future. - Resolved 2 packages in [TIME] + error: Failed to parse: `pyproject.toml` + Caused by: TOML parse error at line 13, column 9 + | + 13 | [[tool.uv.index]] + | ^^^^^^^^^^^^^^^^^ + found multiple indexes with `default = true`; only one index may be marked as default "); Ok(()) diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index d0615d55e1090..99d07795c6165 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -13002,13 +13002,12 @@ fn pip_install_build_dependencies_respect_locked_versions() -> Result<()> { "#})?; // The child should be built with anyio 4.0 - uv_snapshot!(context.filters(), context.pip_install().arg(".").env(EnvVars::EXPECTED_ANYIO_VERSION, "4.0"), @" + uv_snapshot!(context.filters(), context.pip_install().arg(".").env(EnvVars::EXPECTED_ANYIO_VERSION, "4.0"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Installed [N] packages in [TIME] @@ -13036,13 +13035,12 @@ fn pip_install_build_dependencies_respect_locked_versions() -> Result<()> { // The child should be rebuilt with anyio 3.7, without `--reinstall` uv_snapshot!(context.filters(), context.pip_install().arg(".") - .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "4.0"), @" + .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "4.0"), @r" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] × Failed to build `child @ file://[TEMP_DIR]/child` ├─▶ The build backend returned an error @@ -13056,13 +13054,12 @@ fn pip_install_build_dependencies_respect_locked_versions() -> Result<()> { "); uv_snapshot!(context.filters(), context.pip_install().arg(".") - .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "3.7"), @" + .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "3.7"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Uninstalled [N] packages in [TIME] @@ -13073,24 +13070,6 @@ fn pip_install_build_dependencies_respect_locked_versions() -> Result<()> { ~ parent==0.1.0 (from file://[TEMP_DIR]/) "); - // With preview enabled, there's no warning - uv_snapshot!(context.filters(), context.pip_install().arg(".") - .arg("--preview-features").arg("extra-build-dependencies") - .arg("--reinstall-package").arg("child") - .env(EnvVars::EXPECTED_ANYIO_VERSION, "3.7"), @" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Resolved [N] packages in [TIME] - Prepared [N] packages in [TIME] - Uninstalled [N] packages in [TIME] - Installed [N] packages in [TIME] - ~ child==0.1.0 (from file://[TEMP_DIR]/child) - ~ parent==0.1.0 (from file://[TEMP_DIR]/) - "); - Ok(()) } @@ -13859,7 +13838,13 @@ fn build_backend_wrong_wheel_platform() -> Result<()> { // (compatible host, incompatible host) x (compatible target, incompatible target) // A Python 3.13 host with a 3.13 implicit target works. - context.venv().arg("-p").arg("3.13").assert().success(); + context + .venv() + .arg("--clear") + .arg("-p") + .arg("3.13") + .assert() + .success(); uv_snapshot!(context.filters(), context.pip_install().arg("./child"), @" success: true exit_code: 0 @@ -13873,7 +13858,13 @@ fn build_backend_wrong_wheel_platform() -> Result<()> { "); // A Python 3.13 host with a 3.12 explicit target fails. - context.venv().arg("-p").arg("3.13").assert().success(); + context + .venv() + .arg("--clear") + .arg("-p") + .arg("3.13") + .assert() + .success(); uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("./child"), @" success: false exit_code: 1 @@ -13886,8 +13877,14 @@ fn build_backend_wrong_wheel_platform() -> Result<()> { "); // A python 3.12 host with a 3.13 explicit target works. - context.venv().arg("-p").arg("3.13").assert().success(); - uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.13").arg("./child"), @" + context + .venv() + .arg("--clear") + .arg("-p") + .arg("3.13") + .assert() + .success(); + uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.13").arg("./child"), @r" success: true exit_code: 0 ----- stdout ----- @@ -13895,13 +13892,18 @@ fn build_backend_wrong_wheel_platform() -> Result<()> { ----- stderr ----- Resolved 1 package in [TIME] Prepared 1 package in [TIME] - Uninstalled 1 package in [TIME] Installed 1 package in [TIME] - ~ py313==0.1.0 (from file://[TEMP_DIR]/child) + + py313==0.1.0 (from file://[TEMP_DIR]/child) "); // A Python 3.13 host with a 3.12 explicit target fails. - context.venv().arg("-p").arg("3.13").assert().success(); + context + .venv() + .arg("--clear") + .arg("-p") + .arg("3.13") + .assert() + .success(); uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("./child"), @" success: false exit_code: 1 @@ -13936,7 +13938,13 @@ fn build_backend_wrong_wheel_platform() -> Result<()> { .touch()?; // A build host of 3.13 works. - context.venv().arg("-p").arg("3.13").assert().success(); + context + .venv() + .arg("--clear") + .arg("-p") + .arg("3.13") + .assert() + .success(); uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("."), @" success: true exit_code: 0 @@ -13950,7 +13958,13 @@ fn build_backend_wrong_wheel_platform() -> Result<()> { "); // A build host of 3.12 fails. - context.venv().arg("-p").arg("3.12").assert().success(); + context + .venv() + .arg("--clear") + .arg("-p") + .arg("3.12") + .assert() + .success(); uv_snapshot!(context.filters(), context.pip_install().arg("--python-version").arg("3.12").arg("."), @" success: false exit_code: 1 diff --git a/crates/uv/tests/it/python_install.rs b/crates/uv/tests/it/python_install.rs index 001dec35f4029..31486c8f0610e 100644 --- a/crates/uv/tests/it/python_install.rs +++ b/crates/uv/tests/it/python_install.rs @@ -3486,12 +3486,12 @@ fn python_install_pyodide() { ----- stderr ----- Installed Python 3.13.2 in [TIME] - + pyodide-3.13.2-emscripten-wasm32-musl (python3.13) + + pyodide-3.13.2-emscripten-wasm32-musl (pyodide3.13) "); let bin_python = context .bin_dir - .child(format!("python3.13{}", std::env::consts::EXE_SUFFIX)); + .child(format!("pyodide3.13{}", std::env::consts::EXE_SUFFIX)); // The executable should be installed in the bin directory bin_python.assert(predicate::path::exists()); @@ -3562,7 +3562,7 @@ fn python_install_pyodide() { ----- stderr ----- Installed Python 3.13.2 in [TIME] - + pyodide-3.13.2-emscripten-wasm32-musl (python3.13) + + pyodide-3.13.2-emscripten-wasm32-musl (pyodide3.13) "); context.python_uninstall().arg("--all").assert().success(); @@ -3575,7 +3575,7 @@ fn python_install_pyodide() { ----- stderr ----- Installed Python 3.13.2 in [TIME] - + pyodide-3.13.2-emscripten-wasm32-musl (python3.13) + + pyodide-3.13.2-emscripten-wasm32-musl (pyodide3.13) "); // Find via `pyodide`` @@ -3738,14 +3738,14 @@ fn python_install_build_version_pypy() { uv_snapshot!(context.filters(), context.python_install() .arg("pypy3.10") - .env(EnvVars::UV_PYTHON_PYPY_BUILD, "7.3.19"), @" + .env(EnvVars::UV_PYTHON_PYPY_BUILD, "7.3.19"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Installed Python 3.10.16 in [TIME] - + pypy-3.10.16-[PLATFORM] (python3.10) + + pypy-3.10.16-[PLATFORM] (pypy3.10) "); // A BUILD file should be present with the version @@ -4254,7 +4254,7 @@ fn python_install_compile_bytecode_pyodide() { ----- stderr ----- Installed Python 3.13.2 in [TIME] - + pyodide-3.13.2-emscripten-wasm32-musl (python3.13) + + pyodide-3.13.2-emscripten-wasm32-musl (pyodide3.13) No compatible versions to bytecode compile (skipped 1) "); @@ -4275,14 +4275,14 @@ fn python_install_compile_bytecode_graalpy() { .with_python_download_cache(); // Should work for graalpy - uv_snapshot!(context.filters(), context.python_install().arg("--compile-bytecode").arg("graalpy-3.12"), @" + uv_snapshot!(context.filters(), context.python_install().arg("--compile-bytecode").arg("graalpy-3.12"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Installed Python 3.12.0 in [TIME] - + graalpy-3.12.0-[PLATFORM] (python3.12) + + graalpy-3.12.0-[PLATFORM] (graalpy3.12) Bytecode compiled [COUNT] files in [TIME] "); } @@ -4298,14 +4298,14 @@ fn python_install_compile_bytecode_pypy() { .with_python_download_cache(); // Should work for pypy - uv_snapshot!(context.filters(), context.python_install().arg("--compile-bytecode").arg("pypy-3.11"), @" + uv_snapshot!(context.filters(), context.python_install().arg("--compile-bytecode").arg("pypy-3.11"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Installed Python 3.11.13 in [TIME] - + pypy-3.11.13-[PLATFORM] (python3.11) + + pypy-3.11.13-[PLATFORM] (pypy3.11) Bytecode compiled [COUNT] files in [TIME] "); } diff --git a/crates/uv/tests/it/python_module.rs b/crates/uv/tests/it/python_module.rs index 0b1489c09fc99..3fc0eec32cbe1 100644 --- a/crates/uv/tests/it/python_module.rs +++ b/crates/uv/tests/it/python_module.rs @@ -197,7 +197,7 @@ fn find_uv_bin_base_prefix() { " ); - context.venv().assert().success(); + context.venv().arg("--clear").assert().success(); // Mutate `base_prefix` to simulate lookup in a system Python installation uv_snapshot!(context.filters(), context.python_command() diff --git a/crates/uv/tests/it/run.rs b/crates/uv/tests/it/run.rs index 9019ab0256e12..bdbeecbd985cb 100644 --- a/crates/uv/tests/it/run.rs +++ b/crates/uv/tests/it/run.rs @@ -1478,7 +1478,12 @@ fn run_with_overlay_interpreter() -> Result<()> { "); // Switch to a relocatable virtual environment. - context.venv().arg("--relocatable").assert().success(); + context + .venv() + .arg("--allow-existing") + .arg("--relocatable") + .assert() + .success(); // Cleanup previous shutil fs_err::remove_file(context.temp_dir.child("main"))?; diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index e946ce43c2fd1..49b090d5d72d6 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -1953,13 +1953,12 @@ fn sync_extra_build_dependencies() -> Result<()> { "#})?; context.venv().arg("--clear").assert().success(); - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Installed [N] packages in [TIME] @@ -1967,13 +1966,12 @@ fn sync_extra_build_dependencies() -> Result<()> { "); context.venv().arg("--clear").assert().success(); - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Installed [N] packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) @@ -1996,13 +1994,12 @@ fn sync_extra_build_dependencies() -> Result<()> { "#})?; context.venv().arg("--clear").assert().success(); - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] × Failed to build `child @ file://[TEMP_DIR]/child` ├─▶ The build backend returned an error @@ -2065,13 +2062,12 @@ fn sync_extra_build_dependencies() -> Result<()> { // Confirm that `bad_child` fails if anyio is provided context.venv().arg("--clear").assert().success(); - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] × Failed to build `bad-child @ file://[TEMP_DIR]/bad_child` ├─▶ The build backend returned an error @@ -2101,13 +2097,12 @@ fn sync_extra_build_dependencies() -> Result<()> { "#})?; context.venv().arg("--clear").assert().success(); - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Installed [N] packages in [TIME] @@ -2194,13 +2189,12 @@ fn sync_extra_build_dependencies_setuptools_legacy() -> Result<()> { "#})?; context.venv().arg("--clear").assert().success(); - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Installed [N] packages in [TIME] @@ -2301,13 +2295,12 @@ fn sync_extra_build_dependencies_setuptools() -> Result<()> { "#})?; context.venv().arg("--clear").assert().success(); - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Installed [N] packages in [TIME] @@ -2376,13 +2369,12 @@ fn sync_extra_build_dependencies_sources() -> Result<()> { })?; // Running `uv sync` should succeed, as `anyio` is provided as a source - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Installed [N] packages in [TIME] @@ -2512,13 +2504,12 @@ fn sync_extra_build_dependencies_index() -> Result<()> { // The child should be rebuilt with `3.5` on reinstall, the "latest" on Test PyPI. uv_snapshot!(context.filters(), context.sync() - .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "4.3"), @" + .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "4.3"), @r" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] × Failed to build `child @ file://[TEMP_DIR]/child` ├─▶ The build backend returned an error @@ -2532,13 +2523,12 @@ fn sync_extra_build_dependencies_index() -> Result<()> { "); uv_snapshot!(context.filters(), context.sync() - .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "3.5"), @" + .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "3.5"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Uninstalled [N] packages in [TIME] @@ -2610,13 +2600,12 @@ fn sync_extra_build_dependencies_sources_from_child() -> Result<()> { })?; // Running `uv sync` should fail due to the unapplied source - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--refresh"), @" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--refresh"), @r" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] × Failed to build `child @ file://[TEMP_DIR]/child` ├─▶ The build backend returned an error @@ -2718,13 +2707,12 @@ fn sync_build_dependencies_module_error_hints() -> Result<()> { "#})?; context.venv().arg("--clear").assert().success(); - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Installed [N] packages in [TIME] @@ -2748,7 +2736,6 @@ fn sync_build_dependencies_module_error_hints() -> Result<()> { ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] × Failed to build `child @ file://[TEMP_DIR]/child` ├─▶ The build backend returned an error @@ -2786,13 +2773,12 @@ fn sync_build_dependencies_module_error_hints() -> Result<()> { "#})?; context.venv().arg("--clear").assert().success(); - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Installed [N] packages in [TIME] @@ -13696,13 +13682,12 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> { "#})?; // The child should be built with anyio 4.0 - uv_snapshot!(context.filters(), context.sync().env(EnvVars::EXPECTED_ANYIO_VERSION, "4.0"), @" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::EXPECTED_ANYIO_VERSION, "4.0"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Installed [N] packages in [TIME] @@ -13729,13 +13714,12 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> { // The child should be rebuilt with anyio 3.7, without `--reinstall` uv_snapshot!(context.filters(), context.sync() - .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "4.0"), @" + .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "4.0"), @r" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] × Failed to build `child @ file://[TEMP_DIR]/child` ├─▶ The build backend returned an error @@ -13749,13 +13733,12 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> { "); uv_snapshot!(context.filters(), context.sync() - .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "3.7"), @" + .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "3.7"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] Prepared [N] packages in [TIME] Uninstalled [N] packages in [TIME] @@ -13765,23 +13748,6 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> { ~ child==0.1.0 (from file://[TEMP_DIR]/child) "); - // With preview enabled, there's no warning - uv_snapshot!(context.filters(), context.sync() - .arg("--preview-features").arg("extra-build-dependencies") - .arg("--reinstall-package").arg("child") - .env(EnvVars::EXPECTED_ANYIO_VERSION, "3.7"), @" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Resolved [N] packages in [TIME] - Prepared [N] packages in [TIME] - Uninstalled [N] packages in [TIME] - Installed [N] packages in [TIME] - ~ child==0.1.0 (from file://[TEMP_DIR]/child) - "); - // Now, we'll set a constraint in the parent project pyproject_toml.write_str(indoc! {r#" [project] @@ -13812,13 +13778,12 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> { // This should fail uv_snapshot!(context.filters(), context.sync() - .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "4.1"), @" + .arg("--reinstall-package").arg("child").env(EnvVars::EXPECTED_ANYIO_VERSION, "4.1"), @r" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] × Failed to build `child @ file://[TEMP_DIR]/child` ├─▶ Failed to resolve requirements from `build-system.requires` and `extra-build-dependencies` @@ -13842,13 +13807,12 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> { child = [{ requirement = "anyio>4", match-runtime = true }] "#})?; - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved [N] packages in [TIME] error: Dependencies marked with `match-runtime = true` cannot include version specifiers, but found: `anyio>4` "); @@ -14012,13 +13976,12 @@ fn reject_unmatched_runtime() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock(), @" + uv_snapshot!(context.filters(), context.lock(), @r" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. × Failed to download and build `source-distribution==0.0.3` ╰─▶ Extra build requirement `iniconfig` was declared with `match-runtime = true`, but `source-distribution` does not declare static metadata, making runtime-matching impossible help: `source-distribution` (v0.0.3) was included because `foo` (v0.1.0) depends on `source-distribution` @@ -14483,13 +14446,12 @@ fn match_runtime_optional() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved 3 packages in [TIME] Audited in [TIME] "); @@ -14575,13 +14537,12 @@ fn sync_extra_build_dependencies_cache() -> Result<()> { "#})?; // Running `uv sync` should rebuild the child package with the new build dependency. - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] @@ -14590,13 +14551,12 @@ fn sync_extra_build_dependencies_cache() -> Result<()> { "); // Running `uv sync` again should be a no-op. - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved 2 packages in [TIME] Audited 1 package in [TIME] "); @@ -14616,13 +14576,12 @@ fn sync_extra_build_dependencies_cache() -> Result<()> { child = [{ requirement = "iniconfig>0", match-runtime = false }] "#})?; - uv_snapshot!(context.filters(), context.sync(), @" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning. Resolved 2 packages in [TIME] Prepared 1 package in [TIME] Uninstalled 1 package in [TIME] diff --git a/crates/uv/tests/it/tool_install.rs b/crates/uv/tests/it/tool_install.rs index 241749841a24d..6de4c61154af4 100644 --- a/crates/uv/tests/it/tool_install.rs +++ b/crates/uv/tests/it/tool_install.rs @@ -3,6 +3,7 @@ use std::collections::BTreeSet; use std::process::Command; use anyhow::Result; +use assert_cmd::assert::OutputAssertExt; use assert_fs::{ assert::PathAssert, fixture::{FileTouch, FileWriteStr, PathChild}, @@ -180,15 +181,20 @@ fn tool_install() { } #[test] -fn tool_install_with_global_python() -> Result<()> { - let context = TestContext::new_with_versions(&["3.11", "3.12"]) +fn tool_install_python_from_global_version_file() { + let context = TestContext::new_with_versions(&["3.11", "3.12", "3.13"]) .with_filtered_counts() .with_filtered_exe_suffix(); let tool_dir = context.temp_dir.child("tools"); let bin_dir = context.temp_dir.child("bin"); - let uv = context.user_config_dir.child("uv"); - let versions = uv.child(".python-version"); - versions.write_str("3.11")?; + + // Pin to 3.12 + context + .python_pin() + .arg("3.12") + .arg("--global") + .assert() + .success(); // Install a tool uv_snapshot!(context.filters(), context.tool_install() @@ -214,37 +220,158 @@ fn tool_install_with_global_python() -> Result<()> { Installed 1 executable: flask "); - tool_dir.child("flask").assert(predicate::path::is_dir()); - assert!( - bin_dir - .child(format!("flask{}", std::env::consts::EXE_SUFFIX)) - .exists() - ); + // It should use the version from the global file + uv_snapshot!(context.filters(), Command::new("flask").arg("--version").env(EnvVars::PATH, bin_dir.as_os_str()), @r" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.[X] + Flask 3.0.2 + Werkzeug 3.0.1 + + ----- stderr ----- + "); + + // Change global version + context + .python_pin() + .arg("3.13") + .arg("--global") + .assert() + .success(); + + // Installing flask again should be a no-op, even though the global pin changed + uv_snapshot!(context.filters(), context.tool_install() + .arg("flask") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + `flask` is already installed + "); uv_snapshot!(context.filters(), Command::new("flask").arg("--version").env(EnvVars::PATH, bin_dir.as_os_str()), @" success: true exit_code: 0 ----- stdout ----- - Python 3.11.[X] + Python 3.12.[X] Flask 3.0.2 Werkzeug 3.0.1 ----- stderr ----- "); - // Change global version - uv_snapshot!(context.filters(), context.python_pin().arg("3.12").arg("--global"), - @" + // Using `--upgrade` forces us to check the environment + uv_snapshot!(context.filters(), context.tool_install() + .arg("flask") + .arg("--upgrade") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @" success: true exit_code: 0 ----- stdout ----- - Updated `[UV_USER_CONFIG_DIR]/.python-version` from `3.11` -> `3.12` ----- stderr ----- - " - ); + Resolved [N] packages in [TIME] + Audited [N] packages in [TIME] + Installed 1 executable: flask + "); + + // This will not change to the new global pin, since there was not a reinstall request + uv_snapshot!(context.filters(), Command::new("flask").arg("--version").env(EnvVars::PATH, bin_dir.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.[X] + Flask 3.0.2 + Werkzeug 3.0.1 + + ----- stderr ----- + "); + + // Using `--reinstall` forces us to install flask again + uv_snapshot!(context.filters(), context.tool_install() + .arg("flask") + .arg("--reinstall") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- - // Install flask again + ----- stderr ----- + Ignoring existing environment for `flask`: the Python interpreter does not match the environment interpreter + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + blinker==1.7.0 + + click==8.1.7 + + flask==3.0.2 + + itsdangerous==2.1.2 + + jinja2==3.1.3 + + markupsafe==2.1.5 + + werkzeug==3.0.1 + Installed 1 executable: flask + "); + + // This will change to the new global pin, since there was not an explicit request recorded in + // the receipt + uv_snapshot!(context.filters(), Command::new("flask").arg("--version").env(EnvVars::PATH, bin_dir.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.13.[X] + Flask 3.0.2 + Werkzeug 3.0.1 + + ----- stderr ----- + "); + + // If we request a specific Python version, it takes precedence over the pin + uv_snapshot!(context.filters(), context.tool_install() + .arg("flask") + .arg("--python") + .arg("3.11") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Ignoring existing environment for `flask`: the requested Python interpreter does not match the environment interpreter + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + blinker==1.7.0 + + click==8.1.7 + + flask==3.0.2 + + itsdangerous==2.1.2 + + jinja2==3.1.3 + + markupsafe==2.1.5 + + werkzeug==3.0.1 + Installed 1 executable: flask + "); + + uv_snapshot!(context.filters(), Command::new("flask").arg("--version").env(EnvVars::PATH, bin_dir.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.11.[X] + Flask 3.0.2 + Werkzeug 3.0.1 + + ----- stderr ----- + "); + + // Use `--reinstall` to install flask again uv_snapshot!(context.filters(), context.tool_install() .arg("flask") .arg("--reinstall") @@ -270,8 +397,7 @@ fn tool_install_with_global_python() -> Result<()> { Installed 1 executable: flask "); - // Currently, when reinstalling a tool we use the original version the tool - // was installed with, not the most up-to-date global version + // We should continue to use the version from the install, not the global pin uv_snapshot!(context.filters(), Command::new("flask").arg("--version").env(EnvVars::PATH, bin_dir.as_os_str()), @" success: true exit_code: 0 @@ -282,8 +408,6 @@ fn tool_install_with_global_python() -> Result<()> { ----- stderr ----- "); - - Ok(()) } #[test] diff --git a/crates/uv/tests/it/tool_run.rs b/crates/uv/tests/it/tool_run.rs index ad7bb487bc25e..e5f0ce24fa442 100644 --- a/crates/uv/tests/it/tool_run.rs +++ b/crates/uv/tests/it/tool_run.rs @@ -2396,6 +2396,93 @@ fn tool_run_hint_version_not_available() { "); } +#[test] +fn tool_run_python_from_global_version_file() { + let context = TestContext::new_with_versions(&["3.12", "3.11"]) + .with_filtered_counts() + .with_filtered_python_sources(); + + context + .python_pin() + .arg("3.11") + .arg("--global") + .assert() + .success(); + + uv_snapshot!(context.filters(), context.tool_run() + .arg("python") + .arg("--version"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.11.[X] + + ----- stderr ----- + Resolved in [TIME] + Audited in [TIME] + "###); +} + +#[test] +fn tool_run_python_version_overrides_global_pin() { + let context = TestContext::new_with_versions(&["3.12", "3.11"]) + .with_filtered_counts() + .with_filtered_python_sources(); + + // Set global pin to 3.11 + context + .python_pin() + .arg("3.11") + .arg("--global") + .assert() + .success(); + + // Explicitly request python3.12, should override global pin + uv_snapshot!(context.filters(), context.tool_run() + .arg("python3.12") + .arg("--version"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.[X] + + ----- stderr ----- + Resolved in [TIME] + Audited in [TIME] + "###); +} + +#[test] +fn tool_run_python_with_explicit_default_bypasses_global_pin() { + let context = TestContext::new_with_versions(&["3.12", "3.11"]) + .with_filtered_counts() + .with_filtered_python_sources(); + + // Set global pin to 3.11 + context + .python_pin() + .arg("3.11") + .arg("--global") + .assert() + .success(); + + // Explicitly request --python default, should bypass global pin and use system default (3.12) + uv_snapshot!(context.filters(), context.tool_run() + .arg("--python") + .arg("default") + .arg("python") + .arg("--version"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.[X] + + ----- stderr ----- + Resolved in [TIME] + Audited in [TIME] + "###); +} + #[test] fn tool_run_python_from() { let context = TestContext::new_with_versions(&["3.12", "3.11"]) diff --git a/crates/uv/tests/it/venv.rs b/crates/uv/tests/it/venv.rs index 928166d1e0b58..0936105bbd159 100644 --- a/crates/uv/tests/it/venv.rs +++ b/crates/uv/tests/it/venv.rs @@ -36,16 +36,16 @@ fn create_venv() { uv_snapshot!(context.filters(), context.venv() .arg(context.venv.as_os_str()) .arg("--python") - .arg("3.12"), @" - success: true - exit_code: 0 + .arg("3.12"), @r" + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it - Activate with: source .venv/[BIN]/activate + error: Failed to create virtual environment + Caused by: A virtual environment already exists at `[VENV]/`. Use `--clear` to replace it " ); @@ -221,7 +221,7 @@ fn virtual_empty() -> Result<()> { wow = "someconfig" "#})?; - uv_snapshot!(context.filters(), context.venv(), @" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r" success: true exit_code: 0 ----- stdout ----- @@ -229,7 +229,6 @@ fn virtual_empty() -> Result<()> { ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it Activate with: source .venv/[BIN]/activate "); @@ -250,7 +249,7 @@ fn virtual_dependency_group() -> Result<()> { dev = ["sniffio"] "#})?; - uv_snapshot!(context.filters(), context.venv(), @" + uv_snapshot!(context.filters(), context.venv().arg("--clear"), @r" success: true exit_code: 0 ----- stdout ----- @@ -258,7 +257,6 @@ fn virtual_dependency_group() -> Result<()> { ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it Activate with: source .venv/[BIN]/activate "); @@ -1302,6 +1300,11 @@ fn verify_pyvenv_cfg_relocatable() { activate_nu.assert(predicates::str::contains( r"let virtual_env = (path self | path dirname | path dirname)", )); + + // csh cannot determine its own script location, so activate.csh should not + // be generated when --relocatable is used. + let activate_csh = scripts.child("activate.csh"); + activate_csh.assert(predicates::path::missing()); } /// Ensure that a nested virtual environment uses the same `home` directory as the parent. @@ -1457,19 +1460,19 @@ fn venv_python_preference() { Activate with: source .venv/[BIN]/activate "); - uv_snapshot!(context.filters(), context.venv().arg("--no-managed-python"), @" - success: true - exit_code: 0 + uv_snapshot!(context.filters(), context.venv().arg("--no-managed-python"), @r" + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- Using CPython 3.11.[X] interpreter at: [PYTHON-3.11] Creating virtual environment at: .venv - warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it - Activate with: source .venv/[BIN]/activate + error: Failed to create virtual environment + Caused by: A virtual environment already exists at `.venv`. Use `--clear` to replace it "); - uv_snapshot!(context.filters(), context.venv().arg("--no-managed-python"), @" + uv_snapshot!(context.filters(), context.venv().arg("--clear").arg("--no-managed-python"), @" success: true exit_code: 0 ----- stdout ----- @@ -1477,23 +1480,22 @@ fn venv_python_preference() { ----- stderr ----- Using CPython 3.11.[X] interpreter at: [PYTHON-3.11] Creating virtual environment at: .venv - warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it Activate with: source .venv/[BIN]/activate "); - uv_snapshot!(context.filters(), context.venv(), @" - success: true - exit_code: 0 + uv_snapshot!(context.filters(), context.venv(), @r" + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- Using CPython 3.12.[X] Creating virtual environment at: .venv - warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it - Activate with: source .venv/[BIN]/activate + error: Failed to create virtual environment + Caused by: A virtual environment already exists at `.venv`. Use `--clear` to replace it "); - uv_snapshot!(context.filters(), context.venv().arg("--managed-python"), @" + uv_snapshot!(context.filters(), context.venv().arg("--clear").arg("--managed-python"), @" success: true exit_code: 0 ----- stdout ----- @@ -1501,7 +1503,6 @@ fn venv_python_preference() { ----- stderr ----- Using CPython 3.12.[X] Creating virtual environment at: .venv - warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it Activate with: source .venv/[BIN]/activate "); } @@ -1599,11 +1600,12 @@ fn create_venv_symlink_recreate_preservation() -> Result<()> { // Verify symlink is preserved after first creation assert!(symlink_path.path().is_symlink()); - // Run uv venv again WITHOUT --clear to test recreation behavior + // Run uv venv again with --clear to test symlink preservation during recreation uv_snapshot!(context.filters(), context.venv() .arg(symlink_path.as_os_str()) + .arg("--clear") .arg("--python") - .arg("3.12"), @" + .arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1611,7 +1613,6 @@ fn create_venv_symlink_recreate_preservation() -> Result<()> { ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it Activate with: source .venv/[BIN]/activate " ); @@ -1663,11 +1664,12 @@ fn create_venv_nested_symlink_preservation() -> Result<()> { assert!(symlink_path.path().is_symlink()); assert!(intermediate_link.path().is_symlink()); - // Run uv venv again to test nested symlink preservation during recreation + // Run uv venv again with --clear to test nested symlink preservation during recreation uv_snapshot!(context.filters(), context.venv() .arg(symlink_path.as_os_str()) + .arg("--clear") .arg("--python") - .arg("3.12"), @" + .arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1675,7 +1677,6 @@ fn create_venv_nested_symlink_preservation() -> Result<()> { ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] Creating virtual environment at: .venv - warning: A virtual environment already exists at `.venv`. In the future, uv will require `--clear` to replace it Activate with: source .venv/[BIN]/activate " ); diff --git a/crates/uv/tests/it/workspace_dir.rs b/crates/uv/tests/it/workspace_dir.rs index 9fc47b025572e..a85933caed12f 100644 --- a/crates/uv/tests/it/workspace_dir.rs +++ b/crates/uv/tests/it/workspace_dir.rs @@ -21,7 +21,7 @@ fn workspace_dir_simple() { [TEMP_DIR]/foo ----- stderr ----- - warning: The `uv workspace dir` command is experimental and may change without warning. Pass `--preview-features workspace-dir` to disable this warning. + " ); } @@ -42,7 +42,7 @@ fn workspace_dir_specific_package() { [TEMP_DIR]/foo ----- stderr ----- - warning: The `uv workspace dir` command is experimental and may change without warning. Pass `--preview-features workspace-dir` to disable this warning. + " ); @@ -54,7 +54,7 @@ fn workspace_dir_specific_package() { [TEMP_DIR]/foo/bar ----- stderr ----- - warning: The `uv workspace dir` command is experimental and may change without warning. Pass `--preview-features workspace-dir` to disable this warning. + " ); } @@ -80,7 +80,7 @@ fn workspace_metadata_from_member() -> Result<()> { [TEMP_DIR]/workspace ----- stderr ----- - warning: The `uv workspace dir` command is experimental and may change without warning. Pass `--preview-features workspace-dir` to disable this warning. + " ); @@ -97,13 +97,12 @@ fn workspace_dir_package_doesnt_exist() { let workspace = context.temp_dir.child("foo"); - uv_snapshot!(context.filters(), context.workspace_dir().arg("--package").arg("bar").current_dir(&workspace), @" + uv_snapshot!(context.filters(), context.workspace_dir().arg("--package").arg("bar").current_dir(&workspace), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - warning: The `uv workspace dir` command is experimental and may change without warning. Pass `--preview-features workspace-dir` to disable this warning. error: Package `bar` not found in workspace. " ); @@ -114,13 +113,12 @@ fn workspace_dir_package_doesnt_exist() { fn workspace_metadata_no_project() { let context = TestContext::new("3.12"); - uv_snapshot!(context.filters(), context.workspace_dir(), @" + uv_snapshot!(context.filters(), context.workspace_dir(), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - warning: The `uv workspace dir` command is experimental and may change without warning. Pass `--preview-features workspace-dir` to disable this warning. error: No `pyproject.toml` found in current directory or any parent directory " ); diff --git a/crates/uv/tests/it/workspace_list.rs b/crates/uv/tests/it/workspace_list.rs index e120722dba38d..b941a1e51a993 100644 --- a/crates/uv/tests/it/workspace_list.rs +++ b/crates/uv/tests/it/workspace_list.rs @@ -21,7 +21,7 @@ fn workspace_list_simple() { foo ----- stderr ----- - warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning. + " ); @@ -32,7 +32,7 @@ fn workspace_list_simple() { [TEMP_DIR]/foo ----- stderr ----- - warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning. + " ); } @@ -59,7 +59,7 @@ fn workspace_list_root_workspace() -> Result<()> { seeds ----- stderr ----- - warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning. + " ); @@ -88,7 +88,7 @@ fn workspace_list_virtual_workspace() -> Result<()> { seeds ----- stderr ----- - warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning. + " ); @@ -119,7 +119,7 @@ fn workspace_list_from_member() -> Result<()> { seeds ----- stderr ----- - warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning. + " ); @@ -160,7 +160,7 @@ fn workspace_list_multiple_members() { pkg-c ----- stderr ----- - warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning. + " ); @@ -173,7 +173,7 @@ fn workspace_list_multiple_members() { [TEMP_DIR]/pkg-a/pkg-c ----- stderr ----- - warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning. + " ); } @@ -194,7 +194,7 @@ fn workspace_list_single_project() { my-project ----- stderr ----- - warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning. + " ); } @@ -219,7 +219,7 @@ fn workspace_list_with_excluded() -> Result<()> { albatross ----- stderr ----- - warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning. + " ); @@ -231,13 +231,12 @@ fn workspace_list_with_excluded() -> Result<()> { fn workspace_list_no_project() { let context = TestContext::new("3.12"); - uv_snapshot!(context.filters(), context.workspace_list(), @" + uv_snapshot!(context.filters(), context.workspace_list(), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning. error: No `pyproject.toml` found in current directory or any parent directory " ); diff --git a/docs/guides/integration/docker.md b/docs/guides/integration/docker.md index 446056f9bc4aa..8f0aea795eb1a 100644 --- a/docs/guides/integration/docker.md +++ b/docs/guides/integration/docker.md @@ -38,29 +38,30 @@ The following distroless images are available: And the following derived images are available: -- Based on `alpine:3.22`: +- Based on `alpine:3.23`: - `ghcr.io/astral-sh/uv:alpine` + - `ghcr.io/astral-sh/uv:alpine3.23` +- Based on `alpine:3.22`: - `ghcr.io/astral-sh/uv:alpine3.22` -- Based on `alpine:3.21`: - - `ghcr.io/astral-sh/uv:alpine3.21` - Based on `debian:trixie-slim`: - `ghcr.io/astral-sh/uv:debian-slim` - `ghcr.io/astral-sh/uv:trixie-slim` -- Based on `debian:bookworm-slim`: - - `ghcr.io/astral-sh/uv:bookworm-slim` - Based on `buildpack-deps:trixie`: - `ghcr.io/astral-sh/uv:debian` - `ghcr.io/astral-sh/uv:trixie` -- Based on `buildpack-deps:bookworm`: - - `ghcr.io/astral-sh/uv:bookworm` - Based on `python3.x-alpine`: - `ghcr.io/astral-sh/uv:python3.14-alpine` + - `ghcr.io/astral-sh/uv:python3.14-alpine3.23` - `ghcr.io/astral-sh/uv:python3.13-alpine` + - `ghcr.io/astral-sh/uv:python3.13-alpine3.23` - `ghcr.io/astral-sh/uv:python3.12-alpine` + - `ghcr.io/astral-sh/uv:python3.12-alpine3.23` - `ghcr.io/astral-sh/uv:python3.11-alpine` + - `ghcr.io/astral-sh/uv:python3.11-alpine3.23` - `ghcr.io/astral-sh/uv:python3.10-alpine` + - `ghcr.io/astral-sh/uv:python3.10-alpine3.23` - `ghcr.io/astral-sh/uv:python3.9-alpine` - - `ghcr.io/astral-sh/uv:python3.8-alpine` + - `ghcr.io/astral-sh/uv:python3.9-alpine3.22` - Based on `python3.x-trixie`: - `ghcr.io/astral-sh/uv:python3.14-trixie` - `ghcr.io/astral-sh/uv:python3.13-trixie` @@ -75,22 +76,6 @@ And the following derived images are available: - `ghcr.io/astral-sh/uv:python3.11-trixie-slim` - `ghcr.io/astral-sh/uv:python3.10-trixie-slim` - `ghcr.io/astral-sh/uv:python3.9-trixie-slim` -- Based on `python3.x-bookworm`: - - `ghcr.io/astral-sh/uv:python3.14-bookworm` - - `ghcr.io/astral-sh/uv:python3.13-bookworm` - - `ghcr.io/astral-sh/uv:python3.12-bookworm` - - `ghcr.io/astral-sh/uv:python3.11-bookworm` - - `ghcr.io/astral-sh/uv:python3.10-bookworm` - - `ghcr.io/astral-sh/uv:python3.9-bookworm` - - `ghcr.io/astral-sh/uv:python3.8-bookworm` -- Based on `python3.x-slim-bookworm`: - - `ghcr.io/astral-sh/uv:python3.14-bookworm-slim` - - `ghcr.io/astral-sh/uv:python3.13-bookworm-slim` - - `ghcr.io/astral-sh/uv:python3.12-bookworm-slim` - - `ghcr.io/astral-sh/uv:python3.11-bookworm-slim` - - `ghcr.io/astral-sh/uv:python3.10-bookworm-slim` - - `ghcr.io/astral-sh/uv:python3.9-bookworm-slim` - - `ghcr.io/astral-sh/uv:python3.8-bookworm-slim` As with the distroless image, each derived image is published with uv version tags as diff --git a/docs/reference/policies/platforms.md b/docs/reference/policies/platforms.md index 3e9ad0e0412ad..00b55c5d88662 100644 --- a/docs/reference/policies/platforms.md +++ b/docs/reference/policies/platforms.md @@ -15,7 +15,6 @@ uv has Tier 2 support (["guaranteed to build"](https://doc.rust-lang.org/beta/rustc/platform-support.html)) for the following platforms: -- Linux (PPC64) - Linux (PPC64LE) - Linux (RISC-V64) - Linux (aarch64)