Compare commits

..

No commits in common. "main" and "0.9.16" have entirely different histories.
main ... 0.9.16

479 changed files with 14796 additions and 8377 deletions

View File

@ -1,81 +0,0 @@
# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///
"""Post-edit hook to auto-format files after Claude edits."""
import json
import subprocess
import sys
from pathlib import Path
def format_rust(file_path: str, cwd: str) -> None:
"""Format Rust files with cargo fmt."""
try:
subprocess.run(
["cargo", "fmt", "--", file_path],
cwd=cwd,
capture_output=True,
)
except FileNotFoundError:
pass
def format_python(file_path: str, cwd: str) -> None:
"""Format Python files with ruff."""
try:
subprocess.run(
["uvx", "ruff", "format", file_path],
cwd=cwd,
capture_output=True,
)
except FileNotFoundError:
pass
def format_prettier(file_path: str, cwd: str, prose_wrap: bool = False) -> None:
"""Format files with prettier."""
args = ["npx", "prettier", "--write"]
if prose_wrap:
args.extend(["--prose-wrap", "always"])
args.append(file_path)
try:
subprocess.run(args, cwd=cwd, capture_output=True)
except FileNotFoundError:
pass
def main() -> None:
import os
input_data = json.load(sys.stdin)
tool_name = input_data.get("tool_name")
tool_input = input_data.get("tool_input", {})
file_path = tool_input.get("file_path")
# Only process Write, Edit, and MultiEdit tools
if tool_name not in ("Write", "Edit", "MultiEdit"):
return
if not file_path:
return
cwd = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
path = Path(file_path)
ext = path.suffix
if ext == ".rs":
format_rust(file_path, cwd)
elif ext in (".py", ".pyi"):
format_python(file_path, cwd)
elif ext in (".json5", ".yaml", ".yml"):
format_prettier(file_path, cwd)
elif ext == ".md":
format_prettier(file_path, cwd, prose_wrap=True)
if __name__ == "__main__":
main()

View File

@ -1,15 +0,0 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "uv run .claude/hooks/post-edit-format.py"
}
]
}
]
}
}

View File

@ -98,7 +98,7 @@ jobs:
macos-x86_64: macos-x86_64:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: depot-macos-14 runs-on: macos-14
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with: with:
@ -157,7 +157,7 @@ jobs:
macos-aarch64: macos-aarch64:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: depot-macos-14 runs-on: macos-14
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with: with:
@ -417,7 +417,7 @@ jobs:
linux-arm: linux-arm:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: depot-ubuntu-22.04-8 runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
strategy: strategy:
matrix: matrix:
@ -956,7 +956,7 @@ jobs:
musllinux-cross: musllinux-cross:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
runs-on: depot-ubuntu-22.04-8 runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
platform: platform:

View File

@ -184,13 +184,13 @@ jobs:
- buildpack-deps:trixie,trixie,debian - buildpack-deps:trixie,trixie,debian
- debian:bookworm-slim,bookworm-slim - debian:bookworm-slim,bookworm-slim
- buildpack-deps:bookworm,bookworm - buildpack-deps:bookworm,bookworm
- python:3.14-alpine3.23,python3.14-alpine3.23,python3.14-alpine - python:3.14-alpine,python3.14-alpine
- python:3.13-alpine3.23,python3.13-alpine3.23,python3.13-alpine - python:3.13-alpine,python3.13-alpine
- python:3.12-alpine3.23,python3.12-alpine3.23,python3.12-alpine - python:3.12-alpine,python3.12-alpine
- python:3.11-alpine3.23,python3.11-alpine3.23,python3.11-alpine - python:3.11-alpine,python3.11-alpine
- python:3.10-alpine3.23,python3.10-alpine3.23,python3.10-alpine - python:3.10-alpine,python3.10-alpine
- python:3.9-alpine3.22,python3.9-alpine3.22,python3.9-alpine - python:3.9-alpine,python3.9-alpine
- python:3.8-alpine3.20,python3.8-alpine3.20,python3.8-alpine - python:3.8-alpine,python3.8-alpine
- python:3.14-trixie,python3.14-trixie - python:3.14-trixie,python3.14-trixie
- python:3.13-trixie,python3.13-trixie - python:3.13-trixie,python3.13-trixie
- python:3.12-trixie,python3.12-trixie - python:3.12-trixie,python3.12-trixie

View File

@ -27,8 +27,6 @@ jobs:
outputs: outputs:
# Flag that is raised when any code is changed # Flag that is raised when any code is changed
code: ${{ steps.changed.outputs.code_any_changed }} code: ${{ steps.changed.outputs.code_any_changed }}
# Flag that is raised when uv.schema.json is changed (e.g., in a release PR)
schema: ${{ steps.changed.outputs.schema_changed }}
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with: with:
@ -42,16 +40,10 @@ jobs:
CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha || 'origin/main' }}...HEAD) CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha || 'origin/main' }}...HEAD)
CODE_CHANGED=false CODE_CHANGED=false
SCHEMA_CHANGED=false
while IFS= read -r file; do while IFS= read -r file; do
# Check if the schema file changed (e.g., in a release PR) # Generated markdown and JSON files are checked during test runs.
if [[ "${file}" == "uv.schema.json" ]]; then if [[ "${file}" =~ ^docs/ && ! "${file}" =~ ^docs/reference/(cli|settings).md && ! "${file}" =~ ^docs/reference/environment.md ]]; then
echo "Detected schema change: ${file}"
SCHEMA_CHANGED=true
fi
if [[ "${file}" =~ ^docs/ ]]; then
echo "Skipping ${file} (matches docs/ pattern)" echo "Skipping ${file} (matches docs/ pattern)"
continue continue
fi fi
@ -78,7 +70,6 @@ jobs:
done <<< "${CHANGED_FILES}" done <<< "${CHANGED_FILES}"
echo "code_any_changed=${CODE_CHANGED}" >> "${GITHUB_OUTPUT}" echo "code_any_changed=${CODE_CHANGED}" >> "${GITHUB_OUTPUT}"
echo "schema_changed=${SCHEMA_CHANGED}" >> "${GITHUB_OUTPUT}"
lint: lint:
timeout-minutes: 10 timeout-minutes: 10
name: "lint" name: "lint"
@ -217,12 +208,7 @@ jobs:
with: with:
save-if: ${{ github.ref == 'refs/heads/main' }} save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Generate all" - name: "Generate all"
run: cargo dev generate-all --mode dry-run run: cargo dev generate-all --mode check
- name: "Check sysconfig mappings"
run: cargo dev generate-sysconfig-metadata --mode check
- name: "Check JSON schema"
if: ${{ needs.determine_changes.outputs.schema == 'true' }}
run: cargo dev generate-json-schema --mode check
cargo-shear: cargo-shear:
timeout-minutes: 10 timeout-minutes: 10
@ -233,7 +219,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: "Install cargo shear" - name: "Install cargo shear"
uses: taiki-e/install-action@d850aa816998e5cf15f67a78c7b933f2a5033f8a # v2.63.3 uses: taiki-e/install-action@a416ddeedbd372e614cc1386e8b642692f66865e # v2.57.1
with: with:
tool: cargo-shear tool: cargo-shear
- run: cargo shear - run: cargo shear
@ -289,7 +275,6 @@ jobs:
UV_HTTP_RETRIES: 5 UV_HTTP_RETRIES: 5
run: | run: |
cargo nextest run \ cargo nextest run \
--cargo-profile fast-build \
--features python-patch,native-auth,secret-service \ --features python-patch,native-auth,secret-service \
--workspace \ --workspace \
--status-level skip --failure-output immediate-final --no-fail-fast -j 20 --final-status-level slow --status-level skip --failure-output immediate-final --no-fail-fast -j 20 --final-status-level slow
@ -331,7 +316,6 @@ jobs:
UV_HTTP_RETRIES: 5 UV_HTTP_RETRIES: 5
run: | run: |
cargo nextest run \ cargo nextest run \
--cargo-profile fast-build \
--no-default-features \ --no-default-features \
--features python,python-managed,pypi,git,git-lfs,performance,crates-io,native-auth,apple-native \ --features python,python-managed,pypi,git,git-lfs,performance,crates-io,native-auth,apple-native \
--workspace \ --workspace \
@ -387,7 +371,6 @@ jobs:
shell: bash shell: bash
run: | run: |
cargo nextest run \ cargo nextest run \
--cargo-profile fast-build \
--no-default-features \ --no-default-features \
--features python,pypi,python-managed,native-auth,windows-native \ --features python,pypi,python-managed,native-auth,windows-native \
--workspace \ --workspace \
@ -527,14 +510,6 @@ jobs:
version: "0.9.13" version: "0.9.13"
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Generate reference documentation"
run: |
cargo dev generate-options-reference
cargo dev generate-cli-reference
cargo dev generate-env-vars-reference
- name: "Add SSH key" - name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }} if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
@ -564,15 +539,15 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Build" - name: "Build"
run: cargo build --profile no-debug run: cargo build
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: uv-linux-libc-${{ github.sha }} name: uv-linux-libc-${{ github.sha }}
path: | path: |
./target/no-debug/uv ./target/debug/uv
./target/no-debug/uvx ./target/debug/uvx
retention-days: 1 retention-days: 1
build-binary-linux-aarch64: build-binary-linux-aarch64:
@ -591,15 +566,15 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Build" - name: "Build"
run: cargo build --profile no-debug run: cargo build
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: uv-linux-aarch64-${{ github.sha }} name: uv-linux-aarch64-${{ github.sha }}
path: | path: |
./target/no-debug/uv ./target/debug/uv
./target/no-debug/uvx ./target/debug/uvx
retention-days: 1 retention-days: 1
build-binary-linux-musl: build-binary-linux-musl:
@ -623,15 +598,15 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Build" - name: "Build"
run: cargo build --profile no-debug --target x86_64-unknown-linux-musl --bin uv --bin uvx run: cargo build --target x86_64-unknown-linux-musl --bin uv --bin uvx
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: uv-linux-musl-${{ github.sha }} name: uv-linux-musl-${{ github.sha }}
path: | path: |
./target/x86_64-unknown-linux-musl/no-debug/uv ./target/x86_64-unknown-linux-musl/debug/uv
./target/x86_64-unknown-linux-musl/no-debug/uvx ./target/x86_64-unknown-linux-musl/debug/uvx
retention-days: 1 retention-days: 1
build-binary-macos-aarch64: build-binary-macos-aarch64:
@ -649,15 +624,15 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Build" - name: "Build"
run: cargo build --profile no-debug --bin uv --bin uvx run: cargo build --bin uv --bin uvx
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: uv-macos-aarch64-${{ github.sha }} name: uv-macos-aarch64-${{ github.sha }}
path: | path: |
./target/no-debug/uv ./target/debug/uv
./target/no-debug/uvx ./target/debug/uvx
retention-days: 1 retention-days: 1
build-binary-macos-x86_64: build-binary-macos-x86_64:
@ -675,15 +650,15 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Build" - name: "Build"
run: cargo build --profile no-debug --bin uv --bin uvx run: cargo build --bin uv --bin uvx
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: uv-macos-x86_64-${{ github.sha }} name: uv-macos-x86_64-${{ github.sha }}
path: | path: |
./target/no-debug/uv ./target/debug/uv
./target/no-debug/uvx ./target/debug/uvx
retention-days: 1 retention-days: 1
build-binary-windows-x86_64: build-binary-windows-x86_64:
@ -711,15 +686,15 @@ jobs:
- name: "Build" - name: "Build"
working-directory: ${{ env.UV_WORKSPACE }} working-directory: ${{ env.UV_WORKSPACE }}
run: cargo build --profile no-debug --bin uv --bin uvx run: cargo build --bin uv --bin uvx
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: uv-windows-x86_64-${{ github.sha }} name: uv-windows-x86_64-${{ github.sha }}
path: | path: |
${{ env.UV_WORKSPACE }}/target/no-debug/uv.exe ${{ env.UV_WORKSPACE }}/target/debug/uv.exe
${{ env.UV_WORKSPACE }}/target/no-debug/uvx.exe ${{ env.UV_WORKSPACE }}/target/debug/uvx.exe
retention-days: 1 retention-days: 1
build-binary-windows-aarch64: build-binary-windows-aarch64:
@ -751,15 +726,15 @@ jobs:
- name: "Build" - name: "Build"
working-directory: ${{ env.UV_WORKSPACE }} working-directory: ${{ env.UV_WORKSPACE }}
run: cargo build --profile no-debug --target aarch64-pc-windows-msvc run: cargo build --target aarch64-pc-windows-msvc
- name: "Upload binary" - name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: uv-windows-aarch64-${{ github.sha }} name: uv-windows-aarch64-${{ github.sha }}
path: | path: |
${{ env.UV_WORKSPACE }}/target/aarch64-pc-windows-msvc/no-debug/uv.exe ${{ env.UV_WORKSPACE }}/target/aarch64-pc-windows-msvc/debug/uv.exe
${{ env.UV_WORKSPACE }}/target/aarch64-pc-windows-msvc/no-debug/uvx.exe ${{ env.UV_WORKSPACE }}/target/aarch64-pc-windows-msvc/debug/uvx.exe
retention-days: 1 retention-days: 1
build-binary-msrv: build-binary-msrv:
@ -784,10 +759,10 @@ jobs:
- name: "Install mold" - name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- run: cargo +${MSRV} build --profile no-debug - run: cargo +${MSRV} build
env: env:
MSRV: ${{ steps.msrv.outputs.value }} MSRV: ${{ steps.msrv.outputs.value }}
- run: ./target/no-debug/uv --version - run: ./target/debug/uv --version
build-binary-freebsd: build-binary-freebsd:
needs: determine_changes needs: determine_changes
@ -808,7 +783,7 @@ jobs:
chmod +x cross chmod +x cross
mv cross /usr/local/bin/cross mv cross /usr/local/bin/cross
cross build --target x86_64-unknown-freebsd --profile no-debug cross build --target x86_64-unknown-freebsd
- name: Test in Firecracker VM - name: Test in Firecracker VM
uses: acj/freebsd-firecracker-action@a5a3fc1709c5b5368141a5699f10259aca3cd965 # v0.6.0 uses: acj/freebsd-firecracker-action@a5a3fc1709c5b5368141a5699f10259aca3cd965 # v0.6.0
@ -822,8 +797,8 @@ jobs:
cat <<EOF > $include_path cat <<EOF > $include_path
target target
target/x86_64-unknown-freebsd target/x86_64-unknown-freebsd
target/x86_64-unknown-freebsd/no-debug target/x86_64-unknown-freebsd/debug
target/x86_64-unknown-freebsd/no-debug/uv target/x86_64-unknown-freebsd/debug/uv
EOF EOF
rsync -r -e "ssh" \ rsync -r -e "ssh" \
@ -833,7 +808,7 @@ jobs:
--exclude "*" \ --exclude "*" \
. firecracker: . firecracker:
run-in-vm: | run-in-vm: |
mv target/x86_64-unknown-freebsd/no-debug/uv uv mv target/x86_64-unknown-freebsd/debug/uv uv
chmod +x uv chmod +x uv
./uv --version ./uv --version
@ -2067,22 +2042,22 @@ jobs:
# Test the main path (`build_wheel`) through pip # Test the main path (`build_wheel`) through pip
./uv venv -v --seed ./uv venv -v --seed
./uv run --no-project python -m pip install -v test/packages/built-by-uv --find-links crates/uv-build/dist --no-index --no-deps ./uv run --no-project python -m pip install -v scripts/packages/built-by-uv --find-links crates/uv-build/dist --no-index --no-deps
./uv run --no-project python -c "from built_by_uv import greet; print(greet())" ./uv run --no-project python -c "from built_by_uv import greet; print(greet())"
# Test both `build_wheel` and `build_sdist` through uv # Test both `build_wheel` and `build_sdist` through uv
./uv venv -c -v ./uv venv -c -v
./uv build -v --force-pep517 test/packages/built-by-uv --find-links crates/uv-build/dist --offline ./uv build -v --force-pep517 scripts/packages/built-by-uv --find-links crates/uv-build/dist --offline
./uv pip install -v test/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps ./uv pip install -v scripts/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps
./uv run --no-project python -c "from built_by_uv import greet; print(greet())" ./uv run --no-project python -c "from built_by_uv import greet; print(greet())"
# Test both `build_wheel` and `build_sdist` through the official `build` # Test both `build_wheel` and `build_sdist` through the official `build`
rm -rf test/packages/built-by-uv/dist/ rm -rf scripts/packages/built-by-uv/dist/
./uv venv -c -v ./uv venv -c -v
./uv pip install build ./uv pip install build
# Add the uv binary to PATH for `build` to find # Add the uv binary to PATH for `build` to find
PATH="$(pwd):$PATH" UV_OFFLINE=1 UV_FIND_LINKS=crates/uv-build/dist ./uv run --no-project python -m build -v --installer uv test/packages/built-by-uv PATH="$(pwd):$PATH" UV_OFFLINE=1 UV_FIND_LINKS=crates/uv-build/dist ./uv run --no-project python -m build -v --installer uv scripts/packages/built-by-uv
./uv pip install -v test/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps ./uv pip install -v scripts/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps
./uv run --no-project python -c "from built_by_uv import greet; print(greet())" ./uv run --no-project python -c "from built_by_uv import greet; print(greet())"
cache-test-ubuntu: cache-test-ubuntu:
@ -2946,8 +2921,8 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev
cargo run --bin uv -- venv --cache-dir .cache cargo run --bin uv -- venv --cache-dir .cache
cargo run --bin uv -- pip compile test/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache cargo run --bin uv -- pip compile scripts/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
cargo run --bin uv -- pip compile test/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache cargo run --bin uv -- pip compile scripts/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
- name: "Build benchmarks" - name: "Build benchmarks"
run: cargo codspeed build --profile profiling -p uv-bench run: cargo codspeed build --profile profiling -p uv-bench
@ -2986,8 +2961,8 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev
cargo run --bin uv -- venv --cache-dir .cache cargo run --bin uv -- venv --cache-dir .cache
cargo run --bin uv -- pip compile test/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache cargo run --bin uv -- pip compile scripts/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
cargo run --bin uv -- pip compile test/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache cargo run --bin uv -- pip compile scripts/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
- name: "Build benchmarks" - name: "Build benchmarks"
run: cargo codspeed build --profile profiling -p uv-bench run: cargo codspeed build --profile profiling -p uv-bench

