Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
8 changes: 8 additions & 0 deletions docs/notes/2.32.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ Copying the `mypy` cache back to the ["named cache"](https://www.pantsbuild.org/

The mypy subsystem now supports a new `cache_mode="none"` to disable mypy's caching entirely. This is slower and intended as an "escape valve". It is hoped that on the latest mypy with the above Pants side fixes it will not be necessary.

A new **experimental** `[python].pex_builder` option allows using [uv](https://github.com/astral-sh/uv) to install
dependencies when building PEX binaries via `pants package`. When set to `"uv"`, Pants creates a virtual environment
with uv, then passes it to PEX via `--venv-repository` so PEX packages from the pre-populated venv instead of
resolving with pip. When a PEX-native lockfile is available, uv installs the exact pinned versions from the lockfile
with `--no-deps`, preserving reproducibility. This only applies to non-internal, non-cross-platform PEX builds with
explicit requirement strings and a local Python interpreter; other builds silently fall back to pip.
See [#20679](https://github.com/pantsbuild/pants/issues/20679) for background.

The `runtime` field of [`aws_python_lambda_layer`](https://www.pantsbuild.org/2.32/reference/targets/python_aws_lambda_layer#runtime) or [`aws_python_lambda_function`](https://www.pantsbuild.org/2.32/reference/targets/python_aws_lambda_function#runtime) now has built-in complete platform configurations for x86-64 and arm64 Python 3.14. This provides stable support for Python 3.14 lambdas out of the box, allowing deleting manual `complete_platforms` configuration if any.

The `grpc-python-plugin` tool now uses an updated `v1.73.1` plugin built from <https://github.com/nhurden/protoc-gen-grpc-python-prebuilt]. This also brings `macos_arm64` support.
Expand Down
3 changes: 2 additions & 1 deletion src/python/pants/backend/python/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from pants.backend.python.macros.python_artifact import PythonArtifact
from pants.backend.python.macros.python_requirements import PythonRequirementsTargetGenerator
from pants.backend.python.macros.uv_requirements import UvRequirementsTargetGenerator
from pants.backend.python.subsystems import debugpy
from pants.backend.python.subsystems import debugpy, uv
from pants.backend.python.target_types import (
PexBinariesGeneratorTarget,
PexBinary,
Expand Down Expand Up @@ -70,6 +70,7 @@ def rules():
# Subsystems
*coverage_py.rules(),
*debugpy.rules(),
*uv.rules(),
# Util rules
*ancestor_files.rules(),
*dependency_inference_rules.rules(),
Expand Down
23 changes: 23 additions & 0 deletions src/python/pants/backend/python/subsystems/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ class LockfileGenerator(enum.Enum):
POETRY = "poetry"


@enum.unique
class PexBuilder(enum.Enum):
pex = "pex"
uv = "uv"


RESOLVE_OPTION_KEY__DEFAULT = "__default__"

_T = TypeVar("_T")
Expand Down Expand Up @@ -312,6 +318,23 @@ def default_to_resolve_interpreter_constraints(self) -> bool:
),
advanced=True,
)
pex_builder = EnumOption(
default=PexBuilder.pex,
help=softwrap(
"""
Which tool to use for installing dependencies when building PEX files.

- `pex` (default): Use pip via PEX.
- `uv` (experimental): Pre-install dependencies into a uv venv, then pass it
to PEX via `--venv-repository`. When a PEX-native lockfile is available,
uv installs the exact pinned versions with `--no-deps`.

Only applies to non-internal, non-cross-platform PEX builds. Other builds
silently fall back to pip.
"""
),
advanced=True,
)
_resolves_to_interpreter_constraints = DictOption[list[str]](
help=softwrap(
"""
Expand Down
78 changes: 78 additions & 0 deletions src/python/pants/backend/python/subsystems/uv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright 2026 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

from dataclasses import dataclass

from pants.core.util_rules.external_tool import (
TemplatedExternalTool,
download_external_tool,
)
from pants.engine.fs import Digest
from pants.engine.platform import Platform
from pants.engine.rules import collect_rules, rule
from pants.option.option_types import ArgsListOption
from pants.util.strutil import softwrap


class Uv(TemplatedExternalTool):
options_scope = "uv"
name = "uv"
help = "The uv Python package manager (https://github.com/astral-sh/uv)."

default_version = "0.6.14"
default_known_versions = [
"0.6.14|macos_x86_64|1d8ecb2eb3b68fb50e4249dc96ac9d2458dc24068848f04f4c5b42af2fd26552|16276555",
"0.6.14|macos_arm64|4ea4731010fbd1bc8e790e07f199f55a5c7c2c732e9b77f85e302b0bee61b756|15138933",
"0.6.14|linux_x86_64|0cac4df0cb3457b154f2039ae471e89cd4e15f3bd790bbb3cb0b8b40d940b93e|17032361",
"0.6.14|linux_arm64|94e22c4be44d205def456427639ca5ca1c1a9e29acc31808a7b28fdd5dcf7f17|15577079",
]
version_constraints = ">=0.6.0,<1.0"

default_url_template = (
"https://github.com/astral-sh/uv/releases/download/{version}/uv-{platform}.tar.gz"
)
default_url_platform_mapping = {
"linux_arm64": "aarch64-unknown-linux-musl",
"linux_x86_64": "x86_64-unknown-linux-musl",
"macos_arm64": "aarch64-apple-darwin",
"macos_x86_64": "x86_64-apple-darwin",
}

def generate_exe(self, plat: Platform) -> str:
platform = self.default_url_platform_mapping[plat.value]
return f"./uv-{platform}/uv"

args_for_uv_pip_install = ArgsListOption(
tool_name="uv",
example="--index-strategy unsafe-first-match",
extra_help=softwrap(
"""
Additional arguments to pass to `uv pip install` invocations.

Used when `[python].pex_builder = "uv"` to pass extra flags to the
`uv pip install` step (e.g. `--index-url`, `--extra-index-url`).
These are NOT passed to the `uv venv` step.
"""
),
)


@dataclass(frozen=True)
class DownloadedUv:
"""The downloaded uv binary with user-configured args."""

digest: Digest
exe: str
args_for_uv_pip_install: tuple[str, ...]


@rule
async def download_uv_binary(uv: Uv, platform: Platform) -> DownloadedUv:
downloaded = await download_external_tool(uv.get_request(platform))
return DownloadedUv(digest=downloaded.digest, exe=downloaded.exe, args_for_uv_pip_install=tuple(uv.args_for_uv_pip_install))


def rules():
return collect_rules()
Loading