Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 4 additions & 21 deletions .github/workflows/build-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 }}
Expand Down
7 changes: 0 additions & 7 deletions .github/workflows/build-release-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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 }}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 }}
Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion crates/uv-bin-install/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
}
}

Expand Down
52 changes: 51 additions & 1 deletion crates/uv-distribution-types/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<IndexName>,
url: IndexUrl,
#[serde(default)]
explicit: bool,
#[serde(default)]
default: bool,
#[serde(default)]
format: IndexFormat,
publish_url: Option<DisplaySafeUrl>,
#[serde(default)]
authenticate: AuthPolicy,
#[serde(default)]
ignore_error_codes: Option<Vec<SerializableStatusCode>>,
#[serde(default)]
cache_control: Option<IndexCacheControl>,
}

impl<'de> Deserialize<'de> for Index {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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 {
Expand Down
14 changes: 13 additions & 1 deletion crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand All @@ -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<H: std::hash::Hasher>(&self, state: &mut H) {
self.to_canonical_string().hash(state);
}
}

impl<'a> serde::Deserialize<'a> for PythonRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
Expand Down
16 changes: 16 additions & 0 deletions crates/uv-python/src/implementation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,22 @@ 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",
Self::PyPy | Self::GraalPy => self.into(),
}
}

/// 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(),
Expand All @@ -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 {
Expand Down
9 changes: 6 additions & 3 deletions crates/uv-python/src/installation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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
Expand All @@ -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
)
Expand Down
1 change: 0 additions & 1 deletion crates/uv-virtualenv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
9 changes: 8 additions & 1 deletion crates/uv-virtualenv/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::io;
use std::path::Path;
use std::path::{Path, PathBuf};

use thiserror::Error;

Expand All @@ -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.
Expand Down
26 changes: 14 additions & 12 deletions crates/uv-virtualenv/src/virtualenv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)] = &[
Expand Down Expand Up @@ -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(),
});
}
}
}
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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()),
};

Expand Down
9 changes: 3 additions & 6 deletions crates/uv-workspace/src/pyproject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down
Loading
Loading