View File

@ -36,14 +36,6 @@ jobs:
with: with:
python-version: 3.12 python-version: 3.12
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Generate reference documentation"
run: |
cargo dev generate-options-reference
cargo dev generate-cli-reference
cargo dev generate-env-vars-reference
- name: "Set docs display name" - name: "Set docs display name"
run: | run: |
version="${VERSION}" version="${VERSION}"

View File

@ -226,7 +226,6 @@ jobs:
needs: needs:
- plan - plan
- host - host
- custom-publish-pypi # DIRTY: see #16989
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
uses: ./.github/workflows/publish-crates.yml uses: ./.github/workflows/publish-crates.yml
with: with:

5
.gitignore vendored
View File

@ -37,11 +37,6 @@ profile.json.gz
# MkDocs # MkDocs
/site /site
# Generated reference docs (use `cargo dev generate-all` to regenerate)
/docs/reference/cli.md
/docs/reference/environment.md
/docs/reference/settings.md
# macOS # macOS
**/.DS_Store **/.DS_Store

View File

@ -4,5 +4,5 @@ PREVIEW-CHANGELOG.md
docs/reference/cli.md docs/reference/cli.md
docs/reference/settings.md docs/reference/settings.md
docs/reference/environment.md docs/reference/environment.md
test/ecosystem/home-assistant-core/LICENSE.md ecosystem/home-assistant-core/LICENSE.md
docs/guides/integration/gitlab.md docs/guides/integration/gitlab.md

View File

@ -3,64 +3,6 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
## 0.9.18
Released on 2025-12-16.
### Enhancements
- Add value hints to command line arguments to improve shell completion accuracy ([#17080](https://github.com/astral-sh/uv/pull/17080))
- Improve error handling in `uv publish` ([#17096](https://github.com/astral-sh/uv/pull/17096))
- Improve rendering of multiline error messages ([#17132](https://github.com/astral-sh/uv/pull/17132))
- Support redirects in `uv publish` ([#17130](https://github.com/astral-sh/uv/pull/17130))
- Include Docker images with the alpine version, e.g., `python3.x-alpine3.23` ([#17100](https://github.com/astral-sh/uv/pull/17100))
### Configuration
- Accept `--torch-backend` in `[tool.uv]` ([#17116](https://github.com/astral-sh/uv/pull/17116))
### Performance
- Speed up `uv cache size` ([#17015](https://github.com/astral-sh/uv/pull/17015))
- Initialize S3 signer once ([#17092](https://github.com/astral-sh/uv/pull/17092))
### Bug fixes
- Avoid panics due to reads on failed requests ([#17098](https://github.com/astral-sh/uv/pull/17098))
- Enforce latest-version in `@latest` requests ([#17114](https://github.com/astral-sh/uv/pull/17114))
- Explicitly set `EntryType` for file entries in tar ([#17043](https://github.com/astral-sh/uv/pull/17043))
- Ignore `pyproject.toml` index username in lockfile comparison ([#16995](https://github.com/astral-sh/uv/pull/16995))
- Relax error when using `uv add` with `UV_GIT_LFS` set ([#17127](https://github.com/astral-sh/uv/pull/17127))
- Support file locks on ExFAT on macOS ([#17115](https://github.com/astral-sh/uv/pull/17115))
- Change schema for `exclude-newer` into optional string ([#17121](https://github.com/astral-sh/uv/pull/17121))
### Documentation
- Drop arm musl caveat from Docker documentation ([#17111](https://github.com/astral-sh/uv/pull/17111))
- Fix version reference in resolver example ([#17085](https://github.com/astral-sh/uv/pull/17085))
- Better documentation for `exclude-newer*` ([#17079](https://github.com/astral-sh/uv/pull/17079))
## 0.9.17
Released on 2025-12-09.
### Enhancements
- Add `torch-tensorrt` and `torchao` to the PyTorch list ([#17053](https://github.com/astral-sh/uv/pull/17053))
- Add hint for misplaced `--verbose` in `uv tool run` ([#17020](https://github.com/astral-sh/uv/pull/17020))
- Add support for relative durations in `exclude-newer` (a.k.a., dependency cooldowns) ([#16814](https://github.com/astral-sh/uv/pull/16814))
- Add support for relocatable nushell activation script ([#17036](https://github.com/astral-sh/uv/pull/17036))
### Bug fixes
- Respect dropped (but explicit) indexes in dependency groups ([#17012](https://github.com/astral-sh/uv/pull/17012))
### Documentation
- Improve `source-exclude` reference docs ([#16832](https://github.com/astral-sh/uv/pull/16832))
- Recommend `UV_NO_DEV` in Docker installs ([#17030](https://github.com/astral-sh/uv/pull/17030))
- Update `UV_VERSION` in docs for GitLab CI/CD ([#17040](https://github.com/astral-sh/uv/pull/17040))
## 0.9.16 ## 0.9.16
Released on 2025-12-06. Released on 2025-12-06.

View File

@ -1,125 +0,0 @@
# Contributor Covenant Code of Conduct
- [Our Pledge](#our-pledge)
- [Our Standards](#our-standards)
- [Enforcement Responsibilities](#enforcement-responsibilities)
- [Scope](#scope)
- [Enforcement](#enforcement)
- [Enforcement Guidelines](#enforcement-guidelines)
- [1. Correction](#1-correction)
- [2. Warning](#2-warning)
- [3. Temporary Ban](#3-temporary-ban)
- [4. Permanent Ban](#4-permanent-ban)
- [Attribution](#attribution)
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a
harassment-free experience for everyone, regardless of age, body size, visible or invisible
disability, ethnicity, sex characteristics, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and
healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the
experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address, without their
explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior
and will take appropriate and fair corrective action in response to any behavior that they deem
inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits,
code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and
will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is
officially representing the community in public spaces. Examples of representing our community
include using an official e-mail address, posting via an official social media account, or acting as
an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community
leaders responsible for enforcement at <hey@astral.sh>. All complaints will be reviewed and
investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any
incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for
any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or
unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the
nature of the violation and an explanation of why the behavior was inappropriate. A public apology
may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people
involved, including unsolicited interaction with those enforcing the Code of Conduct, for a
specified period of time. This includes avoiding interactions in community spaces as well as
external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate
behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the
community for a specified period of time. No public or private interaction with the people involved,
including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this
period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including
sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement
of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available
[here](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the
[FAQ](https://www.contributor-covenant.org/faq). Translations are available
[here](https://www.contributor-covenant.org/translations).
[homepage]: https://www.contributor-covenant.org

View File

@ -102,15 +102,6 @@ cargo run -- venv
cargo run -- pip install requests cargo run -- pip install requests
``` ```
## Crate structure
Rust does not allow circular dependencies between crates. To visualize the crate hierarchy, install
[cargo-depgraph](https://github.com/jplatte/cargo-depgraph) and graphviz, then run:
```shell
cargo depgraph --dedup-transitive-deps --workspace-only | dot -Tpng > graph.png
```
## Running inside a Docker container ## Running inside a Docker container
Source distributions can run arbitrary code on build and can make unwanted modifications to your Source distributions can run arbitrary code on build and can make unwanted modifications to your
@ -136,7 +127,7 @@ Please refer to Ruff's
it applies to uv, too. it applies to uv, too.
We provide diverse sets of requirements for testing and benchmarking the resolver in We provide diverse sets of requirements for testing and benchmarking the resolver in
`test/requirements` and for the installer in `test/requirements/compiled`. `scripts/requirements` and for the installer in `scripts/requirements/compiled`.
You can use `scripts/benchmark` to benchmark predefined workloads between uv versions and with other You can use `scripts/benchmark` to benchmark predefined workloads between uv versions and with other
tools, e.g., from the `scripts/benchmark` directory: tools, e.g., from the `scripts/benchmark` directory:
@ -147,7 +138,7 @@ uv run resolver \
--poetry \ --poetry \
--benchmark \ --benchmark \
resolve-cold \ resolve-cold \
../test/requirements/trio.in ../scripts/requirements/trio.in
``` ```
### Analyzing concurrency ### Analyzing concurrency
@ -157,7 +148,7 @@ visualize parallel requests and find any spots where uv is CPU-bound. Example us
`uv-dev` respectively: `uv-dev` respectively:
```shell ```shell
RUST_LOG=uv=info TRACING_DURATIONS_FILE=target/traces/jupyter.ndjson cargo run --features tracing-durations-export --profile profiling -- pip compile test/requirements/jupyter.in RUST_LOG=uv=info TRACING_DURATIONS_FILE=target/traces/jupyter.ndjson cargo run --features tracing-durations-export --profile profiling -- pip compile scripts/requirements/jupyter.in
``` ```
```shell ```shell

181
Cargo.lock generated
View File

@ -45,9 +45,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "ambient-id" name = "ambient-id"
version = "0.0.7" version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8cad022ed72ad2176498be1c097bb46e598193e92f3491ea0766980edeee168" checksum = "36b48a3b1ad866e5034859be45edd1ebba2f097289c8a34b61623c76f10480f3"
dependencies = [ dependencies = [
"astral-reqwest-middleware", "astral-reqwest-middleware",
"reqwest", "reqwest",
@ -227,9 +227,9 @@ dependencies = [
[[package]] [[package]]
name = "astral-reqwest-retry" name = "astral-reqwest-retry"
version = "0.8.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ab210f6cdf8fd3254d47e5ee27ce60ed34a428ff71b4ae9477b1c84b49498c" checksum = "cb7549bd00f62f73f2e7e76f3f77ccdabb31873f4f02f758ed88ad739d522867"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"astral-reqwest-middleware", "astral-reqwest-middleware",
@ -1038,15 +1038,6 @@ dependencies = [
"itertools 0.10.5", "itertools 0.10.5",
] ]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.6" version = "0.8.6"
@ -1264,16 +1255,6 @@ dependencies = [
"windows-sys 0.61.0", "windows-sys 0.61.0",
] ]
[[package]]
name = "diskus"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec07379c016f78e7ddcd953663b9ed17928ff384928d34d824ed7e463bd3d908"
dependencies = [
"crossbeam-channel",
"rayon",
]
[[package]] [[package]]
name = "dispatch2" name = "dispatch2"
version = "0.3.0" version = "0.3.0"
@ -3462,9 +3443,9 @@ dependencies = [
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.11.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [ dependencies = [
"either", "either",
"rayon-core", "rayon-core",
@ -3472,9 +3453,9 @@ dependencies = [
[[package]] [[package]]
name = "rayon-core" name = "rayon-core"
version = "1.13.0" version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [ dependencies = [
"crossbeam-deque", "crossbeam-deque",
"crossbeam-utils", "crossbeam-utils",
@ -3753,11 +3734,11 @@ dependencies = [
[[package]] [[package]]
name = "retry-policies" name = "retry-policies"
version = "0.5.1" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a4bd6027df676bcb752d3724db0ea3c0c5fc1dd0376fec51ac7dcaf9cc69be" checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c"
dependencies = [ dependencies = [
"rand 0.9.2", "rand 0.8.5",
] ]
[[package]] [[package]]
@ -4397,9 +4378,9 @@ dependencies = [
[[package]] [[package]]
name = "spdx" name = "spdx"
version = "0.13.2" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35107b1c818f4e9cb9e6c4444ca560ba03b4ee1288dcecc6d7830c2023a7609e" checksum = "41cf87c0efffc158b9dde4d6e0567a43e4383adc4c949e687a2039732db2f23a"
dependencies = [ dependencies = [
"smallvec", "smallvec",
] ]
@ -5406,7 +5387,7 @@ dependencies = [
[[package]] [[package]]
name = "uv" name = "uv"
version = "0.9.18" version = "0.9.16"
dependencies = [ dependencies = [
"anstream", "anstream",
"anyhow", "anyhow",
@ -5421,7 +5402,6 @@ dependencies = [
"clap", "clap",
"console 0.16.1", "console 0.16.1",
"ctrlc", "ctrlc",
"diskus",
"dotenvy", "dotenvy",
"dunce", "dunce",
"embed-manifest", "embed-manifest",
@ -5429,6 +5409,7 @@ dependencies = [
"flate2", "flate2",
"fs-err", "fs-err",
"futures", "futures",
"h2",
"http", "http",
"ignore", "ignore",
"indexmap", "indexmap",
@ -5525,7 +5506,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-auth" name = "uv-auth"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arcstr", "arcstr",
@ -5568,7 +5549,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-bench" name = "uv-bench"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"codspeed-criterion-compat", "codspeed-criterion-compat",
@ -5595,7 +5576,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-bin-install" name = "uv-bin-install"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"astral-reqwest-middleware", "astral-reqwest-middleware",
"astral-reqwest-retry", "astral-reqwest-retry",
@ -5606,11 +5587,13 @@ dependencies = [
"thiserror 2.0.17", "thiserror 2.0.17",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tracing",
"url", "url",
"uv-cache", "uv-cache",
"uv-client", "uv-client",
"uv-distribution-filename", "uv-distribution-filename",
"uv-extract", "uv-extract",
"uv-fs",
"uv-pep440", "uv-pep440",
"uv-platform", "uv-platform",
"uv-redacted", "uv-redacted",
@ -5618,7 +5601,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-build" name = "uv-build"
version = "0.9.18" version = "0.9.16"
dependencies = [ dependencies = [
"anstream", "anstream",
"anyhow", "anyhow",
@ -5630,7 +5613,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-build-backend" name = "uv-build-backend"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"astral-version-ranges", "astral-version-ranges",
"base64 0.22.1", "base64 0.22.1",
@ -5646,7 +5629,7 @@ dependencies = [
"schemars", "schemars",
"serde", "serde",
"sha2", "sha2",
"spdx 0.13.2", "spdx 0.12.0",
"tar", "tar",
"tempfile", "tempfile",
"thiserror 2.0.17", "thiserror 2.0.17",
@ -5670,7 +5653,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-build-frontend" name = "uv-build-frontend"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anstream", "anstream",
"fs-err", "fs-err",
@ -5708,7 +5691,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-cache" name = "uv-cache"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"clap", "clap",
"fs-err", "fs-err",
@ -5718,7 +5701,6 @@ dependencies = [
"same-file", "same-file",
"serde", "serde",
"tempfile", "tempfile",
"thiserror 2.0.17",
"tracing", "tracing",
"uv-cache-info", "uv-cache-info",
"uv-cache-key", "uv-cache-key",
@ -5734,7 +5716,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-cache-info" name = "uv-cache-info"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"fs-err", "fs-err",
@ -5751,7 +5733,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-cache-key" name = "uv-cache-key"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"hex", "hex",
"memchr", "memchr",
@ -5763,7 +5745,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-cli" name = "uv-cli"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anstream", "anstream",
"anyhow", "anyhow",
@ -5795,7 +5777,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-client" name = "uv-client"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"astral-reqwest-middleware", "astral-reqwest-middleware",
@ -5858,7 +5840,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-configuration" name = "uv-configuration"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -5887,14 +5869,14 @@ dependencies = [
[[package]] [[package]]
name = "uv-console" name = "uv-console"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"console 0.16.1", "console 0.16.1",
] ]
[[package]] [[package]]
name = "uv-dev" name = "uv-dev"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anstream", "anstream",
"anyhow", "anyhow",
@ -5943,7 +5925,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-dirs" name = "uv-dirs"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"assert_fs", "assert_fs",
"etcetera", "etcetera",
@ -5955,7 +5937,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-dispatch" name = "uv-dispatch"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures", "futures",
@ -5987,7 +5969,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-distribution" name = "uv-distribution"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"astral-reqwest-middleware", "astral-reqwest-middleware",
@ -6036,7 +6018,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-distribution-filename" name = "uv-distribution-filename"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"insta", "insta",
"memchr", "memchr",
@ -6053,7 +6035,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-distribution-types" name = "uv-distribution-types"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"arcstr", "arcstr",
"astral-version-ranges", "astral-version-ranges",
@ -6093,7 +6075,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-extract" name = "uv-extract"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"astral-tokio-tar", "astral-tokio-tar",
"astral_async_zip", "astral_async_zip",
@ -6123,14 +6105,14 @@ dependencies = [
[[package]] [[package]]
name = "uv-flags" name = "uv-flags"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"bitflags 2.9.4", "bitflags 2.9.4",
] ]
[[package]] [[package]]
name = "uv-fs" name = "uv-fs"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"backon", "backon",
"dunce", "dunce",
@ -6154,7 +6136,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-git" name = "uv-git"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"astral-reqwest-middleware", "astral-reqwest-middleware",
@ -6180,7 +6162,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-git-types" name = "uv-git-types"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"serde", "serde",
"thiserror 2.0.17", "thiserror 2.0.17",
@ -6192,7 +6174,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-globfilter" name = "uv-globfilter"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anstream", "anstream",
"fs-err", "fs-err",
@ -6209,7 +6191,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-install-wheel" name = "uv-install-wheel"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_fs", "assert_fs",
@ -6249,7 +6231,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-installer" name = "uv-installer"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-channel", "async-channel",
@ -6290,7 +6272,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-keyring" name = "uv-keyring"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"byteorder", "byteorder",
@ -6307,7 +6289,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-logging" name = "uv-logging"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"jiff", "jiff",
"owo-colors", "owo-colors",
@ -6317,7 +6299,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-macros" name = "uv-macros"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -6327,7 +6309,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-metadata" name = "uv-metadata"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"astral_async_zip", "astral_async_zip",
"fs-err", "fs-err",
@ -6344,7 +6326,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-normalize" name = "uv-normalize"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"rkyv", "rkyv",
"schemars", "schemars",
@ -6354,7 +6336,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-once-map" name = "uv-once-map"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"dashmap", "dashmap",
"futures", "futures",
@ -6363,14 +6345,14 @@ dependencies = [
[[package]] [[package]]
name = "uv-options-metadata" name = "uv-options-metadata"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "uv-pep440" name = "uv-pep440"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"astral-version-ranges", "astral-version-ranges",
"indoc", "indoc",
@ -6384,7 +6366,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-pep508" name = "uv-pep508"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"arcstr", "arcstr",
"astral-version-ranges", "astral-version-ranges",
@ -6413,7 +6395,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-performance-memory-allocator" name = "uv-performance-memory-allocator"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"mimalloc", "mimalloc",
"tikv-jemallocator", "tikv-jemallocator",
@ -6421,7 +6403,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-platform" name = "uv-platform"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"fs-err", "fs-err",
"goblin", "goblin",
@ -6438,7 +6420,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-platform-tags" name = "uv-platform-tags"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"insta", "insta",
"memchr", "memchr",
@ -6451,7 +6433,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-preview" name = "uv-preview"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"bitflags 2.9.4", "bitflags 2.9.4",
"thiserror 2.0.17", "thiserror 2.0.17",
@ -6460,10 +6442,9 @@ dependencies = [
[[package]] [[package]]
name = "uv-publish" name = "uv-publish"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"ambient-id", "ambient-id",
"anstream",
"astral-reqwest-middleware", "astral-reqwest-middleware",
"astral-reqwest-retry", "astral-reqwest-retry",
"astral-tokio-tar", "astral-tokio-tar",
@ -6497,12 +6478,11 @@ dependencies = [
"uv-redacted", "uv-redacted",
"uv-static", "uv-static",
"uv-warnings", "uv-warnings",
"wiremock",
] ]
[[package]] [[package]]
name = "uv-pypi-types" name = "uv-pypi-types"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hashbrown 0.16.1", "hashbrown 0.16.1",
@ -6534,7 +6514,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-python" name = "uv-python"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_fs", "assert_fs",
@ -6596,7 +6576,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-redacted" name = "uv-redacted"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"ref-cast", "ref-cast",
"schemars", "schemars",
@ -6607,7 +6587,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-requirements" name = "uv-requirements"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"configparser", "configparser",
@ -6642,7 +6622,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-requirements-txt" name = "uv-requirements-txt"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_fs", "assert_fs",
@ -6675,7 +6655,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-resolver" name = "uv-resolver"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"arcstr", "arcstr",
"astral-pubgrub", "astral-pubgrub",
@ -6740,7 +6720,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-scripts" name = "uv-scripts"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"fs-err", "fs-err",
"indoc", "indoc",
@ -6764,7 +6744,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-settings" name = "uv-settings"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"clap", "clap",
"fs-err", "fs-err",
@ -6799,7 +6779,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-shell" name = "uv-shell"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"fs-err", "fs-err",
@ -6816,7 +6796,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-small-str" name = "uv-small-str"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"arcstr", "arcstr",
"rkyv", "rkyv",
@ -6826,7 +6806,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-state" name = "uv-state"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"fs-err", "fs-err",
"tempfile", "tempfile",
@ -6835,14 +6815,14 @@ dependencies = [
[[package]] [[package]]
name = "uv-static" name = "uv-static"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"uv-macros", "uv-macros",
] ]
[[package]] [[package]]
name = "uv-tool" name = "uv-tool"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"fs-err", "fs-err",
"pathdiff", "pathdiff",
@ -6871,7 +6851,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-torch" name = "uv-torch"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"clap", "clap",
"either", "either",
@ -6891,7 +6871,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-trampoline-builder" name = "uv-trampoline-builder"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
@ -6908,7 +6888,7 @@ dependencies = [
[[package]] [[package]]
name = "uv-types" name = "uv-types"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dashmap", "dashmap",
@ -6930,11 +6910,11 @@ dependencies = [
[[package]] [[package]]
name = "uv-version" name = "uv-version"
version = "0.9.18" version = "0.9.16"
[[package]] [[package]]
name = "uv-virtualenv" name = "uv-virtualenv"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"console 0.16.1", "console 0.16.1",
"fs-err", "fs-err",
@ -6956,19 +6936,16 @@ dependencies = [
[[package]] [[package]]
name = "uv-warnings" name = "uv-warnings"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anstream", "anstream",
"anyhow",
"indoc",
"insta",
"owo-colors", "owo-colors",
"rustc-hash", "rustc-hash",
] ]
[[package]] [[package]]
name = "uv-workspace" name = "uv-workspace"
version = "0.0.8" version = "0.0.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_fs", "assert_fs",

View File

@ -16,66 +16,66 @@ authors = ["uv"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
[workspace.dependencies] [workspace.dependencies]
uv-auth = { version = "0.0.8", path = "crates/uv-auth" } uv-auth = { version = "0.0.6", path = "crates/uv-auth" }
uv-bin-install = { version = "0.0.8", path = "crates/uv-bin-install" } uv-bin-install = { version = "0.0.6", path = "crates/uv-bin-install" }
uv-build-backend = { version = "0.0.8", path = "crates/uv-build-backend" } uv-build-backend = { version = "0.0.6", path = "crates/uv-build-backend" }
uv-build-frontend = { version = "0.0.8", path = "crates/uv-build-frontend" } uv-build-frontend = { version = "0.0.6", path = "crates/uv-build-frontend" }
uv-cache = { version = "0.0.8", path = "crates/uv-cache" } uv-cache = { version = "0.0.6", path = "crates/uv-cache" }
uv-cache-info = { version = "0.0.8", path = "crates/uv-cache-info" } uv-cache-info = { version = "0.0.6", path = "crates/uv-cache-info" }
uv-cache-key = { version = "0.0.8", path = "crates/uv-cache-key" } uv-cache-key = { version = "0.0.6", path = "crates/uv-cache-key" }
uv-cli = { version = "0.0.8", path = "crates/uv-cli" } uv-cli = { version = "0.0.6", path = "crates/uv-cli" }
uv-client = { version = "0.0.8", path = "crates/uv-client" } uv-client = { version = "0.0.6", path = "crates/uv-client" }
uv-configuration = { version = "0.0.8", path = "crates/uv-configuration" } uv-configuration = { version = "0.0.6", path = "crates/uv-configuration" }
uv-console = { version = "0.0.8", path = "crates/uv-console" } uv-console = { version = "0.0.6", path = "crates/uv-console" }
uv-dirs = { version = "0.0.8", path = "crates/uv-dirs" } uv-dirs = { version = "0.0.6", path = "crates/uv-dirs" }
uv-dispatch = { version = "0.0.8", path = "crates/uv-dispatch" } uv-dispatch = { version = "0.0.6", path = "crates/uv-dispatch" }
uv-distribution = { version = "0.0.8", path = "crates/uv-distribution" } uv-distribution = { version = "0.0.6", path = "crates/uv-distribution" }
uv-distribution-filename = { version = "0.0.8", path = "crates/uv-distribution-filename" } uv-distribution-filename = { version = "0.0.6", path = "crates/uv-distribution-filename" }
uv-distribution-types = { version = "0.0.8", path = "crates/uv-distribution-types" } uv-distribution-types = { version = "0.0.6", path = "crates/uv-distribution-types" }
uv-extract = { version = "0.0.8", path = "crates/uv-extract" } uv-extract = { version = "0.0.6", path = "crates/uv-extract" }
uv-flags = { version = "0.0.8", path = "crates/uv-flags" } uv-flags = { version = "0.0.6", path = "crates/uv-flags" }
uv-fs = { version = "0.0.8", path = "crates/uv-fs", features = ["serde", "tokio"] } uv-fs = { version = "0.0.6", path = "crates/uv-fs", features = ["serde", "tokio"] }
uv-git = { version = "0.0.8", path = "crates/uv-git" } uv-git = { version = "0.0.6", path = "crates/uv-git" }
uv-git-types = { version = "0.0.8", path = "crates/uv-git-types" } uv-git-types = { version = "0.0.6", path = "crates/uv-git-types" }
uv-globfilter = { version = "0.0.8", path = "crates/uv-globfilter" } uv-globfilter = { version = "0.0.6", path = "crates/uv-globfilter" }
uv-install-wheel = { version = "0.0.8", path = "crates/uv-install-wheel", default-features = false } uv-install-wheel = { version = "0.0.6", path = "crates/uv-install-wheel", default-features = false }
uv-installer = { version = "0.0.8", path = "crates/uv-installer" } uv-installer = { version = "0.0.6", path = "crates/uv-installer" }
uv-keyring = { version = "0.0.8", path = "crates/uv-keyring" } uv-keyring = { version = "0.0.6", path = "crates/uv-keyring" }
uv-logging = { version = "0.0.8", path = "crates/uv-logging" } uv-logging = { version = "0.0.6", path = "crates/uv-logging" }
uv-macros = { version = "0.0.8", path = "crates/uv-macros" } uv-macros = { version = "0.0.6", path = "crates/uv-macros" }
uv-metadata = { version = "0.0.8", path = "crates/uv-metadata" } uv-metadata = { version = "0.0.6", path = "crates/uv-metadata" }
uv-normalize = { version = "0.0.8", path = "crates/uv-normalize" } uv-normalize = { version = "0.0.6", path = "crates/uv-normalize" }
uv-once-map = { version = "0.0.8", path = "crates/uv-once-map" } uv-once-map = { version = "0.0.6", path = "crates/uv-once-map" }
uv-options-metadata = { version = "0.0.8", path = "crates/uv-options-metadata" } uv-options-metadata = { version = "0.0.6", path = "crates/uv-options-metadata" }
uv-performance-memory-allocator = { version = "0.0.8", path = "crates/uv-performance-memory-allocator" } uv-performance-memory-allocator = { version = "0.0.6", path = "crates/uv-performance-memory-allocator" }
uv-pep440 = { version = "0.0.8", path = "crates/uv-pep440", features = ["tracing", "rkyv", "version-ranges"] } uv-pep440 = { version = "0.0.6", path = "crates/uv-pep440", features = ["tracing", "rkyv", "version-ranges"] }
uv-pep508 = { version = "0.0.8", path = "crates/uv-pep508", features = ["non-pep508-extensions"] } uv-pep508 = { version = "0.0.6", path = "crates/uv-pep508", features = ["non-pep508-extensions"] }
uv-platform = { version = "0.0.8", path = "crates/uv-platform" } uv-platform = { version = "0.0.6", path = "crates/uv-platform" }
uv-platform-tags = { version = "0.0.8", path = "crates/uv-platform-tags" } uv-platform-tags = { version = "0.0.6", path = "crates/uv-platform-tags" }
uv-preview = { version = "0.0.8", path = "crates/uv-preview" } uv-preview = { version = "0.0.6", path = "crates/uv-preview" }
uv-publish = { version = "0.0.8", path = "crates/uv-publish" } uv-publish = { version = "0.0.6", path = "crates/uv-publish" }
uv-pypi-types = { version = "0.0.8", path = "crates/uv-pypi-types" } uv-pypi-types = { version = "0.0.6", path = "crates/uv-pypi-types" }
uv-python = { version = "0.0.8", path = "crates/uv-python" } uv-python = { version = "0.0.6", path = "crates/uv-python" }
uv-redacted = { version = "0.0.8", path = "crates/uv-redacted" } uv-redacted = { version = "0.0.6", path = "crates/uv-redacted" }
uv-requirements = { version = "0.0.8", path = "crates/uv-requirements" } uv-requirements = { version = "0.0.6", path = "crates/uv-requirements" }
uv-requirements-txt = { version = "0.0.8", path = "crates/uv-requirements-txt" } uv-requirements-txt = { version = "0.0.6", path = "crates/uv-requirements-txt" }
uv-resolver = { version = "0.0.8", path = "crates/uv-resolver" } uv-resolver = { version = "0.0.6", path = "crates/uv-resolver" }
uv-scripts = { version = "0.0.8", path = "crates/uv-scripts" } uv-scripts = { version = "0.0.6", path = "crates/uv-scripts" }
uv-settings = { version = "0.0.8", path = "crates/uv-settings" } uv-settings = { version = "0.0.6", path = "crates/uv-settings" }
uv-shell = { version = "0.0.8", path = "crates/uv-shell" } uv-shell = { version = "0.0.6", path = "crates/uv-shell" }
uv-small-str = { version = "0.0.8", path = "crates/uv-small-str" } uv-small-str = { version = "0.0.6", path = "crates/uv-small-str" }
uv-state = { version = "0.0.8", path = "crates/uv-state" } uv-state = { version = "0.0.6", path = "crates/uv-state" }
uv-static = { version = "0.0.8", path = "crates/uv-static" } uv-static = { version = "0.0.6", path = "crates/uv-static" }
uv-tool = { version = "0.0.8", path = "crates/uv-tool" } uv-tool = { version = "0.0.6", path = "crates/uv-tool" }
uv-torch = { version = "0.0.8", path = "crates/uv-torch" } uv-torch = { version = "0.0.6", path = "crates/uv-torch" }
uv-trampoline-builder = { version = "0.0.8", path = "crates/uv-trampoline-builder" } uv-trampoline-builder = { version = "0.0.6", path = "crates/uv-trampoline-builder" }
uv-types = { version = "0.0.8", path = "crates/uv-types" } uv-types = { version = "0.0.6", path = "crates/uv-types" }
uv-version = { version = "0.9.18", path = "crates/uv-version" } uv-version = { version = "0.9.16", path = "crates/uv-version" }
uv-virtualenv = { version = "0.0.8", path = "crates/uv-virtualenv" } uv-virtualenv = { version = "0.0.6", path = "crates/uv-virtualenv" }
uv-warnings = { version = "0.0.8", path = "crates/uv-warnings" } uv-warnings = { version = "0.0.6", path = "crates/uv-warnings" }
uv-workspace = { version = "0.0.8", path = "crates/uv-workspace" } uv-workspace = { version = "0.0.6", path = "crates/uv-workspace" }
ambient-id = { version = "0.0.7", default-features = false, features = ["astral-reqwest-middleware"] } ambient-id = { version = "0.0.6", default-features = false, features = ["astral-reqwest-middleware"] }
anstream = { version = "0.6.15" } anstream = { version = "0.6.15" }
anyhow = { version = "1.0.89" } anyhow = { version = "1.0.89" }
arcstr = { version = "1.2.0" } arcstr = { version = "1.2.0" }
@ -103,7 +103,6 @@ ctrlc = { version = "3.4.5" }
cyclonedx-bom = { version = "0.8.0" } cyclonedx-bom = { version = "0.8.0" }
dashmap = { version = "6.1.0" } dashmap = { version = "6.1.0" }
data-encoding = { version = "2.6.0" } data-encoding = { version = "2.6.0" }
diskus = { version = "0.9.0", default-features = false }
dotenvy = { version = "0.15.7" } dotenvy = { version = "0.15.7" }
dunce = { version = "1.0.5" } dunce = { version = "1.0.5" }
either = { version = "1.13.0" } either = { version = "1.13.0" }
@ -153,7 +152,7 @@ regex-automata = { version = "0.4.8", default-features = false, features = ["dfa
reqsign = { version = "0.18.0", features = ["aws", "default-context"], default-features = false } reqsign = { version = "0.18.0", features = ["aws", "default-context"], default-features = false }
reqwest = { version = "0.12.22", default-features = false, features = ["json", "gzip", "deflate", "zstd", "stream", "system-proxy", "rustls-tls", "rustls-tls-native-roots", "socks", "multipart", "http2", "blocking"] } reqwest = { version = "0.12.22", default-features = false, features = ["json", "gzip", "deflate", "zstd", "stream", "system-proxy", "rustls-tls", "rustls-tls-native-roots", "socks", "multipart", "http2", "blocking"] }
reqwest-middleware = { version = "0.4.2", package = "astral-reqwest-middleware", features = ["multipart"] } reqwest-middleware = { version = "0.4.2", package = "astral-reqwest-middleware", features = ["multipart"] }
reqwest-retry = { version = "0.8.0", package = "astral-reqwest-retry" } reqwest-retry = { version = "0.7.0", package = "astral-reqwest-retry" }
rkyv = { version = "0.8.8", features = ["bytecheck"] } rkyv = { version = "0.8.8", features = ["bytecheck"] }
rmp-serde = { version = "1.3.0" } rmp-serde = { version = "1.3.0" }
rust-netrc = { version = "0.1.2" } rust-netrc = { version = "0.1.2" }
@ -170,7 +169,7 @@ serde-untagged = { version = "0.1.6" }
serde_json = { version = "1.0.128" } serde_json = { version = "1.0.128" }
sha2 = { version = "0.10.8" } sha2 = { version = "0.10.8" }
smallvec = { version = "1.13.2" } smallvec = { version = "1.13.2" }
spdx = { version = "0.13.0" } spdx = { version = "0.12.0" }
syn = { version = "2.0.77" } syn = { version = "2.0.77" }
sys-info = { version = "0.9.1" } sys-info = { version = "0.9.1" }
tar = { version = "0.4.43" } tar = { version = "0.4.43" }
@ -225,6 +224,9 @@ test-log = { version = "0.2.16", features = ["trace"], default-features = false
tokio-rustls = { version = "0.26.2", default-features = false } tokio-rustls = { version = "0.26.2", default-features = false }
whoami = { version = "1.6.0" } whoami = { version = "1.6.0" }
[workspace.metadata.cargo-shear]
ignored = ["flate2", "xz2", "h2", "uv-performance-memory-allocator"]
[workspace.lints.rust] [workspace.lints.rust]
unsafe_code = "warn" unsafe_code = "warn"
unreachable_pub = "warn" unreachable_pub = "warn"
@ -310,21 +312,12 @@ strip = false
debug = "full" debug = "full"
lto = false lto = false
# Profile for fast test execution: Skip debug info generation, and
# apply basic optimization, which speed up build and running tests.
[profile.fast-build] [profile.fast-build]
inherits = "dev" inherits = "dev"
opt-level = 1 opt-level = 1
debug = 0 debug = 0
strip = "debuginfo" strip = "debuginfo"
# Profile for faster builds: Skip debug info generation, for faster
# builds of smaller binaries.
[profile.no-debug]
inherits = "dev"
debug = 0
strip = "debuginfo"
# Profile to build a minimally sized binary for uv-build # Profile to build a minimally sized binary for uv-build
[profile.minimal-size] [profile.minimal-size]
inherits = "release" inherits = "release"

View File

@ -42,7 +42,7 @@ An extremely fast Python package and project manager, written in Rust.
- 🖥️ Supports macOS, Linux, and Windows. - 🖥️ Supports macOS, Linux, and Windows.
uv is backed by [Astral](https://astral.sh), the creators of uv is backed by [Astral](https://astral.sh), the creators of
[Ruff](https://github.com/astral-sh/ruff) and [ty](https://github.com/astral-sh/ty). [Ruff](https://github.com/astral-sh/ruff).
## Installation ## Installation
@ -192,12 +192,14 @@ uv installs Python and allows quickly switching between versions.
Install multiple Python versions: Install multiple Python versions:
```console ```console
$ uv python install 3.12 3.13 3.14 $ uv python install 3.10 3.11 3.12
Installed 3 versions in 972ms Searching for Python versions matching: Python 3.10
+ cpython-3.12.12-macos-aarch64-none (python3.12) Searching for Python versions matching: Python 3.11
+ cpython-3.13.9-macos-aarch64-none (python3.13) Searching for Python versions matching: Python 3.12
+ cpython-3.14.0-macos-aarch64-none (python3.14) Installed 3 versions in 3.42s
+ cpython-3.10.14-macos-aarch64-none
+ cpython-3.11.9-macos-aarch64-none
+ cpython-3.12.4-macos-aarch64-none
``` ```
Download Python versions as needed: Download Python versions as needed:
@ -268,12 +270,19 @@ Installed 43 packages in 208ms
See the [pip interface documentation](https://docs.astral.sh/uv/pip/index/) to get started. See the [pip interface documentation](https://docs.astral.sh/uv/pip/index/) to get started.
## Platform support
See uv's [platform support](https://docs.astral.sh/uv/reference/platforms/) document.
## Versioning policy
See uv's [versioning policy](https://docs.astral.sh/uv/reference/versioning/) document.
## Contributing ## Contributing
We are passionate about supporting contributors of all levels of experience and would love to see We are passionate about supporting contributors of all levels of experience and would love to see
you get involved in the project. See the you get involved in the project. See the
[contributing guide](https://github.com/astral-sh/uv?tab=contributing-ov-file#contributing) to get [contributing guide](https://github.com/astral-sh/uv/blob/main/CONTRIBUTING.md) to get started.
started.
## FAQ ## FAQ
@ -285,15 +294,6 @@ It's pronounced as "you - vee" ([`/juː viː/`](https://en.wikipedia.org/wiki/He
Just "uv", please. See the [style guide](./STYLE.md#styling-uv) for details. Just "uv", please. See the [style guide](./STYLE.md#styling-uv) for details.
#### What platforms does uv support?
See uv's [platform support](https://docs.astral.sh/uv/reference/platforms/) document.
#### Is uv ready for production?
Yes, uv is stable and widely used in production. See uv's
[versioning policy](https://docs.astral.sh/uv/reference/versioning/) document for details.
## Acknowledgements ## Acknowledgements
uv's dependency resolver uses [PubGrub](https://github.com/pubgrub-rs/pubgrub) under the hood. We're uv's dependency resolver uses [PubGrub](https://github.com/pubgrub-rs/pubgrub) under the hood. We're

View File

@ -1,8 +1,8 @@
[files] [files]
extend-exclude = [ extend-exclude = [
"**/snapshots/", "**/snapshots/",
"test/ecosystem/**", "ecosystem/**",
"test/requirements/**/*.in", "scripts/**/*.in",
"crates/uv-build-frontend/src/pipreqs/mapping", "crates/uv-build-frontend/src/pipreqs/mapping",
] ]
ignore-hidden = false ignore-hidden = false

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-auth" name = "uv-auth"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-auth). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-auth).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -129,15 +129,6 @@ enum TokenState {
Initialized(Option<AccessToken>), Initialized(Option<AccessToken>),
} }
#[derive(Clone)]
enum S3CredentialState {
/// The S3 credential state has not yet been initialized.
Uninitialized,
/// The S3 credential state has been initialized, with either a signer or `None` if
/// no S3 endpoint is configured.
Initialized(Option<Arc<Authentication>>),
}
/// A middleware that adds basic authentication to requests. /// A middleware that adds basic authentication to requests.
/// ///
/// Uses a cache to propagate credentials from previously seen requests and /// Uses a cache to propagate credentials from previously seen requests and
@ -159,8 +150,6 @@ pub struct AuthMiddleware {
pyx_token_store: Option<PyxTokenStore>, pyx_token_store: Option<PyxTokenStore>,
/// Tokens to use for persistent credentials. /// Tokens to use for persistent credentials.
pyx_token_state: Mutex<TokenState>, pyx_token_state: Mutex<TokenState>,
/// Cached S3 credentials to avoid running the credential helper multiple times.
s3_credential_state: Mutex<S3CredentialState>,
preview: Preview, preview: Preview,
} }
@ -183,7 +172,6 @@ impl AuthMiddleware {
base_client: None, base_client: None,
pyx_token_store: None, pyx_token_store: None,
pyx_token_state: Mutex::new(TokenState::Uninitialized), pyx_token_state: Mutex::new(TokenState::Uninitialized),
s3_credential_state: Mutex::new(S3CredentialState::Uninitialized),
preview: Preview::default(), preview: Preview::default(),
} }
} }
@ -690,26 +678,13 @@ impl AuthMiddleware {
return Some(credentials); return Some(credentials);
} }
if S3EndpointProvider::is_s3_endpoint(url, self.preview) { if let Some(credentials) = S3EndpointProvider::credentials_for(url, self.preview)
let mut s3_state = self.s3_credential_state.lock().await; .map(Authentication::from)
.map(Arc::new)
// If the S3 credential state is uninitialized, initialize it. {
let credentials = match &*s3_state { debug!("Found S3 credentials for {url}");
S3CredentialState::Uninitialized => { self.cache().fetches.done(key, Some(credentials.clone()));
trace!("Initializing S3 credentials for {url}"); return Some(credentials);
let signer = S3EndpointProvider::create_signer();
let credentials = Arc::new(Authentication::from(signer));
*s3_state = S3CredentialState::Initialized(Some(credentials.clone()));
Some(credentials)
}
S3CredentialState::Initialized(credentials) => credentials.clone(),
};
if let Some(credentials) = credentials {
debug!("Found S3 credentials for {url}");
self.cache().fetches.done(key, Some(credentials.clone()));
return Some(credentials);
}
} }
// If this is a known URL, authenticate it via the token store. // If this is a known URL, authenticate it via the token store.

View File

@ -66,8 +66,8 @@ static S3_ENDPOINT_REALM: LazyLock<Option<Realm>> = LazyLock::new(|| {
pub(crate) struct S3EndpointProvider; pub(crate) struct S3EndpointProvider;
impl S3EndpointProvider { impl S3EndpointProvider {
/// Returns `true` if the URL matches the configured S3 endpoint. /// Returns the credentials for the S3 endpoint, if available.
pub(crate) fn is_s3_endpoint(url: &Url, preview: Preview) -> bool { pub(crate) fn credentials_for(url: &Url, preview: Preview) -> Option<DefaultSigner> {
if let Some(s3_endpoint_realm) = S3_ENDPOINT_REALM.as_ref().map(RealmRef::from) { if let Some(s3_endpoint_realm) = S3_ENDPOINT_REALM.as_ref().map(RealmRef::from) {
if !preview.is_enabled(PreviewFeatures::S3_ENDPOINT) { if !preview.is_enabled(PreviewFeatures::S3_ENDPOINT) {
warn_user_once!( warn_user_once!(
@ -79,26 +79,19 @@ impl S3EndpointProvider {
// Treat any URL on the same domain or subdomain as available for S3 signing. // Treat any URL on the same domain or subdomain as available for S3 signing.
let realm = RealmRef::from(url); let realm = RealmRef::from(url);
if realm == s3_endpoint_realm || realm.is_subdomain_of(s3_endpoint_realm) { if realm == s3_endpoint_realm || realm.is_subdomain_of(s3_endpoint_realm) {
return true; // TODO(charlie): Can `reqsign` infer the region for us? Profiles, for example,
// often have a region set already.
let region = std::env::var(EnvVars::AWS_REGION)
.map(Cow::Owned)
.unwrap_or_else(|_| {
std::env::var(EnvVars::AWS_DEFAULT_REGION)
.map(Cow::Owned)
.unwrap_or_else(|_| Cow::Borrowed("us-east-1"))
});
let signer = reqsign::aws::default_signer("s3", &region);
return Some(signer);
} }
} }
false None
}
/// Creates a new S3 signer with the configured region.
///
/// This is potentially expensive as it may invoke credential helpers, so the result
/// should be cached.
pub(crate) fn create_signer() -> DefaultSigner {
// TODO(charlie): Can `reqsign` infer the region for us? Profiles, for example,
// often have a region set already.
let region = std::env::var(EnvVars::AWS_REGION)
.map(Cow::Owned)
.unwrap_or_else(|_| {
std::env::var(EnvVars::AWS_DEFAULT_REGION)
.map(Cow::Owned)
.unwrap_or_else(|_| Cow::Borrowed("us-east-1"))
});
reqsign::aws::default_signer("s3", &region)
} }
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-bench" name = "uv-bench"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
publish = false publish = false
authors = { workspace = true } authors = { workspace = true }
@ -22,14 +22,14 @@ name = "uv"
path = "benches/uv.rs" path = "benches/uv.rs"
harness = false harness = false
[dev-dependencies] [dependencies]
uv-cache = { workspace = true } uv-cache = { workspace = true }
uv-client = { workspace = true } uv-client = { workspace = true }
uv-configuration = { workspace = true } uv-configuration = { workspace = true }
uv-dispatch = { workspace = true } uv-dispatch = { workspace = true }
uv-distribution = { workspace = true } uv-distribution = { workspace = true }
uv-distribution-types = { workspace = true } uv-distribution-types = { workspace = true }
uv-extract = { workspace = true } uv-extract = { workspace = true, optional = true }
uv-install-wheel = { workspace = true } uv-install-wheel = { workspace = true }
uv-pep440 = { workspace = true } uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true } uv-pep508 = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-bench). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-bench).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-bin-install" name = "uv-bin-install"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
@ -20,6 +20,7 @@ uv-cache = { workspace = true }
uv-client = { workspace = true } uv-client = { workspace = true }
uv-distribution-filename = { workspace = true } uv-distribution-filename = { workspace = true }
uv-extract = { workspace = true } uv-extract = { workspace = true }
uv-fs = { workspace = true }
uv-pep440 = { workspace = true } uv-pep440 = { workspace = true }
uv-platform = { workspace = true } uv-platform = { workspace = true }
uv-redacted = { workspace = true } uv-redacted = { workspace = true }
@ -33,4 +34,5 @@ tempfile = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tokio-util = { workspace = true } tokio-util = { workspace = true }
tracing = { workspace = true }
url = { workspace = true } url = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-bin-install). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-bin-install).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -6,19 +6,23 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::{Duration, SystemTime};
use futures::TryStreamExt; use futures::TryStreamExt;
use reqwest_retry::RetryPolicy;
use reqwest_retry::policies::ExponentialBackoff; use reqwest_retry::policies::ExponentialBackoff;
use std::fmt; use std::fmt;
use thiserror::Error; use thiserror::Error;
use tokio::io::{AsyncRead, ReadBuf}; use tokio::io::{AsyncRead, ReadBuf};
use tokio_util::compat::FuturesAsyncReadCompatExt; use tokio_util::compat::FuturesAsyncReadCompatExt;
use tracing::debug;
use url::Url; use url::Url;
use uv_distribution_filename::SourceDistExtension; use uv_distribution_filename::SourceDistExtension;
use uv_cache::{Cache, CacheBucket, CacheEntry, Error as CacheError}; use uv_cache::{Cache, CacheBucket, CacheEntry};
use uv_client::{BaseClient, RetryState}; use uv_client::{BaseClient, is_transient_network_error};
use uv_extract::{Error as ExtractError, stream}; use uv_extract::{Error as ExtractError, stream};
use uv_fs::LockedFileError;
use uv_pep440::Version; use uv_pep440::Version;
use uv_platform::Platform; use uv_platform::Platform;
use uv_redacted::DisplaySafeUrl; use uv_redacted::DisplaySafeUrl;
@ -133,12 +137,12 @@ pub enum Error {
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error(transparent)] #[error(transparent)]
Cache(#[from] CacheError), LockedFile(#[from] LockedFileError),
#[error("Failed to detect platform")] #[error("Failed to detect platform")]
Platform(#[from] uv_platform::Error), Platform(#[from] uv_platform::Error),
#[error("Request failed after {retries} {subject}", subject = if *retries > 1 { "retries" } else { "retry" })] #[error("Attempt failed after {retries} {subject}", subject = if *retries > 1 { "retries" } else { "retry" })]
RetriedError { RetriedError {
#[source] #[source]
err: Box<Error>, err: Box<Error>,
@ -147,15 +151,13 @@ pub enum Error {
} }
impl Error { impl Error {
/// Return the number of retries that were made to complete this request before this error was /// Return the number of attempts that were made to complete this request before this error was
/// returned. /// returned. Note that e.g. 3 retries equates to 4 attempts.
/// fn attempts(&self) -> u32 {
/// Note that e.g. 3 retries equates to 4 attempts.
fn retries(&self) -> u32 {
if let Self::RetriedError { retries, .. } = self { if let Self::RetriedError { retries, .. } = self {
return *retries; return retries + 1;
} }
0 1
} }
} }
@ -241,7 +243,9 @@ async fn download_and_unpack_with_retry(
download_url: &Url, download_url: &Url,
cache_entry: &CacheEntry, cache_entry: &CacheEntry,
) -> Result<PathBuf, Error> { ) -> Result<PathBuf, Error> {
let mut retry_state = RetryState::start(*retry_policy, download_url.clone()); let mut total_attempts = 0;
let mut retried_here = false;
let start_time = SystemTime::now();
loop { loop {
let result = download_and_unpack( let result = download_and_unpack(
@ -257,23 +261,40 @@ async fn download_and_unpack_with_retry(
) )
.await; .await;
match result { let result = match result {
Ok(path) => return Ok(path), Ok(path) => Ok(path),
Err(err) => { Err(err) => {
if let Some(backoff) = retry_state.should_retry(&err, err.retries()) { total_attempts += err.attempts();
retry_state.sleep_backoff(backoff).await; let past_retries = total_attempts - 1;
continue;
if is_transient_network_error(&err) {
let retry_decision = retry_policy.should_retry(start_time, past_retries);
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
debug!(
"Transient failure while installing {} {}; retrying...",
binary.name(),
version
);
let duration = execute_after
.duration_since(SystemTime::now())
.unwrap_or_else(|_| Duration::default());
tokio::time::sleep(duration).await;
retried_here = true;
continue;
}
} }
return if retry_state.total_retries() > 0 {
if retried_here {
Err(Error::RetriedError { Err(Error::RetriedError {
err: Box::new(err), err: Box::new(err),
retries: retry_state.total_retries(), retries: past_retries,
}) })
} else { } else {
Err(err) Err(err)
}; }
} }
} };
return result;
} }
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-build-backend" name = "uv-build-backend"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-build-backend). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-build-backend).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -599,7 +599,7 @@ mod tests {
/// platform-independent deterministic builds. /// platform-independent deterministic builds.
#[test] #[test]
fn built_by_uv_building() { fn built_by_uv_building() {
let built_by_uv = Path::new("../../test/packages/built-by-uv"); let built_by_uv = Path::new("../../scripts/packages/built-by-uv");
let src = TempDir::new().unwrap(); let src = TempDir::new().unwrap();
for dir in [ for dir in [
"src", "src",
@ -662,7 +662,7 @@ mod tests {
// Check that the source dist is reproducible across platforms. // Check that the source dist is reproducible across platforms.
assert_snapshot!( assert_snapshot!(
format!("{:x}", sha2::Sha256::digest(fs_err::read(&source_dist_path).unwrap())), format!("{:x}", sha2::Sha256::digest(fs_err::read(&source_dist_path).unwrap())),
@"bb74bff575b135bb39e5c9bce56349441fb0923bb8857e32a5eaf34ec1843967" @"871d1f859140721b67cbeaca074e7a2740c88c38028d0509eba87d1285f1da9e"
); );
// Check both the files we report and the actual files // Check both the files we report and the actual files
assert_snapshot!(format_file_list(build.source_dist_list_files, src.path()), @r" assert_snapshot!(format_file_list(build.source_dist_list_files, src.path()), @r"

View File

@ -70,9 +70,6 @@ pub struct BuildBackendSettings {
pub default_excludes: bool, pub default_excludes: bool,
/// Glob expressions which files and directories to exclude from the source distribution. /// Glob expressions which files and directories to exclude from the source distribution.
///
/// These exclusions are also applied to wheels to ensure that a wheel built from a source tree
/// is consistent with a wheel built from a source distribution.
#[option( #[option(
default = r#"[]"#, default = r#"[]"#,
value_type = "list[str]", value_type = "list[str]",

View File

@ -299,10 +299,6 @@ impl TarGzWriter {
impl DirectoryWriter for TarGzWriter { impl DirectoryWriter for TarGzWriter {
fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error> { fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error> {
let mut header = Header::new_gnu(); let mut header = Header::new_gnu();
// Work around bug in Python's std tar module
// https://github.com/python/cpython/issues/141707
// https://github.com/astral-sh/uv/pull/17043#issuecomment-3636841022
header.set_entry_type(EntryType::Regular);
header.set_size(bytes.len() as u64); header.set_size(bytes.len() as u64);
// Reasonable default to avoid 0o000 permissions, the user's umask will be applied on // Reasonable default to avoid 0o000 permissions, the user's umask will be applied on
// unpacking. // unpacking.
@ -316,10 +312,6 @@ impl DirectoryWriter for TarGzWriter {
fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error> { fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error> {
let metadata = fs_err::metadata(file)?; let metadata = fs_err::metadata(file)?;
let mut header = Header::new_gnu(); let mut header = Header::new_gnu();
// Work around bug in Python's std tar module
// https://github.com/python/cpython/issues/141707
// https://github.com/astral-sh/uv/pull/17043#issuecomment-3636841022
header.set_entry_type(EntryType::Regular);
// Preserve the executable bit, especially for scripts // Preserve the executable bit, especially for scripts
#[cfg(unix)] #[cfg(unix)]
let executable_bit = { let executable_bit = {

View File

@ -840,7 +840,7 @@ mod test {
#[test] #[test]
fn test_prepare_metadata() { fn test_prepare_metadata() {
let metadata_dir = TempDir::new().unwrap(); let metadata_dir = TempDir::new().unwrap();
let built_by_uv = Path::new("../../test/packages/built-by-uv"); let built_by_uv = Path::new("../../scripts/packages/built-by-uv");
metadata(built_by_uv, metadata_dir.path(), "1.0.0+test").unwrap(); metadata(built_by_uv, metadata_dir.path(), "1.0.0+test").unwrap();
let mut files: Vec<_> = WalkDir::new(metadata_dir.path()) let mut files: Vec<_> = WalkDir::new(metadata_dir.path())

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-build-frontend" name = "uv-build-frontend"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-build-frontend). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-build-frontend).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -676,7 +676,7 @@ impl SourceBuild {
// If no `pyproject.toml` is present, by default, proceed with a PEP 517 build using // If no `pyproject.toml` is present, by default, proceed with a PEP 517 build using
// the default backend, to match `build`. `pip` uses `setup.py` directly in this // the default backend, to match `build`. `pip` uses `setup.py` directly in this
// case, but plans to make PEP 517 builds the default in the future. // case, but plans to make PEP 517 builds the default in the future.
// See: https://github.com/pypa/pip/issues/9175. // See: https://github.com/pypa/pip/issues/9175.
Ok((DEFAULT_BACKEND.clone(), None)) Ok((DEFAULT_BACKEND.clone(), None))
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-build" name = "uv-build"
version = "0.9.18" version = "0.9.16"
description = "A Python build backend" description = "A Python build backend"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -1,6 +1,6 @@
[project] [project]
name = "uv-build" name = "uv-build"
version = "0.9.18" version = "0.9.16"
description = "The uv build backend" description = "The uv build backend"
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }] authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
requires-python = ">=3.8" requires-python = ">=3.8"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-cache-info" name = "uv-cache-info"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-cache-info). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-cache-info).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-cache-key" name = "uv-cache-key"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-cache-key). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-cache-key).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-cache" name = "uv-cache"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
@ -34,6 +34,5 @@ rustc-hash = { workspace = true }
same-file = { workspace = true } same-file = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
tempfile = { workspace = true } tempfile = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
walkdir = { workspace = true } walkdir = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-cache). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-cache).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
use uv_static::EnvVars; use uv_static::EnvVars;
use crate::Cache; use crate::Cache;
use clap::{Parser, ValueHint}; use clap::Parser;
use tracing::{debug, warn}; use tracing::{debug, warn};
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
@ -27,7 +27,7 @@ pub struct CacheArgs {
/// `%LOCALAPPDATA%\uv\cache` on Windows. /// `%LOCALAPPDATA%\uv\cache` on Windows.
/// ///
/// To view the location of the cache directory, run `uv cache dir`. /// To view the location of the cache directory, run `uv cache dir`.
#[arg(global = true, long, env = EnvVars::UV_CACHE_DIR, value_hint = ValueHint::DirPath)] #[arg(global = true, long, env = EnvVars::UV_CACHE_DIR)]
pub cache_dir: Option<PathBuf>, pub cache_dir: Option<PathBuf>,
} }

View File

@ -35,17 +35,6 @@ mod wheel;
/// Must be kept in-sync with the version in [`CacheBucket::to_str`]. /// Must be kept in-sync with the version in [`CacheBucket::to_str`].
pub const ARCHIVE_VERSION: u8 = 0; pub const ARCHIVE_VERSION: u8 = 0;
/// Error locking a cache entry or shard
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] io::Error),
#[error("Could not make the path absolute")]
Absolute(#[source] io::Error),
#[error("Could not acquire lock")]
Acquire(#[from] LockedFileError),
}
/// A [`CacheEntry`] which may or may not exist yet. /// A [`CacheEntry`] which may or may not exist yet.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CacheEntry(PathBuf); pub struct CacheEntry(PathBuf);
@ -91,14 +80,14 @@ impl CacheEntry {
} }
/// Acquire the [`CacheEntry`] as an exclusive lock. /// Acquire the [`CacheEntry`] as an exclusive lock.
pub async fn lock(&self) -> Result<LockedFile, Error> { pub async fn lock(&self) -> Result<LockedFile, LockedFileError> {
fs_err::create_dir_all(self.dir())?; fs_err::create_dir_all(self.dir())?;
Ok(LockedFile::acquire( LockedFile::acquire(
self.path(), self.path(),
LockedFileMode::Exclusive, LockedFileMode::Exclusive,
self.path().display(), self.path().display(),
) )
.await?) .await
} }
} }
@ -125,14 +114,14 @@ impl CacheShard {
} }
/// Acquire the cache entry as an exclusive lock. /// Acquire the cache entry as an exclusive lock.
pub async fn lock(&self) -> Result<LockedFile, Error> { pub async fn lock(&self) -> Result<LockedFile, LockedFileError> {
fs_err::create_dir_all(self.as_ref())?; fs_err::create_dir_all(self.as_ref())?;
Ok(LockedFile::acquire( LockedFile::acquire(
self.join(".lock"), self.join(".lock"),
LockedFileMode::Exclusive, LockedFileMode::Exclusive,
self.display(), self.display(),
) )
.await?) .await
} }
/// Return the [`CacheShard`] as a [`PathBuf`]. /// Return the [`CacheShard`] as a [`PathBuf`].
@ -402,7 +391,7 @@ impl Cache {
} }
/// Populate the cache scaffold. /// Populate the cache scaffold.
fn create_base_files(root: &PathBuf) -> io::Result<()> { fn create_base_files(root: &PathBuf) -> Result<(), io::Error> {
// Create the cache directory, if it doesn't exist. // Create the cache directory, if it doesn't exist.
fs_err::create_dir_all(root)?; fs_err::create_dir_all(root)?;
@ -452,7 +441,7 @@ impl Cache {
} }
/// Initialize the [`Cache`]. /// Initialize the [`Cache`].
pub async fn init(self) -> Result<Self, Error> { pub async fn init(self) -> Result<Self, LockedFileError> {
let root = &self.root; let root = &self.root;
Self::create_base_files(root)?; Self::create_base_files(root)?;
@ -477,18 +466,18 @@ impl Cache {
); );
None None
} }
Err(err) => return Err(err.into()), Err(err) => return Err(err),
}; };
Ok(Self { Ok(Self {
root: std::path::absolute(root).map_err(Error::Absolute)?, root: std::path::absolute(root)?,
lock_file, lock_file,
..self ..self
}) })
} }
/// Initialize the [`Cache`], assuming that there are no other uv processes running. /// Initialize the [`Cache`], assuming that there are no other uv processes running.
pub fn init_no_wait(self) -> Result<Option<Self>, Error> { pub fn init_no_wait(self) -> Result<Option<Self>, io::Error> {
let root = &self.root; let root = &self.root;
Self::create_base_files(root)?; Self::create_base_files(root)?;
@ -502,7 +491,7 @@ impl Cache {
return Ok(None); return Ok(None);
}; };
Ok(Some(Self { Ok(Some(Self {
root: std::path::absolute(root).map_err(Error::Absolute)?, root: std::path::absolute(root)?,
lock_file: Some(Arc::new(lock_file)), lock_file: Some(Arc::new(lock_file)),
..self ..self
})) }))
@ -542,7 +531,7 @@ impl Cache {
/// Remove a package from the cache. /// Remove a package from the cache.
/// ///
/// Returns the number of entries removed from the cache. /// Returns the number of entries removed from the cache.
pub fn remove(&self, name: &PackageName) -> io::Result<Removal> { pub fn remove(&self, name: &PackageName) -> Result<Removal, io::Error> {
// Collect the set of referenced archives. // Collect the set of referenced archives.
let references = self.find_archive_references()?; let references = self.find_archive_references()?;

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-cli" name = "uv-cli"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-cli). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-cli).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

File diff suppressed because it is too large Load Diff

View File

@ -366,7 +366,6 @@ pub fn resolver_options(
exclude_newer_package.unwrap_or_default(), exclude_newer_package.unwrap_or_default(),
), ),
link_mode, link_mode,
torch_backend: None,
no_build: flag(no_build, build, "build"), no_build: flag(no_build, build, "build"),
no_build_package: Some(no_build_package), no_build_package: Some(no_build_package),
no_binary: flag(no_binary, binary, "binary"), no_binary: flag(no_binary, binary, "binary"),
@ -496,6 +495,5 @@ pub fn resolver_installer_options(
Some(no_binary_package) Some(no_binary_package)
}, },
no_sources: if no_sources { Some(true) } else { None }, no_sources: if no_sources { Some(true) } else { None },
torch_backend: None,
} }
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-client" name = "uv-client"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-client). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-client).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -4,7 +4,7 @@ use std::fmt::Write;
use std::num::ParseIntError; use std::num::ParseIntError;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, SystemTime}; use std::time::Duration;
use std::{env, io, iter}; use std::{env, io, iter};
use anyhow::anyhow; use anyhow::anyhow;
@ -20,8 +20,8 @@ use reqwest::{Client, ClientBuilder, IntoUrl, Proxy, Request, Response, multipar
use reqwest_middleware::{ClientWithMiddleware, Middleware}; use reqwest_middleware::{ClientWithMiddleware, Middleware};
use reqwest_retry::policies::ExponentialBackoff; use reqwest_retry::policies::ExponentialBackoff;
use reqwest_retry::{ use reqwest_retry::{
RetryPolicy, RetryTransientMiddleware, Retryable, RetryableStrategy, default_on_request_error, DefaultRetryableStrategy, RetryTransientMiddleware, Retryable, RetryableStrategy,
default_on_request_success, default_on_request_error,
}; };
use thiserror::Error; use thiserror::Error;
use tracing::{debug, trace}; use tracing::{debug, trace};
@ -50,7 +50,7 @@ pub const DEFAULT_RETRIES: u32 = 3;
/// Maximum number of redirects to follow before giving up. /// Maximum number of redirects to follow before giving up.
/// ///
/// This is the default used by [`reqwest`]. /// This is the default used by [`reqwest`].
pub const DEFAULT_MAX_REDIRECTS: u32 = 10; const DEFAULT_MAX_REDIRECTS: u32 = 10;
/// Selectively skip parts or the entire auth middleware. /// Selectively skip parts or the entire auth middleware.
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
@ -104,8 +104,6 @@ pub enum RedirectPolicy {
BypassMiddleware, BypassMiddleware,
/// Handle redirects manually, re-triggering our custom middleware for each request. /// Handle redirects manually, re-triggering our custom middleware for each request.
RetriggerMiddleware, RetriggerMiddleware,
/// No redirect for non-cloneable (e.g., streaming) requests with custom redirect logic.
NoRedirect,
} }
impl RedirectPolicy { impl RedirectPolicy {
@ -113,7 +111,6 @@ impl RedirectPolicy {
match self { match self {
Self::BypassMiddleware => reqwest::redirect::Policy::default(), Self::BypassMiddleware => reqwest::redirect::Policy::default(),
Self::RetriggerMiddleware => reqwest::redirect::Policy::none(), Self::RetriggerMiddleware => reqwest::redirect::Policy::none(),
Self::NoRedirect => reqwest::redirect::Policy::none(),
} }
} }
} }
@ -732,7 +729,6 @@ impl RedirectClientWithMiddleware {
match self.redirect_policy { match self.redirect_policy {
RedirectPolicy::BypassMiddleware => self.client.execute(req).await, RedirectPolicy::BypassMiddleware => self.client.execute(req).await,
RedirectPolicy::RetriggerMiddleware => self.execute_with_redirect_handling(req).await, RedirectPolicy::RetriggerMiddleware => self.execute_with_redirect_handling(req).await,
RedirectPolicy::NoRedirect => self.client.execute(req).await,
} }
} }
@ -1019,15 +1015,21 @@ impl<'a> RequestBuilder<'a> {
} }
} }
/// An extension over [`DefaultRetryableStrategy`] that logs transient request failures and /// Extends [`DefaultRetryableStrategy`], to log transient request failures and additional retry cases.
/// adds additional retry cases.
pub struct UvRetryableStrategy; pub struct UvRetryableStrategy;
impl RetryableStrategy for UvRetryableStrategy { impl RetryableStrategy for UvRetryableStrategy {
fn handle(&self, res: &Result<Response, reqwest_middleware::Error>) -> Option<Retryable> { fn handle(&self, res: &Result<Response, reqwest_middleware::Error>) -> Option<Retryable> {
let retryable = match res { // Use the default strategy and check for additional transient error cases.
Ok(success) => default_on_request_success(success), let retryable = match DefaultRetryableStrategy.handle(res) {
Err(err) => retryable_on_request_failure(err), None | Some(Retryable::Fatal)
if res
.as_ref()
.is_err_and(|err| is_transient_network_error(err)) =>
{
Some(Retryable::Transient)
}
default => default,
}; };
// Log on transient errors // Log on transient errors
@ -1053,13 +1055,11 @@ impl RetryableStrategy for UvRetryableStrategy {
/// Whether the error looks like a network error that should be retried. /// Whether the error looks like a network error that should be retried.
/// ///
/// This is an extension over [`reqwest_middleware::default_on_request_failure`], which is missing /// There are two cases that the default retry strategy is missing:
/// a number of cases:
/// * Inside the reqwest or reqwest-middleware error is an `io::Error` such as a broken pipe /// * Inside the reqwest or reqwest-middleware error is an `io::Error` such as a broken pipe
/// * When streaming a response, a reqwest error may be hidden several layers behind errors /// * When streaming a response, a reqwest error may be hidden several layers behind errors
/// of different crates processing the stream, including `io::Error` layers /// of different crates processing the stream, including `io::Error` layers.
/// * Any `h2` error pub fn is_transient_network_error(err: &(dyn Error + 'static)) -> bool {
fn retryable_on_request_failure(err: &(dyn Error + 'static)) -> Option<Retryable> {
// First, try to show a nice trace log // First, try to show a nice trace log
if let Some((Some(status), Some(url))) = find_source::<WrappedReqwestError>(&err) if let Some((Some(status), Some(url))) = find_source::<WrappedReqwestError>(&err)
.map(|request_err| (request_err.status(), request_err.url())) .map(|request_err| (request_err.status(), request_err.url()))
@ -1074,32 +1074,29 @@ fn retryable_on_request_failure(err: &(dyn Error + 'static)) -> Option<Retryable
// crates // crates
let mut current_source = Some(err); let mut current_source = Some(err);
while let Some(source) = current_source { while let Some(source) = current_source {
// Handle different kinds of reqwest error nesting not accessible by downcast. if let Some(reqwest_err) = source.downcast_ref::<WrappedReqwestError>() {
let reqwest_err = if let Some(reqwest_err) = source.downcast_ref::<reqwest::Error>() { has_known_error = true;
Some(reqwest_err) if let reqwest_middleware::Error::Reqwest(reqwest_err) = &**reqwest_err {
} else if let Some(reqwest_err) = source if default_on_request_error(reqwest_err) == Some(Retryable::Transient) {
.downcast_ref::<WrappedReqwestError>() trace!("Retrying nested reqwest middleware error");
.and_then(|err| err.inner()) return true;
{ }
Some(reqwest_err) if is_retryable_status_error(reqwest_err) {
} else if let Some(reqwest_middleware::Error::Reqwest(reqwest_err)) = trace!("Retrying nested reqwest middleware status code error");
source.downcast_ref::<reqwest_middleware::Error>() return true;
{ }
Some(reqwest_err) }
} else {
None trace!("Cannot retry nested reqwest middleware error");
}; } else if let Some(reqwest_err) = source.downcast_ref::<reqwest::Error>() {
if let Some(reqwest_err) = reqwest_err {
has_known_error = true; has_known_error = true;
// Ignore the default retry strategy returning fatal.
if default_on_request_error(reqwest_err) == Some(Retryable::Transient) { if default_on_request_error(reqwest_err) == Some(Retryable::Transient) {
trace!("Retrying nested reqwest error"); trace!("Retrying nested reqwest error");
return Some(Retryable::Transient); return true;
} }
if is_retryable_status_error(reqwest_err) { if is_retryable_status_error(reqwest_err) {
trace!("Retrying nested reqwest status code error"); trace!("Retrying nested reqwest status code error");
return Some(Retryable::Transient); return true;
} }
trace!("Cannot retry nested reqwest error"); trace!("Cannot retry nested reqwest error");
@ -1107,7 +1104,7 @@ fn retryable_on_request_failure(err: &(dyn Error + 'static)) -> Option<Retryable
// All h2 errors look like errors that should be retried // All h2 errors look like errors that should be retried
// https://github.com/astral-sh/uv/issues/15916 // https://github.com/astral-sh/uv/issues/15916
trace!("Retrying nested h2 error"); trace!("Retrying nested h2 error");
return Some(Retryable::Transient); return true;
} else if let Some(io_err) = source.downcast_ref::<io::Error>() { } else if let Some(io_err) = source.downcast_ref::<io::Error>() {
has_known_error = true; has_known_error = true;
let retryable_io_err_kinds = [ let retryable_io_err_kinds = [
@ -1124,7 +1121,7 @@ fn retryable_on_request_failure(err: &(dyn Error + 'static)) -> Option<Retryable
]; ];
if retryable_io_err_kinds.contains(&io_err.kind()) { if retryable_io_err_kinds.contains(&io_err.kind()) {
trace!("Retrying error: `{}`", io_err.kind()); trace!("Retrying error: `{}`", io_err.kind());
return Some(Retryable::Transient); return true;
} }
trace!( trace!(
@ -1139,81 +1136,7 @@ fn retryable_on_request_failure(err: &(dyn Error + 'static)) -> Option<Retryable
if !has_known_error { if !has_known_error {
trace!("Cannot retry error: Neither an IO error nor a reqwest error"); trace!("Cannot retry error: Neither an IO error nor a reqwest error");
} }
false
None
}
/// Per-request retry state and policy.
pub struct RetryState {
retry_policy: ExponentialBackoff,
start_time: SystemTime,
total_retries: u32,
url: DisplaySafeUrl,
}
impl RetryState {
/// Initialize the [`RetryState`] and start the backoff timer.
pub fn start(retry_policy: ExponentialBackoff, url: impl Into<DisplaySafeUrl>) -> Self {
Self {
retry_policy,
start_time: SystemTime::now(),
total_retries: 0,
url: url.into(),
}
}
/// The number of retries across all requests.
///
/// After a failed retryable request, this equals the maximum number of retries.
pub fn total_retries(&self) -> u32 {
self.total_retries
}
/// Determines whether request should be retried.
///
/// Takes the number of retries from nested layers associated with the specific `err` type as
/// `error_retries`.
///
/// Returns the backoff duration if the request should be retried.
#[must_use]
pub fn should_retry(
&mut self,
err: &(dyn Error + 'static),
error_retries: u32,
) -> Option<Duration> {
// If the middleware performed any retries, consider them in our budget.
self.total_retries += error_retries;
match retryable_on_request_failure(err) {
Some(Retryable::Transient) => {
let retry_decision = self
.retry_policy
.should_retry(self.start_time, self.total_retries);
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
let duration = execute_after
.duration_since(SystemTime::now())
.unwrap_or_else(|_| Duration::default());
self.total_retries += 1;
return Some(duration);
}
None
}
Some(Retryable::Fatal) | None => None,
}
}
/// Wait before retrying the request.
pub async fn sleep_backoff(&self, duration: Duration) {
debug!(
"Transient failure while handling response from {}; retrying after {:.1}s...",
self.url,
duration.as_secs_f32(),
);
// TODO(konsti): Should we show a spinner plus a message in the CLI while
// waiting?
tokio::time::sleep(duration).await;
}
} }
/// Whether the error is a status code error that is retryable. /// Whether the error is a status code error that is retryable.
@ -1458,7 +1381,7 @@ mod tests {
let middleware_client = ClientWithMiddleware::default(); let middleware_client = ClientWithMiddleware::default();
let mut retried = Vec::new(); let mut retried = Vec::new();
for status in 100..599 { for status in 100..599 {
// Test all standard status codes and an example for a non-RFC code used in the wild. // Test all standard status codes and and example for a non-RFC code used in the wild.
if StatusCode::from_u16(status)?.canonical_reason().is_none() && status != 420 { if StatusCode::from_u16(status)?.canonical_reason().is_none() && status != 420 {
continue; continue;
} }
@ -1474,7 +1397,7 @@ mod tests {
.await; .await;
let middleware_retry = let middleware_retry =
UvRetryableStrategy.handle(&response) == Some(Retryable::Transient); DefaultRetryableStrategy.handle(&response) == Some(Retryable::Transient);
let response = client let response = client
.get(format!("{}/{}", server.uri(), status)) .get(format!("{}/{}", server.uri(), status))
@ -1483,7 +1406,7 @@ mod tests {
let uv_retry = match response.error_for_status() { let uv_retry = match response.error_for_status() {
Ok(_) => false, Ok(_) => false,
Err(err) => retryable_on_request_failure(&err) == Some(Retryable::Transient), Err(err) => is_transient_network_error(&err),
}; };
// Ensure we're retrying the same status code as the reqwest_retry crate. We may choose // Ensure we're retrying the same status code as the reqwest_retry crate. We may choose

View File

@ -1,7 +1,9 @@
use std::time::{Duration, SystemTime};
use std::{borrow::Cow, path::Path}; use std::{borrow::Cow, path::Path};
use futures::FutureExt; use futures::FutureExt;
use reqwest::{Request, Response}; use reqwest::{Request, Response};
use reqwest_retry::RetryPolicy;
use rkyv::util::AlignedVec; use rkyv::util::AlignedVec;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -12,7 +14,7 @@ use uv_fs::write_atomic;
use uv_redacted::DisplaySafeUrl; use uv_redacted::DisplaySafeUrl;
use crate::BaseClient; use crate::BaseClient;
use crate::base_client::RetryState; use crate::base_client::is_transient_network_error;
use crate::error::ProblemDetails; use crate::error::ProblemDetails;
use crate::{ use crate::{
Error, ErrorKind, Error, ErrorKind,
@ -117,35 +119,51 @@ where
} }
} }
/// Dispatch type: Either a cached client error or a (user specified) error from the callback. /// Dispatch type: Either a cached client error or a (user specified) error from the callback
#[derive(Debug)]
pub enum CachedClientError<CallbackError: std::error::Error + 'static> { pub enum CachedClientError<CallbackError: std::error::Error + 'static> {
/// The client tracks retries internally. Client {
Client(Error), retries: Option<u32>,
/// Track retries before a callback explicitly, as we can't attach them to the callback error err: Error,
/// type. },
Callback { retries: u32, err: CallbackError }, Callback {
retries: Option<u32>,
err: CallbackError,
},
} }
impl<CallbackError: std::error::Error + 'static> CachedClientError<CallbackError> { impl<CallbackError: std::error::Error + 'static> CachedClientError<CallbackError> {
/// Attach the combined number of retries to the error context, discarding the previous count. /// Attach the number of retries to the error context.
///
/// Adds to existing errors if any, in case different layers retried.
fn with_retries(self, retries: u32) -> Self { fn with_retries(self, retries: u32) -> Self {
match self { match self {
Self::Client(err) => Self::Client(err.with_retries(retries)), Self::Client {
Self::Callback { retries: _, err } => Self::Callback { retries, err }, retries: existing_retries,
err,
} => Self::Client {
retries: Some(existing_retries.unwrap_or_default() + retries),
err,
},
Self::Callback {
retries: existing_retries,
err,
} => Self::Callback {
retries: Some(existing_retries.unwrap_or_default() + retries),
err,
},
} }
} }
fn retries(&self) -> u32 { fn retries(&self) -> Option<u32> {
match self { match self {
Self::Client(err) => err.retries(), Self::Client { retries, .. } => *retries,
Self::Callback { retries, .. } => *retries, Self::Callback { retries, .. } => *retries,
} }
} }
fn error(&self) -> &(dyn std::error::Error + 'static) { fn error(&self) -> &(dyn std::error::Error + 'static) {
match self { match self {
Self::Client(err) => err, Self::Client { err, .. } => err,
Self::Callback { err, .. } => err, Self::Callback { err, .. } => err,
} }
} }
@ -153,7 +171,10 @@ impl<CallbackError: std::error::Error + 'static> CachedClientError<CallbackError
impl<CallbackError: std::error::Error + 'static> From<Error> for CachedClientError<CallbackError> { impl<CallbackError: std::error::Error + 'static> From<Error> for CachedClientError<CallbackError> {
fn from(error: Error) -> Self { fn from(error: Error) -> Self {
Self::Client(error) Self::Client {
retries: None,
err: error,
}
} }
} }
@ -161,7 +182,10 @@ impl<CallbackError: std::error::Error + 'static> From<ErrorKind>
for CachedClientError<CallbackError> for CachedClientError<CallbackError>
{ {
fn from(error: ErrorKind) -> Self { fn from(error: ErrorKind) -> Self {
Self::Client(error.into()) Self::Client {
retries: None,
err: error.into(),
}
} }
} }
@ -169,10 +193,16 @@ impl<E: Into<Self> + std::error::Error + 'static> From<CachedClientError<E>> for
/// Attach retry error context, if there were retries. /// Attach retry error context, if there were retries.
fn from(error: CachedClientError<E>) -> Self { fn from(error: CachedClientError<E>) -> Self {
match error { match error {
CachedClientError::Client(error) => error, CachedClientError::Client {
CachedClientError::Callback { retries, err } => { retries: Some(retries),
Self::new(err.into().into_kind(), retries) err,
} } => Self::new(err.into_kind(), retries),
CachedClientError::Client { retries: None, err } => err,
CachedClientError::Callback {
retries: Some(retries),
err,
} => Self::new(err.into().into_kind(), retries),
CachedClientError::Callback { retries: None, err } => err.into(),
} }
} }
} }
@ -420,16 +450,10 @@ impl CachedClient {
response_callback: Callback, response_callback: Callback,
) -> Result<Payload::Target, CachedClientError<CallBackError>> { ) -> Result<Payload::Target, CachedClientError<CallBackError>> {
let new_cache = info_span!("new_cache", file = %cache_entry.path().display()); let new_cache = info_span!("new_cache", file = %cache_entry.path().display());
// Capture retries from the retry middleware
let retries = response
.extensions()
.get::<reqwest_retry::RetryCount>()
.map(|retries| retries.value())
.unwrap_or_default();
let data = response_callback(response) let data = response_callback(response)
.boxed_local() .boxed_local()
.await .await
.map_err(|err| CachedClientError::Callback { retries, err })?; .map_err(|err| CachedClientError::Callback { retries: None, err })?;
let Some(cache_policy) = cache_policy else { let Some(cache_policy) = cache_policy else {
return Ok(data.into_target()); return Ok(data.into_target());
}; };
@ -638,10 +662,16 @@ impl CachedClient {
} else { } else {
None None
}; };
return Err(Error::new( return Err(CachedClientError::<Error>::Client {
ErrorKind::from_reqwest_with_problem_details(url, status_error, problem_details), retries: retry_count,
retry_count.unwrap_or_default(), err: ErrorKind::from_reqwest_with_problem_details(
)); url,
status_error,
problem_details,
)
.into(),
}
.into());
} }
let cache_policy = cache_policy_builder.build(&response); let cache_policy = cache_policy_builder.build(&response);
@ -690,23 +720,49 @@ impl CachedClient {
cache_control: CacheControl<'_>, cache_control: CacheControl<'_>,
response_callback: Callback, response_callback: Callback,
) -> Result<Payload::Target, CachedClientError<CallBackError>> { ) -> Result<Payload::Target, CachedClientError<CallBackError>> {
let mut retry_state = RetryState::start(self.uncached().retry_policy(), req.url().clone()); let mut past_retries = 0;
let start_time = SystemTime::now();
let retry_policy = self.uncached().retry_policy();
loop { loop {
let fresh_req = req.try_clone().expect("HTTP request must be cloneable"); let fresh_req = req.try_clone().expect("HTTP request must be cloneable");
let result = self let result = self
.get_cacheable(fresh_req, cache_entry, cache_control, &response_callback) .get_cacheable(fresh_req, cache_entry, cache_control, &response_callback)
.await; .await;
match result { // Check if the middleware already performed retries
Ok(ok) => return Ok(ok), let middleware_retries = match &result {
Err(err) => { Err(err) => err.retries().unwrap_or_default(),
if let Some(backoff) = retry_state.should_retry(err.error(), err.retries()) { Ok(_) => 0,
retry_state.sleep_backoff(backoff).await; };
continue;
} if result
return Err(err.with_retries(retry_state.total_retries())); .as_ref()
.is_err_and(|err| is_transient_network_error(err.error()))
{
// If middleware already retried, consider that in our retry budget
let total_retries = past_retries + middleware_retries;
let retry_decision = retry_policy.should_retry(start_time, total_retries);
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
let duration = execute_after
.duration_since(SystemTime::now())
.unwrap_or_else(|_| Duration::default());
debug!(
"Transient failure while handling response from {}; retrying after {:.1}s...",
req.url(),
duration.as_secs_f32(),
);
tokio::time::sleep(duration).await;
past_retries += 1;
continue;
} }
} }
if past_retries > 0 {
return result.map_err(|err| err.with_retries(past_retries));
}
return result;
} }
} }
@ -724,23 +780,48 @@ impl CachedClient {
cache_control: CacheControl<'_>, cache_control: CacheControl<'_>,
response_callback: Callback, response_callback: Callback,
) -> Result<Payload, CachedClientError<CallBackError>> { ) -> Result<Payload, CachedClientError<CallBackError>> {
let mut retry_state = RetryState::start(self.uncached().retry_policy(), req.url().clone()); let mut past_retries = 0;
let start_time = SystemTime::now();
let retry_policy = self.uncached().retry_policy();
loop { loop {
let fresh_req = req.try_clone().expect("HTTP request must be cloneable"); let fresh_req = req.try_clone().expect("HTTP request must be cloneable");
let result = self let result = self
.skip_cache(fresh_req, cache_entry, cache_control, &response_callback) .skip_cache(fresh_req, cache_entry, cache_control, &response_callback)
.await; .await;
match result { // Check if the middleware already performed retries
Ok(ok) => return Ok(ok), let middleware_retries = match &result {
Err(err) => { Err(err) => err.retries().unwrap_or_default(),
if let Some(backoff) = retry_state.should_retry(err.error(), err.retries()) { _ => 0,
retry_state.sleep_backoff(backoff).await; };
continue;
} if result
return Err(err.with_retries(retry_state.total_retries())); .as_ref()
.err()
.is_some_and(|err| is_transient_network_error(err.error()))
{
let total_retries = past_retries + middleware_retries;
let retry_decision = retry_policy.should_retry(start_time, total_retries);
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
let duration = execute_after
.duration_since(SystemTime::now())
.unwrap_or_else(|_| Duration::default());
debug!(
"Transient failure while handling response from {}; retrying after {}s...",
req.url(),
duration.as_secs(),
);
tokio::time::sleep(duration).await;
past_retries += 1;
continue;
} }
} }
if past_retries > 0 {
return result.map_err(|err| err.with_retries(past_retries));
}
return result;
} }
} }
} }

View File

@ -5,8 +5,8 @@ use std::fmt::{Display, Formatter};
use std::ops::Deref; use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
use uv_cache::Error as CacheError;
use uv_distribution_filename::{WheelFilename, WheelFilenameError}; use uv_distribution_filename::{WheelFilename, WheelFilenameError};
use uv_fs::LockedFileError;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_redacted::DisplaySafeUrl; use uv_redacted::DisplaySafeUrl;
@ -123,11 +123,6 @@ impl Error {
&self.kind &self.kind
} }
pub(crate) fn with_retries(mut self, retries: u32) -> Self {
self.retries = retries;
self
}
/// Create a new error from a JSON parsing error. /// Create a new error from a JSON parsing error.
pub(crate) fn from_json_err(err: serde_json::Error, url: DisplaySafeUrl) -> Self { pub(crate) fn from_json_err(err: serde_json::Error, url: DisplaySafeUrl) -> Self {
ErrorKind::BadJson { source: err, url }.into() ErrorKind::BadJson { source: err, url }.into()
@ -344,7 +339,7 @@ pub enum ErrorKind {
CacheWrite(#[source] std::io::Error), CacheWrite(#[source] std::io::Error),
#[error("Failed to acquire lock on the client cache")] #[error("Failed to acquire lock on the client cache")]
CacheLock(#[source] CacheError), CacheLock(#[source] LockedFileError),
#[error(transparent)] #[error(transparent)]
Io(std::io::Error), Io(std::io::Error),
@ -430,33 +425,13 @@ impl WrappedReqwestError {
problem_details: Option<ProblemDetails>, problem_details: Option<ProblemDetails>,
) -> Self { ) -> Self {
Self { Self {
error: Self::filter_retries_from_error(error), error,
problem_details: problem_details.map(Box::new), problem_details: problem_details.map(Box::new),
} }
} }
/// Drop `RetryError::WithRetries` to avoid reporting the number of retries twice.
///
/// We attach the number of errors outside by adding the retry counts from the retry middleware
/// and from uv's outer retry loop for streaming bodies. Stripping the inner count from the
/// error context avoids showing two numbers.
fn filter_retries_from_error(error: reqwest_middleware::Error) -> reqwest_middleware::Error {
match error {
reqwest_middleware::Error::Middleware(error) => {
match error.downcast::<reqwest_retry::RetryError>() {
Ok(
reqwest_retry::RetryError::WithRetries { err, .. }
| reqwest_retry::RetryError::Error(err),
) => err,
Err(error) => reqwest_middleware::Error::Middleware(error),
}
}
error @ reqwest_middleware::Error::Reqwest(_) => error,
}
}
/// Return the inner [`reqwest::Error`] from the error chain, if it exists. /// Return the inner [`reqwest::Error`] from the error chain, if it exists.
pub fn inner(&self) -> Option<&reqwest::Error> { fn inner(&self) -> Option<&reqwest::Error> {
match &self.error { match &self.error {
reqwest_middleware::Error::Reqwest(err) => Some(err), reqwest_middleware::Error::Reqwest(err) => Some(err),
reqwest_middleware::Error::Middleware(err) => err.chain().find_map(|err| { reqwest_middleware::Error::Middleware(err) => err.chain().find_map(|err| {
@ -520,7 +495,6 @@ impl WrappedReqwestError {
impl From<reqwest::Error> for WrappedReqwestError { impl From<reqwest::Error> for WrappedReqwestError {
fn from(error: reqwest::Error) -> Self { fn from(error: reqwest::Error) -> Self {
Self { Self {
// No need to filter retries as this error does not have retries.
error: error.into(), error: error.into(),
problem_details: None, problem_details: None,
} }
@ -530,7 +504,7 @@ impl From<reqwest::Error> for WrappedReqwestError {
impl From<reqwest_middleware::Error> for WrappedReqwestError { impl From<reqwest_middleware::Error> for WrappedReqwestError {
fn from(error: reqwest_middleware::Error) -> Self { fn from(error: reqwest_middleware::Error) -> Self {
Self { Self {
error: Self::filter_retries_from_error(error), error,
problem_details: None, problem_details: None,
} }
} }

View File

@ -246,7 +246,7 @@ impl<'a> FlatIndexClient<'a> {
.collect(); .collect();
Ok(FlatIndexEntries::from_entries(files)) Ok(FlatIndexEntries::from_entries(files))
} }
Err(CachedClientError::Client(err)) if err.is_offline() => { Err(CachedClientError::Client { err, .. }) if err.is_offline() => {
Ok(FlatIndexEntries::offline()) Ok(FlatIndexEntries::offline())
} }
Err(err) => Err(err.into()), Err(err) => Err(err.into()),

View File

@ -1,7 +1,7 @@
pub use base_client::{ pub use base_client::{
AuthIntegration, BaseClient, BaseClientBuilder, DEFAULT_MAX_REDIRECTS, DEFAULT_RETRIES, AuthIntegration, BaseClient, BaseClientBuilder, DEFAULT_RETRIES, ExtraMiddleware,
ExtraMiddleware, RedirectClientWithMiddleware, RedirectPolicy, RequestBuilder, RedirectClientWithMiddleware, RequestBuilder, RetryParsingError, UvRetryableStrategy,
RetryParsingError, RetryState, UvRetryableStrategy, is_transient_network_error,
}; };
pub use cached_client::{CacheControl, CachedClient, CachedClientError, DataWithCachePolicy}; pub use cached_client::{CacheControl, CachedClient, CachedClientError, DataWithCachePolicy};
pub use error::{Error, ErrorKind, WrappedReqwestError}; pub use error::{Error, ErrorKind, WrappedReqwestError};

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-configuration" name = "uv-configuration"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-configuration). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-configuration).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -94,32 +94,3 @@ wheels/
# Virtual environments # Virtual environments
.venv .venv
"; ";
/// Setting for Git LFS (Large File Storage) support.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GitLfsSetting {
/// Git LFS is disabled (default).
#[default]
Disabled,
/// Git LFS is enabled. Tracks whether it came from an environment variable.
Enabled { from_env: bool },
}
impl GitLfsSetting {
pub fn new(from_arg: Option<bool>, from_env: Option<bool>) -> Self {
match (from_arg, from_env) {
(Some(true), _) => Self::Enabled { from_env: false },
(_, Some(true)) => Self::Enabled { from_env: true },
_ => Self::Disabled,
}
}
}
impl From<GitLfsSetting> for Option<bool> {
fn from(setting: GitLfsSetting) -> Self {
match setting {
GitLfsSetting::Enabled { .. } => Some(true),
GitLfsSetting::Disabled => None,
}
}
}

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-console" name = "uv-console"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-console). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-console).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-dev" name = "uv-dev"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
publish = false publish = false
@ -79,4 +79,4 @@ performance-memory-allocator = ["dep:uv-performance-memory-allocator"]
render = ["poloto", "resvg", "tagu"] render = ["poloto", "resvg", "tagu"]
[package.metadata.cargo-shear] [package.metadata.cargo-shear]
ignored = ["uv-performance-memory-allocator"] ignored = ["flate2", "uv-extract", "uv-performance-memory-allocator"]

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-dev). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-dev).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -342,3 +342,31 @@ fn emit_possible_options(opt: &clap::Arg, output: &mut String) {
output.push_str(&markdown::to_html(&value)); output.push_str(&markdown::to_html(&value));
} }
} }
#[cfg(test)]
mod tests {
use std::env;
use anyhow::Result;
use uv_static::EnvVars;
use crate::generate_all::Mode;
use super::{Args, main};
#[test]
fn test_generate_cli_reference() -> Result<()> {
// Skip this test in CI to avoid redundancy with the dedicated CI job
if env::var_os(EnvVars::CI).is_some() {
return Ok(());
}
let mode = if env::var(EnvVars::UV_UPDATE_SCHEMA).as_deref() == Ok("1") {
Mode::Write
} else {
Mode::Check
};
main(&Args { mode })
}
}

View File

@ -106,3 +106,31 @@ fn render(var: &str, doc: &str, added_in: Option<&str>) -> String {
format!("### `{var}`\n\n{doc}\n\n") format!("### `{var}`\n\n{doc}\n\n")
} }
} }
#[cfg(test)]
mod tests {
use std::env;
use anyhow::Result;
use uv_static::EnvVars;
use crate::generate_all::Mode;
use super::{Args, main};
#[test]
fn test_generate_env_vars_reference() -> Result<()> {
// Skip this test in CI to avoid redundancy with the dedicated CI job
if env::var_os(EnvVars::CI).is_some() {
return Ok(());
}
let mode = if env::var(EnvVars::UV_UPDATE_SCHEMA).as_deref() == Ok("1") {
Mode::Write
} else {
Mode::Check
};
main(&Args { mode })
}
}

View File

@ -387,3 +387,31 @@ impl Visit for CollectOptionsVisitor {
self.fields.push((name.to_owned(), field)); self.fields.push((name.to_owned(), field));
} }
} }
#[cfg(test)]
mod tests {
use std::env;
use anyhow::Result;
use uv_static::EnvVars;
use crate::generate_all::Mode;
use super::{Args, main};
#[test]
fn test_generate_options_reference() -> Result<()> {
// Skip this test in CI to avoid redundancy with the dedicated CI job
if env::var_os(EnvVars::CI).is_some() {
return Ok(());
}
let mode = if env::var(EnvVars::UV_UPDATE_SCHEMA).as_deref() == Ok("1") {
Mode::Write
} else {
Mode::Check
};
main(&Args { mode })
}
}

View File

@ -11,7 +11,7 @@ use crate::ROOT_DIR;
use crate::generate_all::Mode; use crate::generate_all::Mode;
/// Contains current supported targets /// Contains current supported targets
const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20251217/cpython-unix/targets.yml"; const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20251205/cpython-unix/targets.yml";
#[derive(clap::Args)] #[derive(clap::Args)]
pub(crate) struct Args { pub(crate) struct Args {
@ -130,7 +130,7 @@ async fn generate() -> Result<String> {
output.push_str("//! DO NOT EDIT\n"); output.push_str("//! DO NOT EDIT\n");
output.push_str("//!\n"); output.push_str("//!\n");
output.push_str("//! Generated with `cargo run dev generate-sysconfig-metadata`\n"); output.push_str("//! Generated with `cargo run dev generate-sysconfig-metadata`\n");
output.push_str("//! Targets from <https://github.com/astral-sh/python-build-standalone/blob/20251217/cpython-unix/targets.yml>\n"); output.push_str("//! Targets from <https://github.com/astral-sh/python-build-standalone/blob/20251205/cpython-unix/targets.yml>\n");
output.push_str("//!\n"); output.push_str("//!\n");
// Disable clippy/fmt // Disable clippy/fmt

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-dirs" name = "uv-dirs"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-dirs). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-dirs).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-dispatch" name = "uv-dispatch"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-dispatch). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-dispatch).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-distribution-filename" name = "uv-distribution-filename"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-distribution-filename). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-distribution-filename).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-distribution-types" name = "uv-distribution-types"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-distribution-types). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-distribution-types).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -32,18 +32,15 @@ impl IndexCacheControl {
/// Return the default files cache control headers for the given index URL, if applicable. /// Return the default files cache control headers for the given index URL, if applicable.
pub fn artifact_cache_control(url: &Url) -> Option<&'static str> { pub fn artifact_cache_control(url: &Url) -> Option<&'static str> {
let dominated_by_pytorch_or_nvidia = url.host_str().is_some_and(|host| { if url
host.eq_ignore_ascii_case("download.pytorch.org") .host_str()
|| host.eq_ignore_ascii_case("pypi.nvidia.com") .is_some_and(|host| host.ends_with("pytorch.org"))
}); {
if dominated_by_pytorch_or_nvidia {
// Some wheels in the PyTorch registry were accidentally uploaded with `no-cache,no-store,must-revalidate`. // Some wheels in the PyTorch registry were accidentally uploaded with `no-cache,no-store,must-revalidate`.
// The PyTorch team plans to correct this in the future, but in the meantime we override // The PyTorch team plans to correct this in the future, but in the meantime we override
// the cache control headers to allow caching of static files. // the cache control headers to allow caching of static files.
// //
// See: https://github.com/pytorch/pytorch/pull/149218 // See: https://github.com/pytorch/pytorch/pull/149218
//
// The same issue applies to files hosted on `pypi.nvidia.com`.
Some("max-age=365000000, immutable, public") Some("max-age=365000000, immutable, public")
} else { } else {
None None

View File

@ -875,43 +875,4 @@ mod tests {
Some("max-age=3600") Some("max-age=3600")
); );
} }
#[test]
fn test_nvidia_default_cache_control() {
// Test that NVIDIA indexes get default cache control from the getter methods
let indexes = vec![Index {
name: Some(IndexName::from_str("nvidia").unwrap()),
url: IndexUrl::from_str("https://pypi.nvidia.com").unwrap(),
cache_control: None, // No explicit cache control
explicit: false,
default: false,
origin: None,
format: IndexFormat::Simple,
publish_url: None,
authenticate: uv_auth::AuthPolicy::default(),
ignore_error_codes: None,
}];
let index_urls = IndexUrls::from_indexes(indexes.clone());
let index_locations = IndexLocations::new(indexes, Vec::new(), false);
let nvidia_url = IndexUrl::from_str("https://pypi.nvidia.com").unwrap();
// IndexUrls should return the default for NVIDIA
assert_eq!(index_urls.simple_api_cache_control_for(&nvidia_url), None);
assert_eq!(
index_urls.artifact_cache_control_for(&nvidia_url),
Some("max-age=365000000, immutable, public")
);
// IndexLocations should also return the default for NVIDIA
assert_eq!(
index_locations.simple_api_cache_control_for(&nvidia_url),
None
);
assert_eq!(
index_locations.artifact_cache_control_for(&nvidia_url),
Some("max-age=365000000, immutable, public")
);
}
} }

View File

@ -159,9 +159,9 @@ pub enum InstalledVersion<'a> {
Url(&'a DisplaySafeUrl, &'a Version), Url(&'a DisplaySafeUrl, &'a Version),
} }
impl<'a> InstalledVersion<'a> { impl InstalledVersion<'_> {
/// If it is a URL, return its value. /// If it is a URL, return its value.
pub fn url(&self) -> Option<&'a DisplaySafeUrl> { pub fn url(&self) -> Option<&DisplaySafeUrl> {
match self { match self {
Self::Version(_) => None, Self::Version(_) => None,
Self::Url(url, _) => Some(url), Self::Url(url, _) => Some(url),
@ -169,7 +169,7 @@ impl<'a> InstalledVersion<'a> {
} }
/// If it is a version, return its value. /// If it is a version, return its value.
pub fn version(&self) -> &'a Version { pub fn version(&self) -> &Version {
match self { match self {
Self::Version(version) => version, Self::Version(version) => version,
Self::Url(_, version) => version, Self::Url(_, version) => version,

View File

@ -24,7 +24,7 @@ impl IndexStatusCodeStrategy {
pub fn from_index_url(url: &Url) -> Self { pub fn from_index_url(url: &Url) -> Self {
if url if url
.host_str() .host_str()
.is_some_and(|host| host.eq_ignore_ascii_case("download.pytorch.org")) .is_some_and(|host| host.ends_with("pytorch.org"))
{ {
// The PyTorch registry returns a 403 when a package is not found, so // The PyTorch registry returns a 403 when a package is not found, so
// we ignore them when deciding whether to search other indexes. // we ignore them when deciding whether to search other indexes.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-distribution" name = "uv-distribution"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-distribution). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-distribution).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -721,7 +721,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
.await .await
.map_err(|err| match err { .map_err(|err| match err {
CachedClientError::Callback { err, .. } => err, CachedClientError::Callback { err, .. } => err,
CachedClientError::Client(err) => Error::Client(err), CachedClientError::Client { err, .. } => Error::Client(err),
})?; })?;
// If the archive is missing the required hashes, or has since been removed, force a refresh. // If the archive is missing the required hashes, or has since been removed, force a refresh.
@ -745,7 +745,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
.await .await
.map_err(|err| match err { .map_err(|err| match err {
CachedClientError::Callback { err, .. } => err, CachedClientError::Callback { err, .. } => err,
CachedClientError::Client(err) => Error::Client(err), CachedClientError::Client { err, .. } => Error::Client(err),
}) })
}) })
.await? .await?
@ -926,7 +926,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
.await .await
.map_err(|err| match err { .map_err(|err| match err {
CachedClientError::Callback { err, .. } => err, CachedClientError::Callback { err, .. } => err,
CachedClientError::Client(err) => Error::Client(err), CachedClientError::Client { err, .. } => Error::Client(err),
})?; })?;
// If the archive is missing the required hashes, or has since been removed, force a refresh. // If the archive is missing the required hashes, or has since been removed, force a refresh.
@ -950,7 +950,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
.await .await
.map_err(|err| match err { .map_err(|err| match err {
CachedClientError::Callback { err, .. } => err, CachedClientError::Callback { err, .. } => err,
CachedClientError::Client(err) => Error::Client(err), CachedClientError::Client { err, .. } => Error::Client(err),
}) })
}) })
.await? .await?

View File

@ -5,11 +5,10 @@ use tokio::task::JoinError;
use zip::result::ZipError; use zip::result::ZipError;
use crate::metadata::MetadataError; use crate::metadata::MetadataError;
use uv_cache::Error as CacheError;
use uv_client::WrappedReqwestError; use uv_client::WrappedReqwestError;
use uv_distribution_filename::{WheelFilename, WheelFilenameError}; use uv_distribution_filename::{WheelFilename, WheelFilenameError};
use uv_distribution_types::{InstalledDist, InstalledDistError, IsBuildBackendError}; use uv_distribution_types::{InstalledDist, InstalledDistError, IsBuildBackendError};
use uv_fs::Simplified; use uv_fs::{LockedFileError, Simplified};
use uv_git::GitError; use uv_git::GitError;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::{Version, VersionSpecifiers}; use uv_pep440::{Version, VersionSpecifiers};
@ -43,7 +42,7 @@ pub enum Error {
#[error("Failed to write to the distribution cache")] #[error("Failed to write to the distribution cache")]
CacheWrite(#[source] std::io::Error), CacheWrite(#[source] std::io::Error),
#[error("Failed to acquire lock on the distribution cache")] #[error("Failed to acquire lock on the distribution cache")]
CacheLock(#[source] CacheError), CacheLock(#[source] LockedFileError),
#[error("Failed to deserialize cache entry")] #[error("Failed to deserialize cache entry")]
CacheDecode(#[from] rmp_serde::decode::Error), CacheDecode(#[from] rmp_serde::decode::Error),
#[error("Failed to serialize cache entry")] #[error("Failed to serialize cache entry")]

View File

@ -823,7 +823,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.await .await
.map_err(|err| match err { .map_err(|err| match err {
CachedClientError::Callback { err, .. } => err, CachedClientError::Callback { err, .. } => err,
CachedClientError::Client(err) => Error::Client(err), CachedClientError::Client { err, .. } => Error::Client(err),
})?; })?;
// If the archive is missing the required hashes, force a refresh. // If the archive is missing the required hashes, force a refresh.
@ -843,7 +843,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.await .await
.map_err(|err| match err { .map_err(|err| match err {
CachedClientError::Callback { err, .. } => err, CachedClientError::Callback { err, .. } => err,
CachedClientError::Client(err) => Error::Client(err), CachedClientError::Client { err, .. } => Error::Client(err),
}) })
}) })
.await .await
@ -2280,7 +2280,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.await .await
.map_err(|err| match err { .map_err(|err| match err {
CachedClientError::Callback { err, .. } => err, CachedClientError::Callback { err, .. } => err,
CachedClientError::Client(err) => Error::Client(err), CachedClientError::Client { err, .. } => Error::Client(err),
}) })
}) })
.await .await

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-extract" name = "uv-extract"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-extract). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-extract).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-flags" name = "uv-flags"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-flags). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-flags).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-fs" name = "uv-fs"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-fs). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-fs).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,4 +1,3 @@
use std::convert::Into;
use std::fmt::Display; use std::fmt::Display;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::LazyLock; use std::sync::LazyLock;
@ -60,18 +59,10 @@ pub enum LockedFileError {
source: io::Error, source: io::Error,
}, },
#[error(transparent)] #[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
JoinError(#[from] tokio::task::JoinError), JoinError(#[from] tokio::task::JoinError),
#[error("Could not create temporary file")]
CreateTemporary(#[source] io::Error),
#[error("Could not persist temporary file `{}`", path.user_display())]
PersistTemporary {
path: PathBuf,
#[source]
source: io::Error,
},
#[error(transparent)]
Io(#[from] io::Error),
} }
impl LockedFileError { impl LockedFileError {
@ -81,8 +72,6 @@ impl LockedFileError {
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
Self::JoinError(_) => None, Self::JoinError(_) => None,
Self::Lock { source, .. } => Some(source), Self::Lock { source, .. } => Some(source),
Self::CreateTemporary(err) => Some(err),
Self::PersistTemporary { source, .. } => Some(source),
Self::Io(err) => Some(err), Self::Io(err) => Some(err),
} }
} }
@ -212,7 +201,7 @@ impl LockedFile {
mode: LockedFileMode, mode: LockedFileMode,
resource: impl Display, resource: impl Display,
) -> Result<Self, LockedFileError> { ) -> Result<Self, LockedFileError> {
let file = Self::create(&path)?; let file = Self::create(path)?;
let resource = resource.to_string(); let resource = resource.to_string();
Self::lock_file(file, mode, &resource).await Self::lock_file(file, mode, &resource).await
} }
@ -233,25 +222,10 @@ impl LockedFile {
} }
#[cfg(unix)] #[cfg(unix)]
fn create(path: impl AsRef<Path>) -> Result<fs_err::File, LockedFileError> { fn create(path: impl AsRef<Path>) -> Result<fs_err::File, std::io::Error> {
use rustix::io::Errno; use std::os::unix::fs::PermissionsExt;
#[allow(clippy::disallowed_types)]
use std::{fs::File, os::unix::fs::PermissionsExt};
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
/// The permissions the lockfile should end up with
const DESIRED_MODE: u32 = 0o666;
#[allow(clippy::disallowed_types)]
fn try_set_permissions(file: &File, path: &Path) {
if let Err(err) = file.set_permissions(std::fs::Permissions::from_mode(DESIRED_MODE)) {
warn!(
"Failed to set permissions on temporary file `{path}`: {err}",
path = path.user_display()
);
}
}
// If path already exists, return it. // If path already exists, return it.
if let Ok(file) = fs_err::OpenOptions::new() if let Ok(file) = fs_err::OpenOptions::new()
.read(true) .read(true)
@ -264,12 +238,16 @@ impl LockedFile {
// Otherwise, create a temporary file with 666 permissions. We must set // Otherwise, create a temporary file with 666 permissions. We must set
// permissions _after_ creating the file, to override the `umask`. // permissions _after_ creating the file, to override the `umask`.
let file = if let Some(parent) = path.as_ref().parent() { let file = if let Some(parent) = path.as_ref().parent() {
NamedTempFile::new_in(parent) NamedTempFile::new_in(parent)?
} else { } else {
NamedTempFile::new() NamedTempFile::new()?
};
if let Err(err) = file
.as_file()
.set_permissions(std::fs::Permissions::from_mode(0o666))
{
warn!("Failed to set permissions on temporary file: {err}");
} }
.map_err(LockedFileError::CreateTemporary)?;
try_set_permissions(file.as_file(), file.path());
// Try to move the file to path, but if path exists now, just open path // Try to move the file to path, but if path exists now, just open path
match file.persist_noclobber(path.as_ref()) { match file.persist_noclobber(path.as_ref()) {
@ -280,60 +258,20 @@ impl LockedFile {
.read(true) .read(true)
.write(true) .write(true)
.open(path.as_ref()) .open(path.as_ref())
.map_err(Into::into)
} else if matches!(
Errno::from_io_error(&err.error),
Some(Errno::NOTSUP | Errno::INVAL)
) {
// Fallback in case `persist_noclobber`, which uses `renameat2` or
// `renameatx_np` under the hood, is not supported by the FS. Linux reports this
// with `EINVAL` and MacOS with `ENOTSUP`. For these reasons and many others,
// there isn't an ErrorKind we can use here, and in fact on MacOS `ENOTSUP` gets
// mapped to `ErrorKind::Other`
// There is a race here where another process has just created the file, and we
// try to open it and get permission errors because the other process hasn't set
// the permission bits yet. This will lead to a transient failure, but unlike
// alternative approaches it won't ever lead to a situation where two processes
// are locking two different files. Also, since `persist_noclobber` is more
// likely to not be supported on special filesystems which don't have permission
// bits, it's less likely to ever matter.
let file = fs_err::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path.as_ref())?;
// We don't want to `try_set_permissions` in cases where another user's process
// has already created the lockfile and changed its permissions because we might
// not have permission to change the permissions which would produce a confusing
// warning.
if file
.metadata()
.is_ok_and(|metadata| metadata.permissions().mode() != DESIRED_MODE)
{
try_set_permissions(file.file(), path.as_ref());
}
Ok(file)
} else { } else {
let temp_path = err.file.into_temp_path(); Err(err.error)
Err(LockedFileError::PersistTemporary {
path: <tempfile::TempPath as AsRef<Path>>::as_ref(&temp_path).to_path_buf(),
source: err.error,
})
} }
} }
} }
} }
#[cfg(not(unix))] #[cfg(not(unix))]
fn create(path: impl AsRef<Path>) -> Result<fs_err::File, LockedFileError> { fn create(path: impl AsRef<Path>) -> std::io::Result<fs_err::File> {
fs_err::OpenOptions::new() fs_err::OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
.create(true) .create(true)
.open(path.as_ref()) .open(path.as_ref())
.map_err(Into::into)
} }
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-git-types" name = "uv-git-types"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-git-types). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-git-types).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-git" name = "uv-git"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-git). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-git).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-globfilter" name = "uv-globfilter"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
readme = "README.md" readme = "README.md"
edition = { workspace = true } edition = { workspace = true }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-install-wheel" name = "uv-install-wheel"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
keywords = ["wheel", "python"] keywords = ["wheel", "python"]

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-install-wheel). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-install-wheel).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-installer" name = "uv-installer"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

View File

@ -5,8 +5,8 @@
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes. is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source This version (0.0.6) is a component of [uv 0.9.16](https://crates.io/crates/uv/0.9.16). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-installer). can be found [here](https://github.com/astral-sh/uv/blob/0.9.16/crates/uv-installer).
See uv's See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning) [crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "uv-keyring" name = "uv-keyring"
version = "0.0.8" version = "0.0.6"
description = "This is an internal component crate of uv" description = "This is an internal component crate of uv"
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }

Some files were not shown because too many files have changed in this diff Show More