mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-08 05:45:00 +00:00
Compare commits
70 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7e48292fac | ||
![]() |
e31f556205 | ||
![]() |
dedced3265 | ||
![]() |
5c6d76ca8b | ||
![]() |
1d20530f2d | ||
![]() |
ddb1577a93 | ||
![]() |
d31e6ad7c7 | ||
![]() |
3a77b9cdd9 | ||
![]() |
1d027bd92a | ||
![]() |
bb738aeb44 | ||
![]() |
fc758bb755 | ||
![]() |
1308c85efe | ||
![]() |
f609e1ddaf | ||
![]() |
eaf517efd8 | ||
![]() |
e8bc3950ef | ||
![]() |
06af93fce7 | ||
![]() |
8afbd86f03 | ||
![]() |
a1cda6213c | ||
![]() |
39cdfe9981 | ||
![]() |
85c0fc963b | ||
![]() |
c3f13d2505 | ||
![]() |
38ee6ec800 | ||
![]() |
71b5ba13d7 | ||
![]() |
5f2857a1c7 | ||
![]() |
a58969feef | ||
![]() |
3bb8ac610c | ||
![]() |
ec54dce919 | ||
![]() |
a6bb65c78d | ||
![]() |
743260b1f5 | ||
![]() |
2f53ea5c5c | ||
![]() |
a9ea756d14 | ||
![]() |
43f67a4a4c | ||
![]() |
a7aa46acc5 | ||
![]() |
b0db548c80 | ||
![]() |
bf5dcf9929 | ||
![]() |
e40d3d5dff | ||
![]() |
87e9ccfb92 | ||
![]() |
06df95adbf | ||
![]() |
29fcd6faee | ||
![]() |
d9f9ed4aec | ||
![]() |
85358fe9c6 | ||
![]() |
c078683217 | ||
![]() |
c777491bf4 | ||
![]() |
9af3e9b6ec | ||
![]() |
43745d2ecf | ||
![]() |
3774a656d7 | ||
![]() |
b1812d111a | ||
![]() |
a3db9a9ae4 | ||
![]() |
c5ca240fb7 | ||
![]() |
7bbdc08dae | ||
![]() |
5f8d4bbf02 | ||
![]() |
9e9505df50 | ||
![]() |
2f9061dcd0 | ||
![]() |
317ce6e245 | ||
![]() |
1c7c174bc8 | ||
![]() |
0372a5b05d | ||
![]() |
ae500c95d2 | ||
![]() |
5cfabd7085 | ||
![]() |
15551a0201 | ||
![]() |
61482da319 | ||
![]() |
b2979d25a8 | ||
![]() |
e44a64ee13 | ||
![]() |
e9533a0e29 | ||
![]() |
40386e438f | ||
![]() |
a8b838dee9 | ||
![]() |
d7e1fced43 | ||
![]() |
7603153f5b | ||
![]() |
d15efb7d91 | ||
![]() |
17b7eec287 | ||
![]() |
c0ebe6871d |
116 changed files with 3642 additions and 1595 deletions
|
@ -1,4 +1,4 @@
|
||||||
[profile.default]
|
[profile.default]
|
||||||
# Mark tests that take longer than 10s as slow.
|
# Mark tests that take longer than 10s as slow.
|
||||||
# Terminate after 90s as a stop-gap measure to terminate on deadlock.
|
# Terminate after 120s as a stop-gap measure to terminate on deadlock.
|
||||||
slow-timeout = { period = "10s", terminate-after = 9 }
|
slow-timeout = { period = "10s", terminate-after = 12 }
|
||||||
|
|
44
.github/workflows/build-binaries.yml
vendored
44
.github/workflows/build-binaries.yml
vendored
|
@ -54,7 +54,7 @@ jobs:
|
||||||
- name: "Prep README.md"
|
- name: "Prep README.md"
|
||||||
run: python scripts/transform_readme.py --target pypi
|
run: python scripts/transform_readme.py --target pypi
|
||||||
- name: "Build sdist"
|
- name: "Build sdist"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
command: sdist
|
command: sdist
|
||||||
args: --out dist
|
args: --out dist
|
||||||
|
@ -74,7 +74,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build sdist uv-build"
|
- name: "Build sdist uv-build"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
command: sdist
|
command: sdist
|
||||||
args: --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
|
args: --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
|
||||||
|
@ -103,7 +103,7 @@ jobs:
|
||||||
|
|
||||||
# uv
|
# uv
|
||||||
- name: "Build wheels - x86_64"
|
- name: "Build wheels - x86_64"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: x86_64
|
target: x86_64
|
||||||
args: --release --locked --out dist --features self-update
|
args: --release --locked --out dist --features self-update
|
||||||
|
@ -133,7 +133,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build wheels uv-build - x86_64"
|
- name: "Build wheels uv-build - x86_64"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: x86_64
|
target: x86_64
|
||||||
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
|
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
|
||||||
|
@ -157,7 +157,7 @@ jobs:
|
||||||
|
|
||||||
# uv
|
# uv
|
||||||
- name: "Build wheels - aarch64"
|
- name: "Build wheels - aarch64"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: aarch64
|
target: aarch64
|
||||||
args: --release --locked --out dist --features self-update
|
args: --release --locked --out dist --features self-update
|
||||||
|
@ -193,7 +193,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build wheels uv-build - aarch64"
|
- name: "Build wheels uv-build - aarch64"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: aarch64
|
target: aarch64
|
||||||
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
|
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
|
||||||
|
@ -231,7 +231,7 @@ jobs:
|
||||||
|
|
||||||
# uv
|
# uv
|
||||||
- name: "Build wheels"
|
- name: "Build wheels"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
args: --release --locked --out dist --features self-update,windows-gui-bin
|
args: --release --locked --out dist --features self-update,windows-gui-bin
|
||||||
|
@ -267,7 +267,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build wheels uv-build"
|
- name: "Build wheels uv-build"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
|
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
|
||||||
|
@ -303,7 +303,7 @@ jobs:
|
||||||
|
|
||||||
# uv
|
# uv
|
||||||
- name: "Build wheels"
|
- name: "Build wheels"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
# Generally, we try to build in a target docker container. In this case however, a
|
# Generally, we try to build in a target docker container. In this case however, a
|
||||||
|
@ -368,7 +368,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build wheels uv-build"
|
- name: "Build wheels uv-build"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
manylinux: auto
|
manylinux: auto
|
||||||
|
@ -412,7 +412,7 @@ jobs:
|
||||||
|
|
||||||
# uv
|
# uv
|
||||||
- name: "Build wheels"
|
- name: "Build wheels"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
# On `aarch64`, use `manylinux: 2_28`; otherwise, use `manylinux: auto`.
|
# On `aarch64`, use `manylinux: 2_28`; otherwise, use `manylinux: auto`.
|
||||||
|
@ -461,7 +461,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build wheels uv-build"
|
- name: "Build wheels uv-build"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
# On `aarch64`, use `manylinux: 2_28`; otherwise, use `manylinux: auto`.
|
# On `aarch64`, use `manylinux: 2_28`; otherwise, use `manylinux: auto`.
|
||||||
|
@ -509,7 +509,7 @@ jobs:
|
||||||
|
|
||||||
# uv
|
# uv
|
||||||
- name: "Build wheels"
|
- name: "Build wheels"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
manylinux: auto
|
manylinux: auto
|
||||||
|
@ -561,7 +561,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build wheels uv-build"
|
- name: "Build wheels uv-build"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
manylinux: auto
|
manylinux: auto
|
||||||
|
@ -614,7 +614,7 @@ jobs:
|
||||||
|
|
||||||
# uv
|
# uv
|
||||||
- name: "Build wheels"
|
- name: "Build wheels"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
manylinux: auto
|
manylinux: auto
|
||||||
|
@ -671,7 +671,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build wheels uv-build"
|
- name: "Build wheels uv-build"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
manylinux: auto
|
manylinux: auto
|
||||||
|
@ -712,7 +712,7 @@ jobs:
|
||||||
|
|
||||||
# uv
|
# uv
|
||||||
- name: "Build wheels"
|
- name: "Build wheels"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
manylinux: auto
|
manylinux: auto
|
||||||
|
@ -761,7 +761,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build wheels uv-build"
|
- name: "Build wheels uv-build"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
manylinux: auto
|
manylinux: auto
|
||||||
|
@ -807,7 +807,7 @@ jobs:
|
||||||
|
|
||||||
# uv
|
# uv
|
||||||
- name: "Build wheels"
|
- name: "Build wheels"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
manylinux: musllinux_1_1
|
manylinux: musllinux_1_1
|
||||||
|
@ -854,7 +854,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build wheels uv-build"
|
- name: "Build wheels uv-build"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
manylinux: musllinux_1_1
|
manylinux: musllinux_1_1
|
||||||
|
@ -901,7 +901,7 @@ jobs:
|
||||||
|
|
||||||
# uv
|
# uv
|
||||||
- name: "Build wheels"
|
- name: "Build wheels"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
manylinux: musllinux_1_1
|
manylinux: musllinux_1_1
|
||||||
|
@ -966,7 +966,7 @@ jobs:
|
||||||
|
|
||||||
# uv-build
|
# uv-build
|
||||||
- name: "Build wheels"
|
- name: "Build wheels"
|
||||||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.platform.target }}
|
target: ${{ matrix.platform.target }}
|
||||||
manylinux: musllinux_1_1
|
manylinux: musllinux_1_1
|
||||||
|
|
4
.github/workflows/build-docker.yml
vendored
4
.github/workflows/build-docker.yml
vendored
|
@ -137,7 +137,7 @@ jobs:
|
||||||
|
|
||||||
- name: Build and push by digest
|
- name: Build and push by digest
|
||||||
id: build
|
id: build
|
||||||
uses: depot/build-push-action@636daae76684e38c301daa0c5eca1c095b24e780 # v1.14.0
|
uses: depot/build-push-action@2583627a84956d07561420dcc1d0eb1f2af3fac0 # v1.15.0
|
||||||
with:
|
with:
|
||||||
project: 7hd4vdzmw5 # astral-sh/uv
|
project: 7hd4vdzmw5 # astral-sh/uv
|
||||||
context: .
|
context: .
|
||||||
|
@ -267,7 +267,7 @@ jobs:
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build-and-push
|
id: build-and-push
|
||||||
uses: depot/build-push-action@636daae76684e38c301daa0c5eca1c095b24e780 # v1.14.0
|
uses: depot/build-push-action@2583627a84956d07561420dcc1d0eb1f2af3fac0 # v1.15.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
project: 7hd4vdzmw5 # astral-sh/uv
|
project: 7hd4vdzmw5 # astral-sh/uv
|
||||||
|
|
226
.github/workflows/ci.yml
vendored
226
.github/workflows/ci.yml
vendored
|
@ -82,7 +82,7 @@ jobs:
|
||||||
run: rustup component add rustfmt
|
run: rustup component add rustfmt
|
||||||
|
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||||
|
|
||||||
- name: "rustfmt"
|
- name: "rustfmt"
|
||||||
run: cargo fmt --all --check
|
run: cargo fmt --all --check
|
||||||
|
@ -126,7 +126,7 @@ jobs:
|
||||||
name: "cargo clippy | ubuntu"
|
name: "cargo clippy | ubuntu"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Check uv_build dependencies"
|
- name: "Check uv_build dependencies"
|
||||||
|
@ -156,7 +156,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
with:
|
with:
|
||||||
workspaces: ${{ env.UV_WORKSPACE }}
|
workspaces: ${{ env.UV_WORKSPACE }}
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ jobs:
|
||||||
name: "cargo dev generate-all"
|
name: "cargo dev generate-all"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
with:
|
with:
|
||||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||||
- name: "Generate all"
|
- name: "Generate all"
|
||||||
|
@ -208,12 +208,12 @@ jobs:
|
||||||
|
|
||||||
- uses: rui314/setup-mold@v1
|
- uses: rui314/setup-mold@v1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
|
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||||
- name: "Install required Python versions"
|
- name: "Install required Python versions"
|
||||||
run: uv python install
|
run: uv python install
|
||||||
|
|
||||||
|
@ -240,12 +240,12 @@ jobs:
|
||||||
|
|
||||||
- uses: rui314/setup-mold@v1
|
- uses: rui314/setup-mold@v1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
|
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||||
- name: "Install required Python versions"
|
- name: "Install required Python versions"
|
||||||
run: uv python install
|
run: uv python install
|
||||||
|
|
||||||
|
@ -279,11 +279,11 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||||
- name: "Install required Python versions"
|
- name: "Install required Python versions"
|
||||||
run: uv python install
|
run: uv python install
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
with:
|
with:
|
||||||
workspaces: ${{ env.UV_WORKSPACE }}
|
workspaces: ${{ env.UV_WORKSPACE }}
|
||||||
|
|
||||||
|
@ -332,7 +332,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
with:
|
with:
|
||||||
workspaces: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
|
workspaces: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
|
||||||
|
|
||||||
|
@ -388,7 +388,7 @@ jobs:
|
||||||
- name: Copy Git Repo to Dev Drive
|
- name: Copy Git Repo to Dev Drive
|
||||||
run: |
|
run: |
|
||||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
with:
|
with:
|
||||||
workspaces: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
|
workspaces: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
|
@ -430,7 +430,7 @@ jobs:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
- name: "Add SSH key"
|
- name: "Add SSH key"
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||||
|
@ -443,7 +443,7 @@ jobs:
|
||||||
|
|
||||||
- name: "Build docs (insiders)"
|
- name: "Build docs (insiders)"
|
||||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||||
run: uvx --with-requirements docs/requirements.txt mkdocs build --strict -f mkdocs.insiders.yml
|
run: uvx --with-requirements docs/requirements-insiders.txt mkdocs build --strict -f mkdocs.insiders.yml
|
||||||
|
|
||||||
build-binary-linux-libc:
|
build-binary-linux-libc:
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
@ -456,7 +456,7 @@ jobs:
|
||||||
|
|
||||||
- uses: rui314/setup-mold@v1
|
- uses: rui314/setup-mold@v1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: cargo build
|
run: cargo build
|
||||||
|
@ -470,6 +470,31 @@ jobs:
|
||||||
./target/debug/uvx
|
./target/debug/uvx
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
|
build-binary-linux-aarch64:
|
||||||
|
timeout-minutes: 10
|
||||||
|
needs: determine_changes
|
||||||
|
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||||
|
runs-on: github-ubuntu-24.04-aarch64-4
|
||||||
|
name: "build binary | linux aarch64"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- uses: rui314/setup-mold@v1
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
|
|
||||||
|
- name: "Build"
|
||||||
|
run: cargo build
|
||||||
|
|
||||||
|
- name: "Upload binary"
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: uv-linux-aarch64-${{ github.sha }}
|
||||||
|
path: |
|
||||||
|
./target/debug/uv
|
||||||
|
./target/debug/uvx
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
build-binary-linux-musl:
|
build-binary-linux-musl:
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
needs: determine_changes
|
needs: determine_changes
|
||||||
|
@ -486,7 +511,7 @@ jobs:
|
||||||
sudo apt-get install musl-tools
|
sudo apt-get install musl-tools
|
||||||
rustup target add x86_64-unknown-linux-musl
|
rustup target add x86_64-unknown-linux-musl
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: cargo build --target x86_64-unknown-linux-musl --bin uv --bin uvx
|
run: cargo build --target x86_64-unknown-linux-musl --bin uv --bin uvx
|
||||||
|
@ -511,7 +536,7 @@ jobs:
|
||||||
|
|
||||||
- uses: rui314/setup-mold@v1
|
- uses: rui314/setup-mold@v1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: cargo build --bin uv --bin uvx
|
run: cargo build --bin uv --bin uvx
|
||||||
|
|
||||||
|
@ -535,7 +560,7 @@ jobs:
|
||||||
|
|
||||||
- uses: rui314/setup-mold@v1
|
- uses: rui314/setup-mold@v1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: cargo build --bin uv --bin uvx
|
run: cargo build --bin uv --bin uvx
|
||||||
|
|
||||||
|
@ -565,7 +590,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
with:
|
with:
|
||||||
workspaces: ${{ env.UV_WORKSPACE }}
|
workspaces: ${{ env.UV_WORKSPACE }}
|
||||||
|
|
||||||
|
@ -600,7 +625,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
Copy-Item -Path "${{ github.workspace }}" -Destination "${{ env.UV_WORKSPACE }}" -Recurse
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
with:
|
with:
|
||||||
workspaces: ${{ env.UV_WORKSPACE }}
|
workspaces: ${{ env.UV_WORKSPACE }}
|
||||||
|
|
||||||
|
@ -637,7 +662,7 @@ jobs:
|
||||||
run: rustup default ${{ steps.msrv.outputs.value }}
|
run: rustup default ${{ steps.msrv.outputs.value }}
|
||||||
- name: "Install mold"
|
- name: "Install mold"
|
||||||
uses: rui314/setup-mold@v1
|
uses: rui314/setup-mold@v1
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
- run: cargo +${{ steps.msrv.outputs.value }} build
|
- run: cargo +${{ steps.msrv.outputs.value }} build
|
||||||
- run: ./target/debug/uv --version
|
- run: ./target/debug/uv --version
|
||||||
|
|
||||||
|
@ -650,7 +675,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
- name: "Cross build"
|
- name: "Cross build"
|
||||||
run: |
|
run: |
|
||||||
# Install cross from `freebsd-firecracker`
|
# Install cross from `freebsd-firecracker`
|
||||||
|
@ -661,7 +686,7 @@ jobs:
|
||||||
cross build --target x86_64-unknown-freebsd
|
cross build --target x86_64-unknown-freebsd
|
||||||
|
|
||||||
- name: Test in Firecracker VM
|
- name: Test in Firecracker VM
|
||||||
uses: acj/freebsd-firecracker-action@6c57bda7113c2f137ef00d54512d61ae9d64365b # v0.5.0
|
uses: acj/freebsd-firecracker-action@136ca0bce2adade21e526ceb07db643ad23dd2dd # v0.5.1
|
||||||
with:
|
with:
|
||||||
verbose: false
|
verbose: false
|
||||||
checkout: false
|
checkout: false
|
||||||
|
@ -770,6 +795,33 @@ jobs:
|
||||||
eval "$(./uv generate-shell-completion bash)"
|
eval "$(./uv generate-shell-completion bash)"
|
||||||
eval "$(./uvx --generate-shell-completion bash)"
|
eval "$(./uvx --generate-shell-completion bash)"
|
||||||
|
|
||||||
|
smoke-test-linux-aarch64:
|
||||||
|
timeout-minutes: 10
|
||||||
|
needs: build-binary-linux-aarch64
|
||||||
|
name: "smoke test | linux aarch64"
|
||||||
|
runs-on: github-ubuntu-24.04-aarch64-2
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: "Download binary"
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
with:
|
||||||
|
name: uv-linux-aarch64-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: "Prepare binary"
|
||||||
|
run: |
|
||||||
|
chmod +x ./uv
|
||||||
|
chmod +x ./uvx
|
||||||
|
|
||||||
|
- name: "Smoke test"
|
||||||
|
run: |
|
||||||
|
./uv run scripts/smoke-test
|
||||||
|
|
||||||
|
- name: "Test shell completions"
|
||||||
|
run: |
|
||||||
|
eval "$(./uv generate-shell-completion bash)"
|
||||||
|
eval "$(./uvx --generate-shell-completion bash)"
|
||||||
|
|
||||||
smoke-test-linux-musl:
|
smoke-test-linux-musl:
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
needs: build-binary-linux-musl
|
needs: build-binary-linux-musl
|
||||||
|
@ -852,7 +904,7 @@ jobs:
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
needs: build-binary-windows-aarch64
|
needs: build-binary-windows-aarch64
|
||||||
name: "smoke test | windows aarch64"
|
name: "smoke test | windows aarch64"
|
||||||
runs-on: github-windows-11-aarch64-4
|
runs-on: windows-11-arm
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
@ -1000,6 +1052,96 @@ jobs:
|
||||||
./uv run python -c ""
|
./uv run python -c ""
|
||||||
./uv run -p 3.13t python -c ""
|
./uv run -p 3.13t python -c ""
|
||||||
|
|
||||||
|
integration-test-windows-aarch64-implicit:
|
||||||
|
timeout-minutes: 10
|
||||||
|
needs: build-binary-windows-aarch64
|
||||||
|
name: "integration test | aarch64 windows implicit"
|
||||||
|
runs-on: windows-11-arm
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Download binary"
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
with:
|
||||||
|
name: uv-windows-aarch64-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: "Install Python via uv (implicitly select x64)"
|
||||||
|
run: |
|
||||||
|
./uv python install -v 3.13
|
||||||
|
|
||||||
|
- name: "Create a virtual environment (stdlib)"
|
||||||
|
run: |
|
||||||
|
& (./uv python find 3.13) -m venv .venv
|
||||||
|
|
||||||
|
- name: "Check version (stdlib)"
|
||||||
|
run: |
|
||||||
|
.venv/Scripts/python --version
|
||||||
|
|
||||||
|
- name: "Create a virtual environment (uv)"
|
||||||
|
run: |
|
||||||
|
./uv venv -p 3.13 --managed-python
|
||||||
|
|
||||||
|
- name: "Check version (uv)"
|
||||||
|
run: |
|
||||||
|
.venv/Scripts/python --version
|
||||||
|
|
||||||
|
- name: "Check is x64"
|
||||||
|
run: |
|
||||||
|
.venv/Scripts/python -c "import sys; exit(1) if 'AMD64' not in sys.version else exit(0)"
|
||||||
|
|
||||||
|
- name: "Check install"
|
||||||
|
run: |
|
||||||
|
./uv pip install -v anyio
|
||||||
|
|
||||||
|
- name: "Check uv run"
|
||||||
|
run: |
|
||||||
|
./uv run python -c ""
|
||||||
|
./uv run -p 3.13 python -c ""
|
||||||
|
|
||||||
|
integration-test-windows-aarch64-explicit:
|
||||||
|
timeout-minutes: 10
|
||||||
|
needs: build-binary-windows-aarch64
|
||||||
|
name: "integration test | aarch64 windows explicit"
|
||||||
|
runs-on: windows-11-arm
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Download binary"
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
with:
|
||||||
|
name: uv-windows-aarch64-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: "Install Python via uv (explicitly select aarch64)"
|
||||||
|
run: |
|
||||||
|
./uv python install -v cpython-3.13-windows-aarch64-none
|
||||||
|
|
||||||
|
- name: "Create a virtual environment (stdlib)"
|
||||||
|
run: |
|
||||||
|
& (./uv python find 3.13) -m venv .venv
|
||||||
|
|
||||||
|
- name: "Check version (stdlib)"
|
||||||
|
run: |
|
||||||
|
.venv/Scripts/python --version
|
||||||
|
|
||||||
|
- name: "Create a virtual environment (uv)"
|
||||||
|
run: |
|
||||||
|
./uv venv -p 3.13 --managed-python
|
||||||
|
|
||||||
|
- name: "Check version (uv)"
|
||||||
|
run: |
|
||||||
|
.venv/Scripts/python --version
|
||||||
|
|
||||||
|
- name: "Check is NOT x64"
|
||||||
|
run: |
|
||||||
|
.venv/Scripts/python -c "import sys; exit(1) if 'AMD64' in sys.version else exit(0)"
|
||||||
|
|
||||||
|
- name: "Check install"
|
||||||
|
run: |
|
||||||
|
./uv pip install -v anyio
|
||||||
|
|
||||||
|
- name: "Check uv run"
|
||||||
|
run: |
|
||||||
|
./uv run python -c ""
|
||||||
|
./uv run -p 3.13 python -c ""
|
||||||
|
|
||||||
integration-test-pypy-linux:
|
integration-test-pypy-linux:
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
needs: build-binary-linux-libc
|
needs: build-binary-linux-libc
|
||||||
|
@ -1443,7 +1585,7 @@ jobs:
|
||||||
run: chmod +x ./uv
|
run: chmod +x ./uv
|
||||||
|
|
||||||
- name: "Configure AWS credentials"
|
- name: "Configure AWS credentials"
|
||||||
uses: aws-actions/configure-aws-credentials@3bb878b6ab43ba8717918141cd07a0ea68cfe7ea
|
uses: aws-actions/configure-aws-credentials@f503a1870408dcf2c35d5c2b8a68e69211042c7d
|
||||||
with:
|
with:
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
@ -1480,9 +1622,9 @@ jobs:
|
||||||
run: ./uv run -p ${{ env.PYTHON_VERSION }} scripts/registries-test.py --uv ./uv --color always --all
|
run: ./uv run -p ${{ env.PYTHON_VERSION }} scripts/registries-test.py --uv ./uv --color always --all
|
||||||
env:
|
env:
|
||||||
RUST_LOG: uv=debug
|
RUST_LOG: uv=debug
|
||||||
# UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }}
|
UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }}
|
||||||
# UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }}
|
UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }}
|
||||||
# UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }}
|
UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }}
|
||||||
UV_TEST_AWS_URL: ${{ secrets.UV_TEST_AWS_URL }}
|
UV_TEST_AWS_URL: ${{ secrets.UV_TEST_AWS_URL }}
|
||||||
UV_TEST_AWS_USERNAME: aws
|
UV_TEST_AWS_USERNAME: aws
|
||||||
UV_TEST_AZURE_TOKEN: ${{ secrets.UV_TEST_AZURE_TOKEN }}
|
UV_TEST_AZURE_TOKEN: ${{ secrets.UV_TEST_AZURE_TOKEN }}
|
||||||
|
@ -2072,7 +2214,7 @@ jobs:
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
needs: build-binary-windows-aarch64
|
needs: build-binary-windows-aarch64
|
||||||
name: "check system | x86-64 python3.13 on windows aarch64"
|
name: "check system | x86-64 python3.13 on windows aarch64"
|
||||||
runs-on: github-windows-11-aarch64-4
|
runs-on: windows-11-arm
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
@ -2090,6 +2232,28 @@ jobs:
|
||||||
- name: "Validate global Python install"
|
- name: "Validate global Python install"
|
||||||
run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe
|
run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe
|
||||||
|
|
||||||
|
system-test-windows-aarch64-aarch64-python-313:
|
||||||
|
timeout-minutes: 10
|
||||||
|
needs: build-binary-windows-aarch64
|
||||||
|
name: "check system | aarch64 python3.13 on windows aarch64"
|
||||||
|
runs-on: windows-11-arm
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||||
|
with:
|
||||||
|
python-version: "3.13"
|
||||||
|
architecture: "arm64"
|
||||||
|
allow-prereleases: true
|
||||||
|
|
||||||
|
- name: "Download binary"
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
with:
|
||||||
|
name: uv-windows-aarch64-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: "Validate global Python install"
|
||||||
|
run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe
|
||||||
|
|
||||||
# Test our PEP 514 integration that installs Python into the Windows registry.
|
# Test our PEP 514 integration that installs Python into the Windows registry.
|
||||||
system-test-windows-registry:
|
system-test-windows-registry:
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
@ -2337,7 +2501,7 @@ jobs:
|
||||||
- name: "Checkout Branch"
|
- name: "Checkout Branch"
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
|
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
@ -2374,7 +2538,7 @@ jobs:
|
||||||
- name: "Checkout Branch"
|
- name: "Checkout Branch"
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||||
|
|
||||||
- name: "Install Rust toolchain"
|
- name: "Install Rust toolchain"
|
||||||
run: rustup show
|
run: rustup show
|
||||||
|
|
4
.github/workflows/publish-pypi.yml
vendored
4
.github/workflows/publish-pypi.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
pattern: wheels_uv-*
|
pattern: wheels_uv-*
|
||||||
|
@ -43,7 +43,7 @@ jobs:
|
||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: "Install uv"
|
- name: "Install uv"
|
||||||
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
with:
|
with:
|
||||||
pattern: wheels_uv_build-*
|
pattern: wheels_uv_build-*
|
||||||
|
|
2
.github/workflows/sync-python-releases.yml
vendored
2
.github/workflows/sync-python-releases.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
|
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||||
with:
|
with:
|
||||||
version: "latest"
|
version: "latest"
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
|
|
|
@ -12,7 +12,7 @@ repos:
|
||||||
- id: validate-pyproject
|
- id: validate-pyproject
|
||||||
|
|
||||||
- repo: https://github.com/crate-ci/typos
|
- repo: https://github.com/crate-ci/typos
|
||||||
rev: v1.33.1
|
rev: v1.34.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: typos
|
- id: typos
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ repos:
|
||||||
types_or: [yaml, json5]
|
types_or: [yaml, json5]
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.11.13
|
rev: v0.12.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|
73
CHANGELOG.md
73
CHANGELOG.md
|
@ -2,6 +2,79 @@
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
|
|
||||||
|
|
||||||
|
## 0.7.19
|
||||||
|
|
||||||
|
The **[uv build backend](https://docs.astral.sh/uv/concepts/build-backend/) is now stable**, and considered ready for production use.
|
||||||
|
|
||||||
|
The uv build backend is a great choice for pure Python projects. It has reasonable defaults, with the goal of requiring zero configuration for most users, but provides flexible configuration to accommodate most Python project structures. It integrates tightly with uv, to improve messaging and user experience. It validates project metadata and structures, preventing common mistakes. And, finally, it's very fast — `uv sync` on a new project (from `uv init`) is 10-30x faster than with other build backends.
|
||||||
|
|
||||||
|
To use uv as a build backend in an existing project, add `uv_build` to the `[build-system]` section in your `pyproject.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[build-system]
|
||||||
|
requires = ["uv_build>=0.7.19,<0.8.0"]
|
||||||
|
build-backend = "uv_build"
|
||||||
|
```
|
||||||
|
|
||||||
|
In a future release, it will replace `hatchling` as the default in `uv init`. As before, uv will remain compatible with all standards-compliant build backends.
|
||||||
|
|
||||||
|
### Python
|
||||||
|
|
||||||
|
- Add PGO distributions of Python for aarch64 Linux, which are more optimized for better performance
|
||||||
|
|
||||||
|
See the [python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250702) for more details.
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
- Ignore Python patch version for `--universal` pip compile ([#14405](https://github.com/astral-sh/uv/pull/14405))
|
||||||
|
- Update the tilde version specifier warning to include more context ([#14335](https://github.com/astral-sh/uv/pull/14335))
|
||||||
|
- Clarify behavior and hint on tool install when no executables are available ([#14423](https://github.com/astral-sh/uv/pull/14423))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Make project and interpreter lock acquisition non-fatal ([#14404](https://github.com/astral-sh/uv/pull/14404))
|
||||||
|
- Includes `sys.prefix` in cached environment keys to avoid `--with` collisions across projects ([#14403](https://github.com/astral-sh/uv/pull/14403))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Add a migration guide from pip to uv projects ([#12382](https://github.com/astral-sh/uv/pull/12382))
|
||||||
|
|
||||||
|
## 0.7.18
|
||||||
|
|
||||||
|
### Python
|
||||||
|
|
||||||
|
- Added arm64 Windows Python 3.11, 3.12, 3.13, and 3.14
|
||||||
|
|
||||||
|
These are not downloaded by default, since x86-64 Python has broader ecosystem support on Windows.
|
||||||
|
However, they can be requested with `cpython-<version>-windows-aarch64`.
|
||||||
|
|
||||||
|
See the [python-build-standalone release](https://github.com/astral-sh/python-build-standalone/releases/tag/20250630) for more details.
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
- Keep track of retries in `ManagedPythonDownload::fetch_with_retry` ([#14378](https://github.com/astral-sh/uv/pull/14378))
|
||||||
|
- Reuse build (virtual) environments across resolution and installation ([#14338](https://github.com/astral-sh/uv/pull/14338))
|
||||||
|
- Improve trace message for cached Python interpreter query ([#14328](https://github.com/astral-sh/uv/pull/14328))
|
||||||
|
- Use parsed URLs for conflicting URL error message ([#14380](https://github.com/astral-sh/uv/pull/14380))
|
||||||
|
|
||||||
|
### Preview features
|
||||||
|
|
||||||
|
- Ignore invalid build backend settings when not building ([#14372](https://github.com/astral-sh/uv/pull/14372))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fix equals-star and tilde-equals with `python_version` and `python_full_version` ([#14271](https://github.com/astral-sh/uv/pull/14271))
|
||||||
|
- Include the canonical path in the interpreter query cache key ([#14331](https://github.com/astral-sh/uv/pull/14331))
|
||||||
|
- Only drop build directories on program exit ([#14304](https://github.com/astral-sh/uv/pull/14304))
|
||||||
|
- Error instead of panic on conflict between global and subcommand flags ([#14368](https://github.com/astral-sh/uv/pull/14368))
|
||||||
|
- Consistently normalize trailing slashes on URLs with no path segments ([#14349](https://github.com/astral-sh/uv/pull/14349))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Add instructions for publishing to JFrog's Artifactory ([#14253](https://github.com/astral-sh/uv/pull/14253))
|
||||||
|
- Edits to the build backend documentation ([#14376](https://github.com/astral-sh/uv/pull/14376))
|
||||||
|
|
||||||
## 0.7.17
|
## 0.7.17
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
195
Cargo.lock
generated
195
Cargo.lock
generated
|
@ -94,6 +94,15 @@ version = "1.0.98"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "approx"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arbitrary"
|
name = "arbitrary"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
@ -180,9 +189,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
version = "2.3.1"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
|
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
"event-listener-strategy",
|
"event-listener-strategy",
|
||||||
|
@ -364,6 +373,15 @@ version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bisection"
|
name = "bisection"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -512,9 +530,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cargo-util"
|
name = "cargo-util"
|
||||||
version = "0.2.20"
|
version = "0.2.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d767bc85f367f6483a6072430b56f5c0d6ee7636751a21a800526d0711753d76"
|
checksum = "c95ec8b2485b20aed818bd7460f8eecc6c87c35c84191b353a3aba9aa1736c36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
|
@ -672,22 +690,27 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codspeed"
|
name = "codspeed"
|
||||||
version = "2.10.1"
|
version = "3.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93f4cce9c27c49c4f101fffeebb1826f41a9df2e7498b7cd4d95c0658b796c6c"
|
checksum = "922018102595f6668cdd09c03f4bff2d951ce2318c6dca4fe11bdcb24b65b2bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bincode",
|
||||||
"colored",
|
"colored",
|
||||||
|
"glob",
|
||||||
"libc",
|
"libc",
|
||||||
|
"nix 0.29.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"statrs",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codspeed-criterion-compat"
|
name = "codspeed-criterion-compat"
|
||||||
version = "2.10.1"
|
version = "3.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3c23d880a28a2aab52d38ca8481dd7a3187157d0a952196b6db1db3c8499725"
|
checksum = "24d8ad82d2383cb74995f58993cbdd2914aed57b2f91f46580310dd81dc3d05a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"codspeed",
|
"codspeed",
|
||||||
"codspeed-criterion-compat-walltime",
|
"codspeed-criterion-compat-walltime",
|
||||||
|
@ -696,9 +719,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codspeed-criterion-compat-walltime"
|
name = "codspeed-criterion-compat-walltime"
|
||||||
version = "2.10.1"
|
version = "3.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b0a2f7365e347f4f22a67e9ea689bf7bc89900a354e22e26cf8a531a42c8fbb"
|
checksum = "61badaa6c452d192a29f8387147888f0ab358553597c3fe9bf8a162ef7c2fa64"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anes",
|
"anes",
|
||||||
"cast",
|
"cast",
|
||||||
|
@ -1142,9 +1165,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener-strategy"
|
name = "event-listener-strategy"
|
||||||
version = "0.5.3"
|
version = "0.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2"
|
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"event-listener",
|
"event-listener",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
@ -1675,7 +1698,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"webpki-roots",
|
"webpki-roots 0.26.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1684,6 +1707,7 @@ version = "0.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
|
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -1691,7 +1715,9 @@ dependencies = [
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"ipnet",
|
||||||
"libc",
|
"libc",
|
||||||
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -1873,9 +1899,9 @@ checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.9.0"
|
version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
|
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.15.4",
|
"hashbrown 0.15.4",
|
||||||
|
@ -1922,6 +1948,16 @@ version = "2.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iri-string"
|
||||||
|
version = "0.7.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
version = "0.4.15"
|
version = "0.4.15"
|
||||||
|
@ -2474,9 +2510,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "owo-colors"
|
name = "owo-colors"
|
||||||
version = "4.2.1"
|
version = "4.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec"
|
checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
|
@ -3039,9 +3075,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.15"
|
version = "0.12.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
@ -3056,18 +3092,14 @@ dependencies = [
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-rustls",
|
"hyper-rustls",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"ipnet",
|
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"once_cell",
|
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"quinn",
|
"quinn",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-native-certs",
|
"rustls-native-certs",
|
||||||
"rustls-pemfile",
|
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -3075,17 +3107,16 @@ dependencies = [
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-socks",
|
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
|
"tower-http",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"wasm-streams",
|
"wasm-streams",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"webpki-roots",
|
"webpki-roots 1.0.1",
|
||||||
"windows-registry 0.4.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3328,15 +3359,6 @@ dependencies = [
|
||||||
"security-framework",
|
"security-framework",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustls-pemfile"
|
|
||||||
version = "2.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
|
||||||
dependencies = [
|
|
||||||
"rustls-pki-types",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pki-types"
|
name = "rustls-pki-types"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
@ -3405,9 +3427,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars"
|
name = "schemars"
|
||||||
version = "1.0.0"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "febc07c7e70b5db4f023485653c754d76e1bbe8d9dbfa20193ce13da9f9633f4"
|
checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"ref-cast",
|
"ref-cast",
|
||||||
|
@ -3419,9 +3441,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schemars_derive"
|
name = "schemars_derive"
|
||||||
version = "1.0.0"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1eeedaab7b1e1d09b5b4661121f4d27f9e7487089b0117833ccd7a882ee1ecc"
|
checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -3720,6 +3742,16 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "statrs"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strict-num"
|
name = "strict-num"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -3935,9 +3967,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "test-log"
|
name = "test-log"
|
||||||
version = "0.2.17"
|
version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7f46083d221181166e5b6f6b1e5f1d499f3a76888826e6cb1d057554157cd0f"
|
checksum = "1e33b98a582ea0be1168eba097538ee8dd4bbe0f2b01b22ac92ea30054e5be7b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"test-log-macros",
|
"test-log-macros",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
@ -3945,9 +3977,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "test-log-macros"
|
name = "test-log-macros"
|
||||||
version = "0.2.17"
|
version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f"
|
checksum = "451b374529930d7601b1eef8d32bc79ae870b6079b069401709c2a8bf9e75f36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -4139,18 +4171,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-socks"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"futures-util",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
|
@ -4233,6 +4253,24 @@ dependencies = [
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.6.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.1",
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"iri-string",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -4570,7 +4608,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uv"
|
name = "uv"
|
||||||
version = "0.7.17"
|
version = "0.7.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
@ -4734,7 +4772,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uv-build"
|
name = "uv-build"
|
||||||
version = "0.7.17"
|
version = "0.7.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"uv-build-backend",
|
"uv-build-backend",
|
||||||
|
@ -5602,7 +5640,7 @@ dependencies = [
|
||||||
"uv-trampoline-builder",
|
"uv-trampoline-builder",
|
||||||
"uv-warnings",
|
"uv-warnings",
|
||||||
"which",
|
"which",
|
||||||
"windows-registry 0.5.2",
|
"windows-registry",
|
||||||
"windows-result 0.3.4",
|
"windows-result 0.3.4",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
@ -5806,7 +5844,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"uv-fs",
|
"uv-fs",
|
||||||
"uv-static",
|
"uv-static",
|
||||||
"windows-registry 0.5.2",
|
"windows-registry",
|
||||||
"windows-result 0.3.4",
|
"windows-result 0.3.4",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
@ -5904,6 +5942,7 @@ name = "uv-types"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"dashmap",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"uv-cache",
|
"uv-cache",
|
||||||
|
@ -5923,7 +5962,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uv-version"
|
name = "uv-version"
|
||||||
version = "0.7.17"
|
version = "0.7.19"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uv-virtualenv"
|
name = "uv-virtualenv"
|
||||||
|
@ -6187,6 +6226,15 @@ dependencies = [
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "weezl"
|
name = "weezl"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
@ -6330,7 +6378,7 @@ dependencies = [
|
||||||
"windows-interface 0.59.1",
|
"windows-interface 0.59.1",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
"windows-result 0.3.4",
|
"windows-result 0.3.4",
|
||||||
"windows-strings 0.4.1",
|
"windows-strings 0.4.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -6400,9 +6448,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.1.1"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-numerics"
|
name = "windows-numerics"
|
||||||
|
@ -6416,24 +6464,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-registry"
|
name = "windows-registry"
|
||||||
version = "0.4.0"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||||
dependencies = [
|
|
||||||
"windows-result 0.3.4",
|
|
||||||
"windows-strings 0.3.1",
|
|
||||||
"windows-targets 0.53.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-registry"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
"windows-result 0.3.4",
|
"windows-result 0.3.4",
|
||||||
"windows-strings 0.4.1",
|
"windows-strings 0.4.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -6465,9 +6502,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-strings"
|
name = "windows-strings"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a"
|
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
|
@ -142,7 +142,7 @@ ref-cast = { version = "1.0.24" }
|
||||||
reflink-copy = { version = "0.1.19" }
|
reflink-copy = { version = "0.1.19" }
|
||||||
regex = { version = "1.10.6" }
|
regex = { version = "1.10.6" }
|
||||||
regex-automata = { version = "0.4.8", default-features = false, features = ["dfa-build", "dfa-search", "perf", "std", "syntax"] }
|
regex-automata = { version = "0.4.8", default-features = false, features = ["dfa-build", "dfa-search", "perf", "std", "syntax"] }
|
||||||
reqwest = { version = "=0.12.15", default-features = false, features = ["json", "gzip", "deflate", "zstd", "stream", "rustls-tls", "rustls-tls-native-roots", "socks", "multipart", "http2", "blocking"] }
|
reqwest = { version = "0.12.22", default-features = false, features = ["json", "gzip", "deflate", "zstd", "stream", "rustls-tls", "rustls-tls-native-roots", "socks", "multipart", "http2", "blocking"] }
|
||||||
reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8", features = ["multipart"] }
|
reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8", features = ["multipart"] }
|
||||||
reqwest-retry = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8" }
|
reqwest-retry = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8" }
|
||||||
rkyv = { version = "0.8.8", features = ["bytecheck"] }
|
rkyv = { version = "0.8.8", features = ["bytecheck"] }
|
||||||
|
|
|
@ -42,8 +42,10 @@ uv-types = { workspace = true }
|
||||||
uv-workspace = { workspace = true }
|
uv-workspace = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
codspeed-criterion-compat = { version = "2.7.2", default-features = false, optional = true }
|
codspeed-criterion-compat = { version = "3.0.2", default-features = false, optional = true }
|
||||||
criterion = { version = "0.6.0", default-features = false, features = ["async_tokio"] }
|
criterion = { version = "0.6.0", default-features = false, features = [
|
||||||
|
"async_tokio",
|
||||||
|
] }
|
||||||
jiff = { workspace = true }
|
jiff = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,6 @@ use uv_macros::OptionsMetadata;
|
||||||
|
|
||||||
/// Settings for the uv build backend (`uv_build`).
|
/// Settings for the uv build backend (`uv_build`).
|
||||||
///
|
///
|
||||||
/// !!! note
|
|
||||||
///
|
|
||||||
/// The uv build backend is currently in preview and may change in any future release.
|
|
||||||
///
|
|
||||||
/// Note that those settings only apply when using the `uv_build` backend, other build backends
|
/// Note that those settings only apply when using the `uv_build` backend, other build backends
|
||||||
/// (such as hatchling) have their own configuration.
|
/// (such as hatchling) have their own configuration.
|
||||||
///
|
///
|
||||||
|
|
|
@ -25,7 +25,7 @@ use tempfile::TempDir;
|
||||||
use tokio::io::AsyncBufReadExt;
|
use tokio::io::AsyncBufReadExt;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::sync::{Mutex, Semaphore};
|
use tokio::sync::{Mutex, Semaphore};
|
||||||
use tracing::{Instrument, debug, info_span, instrument};
|
use tracing::{Instrument, debug, info_span, instrument, warn};
|
||||||
|
|
||||||
use uv_cache_key::cache_digest;
|
use uv_cache_key::cache_digest;
|
||||||
use uv_configuration::PreviewMode;
|
use uv_configuration::PreviewMode;
|
||||||
|
@ -259,8 +259,6 @@ pub struct SourceBuild {
|
||||||
environment_variables: FxHashMap<OsString, OsString>,
|
environment_variables: FxHashMap<OsString, OsString>,
|
||||||
/// Runner for Python scripts.
|
/// Runner for Python scripts.
|
||||||
runner: PythonRunner,
|
runner: PythonRunner,
|
||||||
/// A file lock representing the source tree, currently only used with setuptools.
|
|
||||||
_source_tree_lock: Option<LockedFile>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceBuild {
|
impl SourceBuild {
|
||||||
|
@ -394,23 +392,6 @@ impl SourceBuild {
|
||||||
OsString::from(venv.scripts())
|
OsString::from(venv.scripts())
|
||||||
};
|
};
|
||||||
|
|
||||||
// Depending on the command, setuptools puts `*.egg-info`, `build/`, and `dist/` in the
|
|
||||||
// source tree, and concurrent invocations of setuptools using the same source dir can
|
|
||||||
// stomp on each other. We need to lock something to fix that, but we don't want to dump a
|
|
||||||
// `.lock` file into the source tree that the user will need to .gitignore. Take a global
|
|
||||||
// proxy lock instead.
|
|
||||||
let mut source_tree_lock = None;
|
|
||||||
if pep517_backend.is_setuptools() {
|
|
||||||
debug!("Locking the source tree for setuptools");
|
|
||||||
let canonical_source_path = source_tree.canonicalize()?;
|
|
||||||
let lock_path = std::env::temp_dir().join(format!(
|
|
||||||
"uv-setuptools-{}.lock",
|
|
||||||
cache_digest(&canonical_source_path)
|
|
||||||
));
|
|
||||||
source_tree_lock =
|
|
||||||
Some(LockedFile::acquire(lock_path, source_tree.to_string_lossy()).await?);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the PEP 517 build environment. If build isolation is disabled, we assume the build
|
// Create the PEP 517 build environment. If build isolation is disabled, we assume the build
|
||||||
// environment is already setup.
|
// environment is already setup.
|
||||||
let runner = PythonRunner::new(concurrent_builds, level);
|
let runner = PythonRunner::new(concurrent_builds, level);
|
||||||
|
@ -457,10 +438,34 @@ impl SourceBuild {
|
||||||
environment_variables,
|
environment_variables,
|
||||||
modified_path,
|
modified_path,
|
||||||
runner,
|
runner,
|
||||||
_source_tree_lock: source_tree_lock,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Acquire a lock on the source tree, if necessary.
|
||||||
|
async fn acquire_lock(&self) -> Result<Option<LockedFile>, Error> {
|
||||||
|
// Depending on the command, setuptools puts `*.egg-info`, `build/`, and `dist/` in the
|
||||||
|
// source tree, and concurrent invocations of setuptools using the same source dir can
|
||||||
|
// stomp on each other. We need to lock something to fix that, but we don't want to dump a
|
||||||
|
// `.lock` file into the source tree that the user will need to .gitignore. Take a global
|
||||||
|
// proxy lock instead.
|
||||||
|
let mut source_tree_lock = None;
|
||||||
|
if self.pep517_backend.is_setuptools() {
|
||||||
|
debug!("Locking the source tree for setuptools");
|
||||||
|
let canonical_source_path = self.source_tree.canonicalize()?;
|
||||||
|
let lock_path = env::temp_dir().join(format!(
|
||||||
|
"uv-setuptools-{}.lock",
|
||||||
|
cache_digest(&canonical_source_path)
|
||||||
|
));
|
||||||
|
source_tree_lock = LockedFile::acquire(lock_path, self.source_tree.to_string_lossy())
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire build lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Ok(source_tree_lock)
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_resolved_requirements(
|
async fn get_resolved_requirements(
|
||||||
build_context: &impl BuildContext,
|
build_context: &impl BuildContext,
|
||||||
source_build_context: SourceBuildContext,
|
source_build_context: SourceBuildContext,
|
||||||
|
@ -631,6 +636,9 @@ impl SourceBuild {
|
||||||
return Ok(Some(metadata_dir.clone()));
|
return Ok(Some(metadata_dir.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock the source tree, if necessary.
|
||||||
|
let _lock = self.acquire_lock().await?;
|
||||||
|
|
||||||
// Hatch allows for highly dynamic customization of metadata via hooks. In such cases, Hatch
|
// Hatch allows for highly dynamic customization of metadata via hooks. In such cases, Hatch
|
||||||
// can't uphold the PEP 517 contract, in that the metadata Hatch would return by
|
// can't uphold the PEP 517 contract, in that the metadata Hatch would return by
|
||||||
// `prepare_metadata_for_build_wheel` isn't guaranteed to match that of the built wheel.
|
// `prepare_metadata_for_build_wheel` isn't guaranteed to match that of the built wheel.
|
||||||
|
@ -749,6 +757,9 @@ impl SourceBuild {
|
||||||
|
|
||||||
/// Perform a PEP 517 build for a wheel or source distribution (sdist).
|
/// Perform a PEP 517 build for a wheel or source distribution (sdist).
|
||||||
async fn pep517_build(&self, output_dir: &Path) -> Result<String, Error> {
|
async fn pep517_build(&self, output_dir: &Path) -> Result<String, Error> {
|
||||||
|
// Lock the source tree, if necessary.
|
||||||
|
let _lock = self.acquire_lock().await?;
|
||||||
|
|
||||||
// Write the hook output to a file so that we can read it back reliably.
|
// Write the hook output to a file so that we can read it back reliably.
|
||||||
let outfile = self
|
let outfile = self
|
||||||
.temp_dir
|
.temp_dir
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "uv-build"
|
name = "uv-build"
|
||||||
version = "0.7.17"
|
version = "0.7.19"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
homepage.workspace = true
|
homepage.workspace = true
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "uv-build"
|
name = "uv-build"
|
||||||
version = "0.7.17"
|
version = "0.7.19"
|
||||||
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"
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
use anstream::eprintln;
|
||||||
|
|
||||||
use uv_cache::Refresh;
|
use uv_cache::Refresh;
|
||||||
use uv_configuration::ConfigSettings;
|
use uv_configuration::ConfigSettings;
|
||||||
use uv_resolver::PrereleaseMode;
|
use uv_resolver::PrereleaseMode;
|
||||||
use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};
|
use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};
|
||||||
|
use uv_warnings::owo_colors::OwoColorize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
BuildOptionsArgs, FetchArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs,
|
BuildOptionsArgs, FetchArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs,
|
||||||
|
@ -9,12 +12,27 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Given a boolean flag pair (like `--upgrade` and `--no-upgrade`), resolve the value of the flag.
|
/// Given a boolean flag pair (like `--upgrade` and `--no-upgrade`), resolve the value of the flag.
|
||||||
pub fn flag(yes: bool, no: bool) -> Option<bool> {
|
pub fn flag(yes: bool, no: bool, name: &str) -> Option<bool> {
|
||||||
match (yes, no) {
|
match (yes, no) {
|
||||||
(true, false) => Some(true),
|
(true, false) => Some(true),
|
||||||
(false, true) => Some(false),
|
(false, true) => Some(false),
|
||||||
(false, false) => None,
|
(false, false) => None,
|
||||||
(..) => unreachable!("Clap should make this impossible"),
|
(..) => {
|
||||||
|
eprintln!(
|
||||||
|
"{}{} `{}` and `{}` cannot be used together. \
|
||||||
|
Boolean flags on different levels are currently not supported \
|
||||||
|
(https://github.com/clap-rs/clap/issues/6049)",
|
||||||
|
"error".bold().red(),
|
||||||
|
":".bold(),
|
||||||
|
format!("--{name}").green(),
|
||||||
|
format!("--no-{name}").green(),
|
||||||
|
);
|
||||||
|
// No error forwarding since should eventually be solved on the clap side.
|
||||||
|
#[allow(clippy::exit)]
|
||||||
|
{
|
||||||
|
std::process::exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +44,7 @@ impl From<RefreshArgs> for Refresh {
|
||||||
refresh_package,
|
refresh_package,
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
Self::from_args(flag(refresh, no_refresh), refresh_package)
|
Self::from_args(flag(refresh, no_refresh, "no-refresh"), refresh_package)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +71,7 @@ impl From<ResolverArgs> for PipOptions {
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
upgrade: flag(upgrade, no_upgrade),
|
upgrade: flag(upgrade, no_upgrade, "no-upgrade"),
|
||||||
upgrade_package: Some(upgrade_package),
|
upgrade_package: Some(upgrade_package),
|
||||||
index_strategy,
|
index_strategy,
|
||||||
keyring_provider,
|
keyring_provider,
|
||||||
|
@ -66,7 +84,7 @@ impl From<ResolverArgs> for PipOptions {
|
||||||
},
|
},
|
||||||
config_settings: config_setting
|
config_settings: config_setting
|
||||||
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
||||||
no_build_isolation: flag(no_build_isolation, build_isolation),
|
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
|
||||||
no_build_isolation_package: Some(no_build_isolation_package),
|
no_build_isolation_package: Some(no_build_isolation_package),
|
||||||
exclude_newer,
|
exclude_newer,
|
||||||
link_mode,
|
link_mode,
|
||||||
|
@ -96,16 +114,16 @@ impl From<InstallerArgs> for PipOptions {
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
reinstall: flag(reinstall, no_reinstall),
|
reinstall: flag(reinstall, no_reinstall, "reinstall"),
|
||||||
reinstall_package: Some(reinstall_package),
|
reinstall_package: Some(reinstall_package),
|
||||||
index_strategy,
|
index_strategy,
|
||||||
keyring_provider,
|
keyring_provider,
|
||||||
config_settings: config_setting
|
config_settings: config_setting
|
||||||
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
||||||
no_build_isolation: flag(no_build_isolation, build_isolation),
|
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
|
||||||
exclude_newer,
|
exclude_newer,
|
||||||
link_mode,
|
link_mode,
|
||||||
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
|
compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
|
||||||
no_sources: if no_sources { Some(true) } else { None },
|
no_sources: if no_sources { Some(true) } else { None },
|
||||||
..PipOptions::from(index_args)
|
..PipOptions::from(index_args)
|
||||||
}
|
}
|
||||||
|
@ -140,9 +158,9 @@ impl From<ResolverInstallerArgs> for PipOptions {
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
upgrade: flag(upgrade, no_upgrade),
|
upgrade: flag(upgrade, no_upgrade, "upgrade"),
|
||||||
upgrade_package: Some(upgrade_package),
|
upgrade_package: Some(upgrade_package),
|
||||||
reinstall: flag(reinstall, no_reinstall),
|
reinstall: flag(reinstall, no_reinstall, "reinstall"),
|
||||||
reinstall_package: Some(reinstall_package),
|
reinstall_package: Some(reinstall_package),
|
||||||
index_strategy,
|
index_strategy,
|
||||||
keyring_provider,
|
keyring_provider,
|
||||||
|
@ -155,11 +173,11 @@ impl From<ResolverInstallerArgs> for PipOptions {
|
||||||
fork_strategy,
|
fork_strategy,
|
||||||
config_settings: config_setting
|
config_settings: config_setting
|
||||||
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
||||||
no_build_isolation: flag(no_build_isolation, build_isolation),
|
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
|
||||||
no_build_isolation_package: Some(no_build_isolation_package),
|
no_build_isolation_package: Some(no_build_isolation_package),
|
||||||
exclude_newer,
|
exclude_newer,
|
||||||
link_mode,
|
link_mode,
|
||||||
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
|
compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
|
||||||
no_sources: if no_sources { Some(true) } else { None },
|
no_sources: if no_sources { Some(true) } else { None },
|
||||||
..PipOptions::from(index_args)
|
..PipOptions::from(index_args)
|
||||||
}
|
}
|
||||||
|
@ -289,7 +307,7 @@ pub fn resolver_options(
|
||||||
.filter_map(Maybe::into_option)
|
.filter_map(Maybe::into_option)
|
||||||
.collect()
|
.collect()
|
||||||
}),
|
}),
|
||||||
upgrade: flag(upgrade, no_upgrade),
|
upgrade: flag(upgrade, no_upgrade, "no-upgrade"),
|
||||||
upgrade_package: Some(upgrade_package),
|
upgrade_package: Some(upgrade_package),
|
||||||
index_strategy,
|
index_strategy,
|
||||||
keyring_provider,
|
keyring_provider,
|
||||||
|
@ -303,13 +321,13 @@ pub fn resolver_options(
|
||||||
dependency_metadata: None,
|
dependency_metadata: None,
|
||||||
config_settings: config_setting
|
config_settings: config_setting
|
||||||
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
||||||
no_build_isolation: flag(no_build_isolation, build_isolation),
|
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
|
||||||
no_build_isolation_package: Some(no_build_isolation_package),
|
no_build_isolation_package: Some(no_build_isolation_package),
|
||||||
exclude_newer,
|
exclude_newer,
|
||||||
link_mode,
|
link_mode,
|
||||||
no_build: flag(no_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),
|
no_binary: flag(no_binary, binary, "binary"),
|
||||||
no_binary_package: Some(no_binary_package),
|
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 },
|
||||||
}
|
}
|
||||||
|
@ -386,13 +404,13 @@ pub fn resolver_installer_options(
|
||||||
.filter_map(Maybe::into_option)
|
.filter_map(Maybe::into_option)
|
||||||
.collect()
|
.collect()
|
||||||
}),
|
}),
|
||||||
upgrade: flag(upgrade, no_upgrade),
|
upgrade: flag(upgrade, no_upgrade, "upgrade"),
|
||||||
upgrade_package: if upgrade_package.is_empty() {
|
upgrade_package: if upgrade_package.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(upgrade_package)
|
Some(upgrade_package)
|
||||||
},
|
},
|
||||||
reinstall: flag(reinstall, no_reinstall),
|
reinstall: flag(reinstall, no_reinstall, "reinstall"),
|
||||||
reinstall_package: if reinstall_package.is_empty() {
|
reinstall_package: if reinstall_package.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -410,7 +428,7 @@ pub fn resolver_installer_options(
|
||||||
dependency_metadata: None,
|
dependency_metadata: None,
|
||||||
config_settings: config_setting
|
config_settings: config_setting
|
||||||
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
|
||||||
no_build_isolation: flag(no_build_isolation, build_isolation),
|
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
|
||||||
no_build_isolation_package: if no_build_isolation_package.is_empty() {
|
no_build_isolation_package: if no_build_isolation_package.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -418,14 +436,14 @@ pub fn resolver_installer_options(
|
||||||
},
|
},
|
||||||
exclude_newer,
|
exclude_newer,
|
||||||
link_mode,
|
link_mode,
|
||||||
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
|
compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"),
|
||||||
no_build: flag(no_build, build),
|
no_build: flag(no_build, build, "build"),
|
||||||
no_build_package: if no_build_package.is_empty() {
|
no_build_package: if no_build_package.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(no_build_package)
|
Some(no_build_package)
|
||||||
},
|
},
|
||||||
no_binary: flag(no_binary, binary),
|
no_binary: flag(no_binary, binary, "binary"),
|
||||||
no_binary_package: if no_binary_package.is_empty() {
|
no_binary_package: if no_binary_package.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -982,6 +982,45 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_redirect_preserves_fragment() -> Result<()> {
|
||||||
|
for status in &[301, 302, 303, 307, 308] {
|
||||||
|
let server = MockServer::start().await;
|
||||||
|
Mock::given(method("GET"))
|
||||||
|
.respond_with(
|
||||||
|
ResponseTemplate::new(*status)
|
||||||
|
.insert_header("location", format!("{}/redirect", server.uri())),
|
||||||
|
)
|
||||||
|
.mount(&server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let request = Client::new()
|
||||||
|
.get(format!("{}#fragment", server.uri()))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response = Client::builder()
|
||||||
|
.redirect(reqwest::redirect::Policy::none())
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.execute(request.try_clone().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let redirect_request =
|
||||||
|
request_into_redirect(request, &response, CrossOriginCredentialsPolicy::Secure)?
|
||||||
|
.unwrap();
|
||||||
|
assert!(
|
||||||
|
redirect_request
|
||||||
|
.url()
|
||||||
|
.fragment()
|
||||||
|
.is_some_and(|fragment| fragment == "fragment")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_redirect_removes_authorization_header_on_cross_origin() -> Result<()> {
|
async fn test_redirect_removes_authorization_header_on_cross_origin() -> Result<()> {
|
||||||
for status in &[301, 302, 303, 307, 308] {
|
for status in &[301, 302, 303, 307, 308] {
|
||||||
|
|
|
@ -1416,44 +1416,6 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_redirect_preserve_fragment() -> Result<(), Error> {
|
|
||||||
let redirect_server = MockServer::start().await;
|
|
||||||
|
|
||||||
// Configure the redirect server to respond with a 307 with a relative URL.
|
|
||||||
Mock::given(method("GET"))
|
|
||||||
.respond_with(ResponseTemplate::new(307).insert_header("Location", "/foo".to_string()))
|
|
||||||
.mount(&redirect_server)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
|
||||||
.and(path_regex("/foo"))
|
|
||||||
.respond_with(ResponseTemplate::new(200))
|
|
||||||
.mount(&redirect_server)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let cache = Cache::temp()?;
|
|
||||||
let registry_client = RegistryClientBuilder::new(cache).build();
|
|
||||||
let client = registry_client.cached_client().uncached();
|
|
||||||
|
|
||||||
let mut url = DisplaySafeUrl::parse(&redirect_server.uri())?;
|
|
||||||
url.set_fragment(Some("fragment"));
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
client
|
|
||||||
.for_host(&url)
|
|
||||||
.get(Url::from(url.clone()))
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.url()
|
|
||||||
.to_string(),
|
|
||||||
format!("{}/foo#fragment", redirect_server.uri()),
|
|
||||||
"Requests should preserve fragment"
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ignore_failing_files() {
|
fn ignore_failing_files() {
|
||||||
// 1.7.7 has an invalid requires-python field (double comma), 1.7.8 is valid
|
// 1.7.7 has an invalid requires-python field (double comma), 1.7.8 is valid
|
||||||
|
|
|
@ -4,7 +4,7 @@ use uv_pep508::PackageName;
|
||||||
|
|
||||||
use crate::{PackageNameSpecifier, PackageNameSpecifiers};
|
use crate::{PackageNameSpecifier, PackageNameSpecifiers};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
pub enum BuildKind {
|
pub enum BuildKind {
|
||||||
/// A PEP 517 wheel build.
|
/// A PEP 517 wheel build.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std::{borrow::Cow, str::FromStr};
|
#[cfg(feature = "schemars")]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use uv_pep508::PackageName;
|
use uv_pep508::PackageName;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::str::FromStr;
|
#[cfg(feature = "schemars")]
|
||||||
use std::{borrow::Cow, fmt::Formatter};
|
use std::borrow::Cow;
|
||||||
|
use std::{fmt::Formatter, str::FromStr};
|
||||||
|
|
||||||
use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers, VersionSpecifiersParseError};
|
use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers, VersionSpecifiersParseError};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(
|
||||||
|
Debug, Default, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
|
||||||
|
)]
|
||||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||||
pub enum SourceStrategy {
|
pub enum SourceStrategy {
|
||||||
/// Use `tool.uv.sources` when resolving dependencies.
|
/// Use `tool.uv.sources` when resolving dependencies.
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use std::{borrow::Cow, str::FromStr};
|
#[cfg(feature = "schemars")]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::str::FromStr;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// A host specification (wildcard, or host, with optional scheme and/or port) for which
|
/// A host specification (wildcard, or host, with optional scheme and/or port) for which
|
||||||
|
|
|
@ -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/20250626/cpython-unix/targets.yml";
|
const TARGETS_YML_URL: &str = "https://raw.githubusercontent.com/astral-sh/python-build-standalone/refs/tags/20250702/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/20250626/cpython-unix/targets.yml>\n");
|
output.push_str("//! Targets from <https://github.com/astral-sh/python-build-standalone/blob/20250702/cpython-unix/targets.yml>\n");
|
||||||
output.push_str("//!\n");
|
output.push_str("//!\n");
|
||||||
|
|
||||||
// Disable clippy/fmt
|
// Disable clippy/fmt
|
||||||
|
|
|
@ -11,6 +11,7 @@ use itertools::Itertools;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{debug, instrument, trace};
|
use tracing::{debug, instrument, trace};
|
||||||
|
|
||||||
use uv_build_backend::check_direct_build;
|
use uv_build_backend::check_direct_build;
|
||||||
use uv_build_frontend::{SourceBuild, SourceBuildContext};
|
use uv_build_frontend::{SourceBuild, SourceBuildContext};
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
|
@ -35,8 +36,8 @@ use uv_resolver::{
|
||||||
PythonRequirement, Resolver, ResolverEnvironment,
|
PythonRequirement, Resolver, ResolverEnvironment,
|
||||||
};
|
};
|
||||||
use uv_types::{
|
use uv_types::{
|
||||||
AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, EmptyInstalledPackages, HashStrategy,
|
AnyErrorBuild, BuildArena, BuildContext, BuildIsolation, BuildStack, EmptyInstalledPackages,
|
||||||
InFlight,
|
HashStrategy, InFlight,
|
||||||
};
|
};
|
||||||
use uv_workspace::WorkspaceCache;
|
use uv_workspace::WorkspaceCache;
|
||||||
|
|
||||||
|
@ -179,6 +180,10 @@ impl BuildContext for BuildDispatch<'_> {
|
||||||
&self.shared_state.git
|
&self.shared_state.git
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_arena(&self) -> &BuildArena<SourceBuild> {
|
||||||
|
&self.shared_state.build_arena
|
||||||
|
}
|
||||||
|
|
||||||
fn capabilities(&self) -> &IndexCapabilities {
|
fn capabilities(&self) -> &IndexCapabilities {
|
||||||
&self.shared_state.capabilities
|
&self.shared_state.capabilities
|
||||||
}
|
}
|
||||||
|
@ -448,12 +453,6 @@ impl BuildContext for BuildDispatch<'_> {
|
||||||
build_kind: BuildKind,
|
build_kind: BuildKind,
|
||||||
version_id: Option<&'data str>,
|
version_id: Option<&'data str>,
|
||||||
) -> Result<Option<DistFilename>, BuildDispatchError> {
|
) -> Result<Option<DistFilename>, BuildDispatchError> {
|
||||||
// Direct builds are a preview feature with the uv build backend.
|
|
||||||
if self.preview.is_disabled() {
|
|
||||||
trace!("Preview is disabled, not checking for direct build");
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let source_tree = if let Some(subdir) = subdirectory {
|
let source_tree = if let Some(subdir) = subdirectory {
|
||||||
source.join(subdir)
|
source.join(subdir)
|
||||||
} else {
|
} else {
|
||||||
|
@ -521,6 +520,8 @@ pub struct SharedState {
|
||||||
index: InMemoryIndex,
|
index: InMemoryIndex,
|
||||||
/// The downloaded distributions.
|
/// The downloaded distributions.
|
||||||
in_flight: InFlight,
|
in_flight: InFlight,
|
||||||
|
/// Build directories for any PEP 517 builds executed during resolution or installation.
|
||||||
|
build_arena: BuildArena<SourceBuild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedState {
|
impl SharedState {
|
||||||
|
@ -533,6 +534,7 @@ impl SharedState {
|
||||||
Self {
|
Self {
|
||||||
git: self.git.clone(),
|
git: self.git.clone(),
|
||||||
capabilities: self.capabilities.clone(),
|
capabilities: self.capabilities.clone(),
|
||||||
|
build_arena: self.build_arena.clone(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -556,4 +558,9 @@ impl SharedState {
|
||||||
pub fn capabilities(&self) -> &IndexCapabilities {
|
pub fn capabilities(&self) -> &IndexCapabilities {
|
||||||
&self.capabilities
|
&self.capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the [`BuildArena`] used by the [`SharedState`].
|
||||||
|
pub fn build_arena(&self) -> &BuildArena<SourceBuild> {
|
||||||
|
&self.build_arena
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,10 +171,21 @@ impl UrlString {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`UrlString`] (as a [`Cow`]) with trailing slash removed.
|
/// Return the [`UrlString`] (as a [`Cow`]) with trailing slash removed.
|
||||||
|
///
|
||||||
|
/// This matches the semantics of [`Url::pop_if_empty`], which will not trim a trailing slash if
|
||||||
|
/// it's the only path segment, e.g., `https://example.com/` would be unchanged.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn without_trailing_slash(&self) -> Cow<'_, Self> {
|
pub fn without_trailing_slash(&self) -> Cow<'_, Self> {
|
||||||
self.as_ref()
|
self.as_ref()
|
||||||
.strip_suffix('/')
|
.strip_suffix('/')
|
||||||
|
.filter(|path| {
|
||||||
|
// Only strip the trailing slash if there's _another_ trailing slash that isn't a
|
||||||
|
// part of the scheme.
|
||||||
|
path.split_once("://")
|
||||||
|
.map(|(_scheme, rest)| rest)
|
||||||
|
.unwrap_or(path)
|
||||||
|
.contains('/')
|
||||||
|
})
|
||||||
.map(|path| Cow::Owned(UrlString(SmallString::from(path))))
|
.map(|path| Cow::Owned(UrlString(SmallString::from(path))))
|
||||||
.unwrap_or(Cow::Borrowed(self))
|
.unwrap_or(Cow::Borrowed(self))
|
||||||
}
|
}
|
||||||
|
@ -261,27 +272,49 @@ mod tests {
|
||||||
fn without_fragment() {
|
fn without_fragment() {
|
||||||
// Borrows a URL without a fragment
|
// Borrows a URL without a fragment
|
||||||
let url = UrlString("https://example.com/path".into());
|
let url = UrlString("https://example.com/path".into());
|
||||||
assert_eq!(url.without_fragment(), Cow::Borrowed(&url));
|
assert_eq!(&*url.without_fragment(), &url);
|
||||||
|
assert!(matches!(url.without_fragment(), Cow::Borrowed(_)));
|
||||||
|
|
||||||
// Removes the fragment if present on the URL
|
// Removes the fragment if present on the URL
|
||||||
let url = UrlString("https://example.com/path?query#fragment".into());
|
let url = UrlString("https://example.com/path?query#fragment".into());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
url.without_fragment(),
|
&*url.without_fragment(),
|
||||||
Cow::Owned(UrlString("https://example.com/path?query".into()))
|
&UrlString("https://example.com/path?query".into())
|
||||||
);
|
);
|
||||||
|
assert!(matches!(url.without_fragment(), Cow::Owned(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn without_trailing_slash() {
|
fn without_trailing_slash() {
|
||||||
// Borrows a URL without a slash
|
// Borrows a URL without a slash
|
||||||
let url = UrlString("https://example.com/path".into());
|
let url = UrlString("https://example.com/path".into());
|
||||||
assert_eq!(url.without_trailing_slash(), Cow::Borrowed(&url));
|
assert_eq!(&*url.without_trailing_slash(), &url);
|
||||||
|
assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_)));
|
||||||
|
|
||||||
// Removes the trailing slash if present on the URL
|
// Removes the trailing slash if present on the URL
|
||||||
let url = UrlString("https://example.com/path/".into());
|
let url = UrlString("https://example.com/path/".into());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
url.without_trailing_slash(),
|
&*url.without_trailing_slash(),
|
||||||
Cow::Owned(UrlString("https://example.com/path".into()))
|
&UrlString("https://example.com/path".into())
|
||||||
);
|
);
|
||||||
|
assert!(matches!(url.without_trailing_slash(), Cow::Owned(_)));
|
||||||
|
|
||||||
|
// Does not remove a trailing slash if it's the only path segment
|
||||||
|
let url = UrlString("https://example.com/".into());
|
||||||
|
assert_eq!(&*url.without_trailing_slash(), &url);
|
||||||
|
assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_)));
|
||||||
|
|
||||||
|
// Does not remove a trailing slash if it's the only path segment with a missing scheme
|
||||||
|
let url = UrlString("example.com/".into());
|
||||||
|
assert_eq!(&*url.without_trailing_slash(), &url);
|
||||||
|
assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_)));
|
||||||
|
|
||||||
|
// Removes the trailing slash when the scheme is missing
|
||||||
|
let url = UrlString("example.com/path/".into());
|
||||||
|
assert_eq!(
|
||||||
|
&*url.without_trailing_slash(),
|
||||||
|
&UrlString("example.com/path".into())
|
||||||
|
);
|
||||||
|
assert!(matches!(url.without_trailing_slash(), Cow::Owned(_)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -462,6 +462,19 @@ impl<'a> IndexLocations {
|
||||||
indexes
|
indexes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add all authenticated sources to the cache.
|
||||||
|
pub fn cache_index_credentials(&self) {
|
||||||
|
for index in self.allowed_indexes() {
|
||||||
|
if let Some(credentials) = index.credentials() {
|
||||||
|
let credentials = Arc::new(credentials);
|
||||||
|
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
||||||
|
if let Some(root_url) = index.root_url() {
|
||||||
|
uv_auth::store_credentials(&root_url, credentials.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&IndexLocations> for uv_auth::Indexes {
|
impl From<&IndexLocations> for uv_auth::Indexes {
|
||||||
|
|
|
@ -365,7 +365,7 @@ impl InstalledDist {
|
||||||
pub fn installer(&self) -> Result<Option<String>, InstalledDistError> {
|
pub fn installer(&self) -> Result<Option<String>, InstalledDistError> {
|
||||||
let path = self.install_path().join("INSTALLER");
|
let path = self.install_path().join("INSTALLER");
|
||||||
match fs::read_to_string(path) {
|
match fs::read_to_string(path) {
|
||||||
Ok(installer) => Ok(Some(installer)),
|
Ok(installer) => Ok(Some(installer.trim().to_owned())),
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||||||
Err(err) => Err(err.into()),
|
Err(err) => Err(err.into()),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
//! flags set.
|
//! flags set.
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use std::{borrow::Cow, path::Path};
|
#[cfg(feature = "schemars")]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::{Index, IndexUrl};
|
use crate::{Index, IndexUrl};
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,10 @@ use version_ranges::Ranges;
|
||||||
use uv_distribution_filename::WheelFilename;
|
use uv_distribution_filename::WheelFilename;
|
||||||
use uv_pep440::{
|
use uv_pep440::{
|
||||||
LowerBound, UpperBound, Version, VersionSpecifier, VersionSpecifiers,
|
LowerBound, UpperBound, Version, VersionSpecifier, VersionSpecifiers,
|
||||||
release_specifier_to_range, release_specifiers_to_ranges,
|
release_specifiers_to_ranges,
|
||||||
};
|
};
|
||||||
use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
||||||
use uv_platform_tags::{AbiTag, LanguageTag};
|
use uv_platform_tags::{AbiTag, LanguageTag};
|
||||||
use uv_warnings::warn_user_once;
|
|
||||||
|
|
||||||
/// The `Requires-Python` requirement specifier.
|
/// The `Requires-Python` requirement specifier.
|
||||||
///
|
///
|
||||||
|
@ -67,27 +66,7 @@ impl RequiresPython {
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
// Convert to PubGrub range and perform an intersection.
|
// Convert to PubGrub range and perform an intersection.
|
||||||
let range = specifiers
|
let range = specifiers
|
||||||
.map(|specs| {
|
.map(|specs| release_specifiers_to_ranges(specs.clone()))
|
||||||
// Warn if there’s exactly one `~=` specifier without a patch.
|
|
||||||
if let [spec] = &specs[..] {
|
|
||||||
if spec.is_tilde_without_patch() {
|
|
||||||
if let Some((lo_b, hi_b)) = release_specifier_to_range(spec.clone())
|
|
||||||
.bounding_range()
|
|
||||||
.map(|(l, u)| (l.cloned(), u.cloned()))
|
|
||||||
{
|
|
||||||
let lo_spec = LowerBound::new(lo_b).specifier().unwrap();
|
|
||||||
let hi_spec = UpperBound::new(hi_b).specifier().unwrap();
|
|
||||||
warn_user_once!(
|
|
||||||
"The release specifier (`{spec}`) contains a compatible release \
|
|
||||||
match without a patch version. This will be interpreted as \
|
|
||||||
`{lo_spec}, {hi_spec}`. Did you mean `{spec}.0` to freeze the minor \
|
|
||||||
version?"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
release_specifiers_to_ranges(specs.clone())
|
|
||||||
})
|
|
||||||
.reduce(|acc, r| acc.intersection(&r))?;
|
.reduce(|acc, r| acc.intersection(&r))?;
|
||||||
|
|
||||||
// If the intersection is empty, return `None`.
|
// If the intersection is empty, return `None`.
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std::{borrow::Cow, ops::Deref};
|
#[cfg(feature = "schemars")]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
|
@ -108,6 +108,8 @@ pub enum Error {
|
||||||
CacheHeal(String, HashAlgorithm),
|
CacheHeal(String, HashAlgorithm),
|
||||||
#[error("The source distribution requires Python {0}, but {1} is installed")]
|
#[error("The source distribution requires Python {0}, but {1} is installed")]
|
||||||
RequiresPython(VersionSpecifiers, Version),
|
RequiresPython(VersionSpecifiers, Version),
|
||||||
|
#[error("Failed to identify base Python interpreter")]
|
||||||
|
BaseInterpreter(#[source] std::io::Error),
|
||||||
|
|
||||||
/// A generic request middleware error happened while making a request.
|
/// A generic request middleware error happened while making a request.
|
||||||
/// Refer to the error message for more details.
|
/// Refer to the error message for more details.
|
||||||
|
|
|
@ -43,7 +43,7 @@ use uv_normalize::PackageName;
|
||||||
use uv_pep440::{Version, release_specifiers_to_ranges};
|
use uv_pep440::{Version, release_specifiers_to_ranges};
|
||||||
use uv_platform_tags::Tags;
|
use uv_platform_tags::Tags;
|
||||||
use uv_pypi_types::{HashAlgorithm, HashDigest, HashDigests, PyProjectToml, ResolutionMetadata};
|
use uv_pypi_types::{HashAlgorithm, HashDigest, HashDigests, PyProjectToml, ResolutionMetadata};
|
||||||
use uv_types::{BuildContext, BuildStack, SourceBuildTrait};
|
use uv_types::{BuildContext, BuildKey, BuildStack, SourceBuildTrait};
|
||||||
use uv_workspace::pyproject::ToolUvSources;
|
use uv_workspace::pyproject::ToolUvSources;
|
||||||
|
|
||||||
use crate::distribution_database::ManagedClient;
|
use crate::distribution_database::ManagedClient;
|
||||||
|
@ -2276,6 +2276,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
fs::create_dir_all(&cache_shard)
|
fs::create_dir_all(&cache_shard)
|
||||||
.await
|
.await
|
||||||
.map_err(Error::CacheWrite)?;
|
.map_err(Error::CacheWrite)?;
|
||||||
|
|
||||||
// Try a direct build if that isn't disabled and the uv build backend is used.
|
// Try a direct build if that isn't disabled and the uv build backend is used.
|
||||||
let disk_filename = if let Some(name) = self
|
let disk_filename = if let Some(name) = self
|
||||||
.build_context
|
.build_context
|
||||||
|
@ -2296,27 +2297,73 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
// In the uv build backend, the normalized filename and the disk filename are the same.
|
// In the uv build backend, the normalized filename and the disk filename are the same.
|
||||||
name.to_string()
|
name.to_string()
|
||||||
} else {
|
} else {
|
||||||
self.build_context
|
// Identify the base Python interpreter to use in the cache key.
|
||||||
.setup_build(
|
let base_python = if cfg!(unix) {
|
||||||
source_root,
|
self.build_context
|
||||||
subdirectory,
|
.interpreter()
|
||||||
source_root,
|
.find_base_python()
|
||||||
Some(&source.to_string()),
|
.map_err(Error::BaseInterpreter)?
|
||||||
source.as_dist(),
|
} else {
|
||||||
source_strategy,
|
self.build_context
|
||||||
if source.is_editable() {
|
.interpreter()
|
||||||
BuildKind::Editable
|
.to_base_python()
|
||||||
} else {
|
.map_err(Error::BaseInterpreter)?
|
||||||
BuildKind::Wheel
|
};
|
||||||
},
|
|
||||||
BuildOutput::Debug,
|
let build_kind = if source.is_editable() {
|
||||||
self.build_stack.cloned().unwrap_or_default(),
|
BuildKind::Editable
|
||||||
)
|
} else {
|
||||||
.await
|
BuildKind::Wheel
|
||||||
.map_err(|err| Error::Build(err.into()))?
|
};
|
||||||
.wheel(temp_dir.path())
|
|
||||||
.await
|
let build_key = BuildKey {
|
||||||
.map_err(Error::Build)?
|
base_python: base_python.into_boxed_path(),
|
||||||
|
source_root: source_root.to_path_buf().into_boxed_path(),
|
||||||
|
subdirectory: subdirectory
|
||||||
|
.map(|subdirectory| subdirectory.to_path_buf().into_boxed_path()),
|
||||||
|
source_strategy,
|
||||||
|
build_kind,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(builder) = self.build_context.build_arena().remove(&build_key) {
|
||||||
|
debug!("Creating build environment for: {source}");
|
||||||
|
let wheel = builder.wheel(temp_dir.path()).await.map_err(Error::Build)?;
|
||||||
|
|
||||||
|
// Store the build context.
|
||||||
|
self.build_context.build_arena().insert(build_key, builder);
|
||||||
|
|
||||||
|
wheel
|
||||||
|
} else {
|
||||||
|
debug!("Reusing existing build environment for: {source}");
|
||||||
|
|
||||||
|
let builder = self
|
||||||
|
.build_context
|
||||||
|
.setup_build(
|
||||||
|
source_root,
|
||||||
|
subdirectory,
|
||||||
|
source_root,
|
||||||
|
Some(&source.to_string()),
|
||||||
|
source.as_dist(),
|
||||||
|
source_strategy,
|
||||||
|
if source.is_editable() {
|
||||||
|
BuildKind::Editable
|
||||||
|
} else {
|
||||||
|
BuildKind::Wheel
|
||||||
|
},
|
||||||
|
BuildOutput::Debug,
|
||||||
|
self.build_stack.cloned().unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| Error::Build(err.into()))?;
|
||||||
|
|
||||||
|
// Build the wheel.
|
||||||
|
let wheel = builder.wheel(temp_dir.path()).await.map_err(Error::Build)?;
|
||||||
|
|
||||||
|
// Store the build context.
|
||||||
|
self.build_context.build_arena().insert(build_key, builder);
|
||||||
|
|
||||||
|
wheel
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read the metadata from the wheel.
|
// Read the metadata from the wheel.
|
||||||
|
@ -2371,6 +2418,26 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Identify the base Python interpreter to use in the cache key.
|
||||||
|
let base_python = if cfg!(unix) {
|
||||||
|
self.build_context
|
||||||
|
.interpreter()
|
||||||
|
.find_base_python()
|
||||||
|
.map_err(Error::BaseInterpreter)?
|
||||||
|
} else {
|
||||||
|
self.build_context
|
||||||
|
.interpreter()
|
||||||
|
.to_base_python()
|
||||||
|
.map_err(Error::BaseInterpreter)?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine whether this is an editable or non-editable build.
|
||||||
|
let build_kind = if source.is_editable() {
|
||||||
|
BuildKind::Editable
|
||||||
|
} else {
|
||||||
|
BuildKind::Wheel
|
||||||
|
};
|
||||||
|
|
||||||
// Set up the builder.
|
// Set up the builder.
|
||||||
let mut builder = self
|
let mut builder = self
|
||||||
.build_context
|
.build_context
|
||||||
|
@ -2381,11 +2448,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
Some(&source.to_string()),
|
Some(&source.to_string()),
|
||||||
source.as_dist(),
|
source.as_dist(),
|
||||||
source_strategy,
|
source_strategy,
|
||||||
if source.is_editable() {
|
build_kind,
|
||||||
BuildKind::Editable
|
|
||||||
} else {
|
|
||||||
BuildKind::Wheel
|
|
||||||
},
|
|
||||||
BuildOutput::Debug,
|
BuildOutput::Debug,
|
||||||
self.build_stack.cloned().unwrap_or_default(),
|
self.build_stack.cloned().unwrap_or_default(),
|
||||||
)
|
)
|
||||||
|
@ -2394,6 +2457,21 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
||||||
|
|
||||||
// Build the metadata.
|
// Build the metadata.
|
||||||
let dist_info = builder.metadata().await.map_err(Error::Build)?;
|
let dist_info = builder.metadata().await.map_err(Error::Build)?;
|
||||||
|
|
||||||
|
// Store the build context.
|
||||||
|
self.build_context.build_arena().insert(
|
||||||
|
BuildKey {
|
||||||
|
base_python: base_python.into_boxed_path(),
|
||||||
|
source_root: source_root.to_path_buf().into_boxed_path(),
|
||||||
|
subdirectory: subdirectory
|
||||||
|
.map(|subdirectory| subdirectory.to_path_buf().into_boxed_path()),
|
||||||
|
source_strategy,
|
||||||
|
build_kind,
|
||||||
|
},
|
||||||
|
builder,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return the `.dist-info` directory, if it exists.
|
||||||
let Some(dist_info) = dist_info else {
|
let Some(dist_info) = dist_info else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,11 +2,11 @@ use std::{ffi::OsString, path::PathBuf};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error(transparent)]
|
#[error("Failed to read from zip file")]
|
||||||
Zip(#[from] zip::result::ZipError),
|
Zip(#[from] zip::result::ZipError),
|
||||||
#[error(transparent)]
|
#[error("Failed to read from zip file")]
|
||||||
AsyncZip(#[from] async_zip::error::ZipError),
|
AsyncZip(#[from] async_zip::error::ZipError),
|
||||||
#[error(transparent)]
|
#[error("I/O operation failed during extraction")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error(
|
#[error(
|
||||||
"The top-level of the archive must only contain a list directory, but it contains: {0:?}"
|
"The top-level of the archive must only contain a list directory, but it contains: {0:?}"
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub use {
|
||||||
VersionPatternParseError,
|
VersionPatternParseError,
|
||||||
},
|
},
|
||||||
version_specifier::{
|
version_specifier::{
|
||||||
VersionSpecifier, VersionSpecifierBuildError, VersionSpecifiers,
|
TildeVersionSpecifier, VersionSpecifier, VersionSpecifierBuildError, VersionSpecifiers,
|
||||||
VersionSpecifiersParseError,
|
VersionSpecifiersParseError,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -610,6 +610,24 @@ impl Version {
|
||||||
Self::new(self.release().iter().copied())
|
Self::new(self.release().iter().copied())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the version with any segments apart from the release removed, with trailing zeroes
|
||||||
|
/// trimmed.
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn only_release_trimmed(&self) -> Self {
|
||||||
|
if let Some(last_non_zero) = self.release().iter().rposition(|segment| *segment != 0) {
|
||||||
|
if last_non_zero == self.release().len() {
|
||||||
|
// Already trimmed.
|
||||||
|
self.clone()
|
||||||
|
} else {
|
||||||
|
Self::new(self.release().iter().take(last_non_zero + 1).copied())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// `0` is a valid version.
|
||||||
|
Self::new([0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the version with trailing `.0` release segments removed.
|
/// Return the version with trailing `.0` release segments removed.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
|
|
|
@ -130,10 +130,11 @@ impl From<VersionSpecifier> for Ranges<Version> {
|
||||||
///
|
///
|
||||||
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
||||||
pub fn release_specifiers_to_ranges(specifiers: VersionSpecifiers) -> Ranges<Version> {
|
pub fn release_specifiers_to_ranges(specifiers: VersionSpecifiers) -> Ranges<Version> {
|
||||||
specifiers
|
let mut range = Ranges::full();
|
||||||
.into_iter()
|
for specifier in specifiers {
|
||||||
.map(release_specifier_to_range)
|
range = range.intersection(&release_specifier_to_range(specifier, false));
|
||||||
.fold(Ranges::full(), |acc, range| acc.intersection(&range))
|
}
|
||||||
|
range
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using release-only
|
/// Convert the [`VersionSpecifier`] to a PubGrub-compatible version range, using release-only
|
||||||
|
@ -147,67 +148,57 @@ pub fn release_specifiers_to_ranges(specifiers: VersionSpecifiers) -> Ranges<Ver
|
||||||
/// is allowed for projects that declare `requires-python = ">3.13"`.
|
/// is allowed for projects that declare `requires-python = ">3.13"`.
|
||||||
///
|
///
|
||||||
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
/// See: <https://github.com/pypa/pip/blob/a432c7f4170b9ef798a15f035f5dfdb4cc939f35/src/pip/_internal/resolution/resolvelib/candidates.py#L540>
|
||||||
pub fn release_specifier_to_range(specifier: VersionSpecifier) -> Ranges<Version> {
|
pub fn release_specifier_to_range(specifier: VersionSpecifier, trim: bool) -> Ranges<Version> {
|
||||||
let VersionSpecifier { operator, version } = specifier;
|
let VersionSpecifier { operator, version } = specifier;
|
||||||
|
// Note(konsti): We switched strategies to trimmed for the markers, but we don't want to cause
|
||||||
|
// churn in lockfile requires-python, so we only trim for markers.
|
||||||
|
let version_trimmed = if trim {
|
||||||
|
version.only_release_trimmed()
|
||||||
|
} else {
|
||||||
|
version.only_release()
|
||||||
|
};
|
||||||
match operator {
|
match operator {
|
||||||
Operator::Equal => {
|
// Trailing zeroes are not semantically relevant.
|
||||||
let version = version.only_release();
|
Operator::Equal => Ranges::singleton(version_trimmed),
|
||||||
Ranges::singleton(version)
|
Operator::ExactEqual => Ranges::singleton(version_trimmed),
|
||||||
}
|
Operator::NotEqual => Ranges::singleton(version_trimmed).complement(),
|
||||||
Operator::ExactEqual => {
|
Operator::LessThan => Ranges::strictly_lower_than(version_trimmed),
|
||||||
let version = version.only_release();
|
Operator::LessThanEqual => Ranges::lower_than(version_trimmed),
|
||||||
Ranges::singleton(version)
|
Operator::GreaterThan => Ranges::strictly_higher_than(version_trimmed),
|
||||||
}
|
Operator::GreaterThanEqual => Ranges::higher_than(version_trimmed),
|
||||||
Operator::NotEqual => {
|
|
||||||
let version = version.only_release();
|
// Trailing zeroes are semantically relevant.
|
||||||
Ranges::singleton(version).complement()
|
|
||||||
}
|
|
||||||
Operator::TildeEqual => {
|
Operator::TildeEqual => {
|
||||||
let release = version.release();
|
let release = version.release();
|
||||||
let [rest @ .., last, _] = &*release else {
|
let [rest @ .., last, _] = &*release else {
|
||||||
unreachable!("~= must have at least two segments");
|
unreachable!("~= must have at least two segments");
|
||||||
};
|
};
|
||||||
let upper = Version::new(rest.iter().chain([&(last + 1)]));
|
let upper = Version::new(rest.iter().chain([&(last + 1)]));
|
||||||
let version = version.only_release();
|
Ranges::from_range_bounds(version_trimmed..upper)
|
||||||
Ranges::from_range_bounds(version..upper)
|
|
||||||
}
|
|
||||||
Operator::LessThan => {
|
|
||||||
let version = version.only_release();
|
|
||||||
Ranges::strictly_lower_than(version)
|
|
||||||
}
|
|
||||||
Operator::LessThanEqual => {
|
|
||||||
let version = version.only_release();
|
|
||||||
Ranges::lower_than(version)
|
|
||||||
}
|
|
||||||
Operator::GreaterThan => {
|
|
||||||
let version = version.only_release();
|
|
||||||
Ranges::strictly_higher_than(version)
|
|
||||||
}
|
|
||||||
Operator::GreaterThanEqual => {
|
|
||||||
let version = version.only_release();
|
|
||||||
Ranges::higher_than(version)
|
|
||||||
}
|
}
|
||||||
Operator::EqualStar => {
|
Operator::EqualStar => {
|
||||||
let low = version.only_release();
|
// For (not-)equal-star, trailing zeroes are still before the star.
|
||||||
|
let low_full = version.only_release();
|
||||||
let high = {
|
let high = {
|
||||||
let mut high = low.clone();
|
let mut high = low_full.clone();
|
||||||
let mut release = high.release().to_vec();
|
let mut release = high.release().to_vec();
|
||||||
*release.last_mut().unwrap() += 1;
|
*release.last_mut().unwrap() += 1;
|
||||||
high = high.with_release(release);
|
high = high.with_release(release);
|
||||||
high
|
high
|
||||||
};
|
};
|
||||||
Ranges::from_range_bounds(low..high)
|
Ranges::from_range_bounds(version..high)
|
||||||
}
|
}
|
||||||
Operator::NotEqualStar => {
|
Operator::NotEqualStar => {
|
||||||
let low = version.only_release();
|
// For (not-)equal-star, trailing zeroes are still before the star.
|
||||||
|
let low_full = version.only_release();
|
||||||
let high = {
|
let high = {
|
||||||
let mut high = low.clone();
|
let mut high = low_full.clone();
|
||||||
let mut release = high.release().to_vec();
|
let mut release = high.release().to_vec();
|
||||||
*release.last_mut().unwrap() += 1;
|
*release.last_mut().unwrap() += 1;
|
||||||
high = high.with_release(release);
|
high = high.with_release(release);
|
||||||
high
|
high
|
||||||
};
|
};
|
||||||
Ranges::from_range_bounds(low..high).complement()
|
Ranges::from_range_bounds(version..high).complement()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,8 +213,8 @@ impl LowerBound {
|
||||||
/// These bounds use release-only semantics when comparing versions.
|
/// These bounds use release-only semantics when comparing versions.
|
||||||
pub fn new(bound: Bound<Version>) -> Self {
|
pub fn new(bound: Bound<Version>) -> Self {
|
||||||
Self(match bound {
|
Self(match bound {
|
||||||
Bound::Included(version) => Bound::Included(version.only_release()),
|
Bound::Included(version) => Bound::Included(version.only_release_trimmed()),
|
||||||
Bound::Excluded(version) => Bound::Excluded(version.only_release()),
|
Bound::Excluded(version) => Bound::Excluded(version.only_release_trimmed()),
|
||||||
Bound::Unbounded => Bound::Unbounded,
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -357,8 +348,8 @@ impl UpperBound {
|
||||||
/// These bounds use release-only semantics when comparing versions.
|
/// These bounds use release-only semantics when comparing versions.
|
||||||
pub fn new(bound: Bound<Version>) -> Self {
|
pub fn new(bound: Bound<Version>) -> Self {
|
||||||
Self(match bound {
|
Self(match bound {
|
||||||
Bound::Included(version) => Bound::Included(version.only_release()),
|
Bound::Included(version) => Bound::Included(version.only_release_trimmed()),
|
||||||
Bound::Excluded(version) => Bound::Excluded(version.only_release()),
|
Bound::Excluded(version) => Bound::Excluded(version.only_release_trimmed()),
|
||||||
Bound::Unbounded => Bound::Unbounded,
|
Bound::Unbounded => Bound::Unbounded,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,24 +80,38 @@ impl VersionSpecifiers {
|
||||||
|
|
||||||
// Add specifiers for the holes between the bounds.
|
// Add specifiers for the holes between the bounds.
|
||||||
for (lower, upper) in bounds {
|
for (lower, upper) in bounds {
|
||||||
match (next, lower) {
|
let specifier = match (next, lower) {
|
||||||
// Ex) [3.7, 3.8.5), (3.8.5, 3.9] -> >=3.7,!=3.8.5,<=3.9
|
// Ex) [3.7, 3.8.5), (3.8.5, 3.9] -> >=3.7,!=3.8.5,<=3.9
|
||||||
(Bound::Excluded(prev), Bound::Excluded(lower)) if prev == lower => {
|
(Bound::Excluded(prev), Bound::Excluded(lower)) if prev == lower => {
|
||||||
specifiers.push(VersionSpecifier::not_equals_version(prev.clone()));
|
Some(VersionSpecifier::not_equals_version(prev.clone()))
|
||||||
}
|
}
|
||||||
// Ex) [3.7, 3.8), (3.8, 3.9] -> >=3.7,!=3.8.*,<=3.9
|
// Ex) [3.7, 3.8), (3.8, 3.9] -> >=3.7,!=3.8.*,<=3.9
|
||||||
(Bound::Excluded(prev), Bound::Included(lower))
|
(Bound::Excluded(prev), Bound::Included(lower)) => {
|
||||||
if prev.release().len() == 2
|
match *prev.only_release_trimmed().release() {
|
||||||
&& *lower.release() == [prev.release()[0], prev.release()[1] + 1] =>
|
[major] if *lower.only_release_trimmed().release() == [major, 1] => {
|
||||||
{
|
Some(VersionSpecifier::not_equals_star_version(Version::new([
|
||||||
specifiers.push(VersionSpecifier::not_equals_star_version(prev.clone()));
|
major, 0,
|
||||||
}
|
])))
|
||||||
_ => {
|
}
|
||||||
#[cfg(feature = "tracing")]
|
[major, minor]
|
||||||
warn!(
|
if *lower.only_release_trimmed().release() == [major, minor + 1] =>
|
||||||
"Ignoring unsupported gap in `requires-python` version: {next:?} -> {lower:?}"
|
{
|
||||||
);
|
Some(VersionSpecifier::not_equals_star_version(Version::new([
|
||||||
|
major, minor,
|
||||||
|
])))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(specifier) = specifier {
|
||||||
|
specifiers.push(specifier);
|
||||||
|
} else {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
warn!(
|
||||||
|
"Ignoring unsupported gap in `requires-python` version: {next:?} -> {lower:?}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
next = upper;
|
next = upper;
|
||||||
}
|
}
|
||||||
|
@ -348,6 +362,33 @@ impl VersionSpecifier {
|
||||||
Ok(Self { operator, version })
|
Ok(Self { operator, version })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove all non-release parts of the version.
|
||||||
|
///
|
||||||
|
/// The marker decision diagram relies on the assumption that the negation of a marker tree is
|
||||||
|
/// the complement of the marker space. However, pre-release versions violate this assumption.
|
||||||
|
///
|
||||||
|
/// For example, the marker `python_full_version > '3.9' or python_full_version <= '3.9'`
|
||||||
|
/// does not match `python_full_version == 3.9.0a0` and so cannot simplify to `true`. However,
|
||||||
|
/// its negation, `python_full_version > '3.9' and python_full_version <= '3.9'`, also does not
|
||||||
|
/// match `3.9.0a0` and simplifies to `false`, which violates the algebra decision diagrams
|
||||||
|
/// rely on. For this reason we ignore pre-release versions entirely when evaluating markers.
|
||||||
|
///
|
||||||
|
/// Note that `python_version` cannot take on pre-release values as it is truncated to just the
|
||||||
|
/// major and minor version segments. Thus using release-only specifiers is definitely necessary
|
||||||
|
/// for `python_version` to fully simplify any ranges, such as
|
||||||
|
/// `python_version > '3.9' or python_version <= '3.9'`, which is always `true` for
|
||||||
|
/// `python_version`. For `python_full_version` however, this decision is a semantic change.
|
||||||
|
///
|
||||||
|
/// For Python versions, the major.minor is considered the API version, so unlike the rules
|
||||||
|
/// for package versions in PEP 440, we Python `3.9.0a0` is acceptable for `>= "3.9"`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn only_release(self) -> Self {
|
||||||
|
Self {
|
||||||
|
operator: self.operator,
|
||||||
|
version: self.version.only_release(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `==<version>`
|
/// `==<version>`
|
||||||
pub fn equals_version(version: Version) -> Self {
|
pub fn equals_version(version: Version) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -442,14 +483,23 @@ impl VersionSpecifier {
|
||||||
(Some(VersionSpecifier::equals_version(v1.clone())), None)
|
(Some(VersionSpecifier::equals_version(v1.clone())), None)
|
||||||
}
|
}
|
||||||
// `v >= 3.7 && v < 3.8` is equivalent to `v == 3.7.*`
|
// `v >= 3.7 && v < 3.8` is equivalent to `v == 3.7.*`
|
||||||
(Bound::Included(v1), Bound::Excluded(v2))
|
(Bound::Included(v1), Bound::Excluded(v2)) => {
|
||||||
if v1.release().len() == 2
|
match *v1.only_release_trimmed().release() {
|
||||||
&& *v2.release() == [v1.release()[0], v1.release()[1] + 1] =>
|
[major] if *v2.only_release_trimmed().release() == [major, 1] => {
|
||||||
{
|
let version = Version::new([major, 0]);
|
||||||
(
|
(Some(VersionSpecifier::equals_star_version(version)), None)
|
||||||
Some(VersionSpecifier::equals_star_version(v1.clone())),
|
}
|
||||||
None,
|
[major, minor]
|
||||||
)
|
if *v2.only_release_trimmed().release() == [major, minor + 1] =>
|
||||||
|
{
|
||||||
|
let version = Version::new([major, minor]);
|
||||||
|
(Some(VersionSpecifier::equals_star_version(version)), None)
|
||||||
|
}
|
||||||
|
_ => (
|
||||||
|
VersionSpecifier::from_lower_bound(&Bound::Included(v1.clone())),
|
||||||
|
VersionSpecifier::from_upper_bound(&Bound::Excluded(v2.clone())),
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(lower, upper) => (
|
(lower, upper) => (
|
||||||
VersionSpecifier::from_lower_bound(lower),
|
VersionSpecifier::from_lower_bound(lower),
|
||||||
|
@ -615,11 +665,6 @@ impl VersionSpecifier {
|
||||||
| Operator::NotEqual => false,
|
| Operator::NotEqual => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this is a `~=` specifier without a patch version (e.g. `~=3.11`).
|
|
||||||
pub fn is_tilde_without_patch(&self) -> bool {
|
|
||||||
self.operator == Operator::TildeEqual && self.version.release().len() == 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for VersionSpecifier {
|
impl FromStr for VersionSpecifier {
|
||||||
|
@ -843,6 +888,90 @@ pub(crate) fn parse_version_specifiers(
|
||||||
Ok(version_ranges)
|
Ok(version_ranges)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A simple `~=` version specifier with a major, minor and (optional) patch version, e.g., `~=3.13`
|
||||||
|
/// or `~=3.13.0`.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TildeVersionSpecifier<'a> {
|
||||||
|
inner: Cow<'a, VersionSpecifier>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TildeVersionSpecifier<'a> {
|
||||||
|
/// Create a new [`TildeVersionSpecifier`] from a [`VersionSpecifier`] value.
|
||||||
|
///
|
||||||
|
/// If a [`Operator::TildeEqual`] is not used, or the version includes more than minor and patch
|
||||||
|
/// segments, this will return [`None`].
|
||||||
|
pub fn from_specifier(specifier: VersionSpecifier) -> Option<TildeVersionSpecifier<'a>> {
|
||||||
|
TildeVersionSpecifier::new(Cow::Owned(specifier))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`TildeVersionSpecifier`] from a [`VersionSpecifier`] reference.
|
||||||
|
///
|
||||||
|
/// See [`TildeVersionSpecifier::from_specifier`].
|
||||||
|
pub fn from_specifier_ref(
|
||||||
|
specifier: &'a VersionSpecifier,
|
||||||
|
) -> Option<TildeVersionSpecifier<'a>> {
|
||||||
|
TildeVersionSpecifier::new(Cow::Borrowed(specifier))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(specifier: Cow<'a, VersionSpecifier>) -> Option<Self> {
|
||||||
|
if specifier.operator != Operator::TildeEqual {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if specifier.version().release().len() < 2 || specifier.version().release().len() > 3 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if specifier.version().any_prerelease()
|
||||||
|
|| specifier.version().is_local()
|
||||||
|
|| specifier.version().is_post()
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Self { inner: specifier })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a patch version is present in this tilde version specifier.
|
||||||
|
pub fn has_patch(&self) -> bool {
|
||||||
|
self.inner.version.release().len() == 3
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct the lower and upper bounding version specifiers for this tilde version specifier,
|
||||||
|
/// e.g., for `~=3.13` this would return `>=3.13` and `<4` and for `~=3.13.0` it would
|
||||||
|
/// return `>=3.13.0` and `<3.14`.
|
||||||
|
pub fn bounding_specifiers(&self) -> (VersionSpecifier, VersionSpecifier) {
|
||||||
|
let release = self.inner.version().release();
|
||||||
|
let lower = self.inner.version.clone();
|
||||||
|
let upper = if self.has_patch() {
|
||||||
|
Version::new([release[0], release[1] + 1])
|
||||||
|
} else {
|
||||||
|
Version::new([release[0] + 1])
|
||||||
|
};
|
||||||
|
(
|
||||||
|
VersionSpecifier::greater_than_equal_version(lower),
|
||||||
|
VersionSpecifier::less_than_version(upper),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new tilde `VersionSpecifier` with the given patch version appended.
|
||||||
|
pub fn with_patch_version(&self, patch: u64) -> TildeVersionSpecifier {
|
||||||
|
let mut release = self.inner.version.release().to_vec();
|
||||||
|
if self.has_patch() {
|
||||||
|
release.pop();
|
||||||
|
}
|
||||||
|
release.push(patch);
|
||||||
|
TildeVersionSpecifier::from_specifier(
|
||||||
|
VersionSpecifier::from_version(Operator::TildeEqual, Version::new(release))
|
||||||
|
.expect("We should always derive a valid new version specifier"),
|
||||||
|
)
|
||||||
|
.expect("We should always derive a new tilde version specifier")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TildeVersionSpecifier<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{cmp::Ordering, str::FromStr};
|
use std::{cmp::Ordering, str::FromStr};
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
#[cfg(feature = "schemars")]
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::{Debug, Display, Formatter};
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
|
|
@ -172,7 +172,7 @@ impl InternerGuard<'_> {
|
||||||
),
|
),
|
||||||
// Normalize `python_version` markers to `python_full_version` nodes.
|
// Normalize `python_version` markers to `python_full_version` nodes.
|
||||||
MarkerValueVersion::PythonVersion => {
|
MarkerValueVersion::PythonVersion => {
|
||||||
match python_version_to_full_version(normalize_specifier(specifier)) {
|
match python_version_to_full_version(specifier.only_release()) {
|
||||||
Ok(specifier) => (
|
Ok(specifier) => (
|
||||||
Variable::Version(CanonicalMarkerValueVersion::PythonFullVersion),
|
Variable::Version(CanonicalMarkerValueVersion::PythonFullVersion),
|
||||||
Edges::from_specifier(specifier),
|
Edges::from_specifier(specifier),
|
||||||
|
@ -1214,7 +1214,7 @@ impl Edges {
|
||||||
|
|
||||||
/// Returns the [`Edges`] for a version specifier.
|
/// Returns the [`Edges`] for a version specifier.
|
||||||
fn from_specifier(specifier: VersionSpecifier) -> Edges {
|
fn from_specifier(specifier: VersionSpecifier) -> Edges {
|
||||||
let specifier = release_specifier_to_range(normalize_specifier(specifier));
|
let specifier = release_specifier_to_range(specifier.only_release(), true);
|
||||||
Edges::Version {
|
Edges::Version {
|
||||||
edges: Edges::from_range(&specifier),
|
edges: Edges::from_range(&specifier),
|
||||||
}
|
}
|
||||||
|
@ -1227,9 +1227,9 @@ impl Edges {
|
||||||
let mut range: Ranges<Version> = versions
|
let mut range: Ranges<Version> = versions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|version| {
|
.map(|version| {
|
||||||
let specifier = VersionSpecifier::equals_version(version.clone());
|
let specifier = VersionSpecifier::equals_version(version.only_release());
|
||||||
let specifier = python_version_to_full_version(specifier)?;
|
let specifier = python_version_to_full_version(specifier)?;
|
||||||
Ok(release_specifier_to_range(normalize_specifier(specifier)))
|
Ok(release_specifier_to_range(specifier, true))
|
||||||
})
|
})
|
||||||
.flatten_ok()
|
.flatten_ok()
|
||||||
.collect::<Result<Ranges<_>, NodeId>>()?;
|
.collect::<Result<Ranges<_>, NodeId>>()?;
|
||||||
|
@ -1526,57 +1526,62 @@ impl Edges {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize a [`VersionSpecifier`] before adding it to the tree.
|
|
||||||
fn normalize_specifier(specifier: VersionSpecifier) -> VersionSpecifier {
|
|
||||||
let (operator, version) = specifier.into_parts();
|
|
||||||
|
|
||||||
// The decision diagram relies on the assumption that the negation of a marker tree is
|
|
||||||
// the complement of the marker space. However, pre-release versions violate this assumption.
|
|
||||||
//
|
|
||||||
// For example, the marker `python_full_version > '3.9' or python_full_version <= '3.9'`
|
|
||||||
// does not match `python_full_version == 3.9.0a0` and so cannot simplify to `true`. However,
|
|
||||||
// its negation, `python_full_version > '3.9' and python_full_version <= '3.9'`, also does not
|
|
||||||
// match `3.9.0a0` and simplifies to `false`, which violates the algebra decision diagrams
|
|
||||||
// rely on. For this reason we ignore pre-release versions entirely when evaluating markers.
|
|
||||||
//
|
|
||||||
// Note that `python_version` cannot take on pre-release values as it is truncated to just the
|
|
||||||
// major and minor version segments. Thus using release-only specifiers is definitely necessary
|
|
||||||
// for `python_version` to fully simplify any ranges, such as `python_version > '3.9' or python_version <= '3.9'`,
|
|
||||||
// which is always `true` for `python_version`. For `python_full_version` however, this decision
|
|
||||||
// is a semantic change.
|
|
||||||
let mut release = &*version.release();
|
|
||||||
|
|
||||||
// Strip any trailing `0`s.
|
|
||||||
//
|
|
||||||
// The [`Version`] type ignores trailing `0`s for equality, but still preserves them in its
|
|
||||||
// [`Display`] output. We must normalize all versions by stripping trailing `0`s to remove the
|
|
||||||
// distinction between versions like `3.9` and `3.9.0`. Otherwise, their output would depend on
|
|
||||||
// which form was added to the global marker interner first.
|
|
||||||
//
|
|
||||||
// Note that we cannot strip trailing `0`s for star equality, as `==3.0.*` is different from `==3.*`.
|
|
||||||
if !operator.is_star() {
|
|
||||||
if let Some(end) = release.iter().rposition(|segment| *segment != 0) {
|
|
||||||
if end > 0 {
|
|
||||||
release = &release[..=end];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VersionSpecifier::from_version(operator, Version::new(release)).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the equivalent `python_full_version` specifier for a `python_version` specifier.
|
/// Returns the equivalent `python_full_version` specifier for a `python_version` specifier.
|
||||||
///
|
///
|
||||||
/// Returns `Err` with a constant node if the equivalent comparison is always `true` or `false`.
|
/// Returns `Err` with a constant node if the equivalent comparison is always `true` or `false`.
|
||||||
fn python_version_to_full_version(specifier: VersionSpecifier) -> Result<VersionSpecifier, NodeId> {
|
fn python_version_to_full_version(specifier: VersionSpecifier) -> Result<VersionSpecifier, NodeId> {
|
||||||
|
// Trailing zeroes matter only for (not-)equals-star and tilde-equals. This means that below
|
||||||
|
// the next two blocks, we can use the trimmed release as the release.
|
||||||
|
if specifier.operator().is_star() {
|
||||||
|
// Input python_version python_full_version
|
||||||
|
// ==3.* 3.* 3.*
|
||||||
|
// ==3.0.* 3.0 3.0.*
|
||||||
|
// ==3.0.0.* 3.0 3.0.*
|
||||||
|
// ==3.9.* 3.9 3.9.*
|
||||||
|
// ==3.9.0.* 3.9 3.9.*
|
||||||
|
// ==3.9.0.0.* 3.9 3.9.*
|
||||||
|
// ==3.9.1.* FALSE FALSE
|
||||||
|
// ==3.9.1.0.* FALSE FALSE
|
||||||
|
// ==3.9.1.0.0.* FALSE FALSE
|
||||||
|
return match &*specifier.version().release() {
|
||||||
|
// `3.*`
|
||||||
|
[_major] => Ok(specifier),
|
||||||
|
// Ex) `3.9.*`, `3.9.0.*`, or `3.9.0.0.*`
|
||||||
|
[major, minor, rest @ ..] if rest.iter().all(|x| *x == 0) => {
|
||||||
|
let python_version = Version::new([major, minor]);
|
||||||
|
// Unwrap safety: A star operator with two version segments is always valid.
|
||||||
|
Ok(VersionSpecifier::from_version(*specifier.operator(), python_version).unwrap())
|
||||||
|
}
|
||||||
|
// Ex) `3.9.1.*` or `3.9.0.1.*`
|
||||||
|
_ => Err(NodeId::FALSE),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if *specifier.operator() == Operator::TildeEqual {
|
||||||
|
// python_version python_full_version
|
||||||
|
// ~=3 (not possible)
|
||||||
|
// ~= 3.0 >= 3.0, < 4.0
|
||||||
|
// ~= 3.9 >= 3.9, < 4.0
|
||||||
|
// ~= 3.9.0 == 3.9.*
|
||||||
|
// ~= 3.9.1 FALSE
|
||||||
|
// ~= 3.9.0.0 == 3.9.*
|
||||||
|
// ~= 3.9.0.1 FALSE
|
||||||
|
return match &*specifier.version().release() {
|
||||||
|
// Ex) `3.0`, `3.7`
|
||||||
|
[_major, _minor] => Ok(specifier),
|
||||||
|
// Ex) `3.9`, `3.9.0`, or `3.9.0.0`
|
||||||
|
[major, minor, rest @ ..] if rest.iter().all(|x| *x == 0) => {
|
||||||
|
let python_version = Version::new([major, minor]);
|
||||||
|
Ok(VersionSpecifier::equals_star_version(python_version))
|
||||||
|
}
|
||||||
|
// Ex) `3.9.1` or `3.9.0.1`
|
||||||
|
_ => Err(NodeId::FALSE),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Extract the major and minor version segments if the specifier contains exactly
|
// Extract the major and minor version segments if the specifier contains exactly
|
||||||
// those segments, or if it contains a major segment with an implied minor segment of `0`.
|
// those segments, or if it contains a major segment with an implied minor segment of `0`.
|
||||||
let major_minor = match *specifier.version().release() {
|
let major_minor = match *specifier.version().only_release_trimmed().release() {
|
||||||
// For star operators, we cannot add a trailing `0`.
|
|
||||||
//
|
|
||||||
// `python_version == 3.*` is equivalent to `python_full_version == 3.*`. Adding a
|
|
||||||
// trailing `0` would result in `python_version == 3.0.*`, which is incorrect.
|
|
||||||
[_major] if specifier.operator().is_star() => return Ok(specifier),
|
|
||||||
// Add a trailing `0` for the minor version, which is implied.
|
// Add a trailing `0` for the minor version, which is implied.
|
||||||
// For example, `python_version == 3` matches `3.0.1`, `3.0.2`, etc.
|
// For example, `python_version == 3` matches `3.0.1`, `3.0.2`, etc.
|
||||||
[major] => Some((major, 0)),
|
[major] => Some((major, 0)),
|
||||||
|
@ -1614,9 +1619,10 @@ fn python_version_to_full_version(specifier: VersionSpecifier) -> Result<Version
|
||||||
VersionSpecifier::less_than_version(Version::new([major, minor + 1]))
|
VersionSpecifier::less_than_version(Version::new([major, minor + 1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// `==3.7.*`, `!=3.7.*`, `~=3.7` already represent the equivalent `python_full_version`
|
Operator::EqualStar | Operator::NotEqualStar | Operator::TildeEqual => {
|
||||||
// comparison.
|
// Handled above.
|
||||||
Operator::EqualStar | Operator::NotEqualStar | Operator::TildeEqual => specifier,
|
unreachable!()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let [major, minor, ..] = *specifier.version().release() else {
|
let [major, minor, ..] = *specifier.version().release() else {
|
||||||
|
@ -1624,13 +1630,14 @@ fn python_version_to_full_version(specifier: VersionSpecifier) -> Result<Version
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(match specifier.operator() {
|
Ok(match specifier.operator() {
|
||||||
// `python_version` cannot have more than two release segments, so equality is impossible.
|
// `python_version` cannot have more than two release segments, and we know
|
||||||
Operator::Equal | Operator::ExactEqual | Operator::EqualStar | Operator::TildeEqual => {
|
// that the following release segments aren't purely zeroes so equality is impossible.
|
||||||
|
Operator::Equal | Operator::ExactEqual => {
|
||||||
return Err(NodeId::FALSE);
|
return Err(NodeId::FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Similarly, inequalities are always `true`.
|
// Similarly, inequalities are always `true`.
|
||||||
Operator::NotEqual | Operator::NotEqualStar => return Err(NodeId::TRUE),
|
Operator::NotEqual => return Err(NodeId::TRUE),
|
||||||
|
|
||||||
// `python_version {<,<=} 3.7.8` is equivalent to `python_full_version < 3.8`.
|
// `python_version {<,<=} 3.7.8` is equivalent to `python_full_version < 3.8`.
|
||||||
Operator::LessThan | Operator::LessThanEqual => {
|
Operator::LessThan | Operator::LessThanEqual => {
|
||||||
|
@ -1641,6 +1648,11 @@ fn python_version_to_full_version(specifier: VersionSpecifier) -> Result<Version
|
||||||
Operator::GreaterThan | Operator::GreaterThanEqual => {
|
Operator::GreaterThan | Operator::GreaterThanEqual => {
|
||||||
VersionSpecifier::greater_than_equal_version(Version::new([major, minor + 1]))
|
VersionSpecifier::greater_than_equal_version(Version::new([major, minor + 1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Operator::EqualStar | Operator::NotEqualStar | Operator::TildeEqual => {
|
||||||
|
// Handled above.
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,8 @@ fn collect_dnf(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect whether the range for this edge can be simplified as a star inequality.
|
// Detect whether the range for this edge can be simplified as a star specifier.
|
||||||
if let Some(specifier) = star_range_inequality(&range) {
|
if let Some(specifier) = star_range_specifier(&range) {
|
||||||
path.push(MarkerExpression::Version {
|
path.push(MarkerExpression::Version {
|
||||||
key: marker.key().into(),
|
key: marker.key().into(),
|
||||||
specifier,
|
specifier,
|
||||||
|
@ -343,22 +343,34 @@ where
|
||||||
Some(excluded)
|
Some(excluded)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Some` if the version expression can be simplified as a star inequality with the given
|
/// Returns `Some` if the version range can be simplified as a star specifier.
|
||||||
/// specifier.
|
|
||||||
///
|
///
|
||||||
/// For example, `python_full_version < '3.8' or python_full_version >= '3.9'` can be simplified to
|
/// Only for the two bounds case not covered by [`VersionSpecifier::from_release_only_bounds`].
|
||||||
/// `python_full_version != '3.8.*'`.
|
///
|
||||||
fn star_range_inequality(range: &Ranges<Version>) -> Option<VersionSpecifier> {
|
/// For negative ranges like `python_full_version < '3.8' or python_full_version >= '3.9'`,
|
||||||
|
/// returns `!= '3.8.*'`.
|
||||||
|
fn star_range_specifier(range: &Ranges<Version>) -> Option<VersionSpecifier> {
|
||||||
|
if range.iter().count() != 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// Check for negative star range: two segments [(Unbounded, Excluded(v1)), (Included(v2), Unbounded)]
|
||||||
let (b1, b2) = range.iter().collect_tuple()?;
|
let (b1, b2) = range.iter().collect_tuple()?;
|
||||||
|
if let ((Bound::Unbounded, Bound::Excluded(v1)), (Bound::Included(v2), Bound::Unbounded)) =
|
||||||
match (b1, b2) {
|
(b1, b2)
|
||||||
((Bound::Unbounded, Bound::Excluded(v1)), (Bound::Included(v2), Bound::Unbounded))
|
{
|
||||||
if v1.release().len() == 2
|
match *v1.only_release_trimmed().release() {
|
||||||
&& *v2.release() == [v1.release()[0], v1.release()[1] + 1] =>
|
[major] if *v2.release() == [major, 1] => {
|
||||||
{
|
Some(VersionSpecifier::not_equals_star_version(Version::new([
|
||||||
Some(VersionSpecifier::not_equals_star_version(v1.clone()))
|
major, 0,
|
||||||
|
])))
|
||||||
|
}
|
||||||
|
[major, minor] if *v2.release() == [major, minor + 1] => {
|
||||||
|
Some(VersionSpecifier::not_equals_star_version(v1.clone()))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
_ => None,
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2271,13 +2271,13 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_marker_simplification() {
|
fn test_marker_simplification() {
|
||||||
assert_false("python_version == '3.9.1'");
|
assert_false("python_version == '3.9.1'");
|
||||||
assert_false("python_version == '3.9.0.*'");
|
|
||||||
assert_true("python_version != '3.9.1'");
|
assert_true("python_version != '3.9.1'");
|
||||||
|
|
||||||
// Technically these is are valid substring comparison, but we do not allow them.
|
// This is an edge case that happens to be supported, but is not critical to support.
|
||||||
// e.g., using a version with patch components with `python_version` is considered
|
assert_simplifies(
|
||||||
// impossible to satisfy since the value it is truncated at the minor version
|
"python_version in '3.9.0'",
|
||||||
assert_false("python_version in '3.9.0'");
|
"python_full_version == '3.9.*'",
|
||||||
|
);
|
||||||
// e.g., using a version that is not PEP 440 compliant is considered arbitrary
|
// e.g., using a version that is not PEP 440 compliant is considered arbitrary
|
||||||
assert_true("python_version in 'foo'");
|
assert_true("python_version in 'foo'");
|
||||||
// e.g., including `*` versions, which would require tracking a version specifier
|
// e.g., including `*` versions, which would require tracking a version specifier
|
||||||
|
@ -2287,16 +2287,25 @@ mod test {
|
||||||
assert_true("python_version in '3.9,3.10'");
|
assert_true("python_version in '3.9,3.10'");
|
||||||
assert_true("python_version in '3.9 or 3.10'");
|
assert_true("python_version in '3.9 or 3.10'");
|
||||||
|
|
||||||
// e.g, when one of the values cannot be true
|
// This is an edge case that happens to be supported, but is not critical to support.
|
||||||
// TODO(zanieb): This seems like a quirk of the `python_full_version` normalization, this
|
assert_simplifies(
|
||||||
// should just act as though the patch version isn't present
|
"python_version in '3.9 3.10.0 3.11'",
|
||||||
assert_false("python_version in '3.9 3.10.0 3.11'");
|
"python_full_version >= '3.9' and python_full_version < '3.12'",
|
||||||
|
);
|
||||||
|
|
||||||
assert_simplifies("python_version == '3.9'", "python_full_version == '3.9.*'");
|
assert_simplifies("python_version == '3.9'", "python_full_version == '3.9.*'");
|
||||||
assert_simplifies(
|
assert_simplifies(
|
||||||
"python_version == '3.9.0'",
|
"python_version == '3.9.0'",
|
||||||
"python_full_version == '3.9.*'",
|
"python_full_version == '3.9.*'",
|
||||||
);
|
);
|
||||||
|
assert_simplifies(
|
||||||
|
"python_version == '3.9.0.*'",
|
||||||
|
"python_full_version == '3.9.*'",
|
||||||
|
);
|
||||||
|
assert_simplifies(
|
||||||
|
"python_version == '3.*'",
|
||||||
|
"python_full_version >= '3' and python_full_version < '4'",
|
||||||
|
);
|
||||||
|
|
||||||
// `<version> in`
|
// `<version> in`
|
||||||
// e.g., when the range is not contiguous
|
// e.g., when the range is not contiguous
|
||||||
|
@ -2528,6 +2537,68 @@ mod test {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_python_version_equal_star() {
|
||||||
|
// Input, equivalent with python_version, equivalent with python_full_version
|
||||||
|
let cases = [
|
||||||
|
("3.*", "3.*", "3.*"),
|
||||||
|
("3.0.*", "3.0", "3.0.*"),
|
||||||
|
("3.0.0.*", "3.0", "3.0.*"),
|
||||||
|
("3.9.*", "3.9", "3.9.*"),
|
||||||
|
("3.9.0.*", "3.9", "3.9.*"),
|
||||||
|
("3.9.0.0.*", "3.9", "3.9.*"),
|
||||||
|
];
|
||||||
|
for (input, equal_python_version, equal_python_full_version) in cases {
|
||||||
|
assert_eq!(
|
||||||
|
m(&format!("python_version == '{input}'")),
|
||||||
|
m(&format!("python_version == '{equal_python_version}'")),
|
||||||
|
"{input} {equal_python_version}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
m(&format!("python_version == '{input}'")),
|
||||||
|
m(&format!(
|
||||||
|
"python_full_version == '{equal_python_full_version}'"
|
||||||
|
)),
|
||||||
|
"{input} {equal_python_full_version}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cases_false = ["3.9.1.*", "3.9.1.0.*", "3.9.1.0.0.*"];
|
||||||
|
for input in cases_false {
|
||||||
|
assert!(
|
||||||
|
m(&format!("python_version == '{input}'")).is_false(),
|
||||||
|
"{input}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tilde_equal_normalization() {
|
||||||
|
assert_eq!(
|
||||||
|
m("python_version ~= '3.10.0'"),
|
||||||
|
m("python_version >= '3.10.0' and python_version < '3.11.0'")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Two digit versions such as `python_version` get padded with a zero, so they can never
|
||||||
|
// match
|
||||||
|
assert_eq!(m("python_version ~= '3.10.1'"), MarkerTree::FALSE);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
m("python_version ~= '3.10'"),
|
||||||
|
m("python_version >= '3.10' and python_version < '4.0'")
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
m("python_full_version ~= '3.10.0'"),
|
||||||
|
m("python_full_version >= '3.10.0' and python_full_version < '3.11.0'")
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
m("python_full_version ~= '3.10'"),
|
||||||
|
m("python_full_version >= '3.10' and python_full_version < '4.0'")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// This tests marker implication.
|
/// This tests marker implication.
|
||||||
///
|
///
|
||||||
/// Specifically, these test cases come from a [bug] where `foo` and `bar`
|
/// Specifically, these test cases come from a [bug] where `foo` and `bar`
|
||||||
|
@ -3324,4 +3395,32 @@ mod test {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Case a: There is no version `3` (no trailing zero) in the interner yet.
|
||||||
|
#[test]
|
||||||
|
fn marker_normalization_a() {
|
||||||
|
let left_tree = m("python_version == '3.0.*'");
|
||||||
|
let left = left_tree.try_to_string().unwrap();
|
||||||
|
let right = "python_full_version == '3.0.*'";
|
||||||
|
assert_eq!(left, right, "{left} != {right}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Case b: There is already a version `3` (no trailing zero) in the interner.
|
||||||
|
#[test]
|
||||||
|
fn marker_normalization_b() {
|
||||||
|
m("python_version >= '3' and python_version <= '3.0'");
|
||||||
|
|
||||||
|
let left_tree = m("python_version == '3.0.*'");
|
||||||
|
let left = left_tree.try_to_string().unwrap();
|
||||||
|
let right = "python_full_version == '3.0.*'";
|
||||||
|
assert_eq!(left, right, "{left} != {right}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn marker_normalization_c() {
|
||||||
|
let left_tree = MarkerTree::from_str("python_version == '3.10.0.*'").unwrap();
|
||||||
|
let left = left_tree.try_to_string().unwrap();
|
||||||
|
let right = "python_full_version == '3.10.*'";
|
||||||
|
assert_eq!(left, right, "{left} != {right}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,16 @@ use uv_redacted::DisplaySafeUrl;
|
||||||
use crate::Pep508Url;
|
use crate::Pep508Url;
|
||||||
|
|
||||||
/// A wrapper around [`Url`] that preserves the original string.
|
/// A wrapper around [`Url`] that preserves the original string.
|
||||||
|
///
|
||||||
|
/// The original string is not preserved after serialization/deserialization.
|
||||||
#[derive(Debug, Clone, Eq)]
|
#[derive(Debug, Clone, Eq)]
|
||||||
pub struct VerbatimUrl {
|
pub struct VerbatimUrl {
|
||||||
/// The parsed URL.
|
/// The parsed URL.
|
||||||
url: DisplaySafeUrl,
|
url: DisplaySafeUrl,
|
||||||
/// The URL as it was provided by the user.
|
/// The URL as it was provided by the user.
|
||||||
|
///
|
||||||
|
/// Even if originally set, this will be [`None`] after
|
||||||
|
/// serialization/deserialization.
|
||||||
given: Option<ArcStr>,
|
given: Option<ArcStr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -758,6 +758,14 @@ impl FormMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a FormMetadata {
|
||||||
|
type Item = &'a (&'a str, String);
|
||||||
|
type IntoIter = std::slice::Iter<'a, (&'a str, String)>;
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the upload request.
|
/// Build the upload request.
|
||||||
///
|
///
|
||||||
/// Returns the request and the reporter progress bar id.
|
/// Returns the request and the reporter progress bar id.
|
||||||
|
|
|
@ -3,7 +3,9 @@ use petgraph::{
|
||||||
graph::{DiGraph, NodeIndex},
|
graph::{DiGraph, NodeIndex},
|
||||||
};
|
};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use std::{borrow::Cow, collections::BTreeSet, hash::Hash, rc::Rc};
|
#[cfg(feature = "schemars")]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::{collections::BTreeSet, hash::Hash, rc::Rc};
|
||||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||||
|
|
||||||
use crate::dependency_groups::{DependencyGroupSpecifier, DependencyGroups};
|
use crate::dependency_groups::{DependencyGroupSpecifier, DependencyGroups};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
|
#[cfg(feature = "schemars")]
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,7 +12,7 @@ use futures::TryStreamExt;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use reqwest_retry::RetryPolicy;
|
use reqwest_retry::{RetryError, RetryPolicy};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::io::{AsyncRead, AsyncWriteExt, BufWriter, ReadBuf};
|
use tokio::io::{AsyncRead, AsyncWriteExt, BufWriter, ReadBuf};
|
||||||
|
@ -111,6 +111,33 @@ pub enum Error {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
// Return the number of attempts that were made to complete this request before this error was
|
||||||
|
// returned. Note that e.g. 3 retries equates to 4 attempts.
|
||||||
|
//
|
||||||
|
// It's easier to do arithmetic with "attempts" instead of "retries", because if you have
|
||||||
|
// nested retry loops you can just add up all the attempts directly, while adding up the
|
||||||
|
// retries requires +1/-1 adjustments.
|
||||||
|
fn attempts(&self) -> u32 {
|
||||||
|
// Unfortunately different variants of `Error` track retry counts in different ways. We
|
||||||
|
// could consider unifying the variants we handle here in `Error::from_reqwest_middleware`
|
||||||
|
// instead, but both approaches will be fragile as new variants get added over time.
|
||||||
|
if let Error::NetworkErrorWithRetries { retries, .. } = self {
|
||||||
|
return retries + 1;
|
||||||
|
}
|
||||||
|
// TODO(jack): let-chains are stable as of Rust 1.88. We should use them here as soon as
|
||||||
|
// our rust-version is high enough.
|
||||||
|
if let Error::NetworkMiddlewareError(_, anyhow_error) = self {
|
||||||
|
if let Some(RetryError::WithRetries { retries, .. }) =
|
||||||
|
anyhow_error.downcast_ref::<RetryError>()
|
||||||
|
{
|
||||||
|
return retries + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||||
pub struct ManagedPythonDownload {
|
pub struct ManagedPythonDownload {
|
||||||
key: PythonInstallationKey,
|
key: PythonInstallationKey,
|
||||||
|
@ -695,7 +722,8 @@ impl ManagedPythonDownload {
|
||||||
pypy_install_mirror: Option<&str>,
|
pypy_install_mirror: Option<&str>,
|
||||||
reporter: Option<&dyn Reporter>,
|
reporter: Option<&dyn Reporter>,
|
||||||
) -> Result<DownloadResult, Error> {
|
) -> Result<DownloadResult, Error> {
|
||||||
let mut n_past_retries = 0;
|
let mut total_attempts = 0;
|
||||||
|
let mut retried_here = false;
|
||||||
let start_time = SystemTime::now();
|
let start_time = SystemTime::now();
|
||||||
let retry_policy = client.retry_policy();
|
let retry_policy = client.retry_policy();
|
||||||
loop {
|
loop {
|
||||||
|
@ -710,25 +738,41 @@ impl ManagedPythonDownload {
|
||||||
reporter,
|
reporter,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
if result
|
let result = match result {
|
||||||
.as_ref()
|
Ok(download_result) => Ok(download_result),
|
||||||
.err()
|
Err(err) => {
|
||||||
.is_some_and(|err| is_extended_transient_error(err))
|
// Inner retry loops (e.g. `reqwest-retry` middleware) might make more than one
|
||||||
{
|
// attempt per error we see here.
|
||||||
let retry_decision = retry_policy.should_retry(start_time, n_past_retries);
|
total_attempts += err.attempts();
|
||||||
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
|
// We currently interpret e.g. "3 retries" to mean we should make 4 attempts.
|
||||||
debug!(
|
let n_past_retries = total_attempts - 1;
|
||||||
"Transient failure while handling response for {}; retrying...",
|
if is_extended_transient_error(&err) {
|
||||||
self.key()
|
let retry_decision = retry_policy.should_retry(start_time, n_past_retries);
|
||||||
);
|
if let reqwest_retry::RetryDecision::Retry { execute_after } =
|
||||||
let duration = execute_after
|
retry_decision
|
||||||
.duration_since(SystemTime::now())
|
{
|
||||||
.unwrap_or_else(|_| Duration::default());
|
debug!(
|
||||||
tokio::time::sleep(duration).await;
|
"Transient failure while handling response for {}; retrying...",
|
||||||
n_past_retries += 1;
|
self.key()
|
||||||
continue;
|
);
|
||||||
|
let duration = execute_after
|
||||||
|
.duration_since(SystemTime::now())
|
||||||
|
.unwrap_or_else(|_| Duration::default());
|
||||||
|
tokio::time::sleep(duration).await;
|
||||||
|
retried_here = true;
|
||||||
|
continue; // Retry.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if retried_here {
|
||||||
|
Err(Error::NetworkErrorWithRetries {
|
||||||
|
err: Box::new(err),
|
||||||
|
retries: n_past_retries,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -967,6 +967,31 @@ impl InterpreterInfo {
|
||||||
pub(crate) fn query_cached(executable: &Path, cache: &Cache) -> Result<Self, Error> {
|
pub(crate) fn query_cached(executable: &Path, cache: &Cache) -> Result<Self, Error> {
|
||||||
let absolute = std::path::absolute(executable)?;
|
let absolute = std::path::absolute(executable)?;
|
||||||
|
|
||||||
|
// Provide a better error message if the link is broken or the file does not exist. Since
|
||||||
|
// `canonicalize_executable` does not resolve the file on Windows, we must re-use this logic
|
||||||
|
// for the subsequent metadata read as we may not have actually resolved the path.
|
||||||
|
let handle_io_error = |err: io::Error| -> Error {
|
||||||
|
if err.kind() == io::ErrorKind::NotFound {
|
||||||
|
// Check if it looks like a venv interpreter where the underlying Python
|
||||||
|
// installation was removed.
|
||||||
|
if absolute
|
||||||
|
.symlink_metadata()
|
||||||
|
.is_ok_and(|metadata| metadata.is_symlink())
|
||||||
|
{
|
||||||
|
Error::BrokenSymlink(BrokenSymlink {
|
||||||
|
path: executable.to_path_buf(),
|
||||||
|
venv: uv_fs::is_virtualenv_executable(executable),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Error::NotFound(executable.to_path_buf())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err.into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let canonical = canonicalize_executable(&absolute).map_err(handle_io_error)?;
|
||||||
|
|
||||||
let cache_entry = cache.entry(
|
let cache_entry = cache.entry(
|
||||||
CacheBucket::Interpreter,
|
CacheBucket::Interpreter,
|
||||||
// Shard interpreter metadata by host architecture, operating system, and version, to
|
// Shard interpreter metadata by host architecture, operating system, and version, to
|
||||||
|
@ -978,33 +1003,17 @@ impl InterpreterInfo {
|
||||||
)),
|
)),
|
||||||
// We use the absolute path for the cache entry to avoid cache collisions for relative
|
// We use the absolute path for the cache entry to avoid cache collisions for relative
|
||||||
// paths. But we don't want to query the executable with symbolic links resolved because
|
// paths. But we don't want to query the executable with symbolic links resolved because
|
||||||
// that can change reported values, e.g., `sys.executable`.
|
// that can change reported values, e.g., `sys.executable`. We include the canonical
|
||||||
format!("{}.msgpack", cache_digest(&absolute)),
|
// path in the cache entry as well, otherwise we can have cache collisions if an
|
||||||
|
// absolute path refers to different interpreters with matching ctimes, e.g., if you
|
||||||
|
// have a `.venv/bin/python` pointing to both Python 3.12 and Python 3.13 that were
|
||||||
|
// modified at the same time.
|
||||||
|
format!("{}.msgpack", cache_digest(&(&absolute, &canonical))),
|
||||||
);
|
);
|
||||||
|
|
||||||
// We check the timestamp of the canonicalized executable to check if an underlying
|
// We check the timestamp of the canonicalized executable to check if an underlying
|
||||||
// interpreter has been modified.
|
// interpreter has been modified.
|
||||||
let modified = canonicalize_executable(&absolute)
|
let modified = Timestamp::from_path(canonical).map_err(handle_io_error)?;
|
||||||
.and_then(Timestamp::from_path)
|
|
||||||
.map_err(|err| {
|
|
||||||
if err.kind() == io::ErrorKind::NotFound {
|
|
||||||
// Check if it looks like a venv interpreter where the underlying Python
|
|
||||||
// installation was removed.
|
|
||||||
if absolute
|
|
||||||
.symlink_metadata()
|
|
||||||
.is_ok_and(|metadata| metadata.is_symlink())
|
|
||||||
{
|
|
||||||
Error::BrokenSymlink(BrokenSymlink {
|
|
||||||
path: executable.to_path_buf(),
|
|
||||||
venv: uv_fs::is_virtualenv_executable(executable),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Error::NotFound(executable.to_path_buf())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err.into()
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Read from the cache.
|
// Read from the cache.
|
||||||
if cache
|
if cache
|
||||||
|
@ -1016,7 +1025,7 @@ impl InterpreterInfo {
|
||||||
Ok(cached) => {
|
Ok(cached) => {
|
||||||
if cached.timestamp == modified {
|
if cached.timestamp == modified {
|
||||||
trace!(
|
trace!(
|
||||||
"Cached interpreter info for Python {}, skipping probing: {}",
|
"Found cached interpreter info for Python {}, skipping query of: {}",
|
||||||
cached.data.markers.python_full_version(),
|
cached.data.markers.python_full_version(),
|
||||||
executable.user_display()
|
executable.user_display()
|
||||||
);
|
);
|
||||||
|
|
|
@ -43,15 +43,36 @@ impl Ord for Arch {
|
||||||
return self.variant.cmp(&other.variant);
|
return self.variant.cmp(&other.variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
let native = Arch::from_env();
|
// For the time being, manually make aarch64 windows disfavored
|
||||||
|
// on its own host platform, because most packages don't have wheels for
|
||||||
|
// aarch64 windows, making emulation more useful than native execution!
|
||||||
|
//
|
||||||
|
// The reason we do this in "sorting" and not "supports" is so that we don't
|
||||||
|
// *refuse* to use an aarch64 windows pythons if they happen to be installed
|
||||||
|
// and nothing else is available.
|
||||||
|
//
|
||||||
|
// Similarly if someone manually requests an aarch64 windows install, we
|
||||||
|
// should respect that request (this is the way users should "override"
|
||||||
|
// this behaviour).
|
||||||
|
let preferred = if cfg!(all(windows, target_arch = "aarch64")) {
|
||||||
|
Arch {
|
||||||
|
family: target_lexicon::Architecture::X86_64,
|
||||||
|
variant: None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Prefer native architectures
|
||||||
|
Arch::from_env()
|
||||||
|
};
|
||||||
|
|
||||||
// Prefer native architectures
|
match (
|
||||||
match (self.family == native.family, other.family == native.family) {
|
self.family == preferred.family,
|
||||||
|
other.family == preferred.family,
|
||||||
|
) {
|
||||||
(true, true) => unreachable!(),
|
(true, true) => unreachable!(),
|
||||||
(true, false) => std::cmp::Ordering::Less,
|
(true, false) => std::cmp::Ordering::Less,
|
||||||
(false, true) => std::cmp::Ordering::Greater,
|
(false, true) => std::cmp::Ordering::Greater,
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
// Both non-native, fallback to lexicographic order
|
// Both non-preferred, fallback to lexicographic order
|
||||||
self.family.to_string().cmp(&other.family.to_string())
|
self.family.to_string().cmp(&other.family.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#[cfg(feature = "schemars")]
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! DO NOT EDIT
|
//! DO NOT EDIT
|
||||||
//!
|
//!
|
||||||
//! Generated with `cargo run dev generate-sysconfig-metadata`
|
//! Generated with `cargo run dev generate-sysconfig-metadata`
|
||||||
//! Targets from <https://github.com/astral-sh/python-build-standalone/blob/20250626/cpython-unix/targets.yml>
|
//! Targets from <https://github.com/astral-sh/python-build-standalone/blob/20250702/cpython-unix/targets.yml>
|
||||||
//!
|
//!
|
||||||
#![allow(clippy::all)]
|
#![allow(clippy::all)]
|
||||||
#![cfg_attr(any(), rustfmt::skip)]
|
#![cfg_attr(any(), rustfmt::skip)]
|
||||||
|
@ -15,7 +15,6 @@ use crate::sysconfig::replacements::{ReplacementEntry, ReplacementMode};
|
||||||
pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock<BTreeMap<String, Vec<ReplacementEntry>>> = LazyLock::new(|| {
|
pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock<BTreeMap<String, Vec<ReplacementEntry>>> = LazyLock::new(|| {
|
||||||
BTreeMap::from_iter([
|
BTreeMap::from_iter([
|
||||||
("BLDSHARED".to_string(), vec![
|
("BLDSHARED".to_string(), vec![
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
||||||
|
@ -28,7 +27,6 @@ pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock<BTreeMap<String, Vec<Replac
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "musl-clang".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "musl-clang".to_string() }, to: "cc".to_string() },
|
||||||
]),
|
]),
|
||||||
("CC".to_string(), vec![
|
("CC".to_string(), vec![
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
||||||
|
@ -41,7 +39,6 @@ pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock<BTreeMap<String, Vec<Replac
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "musl-clang".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "musl-clang".to_string() }, to: "cc".to_string() },
|
||||||
]),
|
]),
|
||||||
("CXX".to_string(), vec![
|
("CXX".to_string(), vec![
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-g++".to_string() }, to: "c++".to_string() },
|
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-g++".to_string() }, to: "c++".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-g++".to_string() }, to: "c++".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-g++".to_string() }, to: "c++".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-g++".to_string() }, to: "c++".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-g++".to_string() }, to: "c++".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-g++".to_string() }, to: "c++".to_string() },
|
||||||
|
@ -53,7 +50,6 @@ pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock<BTreeMap<String, Vec<Replac
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "clang++".to_string() }, to: "c++".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "clang++".to_string() }, to: "c++".to_string() },
|
||||||
]),
|
]),
|
||||||
("LDCXXSHARED".to_string(), vec![
|
("LDCXXSHARED".to_string(), vec![
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-g++".to_string() }, to: "c++".to_string() },
|
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-g++".to_string() }, to: "c++".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-g++".to_string() }, to: "c++".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-g++".to_string() }, to: "c++".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-g++".to_string() }, to: "c++".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-g++".to_string() }, to: "c++".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-g++".to_string() }, to: "c++".to_string() },
|
||||||
|
@ -65,7 +61,6 @@ pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock<BTreeMap<String, Vec<Replac
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "clang++".to_string() }, to: "c++".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "clang++".to_string() }, to: "c++".to_string() },
|
||||||
]),
|
]),
|
||||||
("LDSHARED".to_string(), vec![
|
("LDSHARED".to_string(), vec![
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
||||||
|
@ -78,7 +73,6 @@ pub(crate) static DEFAULT_VARIABLE_UPDATES: LazyLock<BTreeMap<String, Vec<Replac
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "musl-clang".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "musl-clang".to_string() }, to: "cc".to_string() },
|
||||||
]),
|
]),
|
||||||
("LINKCC".to_string(), vec![
|
("LINKCC".to_string(), vec![
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/aarch64-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabi-gcc".to_string() }, to: "cc".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/arm-linux-gnueabihf-gcc".to_string() }, to: "cc".to_string() },
|
||||||
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
ReplacementEntry { mode: ReplacementMode::Partial { from: "/usr/bin/mips-linux-gnu-gcc".to_string() }, to: "cc".to_string() },
|
||||||
|
|
|
@ -349,7 +349,7 @@ mod tests {
|
||||||
|
|
||||||
// Cross-compiles use GNU
|
// Cross-compiles use GNU
|
||||||
let sysconfigdata = [
|
let sysconfigdata = [
|
||||||
("CC", "/usr/bin/aarch64-linux-gnu-gcc"),
|
("CC", "/usr/bin/riscv64-linux-gnu-gcc"),
|
||||||
("CXX", "/usr/bin/x86_64-linux-gnu-g++"),
|
("CXX", "/usr/bin/x86_64-linux-gnu-g++"),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/uv-requirements-txt/src/lib.rs
|
source: crates/uv-requirements-txt/src/lib.rs
|
||||||
expression: actual
|
expression: actual
|
||||||
|
snapshot_kind: text
|
||||||
---
|
---
|
||||||
RequirementsTxt {
|
RequirementsTxt {
|
||||||
requirements: [
|
requirements: [
|
||||||
|
@ -23,7 +24,7 @@ RequirementsTxt {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
marker: python_full_version >= '3.8' and python_full_version < '4.0',
|
marker: python_full_version >= '3.8' and python_full_version < '4',
|
||||||
origin: Some(
|
origin: Some(
|
||||||
File(
|
File(
|
||||||
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
||||||
|
@ -54,7 +55,7 @@ RequirementsTxt {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
marker: python_full_version >= '3.8' and python_full_version < '4.0',
|
marker: python_full_version >= '3.8' and python_full_version < '4',
|
||||||
origin: Some(
|
origin: Some(
|
||||||
File(
|
File(
|
||||||
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
||||||
|
@ -85,7 +86,7 @@ RequirementsTxt {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
marker: python_full_version >= '3.8' and python_full_version < '4.0' and sys_platform == 'win32',
|
marker: python_full_version >= '3.8' and python_full_version < '4' and sys_platform == 'win32',
|
||||||
origin: Some(
|
origin: Some(
|
||||||
File(
|
File(
|
||||||
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
||||||
|
@ -116,7 +117,7 @@ RequirementsTxt {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
marker: python_full_version >= '3.8' and python_full_version < '4.0',
|
marker: python_full_version >= '3.8' and python_full_version < '4',
|
||||||
origin: Some(
|
origin: Some(
|
||||||
File(
|
File(
|
||||||
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
||||||
|
@ -148,7 +149,7 @@ RequirementsTxt {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
marker: python_full_version >= '3.8' and python_full_version < '4.0',
|
marker: python_full_version >= '3.8' and python_full_version < '4',
|
||||||
origin: Some(
|
origin: Some(
|
||||||
File(
|
File(
|
||||||
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/uv-requirements-txt/src/lib.rs
|
source: crates/uv-requirements-txt/src/lib.rs
|
||||||
expression: actual
|
expression: actual
|
||||||
|
snapshot_kind: text
|
||||||
---
|
---
|
||||||
RequirementsTxt {
|
RequirementsTxt {
|
||||||
requirements: [
|
requirements: [
|
||||||
|
@ -23,7 +24,7 @@ RequirementsTxt {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
marker: python_full_version >= '3.8' and python_full_version < '4.0',
|
marker: python_full_version >= '3.8' and python_full_version < '4',
|
||||||
origin: Some(
|
origin: Some(
|
||||||
File(
|
File(
|
||||||
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
||||||
|
@ -54,7 +55,7 @@ RequirementsTxt {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
marker: python_full_version >= '3.8' and python_full_version < '4.0',
|
marker: python_full_version >= '3.8' and python_full_version < '4',
|
||||||
origin: Some(
|
origin: Some(
|
||||||
File(
|
File(
|
||||||
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
||||||
|
@ -85,7 +86,7 @@ RequirementsTxt {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
marker: python_full_version >= '3.8' and python_full_version < '4.0' and sys_platform == 'win32',
|
marker: python_full_version >= '3.8' and python_full_version < '4' and sys_platform == 'win32',
|
||||||
origin: Some(
|
origin: Some(
|
||||||
File(
|
File(
|
||||||
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
||||||
|
@ -116,7 +117,7 @@ RequirementsTxt {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
marker: python_full_version >= '3.8' and python_full_version < '4.0',
|
marker: python_full_version >= '3.8' and python_full_version < '4',
|
||||||
origin: Some(
|
origin: Some(
|
||||||
File(
|
File(
|
||||||
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
||||||
|
@ -148,7 +149,7 @@ RequirementsTxt {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
marker: python_full_version >= '3.8' and python_full_version < '4.0',
|
marker: python_full_version >= '3.8' and python_full_version < '4',
|
||||||
origin: Some(
|
origin: Some(
|
||||||
File(
|
File(
|
||||||
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
"<REQUIREMENTS_DIR>/poetry-with-hashes.txt",
|
||||||
|
|
|
@ -13,10 +13,9 @@ use uv_normalize::PackageName;
|
||||||
use uv_pep440::Version;
|
use uv_pep440::Version;
|
||||||
use uv_types::InstalledPackagesProvider;
|
use uv_types::InstalledPackagesProvider;
|
||||||
|
|
||||||
use crate::preferences::{Entry, Preferences};
|
use crate::preferences::{Entry, PreferenceSource, Preferences};
|
||||||
use crate::prerelease::{AllowPrerelease, PrereleaseStrategy};
|
use crate::prerelease::{AllowPrerelease, PrereleaseStrategy};
|
||||||
use crate::resolution_mode::ResolutionStrategy;
|
use crate::resolution_mode::ResolutionStrategy;
|
||||||
use crate::universal_marker::UniversalMarker;
|
|
||||||
use crate::version_map::{VersionMap, VersionMapDistHandle};
|
use crate::version_map::{VersionMap, VersionMapDistHandle};
|
||||||
use crate::{Exclusions, Manifest, Options, ResolverEnvironment};
|
use crate::{Exclusions, Manifest, Options, ResolverEnvironment};
|
||||||
|
|
||||||
|
@ -188,7 +187,7 @@ impl CandidateSelector {
|
||||||
if index.is_some_and(|index| !entry.index().matches(index)) {
|
if index.is_some_and(|index| !entry.index().matches(index)) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Either::Left(std::iter::once((entry.marker(), entry.pin().version())))
|
Either::Left(std::iter::once((entry.pin().version(), entry.source())))
|
||||||
}
|
}
|
||||||
[..] => {
|
[..] => {
|
||||||
type Entries<'a> = SmallVec<[&'a Entry; 3]>;
|
type Entries<'a> = SmallVec<[&'a Entry; 3]>;
|
||||||
|
@ -219,7 +218,7 @@ impl CandidateSelector {
|
||||||
Either::Right(
|
Either::Right(
|
||||||
preferences
|
preferences
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|entry| (entry.marker(), entry.pin().version())),
|
.map(|entry| (entry.pin().version(), entry.source())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -238,7 +237,7 @@ impl CandidateSelector {
|
||||||
/// Return the first preference that satisfies the current range and is allowed.
|
/// Return the first preference that satisfies the current range and is allowed.
|
||||||
fn get_preferred_from_iter<'a, InstalledPackages: InstalledPackagesProvider>(
|
fn get_preferred_from_iter<'a, InstalledPackages: InstalledPackagesProvider>(
|
||||||
&'a self,
|
&'a self,
|
||||||
preferences: impl Iterator<Item = (&'a UniversalMarker, &'a Version)>,
|
preferences: impl Iterator<Item = (&'a Version, PreferenceSource)>,
|
||||||
package_name: &'a PackageName,
|
package_name: &'a PackageName,
|
||||||
range: &Range<Version>,
|
range: &Range<Version>,
|
||||||
version_maps: &'a [VersionMap],
|
version_maps: &'a [VersionMap],
|
||||||
|
@ -246,7 +245,7 @@ impl CandidateSelector {
|
||||||
reinstall: bool,
|
reinstall: bool,
|
||||||
env: &ResolverEnvironment,
|
env: &ResolverEnvironment,
|
||||||
) -> Option<Candidate<'a>> {
|
) -> Option<Candidate<'a>> {
|
||||||
for (marker, version) in preferences {
|
for (version, source) in preferences {
|
||||||
// Respect the version range for this requirement.
|
// Respect the version range for this requirement.
|
||||||
if !range.contains(version) {
|
if !range.contains(version) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -290,9 +289,14 @@ impl CandidateSelector {
|
||||||
let allow = match self.prerelease_strategy.allows(package_name, env) {
|
let allow = match self.prerelease_strategy.allows(package_name, env) {
|
||||||
AllowPrerelease::Yes => true,
|
AllowPrerelease::Yes => true,
|
||||||
AllowPrerelease::No => false,
|
AllowPrerelease::No => false,
|
||||||
// If the pre-release is "global" (i.e., provided via a lockfile, rather than
|
// If the pre-release was provided via an existing file, rather than from the
|
||||||
// a fork), accept it unless pre-releases are completely banned.
|
// current solve, accept it unless pre-releases are completely banned.
|
||||||
AllowPrerelease::IfNecessary => marker.is_true(),
|
AllowPrerelease::IfNecessary => match source {
|
||||||
|
PreferenceSource::Resolver => false,
|
||||||
|
PreferenceSource::Lock
|
||||||
|
| PreferenceSource::Environment
|
||||||
|
| PreferenceSource::RequirementsTxt => true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if !allow {
|
if !allow {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::fmt::Formatter;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
use itertools::Itertools;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use pubgrub::{
|
use pubgrub::{
|
||||||
DefaultStringReporter, DerivationTree, Derived, External, Range, Ranges, Reporter, Term,
|
DefaultStringReporter, DerivationTree, Derived, External, Range, Ranges, Reporter, Term,
|
||||||
|
@ -17,6 +18,8 @@ use uv_normalize::{ExtraName, InvalidNameError, PackageName};
|
||||||
use uv_pep440::{LocalVersionSlice, LowerBound, Version, VersionSpecifier};
|
use uv_pep440::{LocalVersionSlice, LowerBound, Version, VersionSpecifier};
|
||||||
use uv_pep508::{MarkerEnvironment, MarkerExpression, MarkerTree, MarkerValueVersion};
|
use uv_pep508::{MarkerEnvironment, MarkerExpression, MarkerTree, MarkerValueVersion};
|
||||||
use uv_platform_tags::Tags;
|
use uv_platform_tags::Tags;
|
||||||
|
use uv_pypi_types::ParsedUrl;
|
||||||
|
use uv_redacted::DisplaySafeUrl;
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
|
|
||||||
use crate::candidate_selector::CandidateSelector;
|
use crate::candidate_selector::CandidateSelector;
|
||||||
|
@ -56,11 +59,14 @@ pub enum ResolveError {
|
||||||
} else {
|
} else {
|
||||||
format!(" in {env}")
|
format!(" in {env}")
|
||||||
},
|
},
|
||||||
urls.join("\n- "),
|
urls.iter()
|
||||||
|
.map(|url| format!("{}{}", DisplaySafeUrl::from(url.clone()), if url.is_editable() { " (editable)" } else { "" }))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n- ")
|
||||||
)]
|
)]
|
||||||
ConflictingUrls {
|
ConflictingUrls {
|
||||||
package_name: PackageName,
|
package_name: PackageName,
|
||||||
urls: Vec<String>,
|
urls: Vec<ParsedUrl>,
|
||||||
env: ResolverEnvironment,
|
env: ResolverEnvironment,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -71,11 +77,14 @@ pub enum ResolveError {
|
||||||
} else {
|
} else {
|
||||||
format!(" in {env}")
|
format!(" in {env}")
|
||||||
},
|
},
|
||||||
indexes.join("\n- "),
|
indexes.iter()
|
||||||
|
.map(std::string::ToString::to_string)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n- ")
|
||||||
)]
|
)]
|
||||||
ConflictingIndexesForEnvironment {
|
ConflictingIndexesForEnvironment {
|
||||||
package_name: PackageName,
|
package_name: PackageName,
|
||||||
indexes: Vec<String>,
|
indexes: Vec<IndexUrl>,
|
||||||
env: ResolverEnvironment,
|
env: ResolverEnvironment,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -148,7 +157,7 @@ impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
|
pub type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
|
||||||
|
|
||||||
/// A wrapper around [`pubgrub::error::NoSolutionError`] that displays a resolution failure report.
|
/// A wrapper around [`pubgrub::error::NoSolutionError`] that displays a resolution failure report.
|
||||||
pub struct NoSolutionError {
|
pub struct NoSolutionError {
|
||||||
|
@ -359,6 +368,11 @@ impl NoSolutionError {
|
||||||
NoSolutionHeader::new(self.env.clone())
|
NoSolutionHeader::new(self.env.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the conflict derivation tree for external analysis
|
||||||
|
pub fn derivation_tree(&self) -> &ErrorTree {
|
||||||
|
&self.error
|
||||||
|
}
|
||||||
|
|
||||||
/// Hint at limiting the resolver environment if universal resolution failed for a target
|
/// Hint at limiting the resolver environment if universal resolution failed for a target
|
||||||
/// that is not the current platform or not the current Python version.
|
/// that is not the current platform or not the current Python version.
|
||||||
fn hint_disjoint_targets(&self, f: &mut Formatter) -> std::fmt::Result {
|
fn hint_disjoint_targets(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||||
|
@ -396,6 +410,15 @@ impl NoSolutionError {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the packages that are involved in this error.
|
||||||
|
pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
|
||||||
|
self.error
|
||||||
|
.packages()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|p| p.name())
|
||||||
|
.unique()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for NoSolutionError {
|
impl std::fmt::Debug for NoSolutionError {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std::{borrow::Cow, str::FromStr};
|
#[cfg(feature = "schemars")]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use jiff::{Timestamp, ToSpan, tz::TimeZone};
|
use jiff::{Timestamp, ToSpan, tz::TimeZone};
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ impl ForkIndexes {
|
||||||
) -> Result<(), ResolveError> {
|
) -> Result<(), ResolveError> {
|
||||||
if let Some(previous) = self.0.insert(package_name.clone(), index.clone()) {
|
if let Some(previous) = self.0.insert(package_name.clone(), index.clone()) {
|
||||||
if &previous != index {
|
if &previous != index {
|
||||||
let mut conflicts = vec![previous.url.to_string(), index.url.to_string()];
|
let mut conflicts = vec![previous.url, index.url.clone()];
|
||||||
conflicts.sort();
|
conflicts.sort();
|
||||||
return Err(ResolveError::ConflictingIndexesForEnvironment {
|
return Err(ResolveError::ConflictingIndexesForEnvironment {
|
||||||
package_name: package_name.clone(),
|
package_name: package_name.clone(),
|
||||||
|
|
|
@ -2,7 +2,6 @@ use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use uv_distribution_types::Verbatim;
|
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pypi_types::VerbatimParsedUrl;
|
use uv_pypi_types::VerbatimParsedUrl;
|
||||||
|
|
||||||
|
@ -34,10 +33,8 @@ impl ForkUrls {
|
||||||
match self.0.entry(package_name.clone()) {
|
match self.0.entry(package_name.clone()) {
|
||||||
Entry::Occupied(previous) => {
|
Entry::Occupied(previous) => {
|
||||||
if previous.get() != url {
|
if previous.get() != url {
|
||||||
let mut conflicting_url = vec![
|
let mut conflicting_url =
|
||||||
previous.get().verbatim.verbatim().to_string(),
|
vec![previous.get().parsed_url.clone(), url.parsed_url.clone()];
|
||||||
url.verbatim.verbatim().to_string(),
|
|
||||||
];
|
|
||||||
conflicting_url.sort();
|
conflicting_url.sort();
|
||||||
return Err(ResolveError::ConflictingUrls {
|
return Err(ResolveError::ConflictingUrls {
|
||||||
package_name: package_name.clone(),
|
package_name: package_name.clone(),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
pub use dependency_mode::DependencyMode;
|
pub use dependency_mode::DependencyMode;
|
||||||
pub use error::{NoSolutionError, NoSolutionHeader, ResolveError, SentinelRange};
|
pub use error::{ErrorTree, NoSolutionError, NoSolutionHeader, ResolveError, SentinelRange};
|
||||||
pub use exclude_newer::ExcludeNewer;
|
pub use exclude_newer::ExcludeNewer;
|
||||||
pub use exclusions::Exclusions;
|
pub use exclusions::Exclusions;
|
||||||
pub use flat_index::{FlatDistributions, FlatIndex};
|
pub use flat_index::{FlatDistributions, FlatIndex};
|
||||||
|
@ -54,7 +54,7 @@ mod options;
|
||||||
mod pins;
|
mod pins;
|
||||||
mod preferences;
|
mod preferences;
|
||||||
mod prerelease;
|
mod prerelease;
|
||||||
mod pubgrub;
|
pub mod pubgrub;
|
||||||
mod python_requirement;
|
mod python_requirement;
|
||||||
mod redirect;
|
mod redirect;
|
||||||
mod resolution;
|
mod resolution;
|
||||||
|
|
|
@ -34,6 +34,8 @@ pub struct Preference {
|
||||||
/// is part of, otherwise `None`.
|
/// is part of, otherwise `None`.
|
||||||
fork_markers: Vec<UniversalMarker>,
|
fork_markers: Vec<UniversalMarker>,
|
||||||
hashes: HashDigests,
|
hashes: HashDigests,
|
||||||
|
/// The source of the preference.
|
||||||
|
source: PreferenceSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Preference {
|
impl Preference {
|
||||||
|
@ -73,6 +75,7 @@ impl Preference {
|
||||||
.map(String::as_str)
|
.map(String::as_str)
|
||||||
.map(HashDigest::from_str)
|
.map(HashDigest::from_str)
|
||||||
.collect::<Result<_, _>>()?,
|
.collect::<Result<_, _>>()?,
|
||||||
|
source: PreferenceSource::RequirementsTxt,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,6 +94,7 @@ impl Preference {
|
||||||
index: PreferenceIndex::from(package.index(install_path)?),
|
index: PreferenceIndex::from(package.index(install_path)?),
|
||||||
fork_markers: package.fork_markers().to_vec(),
|
fork_markers: package.fork_markers().to_vec(),
|
||||||
hashes: HashDigests::empty(),
|
hashes: HashDigests::empty(),
|
||||||
|
source: PreferenceSource::Lock,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +116,7 @@ impl Preference {
|
||||||
// `pylock.toml` doesn't have fork annotations.
|
// `pylock.toml` doesn't have fork annotations.
|
||||||
fork_markers: vec![],
|
fork_markers: vec![],
|
||||||
hashes: HashDigests::empty(),
|
hashes: HashDigests::empty(),
|
||||||
|
source: PreferenceSource::Lock,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +132,7 @@ impl Preference {
|
||||||
index: PreferenceIndex::Any,
|
index: PreferenceIndex::Any,
|
||||||
fork_markers: vec![],
|
fork_markers: vec![],
|
||||||
hashes: HashDigests::empty(),
|
hashes: HashDigests::empty(),
|
||||||
|
source: PreferenceSource::Environment,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,11 +177,24 @@ impl From<Option<IndexUrl>> for PreferenceIndex {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(crate) enum PreferenceSource {
|
||||||
|
/// The preference is from an installed package in the environment.
|
||||||
|
Environment,
|
||||||
|
/// The preference is from a `uv.ock` file.
|
||||||
|
Lock,
|
||||||
|
/// The preference is from a `requirements.txt` file.
|
||||||
|
RequirementsTxt,
|
||||||
|
/// The preference is from the current solve.
|
||||||
|
Resolver,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct Entry {
|
pub(crate) struct Entry {
|
||||||
marker: UniversalMarker,
|
marker: UniversalMarker,
|
||||||
index: PreferenceIndex,
|
index: PreferenceIndex,
|
||||||
pin: Pin,
|
pin: Pin,
|
||||||
|
source: PreferenceSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
|
@ -193,6 +212,11 @@ impl Entry {
|
||||||
pub(crate) fn pin(&self) -> &Pin {
|
pub(crate) fn pin(&self) -> &Pin {
|
||||||
&self.pin
|
&self.pin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the source of the entry.
|
||||||
|
pub(crate) fn source(&self) -> PreferenceSource {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A set of pinned packages that should be preserved during resolution, if possible.
|
/// A set of pinned packages that should be preserved during resolution, if possible.
|
||||||
|
@ -245,6 +269,7 @@ impl Preferences {
|
||||||
version: preference.version,
|
version: preference.version,
|
||||||
hashes: preference.hashes,
|
hashes: preference.hashes,
|
||||||
},
|
},
|
||||||
|
source: preference.source,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
for fork_marker in preference.fork_markers {
|
for fork_marker in preference.fork_markers {
|
||||||
|
@ -255,6 +280,7 @@ impl Preferences {
|
||||||
version: preference.version.clone(),
|
version: preference.version.clone(),
|
||||||
hashes: preference.hashes.clone(),
|
hashes: preference.hashes.clone(),
|
||||||
},
|
},
|
||||||
|
source: preference.source,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,11 +296,13 @@ impl Preferences {
|
||||||
index: Option<IndexUrl>,
|
index: Option<IndexUrl>,
|
||||||
markers: UniversalMarker,
|
markers: UniversalMarker,
|
||||||
pin: impl Into<Pin>,
|
pin: impl Into<Pin>,
|
||||||
|
source: PreferenceSource,
|
||||||
) {
|
) {
|
||||||
self.0.entry(package_name).or_default().push(Entry {
|
self.0.entry(package_name).or_default().push(Entry {
|
||||||
marker: markers,
|
marker: markers,
|
||||||
index: PreferenceIndex::from(index),
|
index: PreferenceIndex::from(index),
|
||||||
pin: pin.into(),
|
pin: pin.into(),
|
||||||
|
source,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pub(crate) use crate::pubgrub::dependencies::PubGrubDependency;
|
pub(crate) use crate::pubgrub::dependencies::PubGrubDependency;
|
||||||
pub(crate) use crate::pubgrub::distribution::PubGrubDistribution;
|
pub(crate) use crate::pubgrub::distribution::PubGrubDistribution;
|
||||||
pub(crate) use crate::pubgrub::package::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
|
pub use crate::pubgrub::package::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
|
||||||
pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority, PubGrubTiebreaker};
|
pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority, PubGrubTiebreaker};
|
||||||
pub(crate) use crate::pubgrub::report::PubGrubReportFormatter;
|
pub(crate) use crate::pubgrub::report::PubGrubReportFormatter;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::python_requirement::PythonRequirement;
|
||||||
|
|
||||||
/// [`Arc`] wrapper around [`PubGrubPackageInner`] to make cloning (inside PubGrub) cheap.
|
/// [`Arc`] wrapper around [`PubGrubPackageInner`] to make cloning (inside PubGrub) cheap.
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
pub(crate) struct PubGrubPackage(Arc<PubGrubPackageInner>);
|
pub struct PubGrubPackage(Arc<PubGrubPackageInner>);
|
||||||
|
|
||||||
impl Deref for PubGrubPackage {
|
impl Deref for PubGrubPackage {
|
||||||
type Target = PubGrubPackageInner;
|
type Target = PubGrubPackageInner;
|
||||||
|
@ -39,7 +39,7 @@ impl From<PubGrubPackageInner> for PubGrubPackage {
|
||||||
/// package (e.g., `black[colorama]`), and mark it as a dependency of the real package (e.g.,
|
/// package (e.g., `black[colorama]`), and mark it as a dependency of the real package (e.g.,
|
||||||
/// `black`). We then discard the virtual packages at the end of the resolution process.
|
/// `black`). We then discard the virtual packages at the end of the resolution process.
|
||||||
#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
pub(crate) enum PubGrubPackageInner {
|
pub enum PubGrubPackageInner {
|
||||||
/// The root package, which is used to start the resolution process.
|
/// The root package, which is used to start the resolution process.
|
||||||
Root(Option<PackageName>),
|
Root(Option<PackageName>),
|
||||||
/// A Python version.
|
/// A Python version.
|
||||||
|
@ -295,7 +295,7 @@ impl PubGrubPackage {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)]
|
||||||
pub(crate) enum PubGrubPython {
|
pub enum PubGrubPython {
|
||||||
/// The Python version installed in the current environment.
|
/// The Python version installed in the current environment.
|
||||||
Installed,
|
Installed,
|
||||||
/// The Python version for which dependencies are being resolved.
|
/// The Python version for which dependencies are being resolved.
|
||||||
|
|
|
@ -7,7 +7,7 @@ use uv_platform_tags::{AbiTag, Tags};
|
||||||
|
|
||||||
/// The reason why a package or a version cannot be used.
|
/// The reason why a package or a version cannot be used.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub(crate) enum UnavailableReason {
|
pub enum UnavailableReason {
|
||||||
/// The entire package cannot be used.
|
/// The entire package cannot be used.
|
||||||
Package(UnavailablePackage),
|
Package(UnavailablePackage),
|
||||||
/// A single version cannot be used.
|
/// A single version cannot be used.
|
||||||
|
@ -29,7 +29,7 @@ impl Display for UnavailableReason {
|
||||||
/// Most variant are from [`MetadataResponse`] without the error source, since we don't format
|
/// Most variant are from [`MetadataResponse`] without the error source, since we don't format
|
||||||
/// the source and we want to merge unavailable messages across versions.
|
/// the source and we want to merge unavailable messages across versions.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub(crate) enum UnavailableVersion {
|
pub enum UnavailableVersion {
|
||||||
/// Version is incompatible because it has no usable distributions
|
/// Version is incompatible because it has no usable distributions
|
||||||
IncompatibleDist(IncompatibleDist),
|
IncompatibleDist(IncompatibleDist),
|
||||||
/// The wheel metadata was found, but could not be parsed.
|
/// The wheel metadata was found, but could not be parsed.
|
||||||
|
@ -123,7 +123,7 @@ impl From<&MetadataUnavailable> for UnavailableVersion {
|
||||||
|
|
||||||
/// The package is unavailable and cannot be used.
|
/// The package is unavailable and cannot be used.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub(crate) enum UnavailablePackage {
|
pub enum UnavailablePackage {
|
||||||
/// Index lookups were disabled (i.e., `--no-index`) and the package was not found in a flat index (i.e. from `--find-links`).
|
/// Index lookups were disabled (i.e., `--no-index`) and the package was not found in a flat index (i.e. from `--find-links`).
|
||||||
NoIndex,
|
NoIndex,
|
||||||
/// Network requests were disabled (i.e., `--offline`), and the package was not found in the cache.
|
/// Network requests were disabled (i.e., `--offline`), and the package was not found in the cache.
|
||||||
|
|
|
@ -47,7 +47,7 @@ use crate::fork_strategy::ForkStrategy;
|
||||||
use crate::fork_urls::ForkUrls;
|
use crate::fork_urls::ForkUrls;
|
||||||
use crate::manifest::Manifest;
|
use crate::manifest::Manifest;
|
||||||
use crate::pins::FilePins;
|
use crate::pins::FilePins;
|
||||||
use crate::preferences::Preferences;
|
use crate::preferences::{PreferenceSource, Preferences};
|
||||||
use crate::pubgrub::{
|
use crate::pubgrub::{
|
||||||
PubGrubDependency, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
|
PubGrubDependency, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
|
||||||
PubGrubPython,
|
PubGrubPython,
|
||||||
|
@ -447,6 +447,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
.try_universal_markers()
|
.try_universal_markers()
|
||||||
.unwrap_or(UniversalMarker::TRUE),
|
.unwrap_or(UniversalMarker::TRUE),
|
||||||
version.clone(),
|
version.clone(),
|
||||||
|
PreferenceSource::Resolver,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ use same_file::is_same_file;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use uv_cache_key::CanonicalUrl;
|
use uv_cache_key::CanonicalUrl;
|
||||||
use uv_distribution_types::Verbatim;
|
|
||||||
use uv_git::GitResolver;
|
use uv_git::GitResolver;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pep508::{MarkerTree, VerbatimUrl};
|
use uv_pep508::{MarkerTree, VerbatimUrl};
|
||||||
|
@ -170,8 +169,8 @@ impl Urls {
|
||||||
let [allowed_url] = matching_urls.as_slice() else {
|
let [allowed_url] = matching_urls.as_slice() else {
|
||||||
let mut conflicting_urls: Vec<_> = matching_urls
|
let mut conflicting_urls: Vec<_> = matching_urls
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|parsed_url| parsed_url.verbatim.verbatim().to_string())
|
.map(|parsed_url| parsed_url.parsed_url.clone())
|
||||||
.chain(std::iter::once(verbatim_url.verbatim().to_string()))
|
.chain(std::iter::once(parsed_url.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
conflicting_urls.sort();
|
conflicting_urls.sort();
|
||||||
return Err(ResolveError::ConflictingUrls {
|
return Err(ResolveError::ConflictingUrls {
|
||||||
|
|
|
@ -31,6 +31,7 @@ uv-redacted = { workspace = true }
|
||||||
uv-workspace = { workspace = true }
|
uv-workspace = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
dashmap = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use dashmap::DashMap;
|
||||||
|
|
||||||
|
use uv_configuration::{BuildKind, SourceStrategy};
|
||||||
use uv_pep508::PackageName;
|
use uv_pep508::PackageName;
|
||||||
use uv_python::PythonEnvironment;
|
use uv_python::PythonEnvironment;
|
||||||
|
|
||||||
|
@ -37,3 +43,42 @@ impl BuildIsolation<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A key for the build cache, which includes the interpreter, source root, subdirectory, source
|
||||||
|
/// strategy, and build kind.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct BuildKey {
|
||||||
|
pub base_python: Box<Path>,
|
||||||
|
pub source_root: Box<Path>,
|
||||||
|
pub subdirectory: Option<Box<Path>>,
|
||||||
|
pub source_strategy: SourceStrategy,
|
||||||
|
pub build_kind: BuildKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An arena of in-process builds.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BuildArena<T>(Arc<DashMap<BuildKey, T>>);
|
||||||
|
|
||||||
|
impl<T> Default for BuildArena<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Arc::new(DashMap::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for BuildArena<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> BuildArena<T> {
|
||||||
|
/// Insert a build entry into the arena.
|
||||||
|
pub fn insert(&self, key: BuildKey, value: T) {
|
||||||
|
self.0.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a build entry from the arena.
|
||||||
|
pub fn remove(&self, key: &BuildKey) -> Option<T> {
|
||||||
|
self.0.remove(key).map(|entry| entry.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ use uv_pep508::PackageName;
|
||||||
use uv_python::{Interpreter, PythonEnvironment};
|
use uv_python::{Interpreter, PythonEnvironment};
|
||||||
use uv_workspace::WorkspaceCache;
|
use uv_workspace::WorkspaceCache;
|
||||||
|
|
||||||
|
use crate::BuildArena;
|
||||||
|
|
||||||
/// Avoids cyclic crate dependencies between resolver, installer and builder.
|
/// Avoids cyclic crate dependencies between resolver, installer and builder.
|
||||||
///
|
///
|
||||||
/// To resolve the dependencies of a packages, we may need to build one or more source
|
/// To resolve the dependencies of a packages, we may need to build one or more source
|
||||||
|
@ -67,6 +69,9 @@ pub trait BuildContext {
|
||||||
/// Return a reference to the Git resolver.
|
/// Return a reference to the Git resolver.
|
||||||
fn git(&self) -> &GitResolver;
|
fn git(&self) -> &GitResolver;
|
||||||
|
|
||||||
|
/// Return a reference to the build arena.
|
||||||
|
fn build_arena(&self) -> &BuildArena<Self::SourceDistBuilder>;
|
||||||
|
|
||||||
/// Return a reference to the discovered registry capabilities.
|
/// Return a reference to the discovered registry capabilities.
|
||||||
fn capabilities(&self) -> &IndexCapabilities;
|
fn capabilities(&self) -> &IndexCapabilities;
|
||||||
|
|
||||||
|
@ -180,13 +185,13 @@ pub trait InstalledPackagesProvider: Clone + Send + Sync + 'static {
|
||||||
pub struct EmptyInstalledPackages;
|
pub struct EmptyInstalledPackages;
|
||||||
|
|
||||||
impl InstalledPackagesProvider for EmptyInstalledPackages {
|
impl InstalledPackagesProvider for EmptyInstalledPackages {
|
||||||
fn get_packages(&self, _name: &PackageName) -> Vec<&InstalledDist> {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn iter(&self) -> impl Iterator<Item = &InstalledDist> {
|
fn iter(&self) -> impl Iterator<Item = &InstalledDist> {
|
||||||
std::iter::empty()
|
std::iter::empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_packages(&self, _name: &PackageName) -> Vec<&InstalledDist> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`anyhow::Error`]-like wrapper type for [`BuildDispatch`] method return values, that also makes
|
/// [`anyhow::Error`]-like wrapper type for [`BuildDispatch`] method return values, that also makes
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "uv-version"
|
name = "uv-version"
|
||||||
version = "0.7.17"
|
version = "0.7.19"
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
rust-version = { workspace = true }
|
rust-version = { workspace = true }
|
||||||
homepage = { workspace = true }
|
homepage = { workspace = true }
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//!
|
//!
|
||||||
//! Then lowers them into a dependency specification.
|
//! Then lowers them into a dependency specification.
|
||||||
|
|
||||||
|
#[cfg(feature = "schemars")]
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
|
@ -24,6 +25,7 @@ use uv_fs::{PortablePathBuf, relative_to};
|
||||||
use uv_git_types::GitReference;
|
use uv_git_types::GitReference;
|
||||||
use uv_macros::OptionsMetadata;
|
use uv_macros::OptionsMetadata;
|
||||||
use uv_normalize::{DefaultGroups, ExtraName, GroupName, PackageName};
|
use uv_normalize::{DefaultGroups, ExtraName, GroupName, PackageName};
|
||||||
|
use uv_options_metadata::{OptionSet, OptionsMetadata, Visit};
|
||||||
use uv_pep440::{Version, VersionSpecifiers};
|
use uv_pep440::{Version, VersionSpecifiers};
|
||||||
use uv_pep508::MarkerTree;
|
use uv_pep508::MarkerTree;
|
||||||
use uv_pypi_types::{
|
use uv_pypi_types::{
|
||||||
|
@ -610,7 +612,7 @@ pub struct ToolUv {
|
||||||
/// Note that those settings only apply when using the `uv_build` backend, other build backends
|
/// Note that those settings only apply when using the `uv_build` backend, other build backends
|
||||||
/// (such as hatchling) have their own configuration.
|
/// (such as hatchling) have their own configuration.
|
||||||
#[option_group]
|
#[option_group]
|
||||||
pub build_backend: Option<BuildBackendSettings>,
|
pub build_backend: Option<BuildBackendSettingsSchema>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||||
|
@ -1684,3 +1686,44 @@ pub enum DependencyType {
|
||||||
/// A dependency in `dependency-groups.{0}`.
|
/// A dependency in `dependency-groups.{0}`.
|
||||||
Group(GroupName),
|
Group(GroupName),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(test, derive(Serialize))]
|
||||||
|
pub struct BuildBackendSettingsSchema;
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for BuildBackendSettingsSchema {
|
||||||
|
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Ok(BuildBackendSettingsSchema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "schemars")]
|
||||||
|
impl schemars::JsonSchema for BuildBackendSettingsSchema {
|
||||||
|
fn schema_name() -> Cow<'static, str> {
|
||||||
|
BuildBackendSettings::schema_name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||||
|
BuildBackendSettings::json_schema(generator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OptionsMetadata for BuildBackendSettingsSchema {
|
||||||
|
fn record(visit: &mut dyn Visit) {
|
||||||
|
BuildBackendSettings::record(visit);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn documentation() -> Option<&'static str> {
|
||||||
|
BuildBackendSettings::documentation()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metadata() -> OptionSet
|
||||||
|
where
|
||||||
|
Self: Sized + 'static,
|
||||||
|
{
|
||||||
|
BuildBackendSettings::metadata()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "uv"
|
name = "uv"
|
||||||
version = "0.7.17"
|
version = "0.7.19"
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
rust-version = { workspace = true }
|
rust-version = { workspace = true }
|
||||||
homepage = { workspace = true }
|
homepage = { workspace = true }
|
||||||
|
|
|
@ -3,7 +3,6 @@ use std::fmt::Write as _;
|
||||||
use std::io::Write as _;
|
use std::io::Write as _;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::{fmt, io};
|
use std::{fmt, io};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
@ -188,15 +187,6 @@ async fn build_impl(
|
||||||
printer: Printer,
|
printer: Printer,
|
||||||
preview: PreviewMode,
|
preview: PreviewMode,
|
||||||
) -> Result<BuildResult> {
|
) -> Result<BuildResult> {
|
||||||
if list && preview.is_disabled() {
|
|
||||||
// We need the direct build for list and that is preview only.
|
|
||||||
writeln!(
|
|
||||||
printer.stderr(),
|
|
||||||
"The `--list` option is only available in preview mode; add the `--preview` flag to use `--list`"
|
|
||||||
)?;
|
|
||||||
return Ok(BuildResult::Failure);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the resolver settings.
|
// Extract the resolver settings.
|
||||||
let ResolverSettings {
|
let ResolverSettings {
|
||||||
index_locations,
|
index_locations,
|
||||||
|
@ -504,16 +494,7 @@ async fn build_package(
|
||||||
.await?
|
.await?
|
||||||
.into_interpreter();
|
.into_interpreter();
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read build constraints.
|
// Read build constraints.
|
||||||
let build_constraints =
|
let build_constraints =
|
||||||
|
@ -615,10 +596,7 @@ async fn build_package(
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildAction::List
|
BuildAction::List
|
||||||
} else if preview.is_enabled()
|
} else if !force_pep517 && check_direct_build(source.path(), source.path().user_display()) {
|
||||||
&& !force_pep517
|
|
||||||
&& check_direct_build(source.path(), source.path().user_display())
|
|
||||||
{
|
|
||||||
BuildAction::DirectBuild
|
BuildAction::DirectBuild
|
||||||
} else {
|
} else {
|
||||||
BuildAction::Pep517
|
BuildAction::Pep517
|
||||||
|
|
|
@ -3,7 +3,6 @@ use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -338,13 +337,12 @@ pub(crate) async fn pip_compile(
|
||||||
|
|
||||||
// Determine the Python requirement, if the user requested a specific version.
|
// Determine the Python requirement, if the user requested a specific version.
|
||||||
let python_requirement = if universal {
|
let python_requirement = if universal {
|
||||||
let requires_python = RequiresPython::greater_than_equal_version(
|
let requires_python = if let Some(python_version) = python_version.as_ref() {
|
||||||
if let Some(python_version) = python_version.as_ref() {
|
RequiresPython::greater_than_equal_version(&python_version.version)
|
||||||
&python_version.version
|
} else {
|
||||||
} else {
|
let version = interpreter.python_minor_version();
|
||||||
interpreter.python_version()
|
RequiresPython::greater_than_equal_version(&version)
|
||||||
},
|
};
|
||||||
);
|
|
||||||
PythonRequirement::from_requires_python(&interpreter, requires_python)
|
PythonRequirement::from_requires_python(&interpreter, requires_python)
|
||||||
} else if let Some(python_version) = python_version.as_ref() {
|
} else if let Some(python_version) = python_version.as_ref() {
|
||||||
PythonRequirement::from_python_version(&interpreter, python_version)
|
PythonRequirement::from_python_version(&interpreter, python_version)
|
||||||
|
@ -388,16 +386,7 @@ pub(crate) async fn pip_compile(
|
||||||
no_index,
|
no_index,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the PyTorch backend.
|
// Determine the PyTorch backend.
|
||||||
let torch_backend = torch_backend
|
let torch_backend = torch_backend
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use tracing::{Level, debug, enabled};
|
use tracing::{Level, debug, enabled, warn};
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
||||||
|
@ -237,7 +236,13 @@ pub(crate) async fn pip_install(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _lock = environment.lock().await?;
|
let _lock = environment
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
// Determine the markers to use for the resolution.
|
// Determine the markers to use for the resolution.
|
||||||
let interpreter = environment.interpreter();
|
let interpreter = environment.interpreter();
|
||||||
|
@ -334,16 +339,7 @@ pub(crate) async fn pip_install(
|
||||||
no_index,
|
no_index,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the PyTorch backend.
|
// Determine the PyTorch backend.
|
||||||
let torch_backend = torch_backend
|
let torch_backend = torch_backend
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use tracing::debug;
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
||||||
|
@ -212,7 +211,13 @@ pub(crate) async fn pip_sync(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _lock = environment.lock().await?;
|
let _lock = environment
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
let interpreter = environment.interpreter();
|
let interpreter = environment.interpreter();
|
||||||
|
|
||||||
|
@ -267,16 +272,7 @@ pub(crate) async fn pip_sync(
|
||||||
no_index,
|
no_index,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the PyTorch backend.
|
// Determine the PyTorch backend.
|
||||||
let torch_backend = torch_backend
|
let torch_backend = torch_backend
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt::Write;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use itertools::{Either, Itertools};
|
use itertools::{Either, Itertools};
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use tracing::debug;
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::BaseClientBuilder;
|
use uv_client::BaseClientBuilder;
|
||||||
|
@ -100,7 +100,13 @@ pub(crate) async fn pip_uninstall(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _lock = environment.lock().await?;
|
let _lock = environment
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
// Index the current `site-packages` directory.
|
// Index the current `site-packages` directory.
|
||||||
let site_packages = uv_installer::SitePackages::from_environment(&environment)?;
|
let site_packages = uv_installer::SitePackages::from_environment(&environment)?;
|
||||||
|
|
|
@ -10,7 +10,7 @@ use anyhow::{Context, Result, bail};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||||
use tracing::debug;
|
use tracing::{debug, warn};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
|
@ -319,7 +319,13 @@ pub(crate) async fn add(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let _lock = target.acquire_lock().await?;
|
let _lock = target
|
||||||
|
.acquire_lock()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
let client_builder = BaseClientBuilder::new()
|
let client_builder = BaseClientBuilder::new()
|
||||||
.connectivity(network_settings.connectivity)
|
.connectivity(network_settings.connectivity)
|
||||||
|
@ -374,16 +380,7 @@ pub(crate) async fn add(
|
||||||
let hasher = HashStrategy::default();
|
let hasher = HashStrategy::default();
|
||||||
let sources = SourceStrategy::Enabled;
|
let sources = SourceStrategy::Enabled;
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
settings.resolver.index_locations.cache_index_credentials();
|
||||||
for index in settings.resolver.index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::try_from(client_builder)?
|
let client = RegistryClientBuilder::try_from(client_builder)?
|
||||||
|
|
|
@ -44,13 +44,15 @@ impl CachedEnvironment {
|
||||||
printer: Printer,
|
printer: Printer,
|
||||||
preview: PreviewMode,
|
preview: PreviewMode,
|
||||||
) -> Result<Self, ProjectError> {
|
) -> Result<Self, ProjectError> {
|
||||||
let interpreter = Self::base_interpreter(interpreter, cache)?;
|
// Resolve the "base" interpreter, which resolves to an underlying parent interpreter if the
|
||||||
|
// given interpreter is a virtual environment.
|
||||||
|
let base_interpreter = Self::base_interpreter(interpreter, cache)?;
|
||||||
|
|
||||||
// Resolve the requirements with the interpreter.
|
// Resolve the requirements with the interpreter.
|
||||||
let resolution = Resolution::from(
|
let resolution = Resolution::from(
|
||||||
resolve_environment(
|
resolve_environment(
|
||||||
spec,
|
spec,
|
||||||
&interpreter,
|
&base_interpreter,
|
||||||
build_constraints.clone(),
|
build_constraints.clone(),
|
||||||
&settings.resolver,
|
&settings.resolver,
|
||||||
network_settings,
|
network_settings,
|
||||||
|
@ -73,13 +75,34 @@ impl CachedEnvironment {
|
||||||
hash_digest(&distributions)
|
hash_digest(&distributions)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hash the interpreter based on its path.
|
// Construct a hash for the environment.
|
||||||
// TODO(charlie): Come up with a robust hash for the interpreter.
|
//
|
||||||
let interpreter_hash =
|
// Use the canonicalized base interpreter path since that's the interpreter we performed the
|
||||||
cache_digest(&canonicalize_executable(interpreter.sys_executable())?);
|
// resolution with and the interpreter the environment will be created with.
|
||||||
|
//
|
||||||
|
// We also include the canonicalized `sys.prefix` of the non-base interpreter, that is, the
|
||||||
|
// virtual environment's path. Originally, we shared cached environments independent of the
|
||||||
|
// environment they'd be layered on top of. However, this causes collisions as the overlay
|
||||||
|
// `.pth` file can be overridden by another instance of uv. Including this element in the key
|
||||||
|
// avoids this problem at the cost of creating separate cached environments for identical
|
||||||
|
// `--with` invocations across projects. We use `sys.prefix` rather than `sys.executable` so
|
||||||
|
// we can canonicalize it without invalidating the purpose of the element — it'd probably be
|
||||||
|
// safe to just use the absolute `sys.executable` as well.
|
||||||
|
//
|
||||||
|
// TODO(zanieb): Since we're not sharing these environmments across projects, we should move
|
||||||
|
// [`CachedEvnvironment::set_overlay`] etc. here since the values there should be constant
|
||||||
|
// now.
|
||||||
|
//
|
||||||
|
// TODO(zanieb): We should include the version of the base interpreter in the hash, so if
|
||||||
|
// the interpreter at the canonicalized path changes versions we construct a new
|
||||||
|
// environment.
|
||||||
|
let environment_hash = cache_digest(&(
|
||||||
|
&canonicalize_executable(base_interpreter.sys_executable())?,
|
||||||
|
&interpreter.sys_prefix().canonicalize()?,
|
||||||
|
));
|
||||||
|
|
||||||
// Search in the content-addressed cache.
|
// Search in the content-addressed cache.
|
||||||
let cache_entry = cache.entry(CacheBucket::Environments, interpreter_hash, resolution_hash);
|
let cache_entry = cache.entry(CacheBucket::Environments, environment_hash, resolution_hash);
|
||||||
|
|
||||||
if cache.refresh().is_none() {
|
if cache.refresh().is_none() {
|
||||||
if let Ok(root) = cache.resolve_link(cache_entry.path()) {
|
if let Ok(root) = cache.resolve_link(cache_entry.path()) {
|
||||||
|
@ -93,7 +116,7 @@ impl CachedEnvironment {
|
||||||
let temp_dir = cache.venv_dir()?;
|
let temp_dir = cache.venv_dir()?;
|
||||||
let venv = uv_virtualenv::create_venv(
|
let venv = uv_virtualenv::create_venv(
|
||||||
temp_dir.path(),
|
temp_dir.path(),
|
||||||
interpreter,
|
base_interpreter,
|
||||||
uv_virtualenv::Prompt::None,
|
uv_virtualenv::Prompt::None,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -593,16 +593,7 @@ async fn do_lock(
|
||||||
.keyring(*keyring_provider)
|
.keyring(*keyring_provider)
|
||||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for index in target.indexes() {
|
for index in target.indexes() {
|
||||||
if let Some(credentials) = index.credentials() {
|
if let Some(credentials) = index.credentials() {
|
||||||
|
@ -942,7 +933,7 @@ impl ValidatedLock {
|
||||||
lock.prerelease_mode().cyan(),
|
lock.prerelease_mode().cyan(),
|
||||||
options.prerelease_mode.cyan()
|
options.prerelease_mode.cyan()
|
||||||
);
|
);
|
||||||
return Ok(Self::Unusable(lock));
|
return Ok(Self::Preferable(lock));
|
||||||
}
|
}
|
||||||
if lock.fork_strategy() != options.fork_strategy {
|
if lock.fork_strategy() != options.fork_strategy {
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
|
|
|
@ -25,7 +25,7 @@ use uv_fs::{CWD, LockedFile, Simplified};
|
||||||
use uv_git::ResolvedRepositoryReference;
|
use uv_git::ResolvedRepositoryReference;
|
||||||
use uv_installer::{SatisfiesResult, SitePackages};
|
use uv_installer::{SatisfiesResult, SitePackages};
|
||||||
use uv_normalize::{DEV_DEPENDENCIES, DefaultGroups, ExtraName, GroupName, PackageName};
|
use uv_normalize::{DEV_DEPENDENCIES, DefaultGroups, ExtraName, GroupName, PackageName};
|
||||||
use uv_pep440::{Version, VersionSpecifiers};
|
use uv_pep440::{TildeVersionSpecifier, Version, VersionSpecifiers};
|
||||||
use uv_pep508::MarkerTreeContents;
|
use uv_pep508::MarkerTreeContents;
|
||||||
use uv_pypi_types::{ConflictPackage, ConflictSet, Conflicts};
|
use uv_pypi_types::{ConflictPackage, ConflictSet, Conflicts};
|
||||||
use uv_python::{
|
use uv_python::{
|
||||||
|
@ -421,6 +421,30 @@ pub(crate) fn find_requires_python(
|
||||||
if requires_python.is_empty() {
|
if requires_python.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
for ((package, group), specifiers) in &requires_python {
|
||||||
|
if let [spec] = &specifiers[..] {
|
||||||
|
if let Some(spec) = TildeVersionSpecifier::from_specifier_ref(spec) {
|
||||||
|
if spec.has_patch() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (lower, upper) = spec.bounding_specifiers();
|
||||||
|
let spec_0 = spec.with_patch_version(0);
|
||||||
|
let (lower_0, upper_0) = spec_0.bounding_specifiers();
|
||||||
|
warn_user_once!(
|
||||||
|
"The `requires-python` specifier (`{spec}`) in `{package}{group}` \
|
||||||
|
uses the tilde specifier (`~=`) without a patch version. This will be \
|
||||||
|
interpreted as `{lower}, {upper}`. Did you mean `{spec_0}` to constrain the \
|
||||||
|
version as `{lower_0}, {upper_0}`? We recommend only using \
|
||||||
|
the tilde specifier with a patch version to avoid ambiguity.",
|
||||||
|
group = if let Some(group) = group {
|
||||||
|
format!(":{group}")
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
match RequiresPython::intersection(requires_python.iter().map(|(.., specifiers)| specifiers)) {
|
match RequiresPython::intersection(requires_python.iter().map(|(.., specifiers)| specifiers)) {
|
||||||
Some(requires_python) => Ok(Some(requires_python)),
|
Some(requires_python) => Ok(Some(requires_python)),
|
||||||
None => Err(ProjectError::DisjointRequiresPython(requires_python)),
|
None => Err(ProjectError::DisjointRequiresPython(requires_python)),
|
||||||
|
@ -1220,7 +1244,12 @@ impl ProjectEnvironment {
|
||||||
preview: PreviewMode,
|
preview: PreviewMode,
|
||||||
) -> Result<Self, ProjectError> {
|
) -> Result<Self, ProjectError> {
|
||||||
// Lock the project environment to avoid synchronization issues.
|
// Lock the project environment to avoid synchronization issues.
|
||||||
let _lock = ProjectInterpreter::lock(workspace).await?;
|
let _lock = ProjectInterpreter::lock(workspace)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire project environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
let upgradeable = preview.is_enabled()
|
let upgradeable = preview.is_enabled()
|
||||||
&& python
|
&& python
|
||||||
|
@ -1438,7 +1467,13 @@ impl ScriptEnvironment {
|
||||||
preview: PreviewMode,
|
preview: PreviewMode,
|
||||||
) -> Result<Self, ProjectError> {
|
) -> Result<Self, ProjectError> {
|
||||||
// Lock the script environment to avoid synchronization issues.
|
// Lock the script environment to avoid synchronization issues.
|
||||||
let _lock = ScriptInterpreter::lock(script).await?;
|
let _lock = ScriptInterpreter::lock(script)
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire script environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
let upgradeable = python_request
|
let upgradeable = python_request
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_none_or(|request| !request.includes_patch());
|
.is_none_or(|request| !request.includes_patch());
|
||||||
|
@ -1626,16 +1661,7 @@ pub(crate) async fn resolve_names(
|
||||||
.keyring(*keyring_provider)
|
.keyring(*keyring_provider)
|
||||||
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
.allow_insecure_host(network_settings.allow_insecure_host.clone());
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::try_from(client_builder)?
|
let client = RegistryClientBuilder::try_from(client_builder)?
|
||||||
|
@ -1704,7 +1730,7 @@ pub(crate) async fn resolve_names(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) enum PreferenceSource<'lock> {
|
pub(crate) enum PreferenceLocation<'lock> {
|
||||||
/// The preferences should be extracted from a lockfile.
|
/// The preferences should be extracted from a lockfile.
|
||||||
Lock {
|
Lock {
|
||||||
lock: &'lock Lock,
|
lock: &'lock Lock,
|
||||||
|
@ -1719,7 +1745,7 @@ pub(crate) struct EnvironmentSpecification<'lock> {
|
||||||
/// The requirements to include in the environment.
|
/// The requirements to include in the environment.
|
||||||
requirements: RequirementsSpecification,
|
requirements: RequirementsSpecification,
|
||||||
/// The preferences to respect when resolving.
|
/// The preferences to respect when resolving.
|
||||||
preferences: Option<PreferenceSource<'lock>>,
|
preferences: Option<PreferenceLocation<'lock>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RequirementsSpecification> for EnvironmentSpecification<'_> {
|
impl From<RequirementsSpecification> for EnvironmentSpecification<'_> {
|
||||||
|
@ -1732,9 +1758,9 @@ impl From<RequirementsSpecification> for EnvironmentSpecification<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lock> EnvironmentSpecification<'lock> {
|
impl<'lock> EnvironmentSpecification<'lock> {
|
||||||
/// Set the [`PreferenceSource`] for the specification.
|
/// Set the [`PreferenceLocation`] for the specification.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn with_preferences(self, preferences: PreferenceSource<'lock>) -> Self {
|
pub(crate) fn with_preferences(self, preferences: PreferenceLocation<'lock>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
preferences: Some(preferences),
|
preferences: Some(preferences),
|
||||||
..self
|
..self
|
||||||
|
@ -1797,16 +1823,7 @@ pub(crate) async fn resolve_environment(
|
||||||
let marker_env = interpreter.resolver_marker_environment();
|
let marker_env = interpreter.resolver_marker_environment();
|
||||||
let python_requirement = PythonRequirement::from_interpreter(interpreter);
|
let python_requirement = PythonRequirement::from_interpreter(interpreter);
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::try_from(client_builder)?
|
let client = RegistryClientBuilder::try_from(client_builder)?
|
||||||
|
@ -1852,7 +1869,7 @@ pub(crate) async fn resolve_environment(
|
||||||
|
|
||||||
// If an existing lockfile exists, build up a set of preferences.
|
// If an existing lockfile exists, build up a set of preferences.
|
||||||
let preferences = match spec.preferences {
|
let preferences = match spec.preferences {
|
||||||
Some(PreferenceSource::Lock { lock, install_path }) => {
|
Some(PreferenceLocation::Lock { lock, install_path }) => {
|
||||||
let LockedRequirements { preferences, git } =
|
let LockedRequirements { preferences, git } =
|
||||||
read_lock_requirements(lock, install_path, &upgrade)?;
|
read_lock_requirements(lock, install_path, &upgrade)?;
|
||||||
|
|
||||||
|
@ -1864,7 +1881,7 @@ pub(crate) async fn resolve_environment(
|
||||||
|
|
||||||
preferences
|
preferences
|
||||||
}
|
}
|
||||||
Some(PreferenceSource::Entries(entries)) => entries,
|
Some(PreferenceLocation::Entries(entries)) => entries,
|
||||||
None => vec![],
|
None => vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1978,16 +1995,7 @@ pub(crate) async fn sync_environment(
|
||||||
let interpreter = venv.interpreter();
|
let interpreter = venv.interpreter();
|
||||||
let tags = venv.interpreter().tags()?;
|
let tags = venv.interpreter().tags()?;
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::try_from(client_builder)?
|
let client = RegistryClientBuilder::try_from(client_builder)?
|
||||||
|
@ -2193,16 +2201,7 @@ pub(crate) async fn update_environment(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::try_from(client_builder)?
|
let client = RegistryClientBuilder::try_from(client_builder)?
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use tracing::debug;
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_configuration::{
|
use uv_configuration::{
|
||||||
|
@ -281,7 +281,13 @@ pub(crate) async fn remove(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let _lock = target.acquire_lock().await?;
|
let _lock = target
|
||||||
|
.acquire_lock()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
// Determine the lock mode.
|
// Determine the lock mode.
|
||||||
let mode = if locked {
|
let mode = if locked {
|
||||||
|
|
|
@ -49,7 +49,7 @@ use crate::commands::project::install_target::InstallTarget;
|
||||||
use crate::commands::project::lock::LockMode;
|
use crate::commands::project::lock::LockMode;
|
||||||
use crate::commands::project::lock_target::LockTarget;
|
use crate::commands::project::lock_target::LockTarget;
|
||||||
use crate::commands::project::{
|
use crate::commands::project::{
|
||||||
EnvironmentSpecification, PreferenceSource, ProjectEnvironment, ProjectError,
|
EnvironmentSpecification, PreferenceLocation, ProjectEnvironment, ProjectError,
|
||||||
ScriptEnvironment, ScriptInterpreter, UniversalState, WorkspacePython,
|
ScriptEnvironment, ScriptInterpreter, UniversalState, WorkspacePython,
|
||||||
default_dependency_groups, script_specification, update_environment,
|
default_dependency_groups, script_specification, update_environment,
|
||||||
validate_project_requires_python,
|
validate_project_requires_python,
|
||||||
|
@ -240,7 +240,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
||||||
.await?
|
.await?
|
||||||
.into_environment()?;
|
.into_environment()?;
|
||||||
|
|
||||||
let _lock = environment.lock().await?;
|
let _lock = environment
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
// Determine the lock mode.
|
// Determine the lock mode.
|
||||||
let mode = if frozen {
|
let mode = if frozen {
|
||||||
|
@ -386,7 +392,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let _lock = environment.lock().await?;
|
let _lock = environment
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
match update_environment(
|
match update_environment(
|
||||||
environment,
|
environment,
|
||||||
|
@ -699,7 +711,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
||||||
.map(|lock| (lock, project.workspace().install_path().to_owned()));
|
.map(|lock| (lock, project.workspace().install_path().to_owned()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let _lock = venv.lock().await?;
|
let _lock = venv
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
// Determine the lock mode.
|
// Determine the lock mode.
|
||||||
let mode = if frozen {
|
let mode = if frozen {
|
||||||
|
@ -940,10 +958,10 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
||||||
let spec = EnvironmentSpecification::from(spec).with_preferences(
|
let spec = EnvironmentSpecification::from(spec).with_preferences(
|
||||||
if let Some((lock, install_path)) = base_lock.as_ref() {
|
if let Some((lock, install_path)) = base_lock.as_ref() {
|
||||||
// If we have a lockfile, use the locked versions as preferences.
|
// If we have a lockfile, use the locked versions as preferences.
|
||||||
PreferenceSource::Lock { lock, install_path }
|
PreferenceLocation::Lock { lock, install_path }
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, extract preferences from the base environment.
|
// Otherwise, extract preferences from the base environment.
|
||||||
PreferenceSource::Entries(
|
PreferenceLocation::Entries(
|
||||||
base_site_packages
|
base_site_packages
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(Preference::from_installed)
|
.filter_map(Preference::from_installed)
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::sync::Arc;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
||||||
|
@ -169,7 +170,13 @@ pub(crate) async fn sync(
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _lock = environment.lock().await?;
|
let _lock = environment
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.inspect_err(|err| {
|
||||||
|
warn!("Failed to acquire environment lock: {err}");
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
// Notify the user of any environment changes.
|
// Notify the user of any environment changes.
|
||||||
match &environment {
|
match &environment {
|
||||||
|
@ -682,16 +689,7 @@ pub(super) async fn do_sync(
|
||||||
// If necessary, convert editable to non-editable distributions.
|
// If necessary, convert editable to non-editable distributions.
|
||||||
let resolution = apply_editable_mode(resolution, editable);
|
let resolution = apply_editable_mode(resolution, editable);
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate credentials from the target.
|
// Populate credentials from the target.
|
||||||
store_credentials_from_target(target);
|
store_credentials_from_target(target);
|
||||||
|
|
|
@ -385,7 +385,7 @@ async fn lock_and_sync(
|
||||||
let default_groups = default_dependency_groups(project.pyproject_toml())?;
|
let default_groups = default_dependency_groups(project.pyproject_toml())?;
|
||||||
let default_extras = DefaultExtras::default();
|
let default_extras = DefaultExtras::default();
|
||||||
let groups = DependencyGroups::default().with_defaults(default_groups);
|
let groups = DependencyGroups::default().with_defaults(default_groups);
|
||||||
let extras = ExtrasSpecification::from_all_extras().with_defaults(default_extras);
|
let extras = ExtrasSpecification::default().with_defaults(default_extras);
|
||||||
let install_options = InstallOptions::default();
|
let install_options = InstallOptions::default();
|
||||||
|
|
||||||
// Convert to an `AddTarget` by attaching the appropriate interpreter or environment.
|
// Convert to an `AddTarget` by attaching the appropriate interpreter or environment.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -15,7 +16,9 @@ use tracing::{debug, trace};
|
||||||
|
|
||||||
use uv_configuration::PreviewMode;
|
use uv_configuration::PreviewMode;
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_python::downloads::{self, DownloadResult, ManagedPythonDownload, PythonDownloadRequest};
|
use uv_python::downloads::{
|
||||||
|
self, ArchRequest, DownloadResult, ManagedPythonDownload, PythonDownloadRequest,
|
||||||
|
};
|
||||||
use uv_python::managed::{
|
use uv_python::managed::{
|
||||||
ManagedPythonInstallation, ManagedPythonInstallations, PythonMinorVersionLink,
|
ManagedPythonInstallation, ManagedPythonInstallations, PythonMinorVersionLink,
|
||||||
create_link_to_executable, python_executable_dir,
|
create_link_to_executable, python_executable_dir,
|
||||||
|
@ -401,6 +404,7 @@ pub(crate) async fn install(
|
||||||
|
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
let mut downloaded = Vec::with_capacity(downloads.len());
|
let mut downloaded = Vec::with_capacity(downloads.len());
|
||||||
|
let mut requests_by_new_installation = BTreeMap::new();
|
||||||
while let Some((download, result)) = tasks.next().await {
|
while let Some((download, result)) = tasks.next().await {
|
||||||
match result {
|
match result {
|
||||||
Ok(download_result) => {
|
Ok(download_result) => {
|
||||||
|
@ -412,10 +416,19 @@ pub(crate) async fn install(
|
||||||
|
|
||||||
let installation = ManagedPythonInstallation::new(path, download);
|
let installation = ManagedPythonInstallation::new(path, download);
|
||||||
changelog.installed.insert(installation.key().clone());
|
changelog.installed.insert(installation.key().clone());
|
||||||
|
for request in &requests {
|
||||||
|
// Take note of which installations satisfied which requests
|
||||||
|
if request.matches_installation(&installation) {
|
||||||
|
requests_by_new_installation
|
||||||
|
.entry(installation.key().clone())
|
||||||
|
.or_insert(Vec::new())
|
||||||
|
.push(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
if changelog.existing.contains(installation.key()) {
|
if changelog.existing.contains(installation.key()) {
|
||||||
changelog.uninstalled.insert(installation.key().clone());
|
changelog.uninstalled.insert(installation.key().clone());
|
||||||
}
|
}
|
||||||
downloaded.push(installation);
|
downloaded.push(installation.clone());
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
errors.push((download.key().clone(), anyhow::Error::new(err)));
|
errors.push((download.key().clone(), anyhow::Error::new(err)));
|
||||||
|
@ -529,6 +542,42 @@ pub(crate) async fn install(
|
||||||
}
|
}
|
||||||
|
|
||||||
if !changelog.installed.is_empty() {
|
if !changelog.installed.is_empty() {
|
||||||
|
for install_key in &changelog.installed {
|
||||||
|
// Make a note if the selected python is non-native for the architecture,
|
||||||
|
// if none of the matching user requests were explicit
|
||||||
|
let native_arch = Arch::from_env();
|
||||||
|
if install_key.arch().family() != native_arch.family() {
|
||||||
|
let not_explicit =
|
||||||
|
requests_by_new_installation
|
||||||
|
.get(install_key)
|
||||||
|
.and_then(|requests| {
|
||||||
|
let all_non_explicit = requests.iter().all(|request| {
|
||||||
|
if let PythonRequest::Key(key) = &request.request {
|
||||||
|
!matches!(key.arch(), Some(ArchRequest::Explicit(_)))
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if all_non_explicit {
|
||||||
|
requests.iter().next()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(not_explicit) = not_explicit {
|
||||||
|
let native_request =
|
||||||
|
not_explicit.download_request.clone().with_arch(native_arch);
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"{} uv selected a Python distribution with an emulated architecture ({}) for your platform because support for the native architecture ({}) is not yet mature; to override this behaviour, request the native architecture explicitly with: {}",
|
||||||
|
"note:".bold(),
|
||||||
|
install_key.arch(),
|
||||||
|
native_arch,
|
||||||
|
native_request
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if changelog.installed.len() == 1 {
|
if changelog.installed.len() == 1 {
|
||||||
let installed = changelog.installed.iter().next().unwrap();
|
let installed = changelog.installed.iter().next().unwrap();
|
||||||
// Ex) "Installed Python 3.9.7 in 1.68s"
|
// Ex) "Installed Python 3.9.7 in 1.68s"
|
||||||
|
|
|
@ -218,7 +218,7 @@ pub(crate) fn finalize_tool_install(
|
||||||
if target_entry_points.is_empty() {
|
if target_entry_points.is_empty() {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stdout(),
|
printer.stdout(),
|
||||||
"No executables are provided by `{from}`",
|
"No executables are provided by package `{from}`; removing tool",
|
||||||
from = name.cyan()
|
from = name.cyan()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -354,7 +354,9 @@ fn hint_executable_from_dependency(
|
||||||
let command = format!("uv tool install {}", package.name());
|
let command = format!("uv tool install {}", package.name());
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stdout(),
|
printer.stdout(),
|
||||||
"However, an executable with the name `{}` is available via dependency `{}`.\nDid you mean `{}`?",
|
"{}{} An executable with the name `{}` is available via dependency `{}`.\n Did you mean `{}`?",
|
||||||
|
"hint".bold().cyan(),
|
||||||
|
":".bold(),
|
||||||
name.cyan(),
|
name.cyan(),
|
||||||
package.name().cyan(),
|
package.name().cyan(),
|
||||||
command.bold(),
|
command.bold(),
|
||||||
|
@ -363,7 +365,9 @@ fn hint_executable_from_dependency(
|
||||||
packages => {
|
packages => {
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stdout(),
|
printer.stdout(),
|
||||||
"However, an executable with the name `{}` is available via the following dependencies::",
|
"{}{} An executable with the name `{}` is available via the following dependencies::",
|
||||||
|
"hint".bold().cyan(),
|
||||||
|
":".bold(),
|
||||||
name.cyan(),
|
name.cyan(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -372,7 +376,7 @@ fn hint_executable_from_dependency(
|
||||||
}
|
}
|
||||||
writeln!(
|
writeln!(
|
||||||
printer.stdout(),
|
printer.stdout(),
|
||||||
"Did you mean to install one of them instead?"
|
" Did you mean to install one of them instead?"
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -242,16 +242,7 @@ async fn venv_impl(
|
||||||
python.into_interpreter()
|
python.into_interpreter()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
index_locations.cache_index_credentials();
|
||||||
for index in index_locations.allowed_indexes() {
|
|
||||||
if let Some(credentials) = index.credentials() {
|
|
||||||
let credentials = Arc::new(credentials);
|
|
||||||
uv_auth::store_credentials(index.raw_url(), credentials.clone());
|
|
||||||
if let Some(root_url) = index.root_url() {
|
|
||||||
uv_auth::store_credentials(&root_url, credentials.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the discovered Python version is incompatible with the current workspace
|
// Check if the discovered Python version is incompatible with the current workspace
|
||||||
if let Some(requires_python) = requires_python {
|
if let Some(requires_python) = requires_python {
|
||||||
|
|
|
@ -118,16 +118,20 @@ impl GlobalSettings {
|
||||||
},
|
},
|
||||||
show_settings: args.show_settings,
|
show_settings: args.show_settings,
|
||||||
preview: PreviewMode::from(
|
preview: PreviewMode::from(
|
||||||
flag(args.preview, args.no_preview)
|
flag(args.preview, args.no_preview, "preview")
|
||||||
.combine(workspace.and_then(|workspace| workspace.globals.preview))
|
.combine(workspace.and_then(|workspace| workspace.globals.preview))
|
||||||
.unwrap_or(false),
|
.unwrap_or(false),
|
||||||
),
|
),
|
||||||
python_preference,
|
python_preference,
|
||||||
python_downloads: flag(args.allow_python_downloads, args.no_python_downloads)
|
python_downloads: flag(
|
||||||
.map(PythonDownloads::from)
|
args.allow_python_downloads,
|
||||||
.combine(env(env::UV_PYTHON_DOWNLOADS))
|
args.no_python_downloads,
|
||||||
.combine(workspace.and_then(|workspace| workspace.globals.python_downloads))
|
"python-downloads",
|
||||||
.unwrap_or_default(),
|
)
|
||||||
|
.map(PythonDownloads::from)
|
||||||
|
.combine(env(env::UV_PYTHON_DOWNLOADS))
|
||||||
|
.combine(workspace.and_then(|workspace| workspace.globals.python_downloads))
|
||||||
|
.unwrap_or_default(),
|
||||||
// Disable the progress bar with `RUST_LOG` to avoid progress fragments interleaving
|
// Disable the progress bar with `RUST_LOG` to avoid progress fragments interleaving
|
||||||
// with log messages.
|
// with log messages.
|
||||||
no_progress: args.no_progress || std::env::var_os(EnvVars::RUST_LOG).is_some(),
|
no_progress: args.no_progress || std::env::var_os(EnvVars::RUST_LOG).is_some(),
|
||||||
|
@ -161,7 +165,7 @@ pub(crate) struct NetworkSettings {
|
||||||
|
|
||||||
impl NetworkSettings {
|
impl NetworkSettings {
|
||||||
pub(crate) fn resolve(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self {
|
pub(crate) fn resolve(args: &GlobalArgs, workspace: Option<&FilesystemOptions>) -> Self {
|
||||||
let connectivity = if flag(args.offline, args.no_offline)
|
let connectivity = if flag(args.offline, args.no_offline, "offline")
|
||||||
.combine(workspace.and_then(|workspace| workspace.globals.offline))
|
.combine(workspace.and_then(|workspace| workspace.globals.offline))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
|
@ -169,7 +173,7 @@ impl NetworkSettings {
|
||||||
} else {
|
} else {
|
||||||
Connectivity::Online
|
Connectivity::Online
|
||||||
};
|
};
|
||||||
let native_tls = flag(args.native_tls, args.no_native_tls)
|
let native_tls = flag(args.native_tls, args.no_native_tls, "native-tls")
|
||||||
.combine(workspace.and_then(|workspace| workspace.globals.native_tls))
|
.combine(workspace.and_then(|workspace| workspace.globals.native_tls))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
let allow_insecure_host = args
|
let allow_insecure_host = args
|
||||||
|
@ -274,8 +278,12 @@ impl InitSettings {
|
||||||
(_, _, _) => unreachable!("`app`, `lib`, and `script` are mutually exclusive"),
|
(_, _, _) => unreachable!("`app`, `lib`, and `script` are mutually exclusive"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let package = flag(package || build_backend.is_some(), no_package || r#virtual)
|
let package = flag(
|
||||||
.unwrap_or(kind.packaged_by_default());
|
package || build_backend.is_some(),
|
||||||
|
no_package || r#virtual,
|
||||||
|
"virtual",
|
||||||
|
)
|
||||||
|
.unwrap_or(kind.packaged_by_default());
|
||||||
|
|
||||||
let install_mirrors = filesystem
|
let install_mirrors = filesystem
|
||||||
.map(|fs| fs.install_mirrors.clone())
|
.map(|fs| fs.install_mirrors.clone())
|
||||||
|
@ -295,7 +303,7 @@ impl InitSettings {
|
||||||
build_backend,
|
build_backend,
|
||||||
no_readme: no_readme || bare,
|
no_readme: no_readme || bare,
|
||||||
author_from,
|
author_from,
|
||||||
pin_python: flag(pin_python, no_pin_python).unwrap_or(!bare),
|
pin_python: flag(pin_python, no_pin_python, "pin-python").unwrap_or(!bare),
|
||||||
no_workspace,
|
no_workspace,
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
install_mirrors,
|
install_mirrors,
|
||||||
|
@ -398,7 +406,7 @@ impl RunSettings {
|
||||||
false,
|
false,
|
||||||
// TODO(blueraft): support only_extra
|
// TODO(blueraft): support only_extra
|
||||||
vec![],
|
vec![],
|
||||||
flag(all_extras, no_all_extras).unwrap_or_default(),
|
flag(all_extras, no_all_extras, "all-extras").unwrap_or_default(),
|
||||||
),
|
),
|
||||||
groups: DependencyGroups::from_args(
|
groups: DependencyGroups::from_args(
|
||||||
dev,
|
dev,
|
||||||
|
@ -411,7 +419,7 @@ impl RunSettings {
|
||||||
all_groups,
|
all_groups,
|
||||||
),
|
),
|
||||||
editable: EditableMode::from_args(no_editable),
|
editable: EditableMode::from_args(no_editable),
|
||||||
modifications: if flag(exact, inexact).unwrap_or(false) {
|
modifications: if flag(exact, inexact, "inexact").unwrap_or(false) {
|
||||||
Modifications::Exact
|
Modifications::Exact
|
||||||
} else {
|
} else {
|
||||||
Modifications::Sufficient
|
Modifications::Sufficient
|
||||||
|
@ -434,7 +442,7 @@ impl RunSettings {
|
||||||
package,
|
package,
|
||||||
no_project,
|
no_project,
|
||||||
no_sync,
|
no_sync,
|
||||||
active: flag(active, no_active),
|
active: flag(active, no_active, "active"),
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
refresh: Refresh::from(refresh),
|
refresh: Refresh::from(refresh),
|
||||||
settings: ResolverInstallerSettings::combine(
|
settings: ResolverInstallerSettings::combine(
|
||||||
|
@ -1081,7 +1089,7 @@ impl PythonFindSettings {
|
||||||
request,
|
request,
|
||||||
show_version,
|
show_version,
|
||||||
no_project,
|
no_project,
|
||||||
system: flag(system, no_system).unwrap_or_default(),
|
system: flag(system, no_system, "system").unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1116,7 +1124,7 @@ impl PythonPinSettings {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
request,
|
request,
|
||||||
resolved: flag(resolved, no_resolved).unwrap_or(false),
|
resolved: flag(resolved, no_resolved, "resolved").unwrap_or(false),
|
||||||
no_project,
|
no_project,
|
||||||
global,
|
global,
|
||||||
rm,
|
rm,
|
||||||
|
@ -1195,7 +1203,7 @@ impl SyncSettings {
|
||||||
filesystem,
|
filesystem,
|
||||||
);
|
);
|
||||||
|
|
||||||
let check = flag(check, no_check).unwrap_or_default();
|
let check = flag(check, no_check, "check").unwrap_or_default();
|
||||||
let dry_run = if check {
|
let dry_run = if check {
|
||||||
DryRun::Check
|
DryRun::Check
|
||||||
} else {
|
} else {
|
||||||
|
@ -1207,7 +1215,7 @@ impl SyncSettings {
|
||||||
frozen,
|
frozen,
|
||||||
dry_run,
|
dry_run,
|
||||||
script,
|
script,
|
||||||
active: flag(active, no_active),
|
active: flag(active, no_active, "active"),
|
||||||
extras: ExtrasSpecification::from_args(
|
extras: ExtrasSpecification::from_args(
|
||||||
extra.unwrap_or_default(),
|
extra.unwrap_or_default(),
|
||||||
no_extra,
|
no_extra,
|
||||||
|
@ -1215,7 +1223,7 @@ impl SyncSettings {
|
||||||
false,
|
false,
|
||||||
// TODO(blueraft): support only_extra
|
// TODO(blueraft): support only_extra
|
||||||
vec![],
|
vec![],
|
||||||
flag(all_extras, no_all_extras).unwrap_or_default(),
|
flag(all_extras, no_all_extras, "all-extras").unwrap_or_default(),
|
||||||
),
|
),
|
||||||
groups: DependencyGroups::from_args(
|
groups: DependencyGroups::from_args(
|
||||||
dev,
|
dev,
|
||||||
|
@ -1233,7 +1241,7 @@ impl SyncSettings {
|
||||||
no_install_workspace,
|
no_install_workspace,
|
||||||
no_install_package,
|
no_install_package,
|
||||||
),
|
),
|
||||||
modifications: if flag(exact, inexact).unwrap_or(true) {
|
modifications: if flag(exact, inexact, "inexact").unwrap_or(true) {
|
||||||
Modifications::Exact
|
Modifications::Exact
|
||||||
} else {
|
} else {
|
||||||
Modifications::Sufficient
|
Modifications::Sufficient
|
||||||
|
@ -1437,7 +1445,7 @@ impl AddSettings {
|
||||||
Self {
|
Self {
|
||||||
locked,
|
locked,
|
||||||
frozen,
|
frozen,
|
||||||
active: flag(active, no_active),
|
active: flag(active, no_active, "active"),
|
||||||
no_sync,
|
no_sync,
|
||||||
packages,
|
packages,
|
||||||
requirements,
|
requirements,
|
||||||
|
@ -1455,7 +1463,7 @@ impl AddSettings {
|
||||||
package,
|
package,
|
||||||
script,
|
script,
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
editable: flag(editable, no_editable),
|
editable: flag(editable, no_editable, "editable"),
|
||||||
extras: extra.unwrap_or_default(),
|
extras: extra.unwrap_or_default(),
|
||||||
refresh: Refresh::from(refresh),
|
refresh: Refresh::from(refresh),
|
||||||
indexes,
|
indexes,
|
||||||
|
@ -1531,7 +1539,7 @@ impl RemoveSettings {
|
||||||
Self {
|
Self {
|
||||||
locked,
|
locked,
|
||||||
frozen,
|
frozen,
|
||||||
active: flag(active, no_active),
|
active: flag(active, no_active, "active"),
|
||||||
no_sync,
|
no_sync,
|
||||||
packages,
|
packages,
|
||||||
dependency_type,
|
dependency_type,
|
||||||
|
@ -1603,7 +1611,7 @@ impl VersionSettings {
|
||||||
dry_run,
|
dry_run,
|
||||||
locked,
|
locked,
|
||||||
frozen,
|
frozen,
|
||||||
active: flag(active, no_active),
|
active: flag(active, no_active, "active"),
|
||||||
no_sync,
|
no_sync,
|
||||||
package,
|
package,
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
|
@ -1779,7 +1787,7 @@ impl ExportSettings {
|
||||||
false,
|
false,
|
||||||
// TODO(blueraft): support only_extra
|
// TODO(blueraft): support only_extra
|
||||||
vec![],
|
vec![],
|
||||||
flag(all_extras, no_all_extras).unwrap_or_default(),
|
flag(all_extras, no_all_extras, "all-extras").unwrap_or_default(),
|
||||||
),
|
),
|
||||||
groups: DependencyGroups::from_args(
|
groups: DependencyGroups::from_args(
|
||||||
dev,
|
dev,
|
||||||
|
@ -1792,7 +1800,7 @@ impl ExportSettings {
|
||||||
all_groups,
|
all_groups,
|
||||||
),
|
),
|
||||||
editable: EditableMode::from_args(no_editable),
|
editable: EditableMode::from_args(no_editable),
|
||||||
hashes: flag(hashes, no_hashes).unwrap_or(true),
|
hashes: flag(hashes, no_hashes, "hashes").unwrap_or(true),
|
||||||
install_options: InstallOptions::new(
|
install_options: InstallOptions::new(
|
||||||
no_emit_project,
|
no_emit_project,
|
||||||
no_emit_workspace,
|
no_emit_workspace,
|
||||||
|
@ -1801,8 +1809,8 @@ impl ExportSettings {
|
||||||
output_file,
|
output_file,
|
||||||
locked,
|
locked,
|
||||||
frozen,
|
frozen,
|
||||||
include_annotations: flag(annotate, no_annotate).unwrap_or(true),
|
include_annotations: flag(annotate, no_annotate, "annotate").unwrap_or(true),
|
||||||
include_header: flag(header, no_header).unwrap_or(true),
|
include_header: flag(header, no_header, "header").unwrap_or(true),
|
||||||
script,
|
script,
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
refresh: Refresh::from(refresh),
|
refresh: Refresh::from(refresh),
|
||||||
|
@ -1955,30 +1963,42 @@ impl PipCompileSettings {
|
||||||
settings: PipSettings::combine(
|
settings: PipSettings::combine(
|
||||||
PipOptions {
|
PipOptions {
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
system: flag(system, no_system),
|
system: flag(system, no_system, "system"),
|
||||||
no_build: flag(no_build, build),
|
no_build: flag(no_build, build, "build"),
|
||||||
no_binary,
|
no_binary,
|
||||||
only_binary,
|
only_binary,
|
||||||
extra,
|
extra,
|
||||||
all_extras: flag(all_extras, no_all_extras),
|
all_extras: flag(all_extras, no_all_extras, "all-extras"),
|
||||||
no_deps: flag(no_deps, deps),
|
no_deps: flag(no_deps, deps, "deps"),
|
||||||
group: Some(group),
|
group: Some(group),
|
||||||
output_file,
|
output_file,
|
||||||
no_strip_extras: flag(no_strip_extras, strip_extras),
|
no_strip_extras: flag(no_strip_extras, strip_extras, "strip-extras"),
|
||||||
no_strip_markers: flag(no_strip_markers, strip_markers),
|
no_strip_markers: flag(no_strip_markers, strip_markers, "strip-markers"),
|
||||||
no_annotate: flag(no_annotate, annotate),
|
no_annotate: flag(no_annotate, annotate, "annotate"),
|
||||||
no_header: flag(no_header, header),
|
no_header: flag(no_header, header, "header"),
|
||||||
custom_compile_command,
|
custom_compile_command,
|
||||||
generate_hashes: flag(generate_hashes, no_generate_hashes),
|
generate_hashes: flag(generate_hashes, no_generate_hashes, "generate-hashes"),
|
||||||
python_version,
|
python_version,
|
||||||
python_platform,
|
python_platform,
|
||||||
universal: flag(universal, no_universal),
|
universal: flag(universal, no_universal, "universal"),
|
||||||
no_emit_package,
|
no_emit_package,
|
||||||
emit_index_url: flag(emit_index_url, no_emit_index_url),
|
emit_index_url: flag(emit_index_url, no_emit_index_url, "emit-index-url"),
|
||||||
emit_find_links: flag(emit_find_links, no_emit_find_links),
|
emit_find_links: flag(emit_find_links, no_emit_find_links, "emit-find-links"),
|
||||||
emit_build_options: flag(emit_build_options, no_emit_build_options),
|
emit_build_options: flag(
|
||||||
emit_marker_expression: flag(emit_marker_expression, no_emit_marker_expression),
|
emit_build_options,
|
||||||
emit_index_annotation: flag(emit_index_annotation, no_emit_index_annotation),
|
no_emit_build_options,
|
||||||
|
"emit-build-options",
|
||||||
|
),
|
||||||
|
emit_marker_expression: flag(
|
||||||
|
emit_marker_expression,
|
||||||
|
no_emit_marker_expression,
|
||||||
|
"emit-marker-expression",
|
||||||
|
),
|
||||||
|
emit_index_annotation: flag(
|
||||||
|
emit_index_annotation,
|
||||||
|
no_emit_index_annotation,
|
||||||
|
"emit-index-annotation",
|
||||||
|
),
|
||||||
annotation_style,
|
annotation_style,
|
||||||
torch_backend,
|
torch_backend,
|
||||||
..PipOptions::from(resolver)
|
..PipOptions::from(resolver)
|
||||||
|
@ -2050,22 +2070,27 @@ impl PipSyncSettings {
|
||||||
settings: PipSettings::combine(
|
settings: PipSettings::combine(
|
||||||
PipOptions {
|
PipOptions {
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
system: flag(system, no_system),
|
system: flag(system, no_system, "system"),
|
||||||
break_system_packages: flag(break_system_packages, no_break_system_packages),
|
break_system_packages: flag(
|
||||||
|
break_system_packages,
|
||||||
|
no_break_system_packages,
|
||||||
|
"break-system-packages",
|
||||||
|
),
|
||||||
target,
|
target,
|
||||||
prefix,
|
prefix,
|
||||||
require_hashes: flag(require_hashes, no_require_hashes),
|
require_hashes: flag(require_hashes, no_require_hashes, "require-hashes"),
|
||||||
verify_hashes: flag(verify_hashes, no_verify_hashes),
|
verify_hashes: flag(verify_hashes, no_verify_hashes, "verify-hashes"),
|
||||||
no_build: flag(no_build, build),
|
no_build: flag(no_build, build, "build"),
|
||||||
no_binary,
|
no_binary,
|
||||||
only_binary,
|
only_binary,
|
||||||
allow_empty_requirements: flag(
|
allow_empty_requirements: flag(
|
||||||
allow_empty_requirements,
|
allow_empty_requirements,
|
||||||
no_allow_empty_requirements,
|
no_allow_empty_requirements,
|
||||||
|
"allow-empty-requirements",
|
||||||
),
|
),
|
||||||
python_version,
|
python_version,
|
||||||
python_platform,
|
python_platform,
|
||||||
strict: flag(strict, no_strict),
|
strict: flag(strict, no_strict, "strict"),
|
||||||
torch_backend,
|
torch_backend,
|
||||||
..PipOptions::from(installer)
|
..PipOptions::from(installer)
|
||||||
},
|
},
|
||||||
|
@ -2199,7 +2224,7 @@ impl PipInstallSettings {
|
||||||
constraints_from_workspace,
|
constraints_from_workspace,
|
||||||
overrides_from_workspace,
|
overrides_from_workspace,
|
||||||
build_constraints_from_workspace,
|
build_constraints_from_workspace,
|
||||||
modifications: if flag(exact, inexact).unwrap_or(false) {
|
modifications: if flag(exact, inexact, "inexact").unwrap_or(false) {
|
||||||
Modifications::Exact
|
Modifications::Exact
|
||||||
} else {
|
} else {
|
||||||
Modifications::Sufficient
|
Modifications::Sufficient
|
||||||
|
@ -2208,22 +2233,26 @@ impl PipInstallSettings {
|
||||||
settings: PipSettings::combine(
|
settings: PipSettings::combine(
|
||||||
PipOptions {
|
PipOptions {
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
system: flag(system, no_system),
|
system: flag(system, no_system, "system"),
|
||||||
break_system_packages: flag(break_system_packages, no_break_system_packages),
|
break_system_packages: flag(
|
||||||
|
break_system_packages,
|
||||||
|
no_break_system_packages,
|
||||||
|
"break-system-packages",
|
||||||
|
),
|
||||||
target,
|
target,
|
||||||
prefix,
|
prefix,
|
||||||
no_build: flag(no_build, build),
|
no_build: flag(no_build, build, "build"),
|
||||||
no_binary,
|
no_binary,
|
||||||
only_binary,
|
only_binary,
|
||||||
strict: flag(strict, no_strict),
|
strict: flag(strict, no_strict, "strict"),
|
||||||
extra,
|
extra,
|
||||||
all_extras: flag(all_extras, no_all_extras),
|
all_extras: flag(all_extras, no_all_extras, "all-extras"),
|
||||||
group: Some(group),
|
group: Some(group),
|
||||||
no_deps: flag(no_deps, deps),
|
no_deps: flag(no_deps, deps, "deps"),
|
||||||
python_version,
|
python_version,
|
||||||
python_platform,
|
python_platform,
|
||||||
require_hashes: flag(require_hashes, no_require_hashes),
|
require_hashes: flag(require_hashes, no_require_hashes, "require-hashes"),
|
||||||
verify_hashes: flag(verify_hashes, no_verify_hashes),
|
verify_hashes: flag(verify_hashes, no_verify_hashes, "verify-hashes"),
|
||||||
torch_backend,
|
torch_backend,
|
||||||
..PipOptions::from(installer)
|
..PipOptions::from(installer)
|
||||||
},
|
},
|
||||||
|
@ -2267,8 +2296,12 @@ impl PipUninstallSettings {
|
||||||
settings: PipSettings::combine(
|
settings: PipSettings::combine(
|
||||||
PipOptions {
|
PipOptions {
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
system: flag(system, no_system),
|
system: flag(system, no_system, "system"),
|
||||||
break_system_packages: flag(break_system_packages, no_break_system_packages),
|
break_system_packages: flag(
|
||||||
|
break_system_packages,
|
||||||
|
no_break_system_packages,
|
||||||
|
"break-system-packages",
|
||||||
|
),
|
||||||
target,
|
target,
|
||||||
prefix,
|
prefix,
|
||||||
keyring_provider,
|
keyring_provider,
|
||||||
|
@ -2308,8 +2341,8 @@ impl PipFreezeSettings {
|
||||||
settings: PipSettings::combine(
|
settings: PipSettings::combine(
|
||||||
PipOptions {
|
PipOptions {
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
system: flag(system, no_system),
|
system: flag(system, no_system, "system"),
|
||||||
strict: flag(strict, no_strict),
|
strict: flag(strict, no_strict, "strict"),
|
||||||
..PipOptions::default()
|
..PipOptions::default()
|
||||||
},
|
},
|
||||||
filesystem,
|
filesystem,
|
||||||
|
@ -2348,15 +2381,15 @@ impl PipListSettings {
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
editable: flag(editable, exclude_editable),
|
editable: flag(editable, exclude_editable, "exclude-editable"),
|
||||||
exclude,
|
exclude,
|
||||||
format,
|
format,
|
||||||
outdated: flag(outdated, no_outdated).unwrap_or(false),
|
outdated: flag(outdated, no_outdated, "outdated").unwrap_or(false),
|
||||||
settings: PipSettings::combine(
|
settings: PipSettings::combine(
|
||||||
PipOptions {
|
PipOptions {
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
system: flag(system, no_system),
|
system: flag(system, no_system, "system"),
|
||||||
strict: flag(strict, no_strict),
|
strict: flag(strict, no_strict, "strict"),
|
||||||
..PipOptions::from(fetch)
|
..PipOptions::from(fetch)
|
||||||
},
|
},
|
||||||
filesystem,
|
filesystem,
|
||||||
|
@ -2393,8 +2426,8 @@ impl PipShowSettings {
|
||||||
settings: PipSettings::combine(
|
settings: PipSettings::combine(
|
||||||
PipOptions {
|
PipOptions {
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
system: flag(system, no_system),
|
system: flag(system, no_system, "system"),
|
||||||
strict: flag(strict, no_strict),
|
strict: flag(strict, no_strict, "strict"),
|
||||||
..PipOptions::default()
|
..PipOptions::default()
|
||||||
},
|
},
|
||||||
filesystem,
|
filesystem,
|
||||||
|
@ -2442,8 +2475,8 @@ impl PipTreeSettings {
|
||||||
settings: PipSettings::combine(
|
settings: PipSettings::combine(
|
||||||
PipOptions {
|
PipOptions {
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
system: flag(system, no_system),
|
system: flag(system, no_system, "system"),
|
||||||
strict: flag(strict, no_strict),
|
strict: flag(strict, no_strict, "strict"),
|
||||||
..PipOptions::from(fetch)
|
..PipOptions::from(fetch)
|
||||||
},
|
},
|
||||||
filesystem,
|
filesystem,
|
||||||
|
@ -2471,7 +2504,7 @@ impl PipCheckSettings {
|
||||||
settings: PipSettings::combine(
|
settings: PipSettings::combine(
|
||||||
PipOptions {
|
PipOptions {
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
system: flag(system, no_system),
|
system: flag(system, no_system, "system"),
|
||||||
..PipOptions::default()
|
..PipOptions::default()
|
||||||
},
|
},
|
||||||
filesystem,
|
filesystem,
|
||||||
|
@ -2538,15 +2571,15 @@ impl BuildSettings {
|
||||||
sdist,
|
sdist,
|
||||||
wheel,
|
wheel,
|
||||||
list,
|
list,
|
||||||
build_logs: flag(build_logs, no_build_logs).unwrap_or(true),
|
build_logs: flag(build_logs, no_build_logs, "build-logs").unwrap_or(true),
|
||||||
build_constraints: build_constraints
|
build_constraints: build_constraints
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(Maybe::into_option)
|
.filter_map(Maybe::into_option)
|
||||||
.collect(),
|
.collect(),
|
||||||
force_pep517,
|
force_pep517,
|
||||||
hash_checking: HashCheckingMode::from_args(
|
hash_checking: HashCheckingMode::from_args(
|
||||||
flag(require_hashes, no_require_hashes),
|
flag(require_hashes, no_require_hashes, "require-hashes"),
|
||||||
flag(verify_hashes, no_verify_hashes),
|
flag(verify_hashes, no_verify_hashes, "verify-hashes"),
|
||||||
),
|
),
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
refresh: Refresh::from(refresh),
|
refresh: Refresh::from(refresh),
|
||||||
|
@ -2605,7 +2638,7 @@ impl VenvSettings {
|
||||||
settings: PipSettings::combine(
|
settings: PipSettings::combine(
|
||||||
PipOptions {
|
PipOptions {
|
||||||
python: python.and_then(Maybe::into_option),
|
python: python.and_then(Maybe::into_option),
|
||||||
system: flag(system, no_system),
|
system: flag(system, no_system, "system"),
|
||||||
index_strategy,
|
index_strategy,
|
||||||
keyring_provider,
|
keyring_provider,
|
||||||
exclude_newer,
|
exclude_newer,
|
||||||
|
|
|
@ -15,7 +15,7 @@ fn build_basic() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")])
|
.chain([(r"\\\.", "")])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let project = context.temp_dir.child("project");
|
let project = context.temp_dir.child("project");
|
||||||
|
@ -133,7 +133,7 @@ fn build_sdist() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")])
|
.chain([(r"\\\.", "")])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let project = context.temp_dir.child("project");
|
let project = context.temp_dir.child("project");
|
||||||
|
@ -189,7 +189,7 @@ fn build_wheel() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")])
|
.chain([(r"\\\.", "")])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let project = context.temp_dir.child("project");
|
let project = context.temp_dir.child("project");
|
||||||
|
@ -245,7 +245,7 @@ fn build_sdist_wheel() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")])
|
.chain([(r"\\\.", "")])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let project = context.temp_dir.child("project");
|
let project = context.temp_dir.child("project");
|
||||||
|
@ -303,7 +303,7 @@ fn build_wheel_from_sdist() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")])
|
.chain([(r"\\\.", "")])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let project = context.temp_dir.child("project");
|
let project = context.temp_dir.child("project");
|
||||||
|
@ -412,7 +412,7 @@ fn build_fail() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")])
|
.chain([(r"\\\.", "")])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let project = context.temp_dir.child("project");
|
let project = context.temp_dir.child("project");
|
||||||
|
@ -488,7 +488,6 @@ fn build_workspace() -> Result<()> {
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"\\\.", ""),
|
(r"\\\.", ""),
|
||||||
(r"\[project\]", "[PKG]"),
|
(r"\[project\]", "[PKG]"),
|
||||||
(r"\[member\]", "[PKG]"),
|
(r"\[member\]", "[PKG]"),
|
||||||
|
@ -694,7 +693,6 @@ fn build_all_with_failure() -> Result<()> {
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"\\\.", ""),
|
(r"\\\.", ""),
|
||||||
(r"\[project\]", "[PKG]"),
|
(r"\[project\]", "[PKG]"),
|
||||||
(r"\[member-\w+\]", "[PKG]"),
|
(r"\[member-\w+\]", "[PKG]"),
|
||||||
|
@ -840,7 +838,7 @@ fn build_constraints() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")])
|
.chain([(r"\\\.", "")])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let project = context.temp_dir.child("project");
|
let project = context.temp_dir.child("project");
|
||||||
|
@ -901,7 +899,7 @@ fn build_sha() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")])
|
.chain([(r"\\\.", "")])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let project = context.temp_dir.child("project");
|
let project = context.temp_dir.child("project");
|
||||||
|
@ -1187,7 +1185,7 @@ fn build_tool_uv_sources() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")])
|
.chain([(r"\\\.", "")])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let build = context.temp_dir.child("backend");
|
let build = context.temp_dir.child("backend");
|
||||||
|
@ -1337,7 +1335,6 @@ fn build_non_package() -> Result<()> {
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"\\\.", ""),
|
(r"\\\.", ""),
|
||||||
(r"\[project\]", "[PKG]"),
|
(r"\[project\]", "[PKG]"),
|
||||||
(r"\[member\]", "[PKG]"),
|
(r"\[member\]", "[PKG]"),
|
||||||
|
@ -1930,7 +1927,7 @@ fn build_with_nonnormalized_name() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([(r"exit code: 1", "exit status: 1"), (r"\\\.", "")])
|
.chain([(r"\\\.", "")])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let project = context.temp_dir.child("project");
|
let project = context.temp_dir.child("project");
|
||||||
|
@ -1981,3 +1978,60 @@ fn build_with_nonnormalized_name() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that `--force-pep517` is respected.
|
||||||
|
///
|
||||||
|
/// The error messages for a broken project are different for direct builds vs. PEP 517.
|
||||||
|
#[test]
|
||||||
|
fn force_pep517() -> Result<()> {
|
||||||
|
// We need to use a real `uv_build` package.
|
||||||
|
let context = TestContext::new("3.12").with_exclude_newer("2025-05-27T00:00:00Z");
|
||||||
|
|
||||||
|
context
|
||||||
|
.init()
|
||||||
|
.arg("--build-backend")
|
||||||
|
.arg("uv")
|
||||||
|
.assert()
|
||||||
|
.success();
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
[tool.uv.build-backend]
|
||||||
|
module-name = "does_not_exist"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["uv_build>=0.5.15,<10000"]
|
||||||
|
build-backend = "uv_build"
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.build().env("RUST_BACKTRACE", "0"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Building source distribution (uv build backend)...
|
||||||
|
× Failed to build `[TEMP_DIR]/`
|
||||||
|
╰─▶ Expected a Python module at: `src/does_not_exist/__init__.py`
|
||||||
|
");
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.build().arg("--force-pep517").env("RUST_BACKTRACE", "0"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Building source distribution...
|
||||||
|
Error: Missing module directory for `does_not_exist` in `src`. Found: `temp`
|
||||||
|
× Failed to build `[TEMP_DIR]/`
|
||||||
|
├─▶ The build backend returned an error
|
||||||
|
╰─▶ Call to `uv_build.build_sdist` failed (exit status: 1)
|
||||||
|
hint: This usually indicates a problem with the package or the build environment.
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -224,7 +224,6 @@ fn preserve_executable_bit() -> Result<()> {
|
||||||
.init()
|
.init()
|
||||||
.arg("--build-backend")
|
.arg("--build-backend")
|
||||||
.arg("uv")
|
.arg("uv")
|
||||||
.arg("--preview")
|
|
||||||
.arg(&project_dir)
|
.arg(&project_dir)
|
||||||
.assert()
|
.assert()
|
||||||
.success();
|
.success();
|
||||||
|
@ -316,8 +315,7 @@ fn rename_module() -> Result<()> {
|
||||||
uv_snapshot!(context
|
uv_snapshot!(context
|
||||||
.build_backend()
|
.build_backend()
|
||||||
.arg("build-wheel")
|
.arg("build-wheel")
|
||||||
.arg(temp_dir.path())
|
.arg(temp_dir.path()), @r###"
|
||||||
.env("UV_PREVIEW", "1"), @r###"
|
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -391,8 +389,7 @@ fn rename_module_editable_build() -> Result<()> {
|
||||||
uv_snapshot!(context
|
uv_snapshot!(context
|
||||||
.build_backend()
|
.build_backend()
|
||||||
.arg("build-editable")
|
.arg("build-editable")
|
||||||
.arg(temp_dir.path())
|
.arg(temp_dir.path()), @r###"
|
||||||
.env("UV_PREVIEW", "1"), @r###"
|
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -568,8 +565,7 @@ fn build_sdist_with_long_path() -> Result<()> {
|
||||||
uv_snapshot!(context
|
uv_snapshot!(context
|
||||||
.build_backend()
|
.build_backend()
|
||||||
.arg("build-sdist")
|
.arg("build-sdist")
|
||||||
.arg(temp_dir.path())
|
.arg(temp_dir.path()), @r###"
|
||||||
.env("UV_PREVIEW", "1"), @r###"
|
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -602,8 +598,7 @@ fn sdist_error_without_module() -> Result<()> {
|
||||||
uv_snapshot!(context
|
uv_snapshot!(context
|
||||||
.build_backend()
|
.build_backend()
|
||||||
.arg("build-sdist")
|
.arg("build-sdist")
|
||||||
.arg(temp_dir.path())
|
.arg(temp_dir.path()), @r"
|
||||||
.env("UV_PREVIEW", "1"), @r"
|
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -617,8 +612,7 @@ fn sdist_error_without_module() -> Result<()> {
|
||||||
uv_snapshot!(context
|
uv_snapshot!(context
|
||||||
.build_backend()
|
.build_backend()
|
||||||
.arg("build-sdist")
|
.arg("build-sdist")
|
||||||
.arg(temp_dir.path())
|
.arg(temp_dir.path()), @r"
|
||||||
.env("UV_PREVIEW", "1"), @r"
|
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -682,7 +676,6 @@ fn complex_namespace_packages() -> Result<()> {
|
||||||
|
|
||||||
context
|
context
|
||||||
.build()
|
.build()
|
||||||
.arg("--preview")
|
|
||||||
.arg(project.path())
|
.arg(project.path())
|
||||||
.arg("--out-dir")
|
.arg("--out-dir")
|
||||||
.arg(dist.path())
|
.arg(dist.path())
|
||||||
|
@ -731,7 +724,6 @@ fn complex_namespace_packages() -> Result<()> {
|
||||||
context.filters(),
|
context.filters(),
|
||||||
context
|
context
|
||||||
.pip_install()
|
.pip_install()
|
||||||
.arg("--preview")
|
|
||||||
.arg("-e")
|
.arg("-e")
|
||||||
.arg("complex-project-part_a")
|
.arg("complex-project-part_a")
|
||||||
.arg("-e")
|
.arg("-e")
|
||||||
|
@ -778,7 +770,6 @@ fn symlinked_file() -> Result<()> {
|
||||||
let project = context.temp_dir.child("project");
|
let project = context.temp_dir.child("project");
|
||||||
context
|
context
|
||||||
.init()
|
.init()
|
||||||
.arg("--preview")
|
|
||||||
.arg("--build-backend")
|
.arg("--build-backend")
|
||||||
.arg("uv")
|
.arg("uv")
|
||||||
.arg(project.path())
|
.arg(project.path())
|
||||||
|
@ -858,3 +849,40 @@ fn symlinked_file() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ignore invalid build backend settings when not building.
|
||||||
|
///
|
||||||
|
/// They may be from another `uv_build` version that has a different schema.
|
||||||
|
#[test]
|
||||||
|
fn invalid_build_backend_settings_are_ignored() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "built-by-uv"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[tool.uv.build-backend]
|
||||||
|
# Error: `source-include` must be a list
|
||||||
|
source-include = "data/build-script.py"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["uv_build>=10000,<10001"]
|
||||||
|
build-backend = "uv_build"
|
||||||
|
"#})?;
|
||||||
|
|
||||||
|
// Since we are not building, this must pass without complaining about the error in
|
||||||
|
// `tool.uv.build-backend`.
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ pub const INSTA_FILTERS: &[(&str, &str)] = &[
|
||||||
(r"uv\.exe", "uv"),
|
(r"uv\.exe", "uv"),
|
||||||
// uv version display
|
// uv version display
|
||||||
(
|
(
|
||||||
r"uv(-.*)? \d+\.\d+\.\d+(\+\d+)?( \(.*\))?",
|
r"uv(-.*)? \d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?(\+\d+)?( \([^)]*\))?",
|
||||||
r"uv [VERSION] ([COMMIT] DATE)",
|
r"uv [VERSION] ([COMMIT] DATE)",
|
||||||
),
|
),
|
||||||
// Trim end-of-line whitespaces, to allow removing them on save.
|
// Trim end-of-line whitespaces, to allow removing them on save.
|
||||||
|
@ -254,7 +254,7 @@ impl TestContext {
|
||||||
let added_filters = [
|
let added_filters = [
|
||||||
(r"home = .+".to_string(), "home = [PYTHON_HOME]".to_string()),
|
(r"home = .+".to_string(), "home = [PYTHON_HOME]".to_string()),
|
||||||
(
|
(
|
||||||
r"uv = \d+\.\d+\.\d+".to_string(),
|
r"uv = \d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?(\+\d+)?".to_string(),
|
||||||
"uv = [UV_VERSION]".to_string(),
|
"uv = [UV_VERSION]".to_string(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -517,6 +517,8 @@ impl TestContext {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
filters.push((" --link-mode <LINK_MODE>".to_string(), String::new()));
|
filters.push((" --link-mode <LINK_MODE>".to_string(), String::new()));
|
||||||
filters.push((r#"link-mode = "copy"\n"#.to_string(), String::new()));
|
filters.push((r#"link-mode = "copy"\n"#.to_string(), String::new()));
|
||||||
|
// Unix uses "exit status", Windows uses "exit code"
|
||||||
|
filters.push((r"exit code: ".to_string(), "exit status: ".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
filters.extend(
|
filters.extend(
|
||||||
|
|
|
@ -7246,10 +7246,7 @@ fn fail_to_add_revert_project() -> Result<()> {
|
||||||
.child("setup.py")
|
.child("setup.py")
|
||||||
.write_str("1/0")?;
|
.write_str("1/0")?;
|
||||||
|
|
||||||
let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
|
uv_snapshot!(context.filters(), context.add().arg("./child"), @r#"
|
||||||
.chain(context.filters())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
uv_snapshot!(filters, context.add().arg("./child"), @r#"
|
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -7351,10 +7348,7 @@ fn fail_to_edit_revert_project() -> Result<()> {
|
||||||
.child("setup.py")
|
.child("setup.py")
|
||||||
.write_str("1/0")?;
|
.write_str("1/0")?;
|
||||||
|
|
||||||
let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
|
uv_snapshot!(context.filters(), context.add().arg("./child"), @r#"
|
||||||
.chain(context.filters())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
uv_snapshot!(filters, context.add().arg("./child"), @r#"
|
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
|
@ -4551,15 +4551,15 @@ fn lock_requires_python_compatible_specifier() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
warning: The release specifier (`~=3.13`) contains a compatible release match without a patch version. This will be interpreted as `>=3.13, <4`. Did you mean `~=3.13.0` to freeze the minor version?
|
warning: The `requires-python` specifier (`~=3.13`) in `warehouse` uses the tilde specifier (`~=`) without a patch version. This will be interpreted as `>=3.13, <4`. Did you mean `~=3.13.0` to constrain the version as `>=3.13.0, <3.14`? We recommend only using the tilde specifier with a patch version to avoid ambiguity.
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
pyproject_toml.write_str(
|
pyproject_toml.write_str(
|
||||||
r#"
|
r#"
|
||||||
|
@ -5013,14 +5013,14 @@ fn lock_requires_python_not_equal() -> Result<()> {
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let lock = fs_err::read_to_string(&lockfile).unwrap();
|
let lock = fs_err::read_to_string(&lockfile).unwrap();
|
||||||
|
|
||||||
|
@ -23617,10 +23617,7 @@ fn lock_derivation_chain_prod() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([(r"/.*/src", "/[TMP]/src")])
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"/.*/src", "/[TMP]/src"),
|
|
||||||
])
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
uv_snapshot!(filters, context.lock(), @r###"
|
uv_snapshot!(filters, context.lock(), @r###"
|
||||||
|
@ -23677,10 +23674,7 @@ fn lock_derivation_chain_extra() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([(r"/.*/src", "/[TMP]/src")])
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"/.*/src", "/[TMP]/src"),
|
|
||||||
])
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
uv_snapshot!(filters, context.lock(), @r###"
|
uv_snapshot!(filters, context.lock(), @r###"
|
||||||
|
@ -23739,10 +23733,7 @@ fn lock_derivation_chain_group() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([(r"/.*/src", "/[TMP]/src")])
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"/.*/src", "/[TMP]/src"),
|
|
||||||
])
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
uv_snapshot!(filters, context.lock(), @r###"
|
uv_snapshot!(filters, context.lock(), @r###"
|
||||||
|
@ -23812,10 +23803,7 @@ fn lock_derivation_chain_extended() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([(r"/.*/src", "/[TMP]/src")])
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"/.*/src", "/[TMP]/src"),
|
|
||||||
])
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
uv_snapshot!(filters, context.lock(), @r###"
|
uv_snapshot!(filters, context.lock(), @r###"
|
||||||
|
@ -27522,7 +27510,7 @@ fn windows_arm() -> Result<()> {
|
||||||
lock, @r#"
|
lock, @r#"
|
||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 2
|
||||||
requires-python = ">=3.12.[X], <3.13"
|
requires-python = "==3.12.*"
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"platform_machine == 'x86_64' and sys_platform == 'linux'",
|
"platform_machine == 'x86_64' and sys_platform == 'linux'",
|
||||||
"platform_machine == 'AMD64' and sys_platform == 'win32'",
|
"platform_machine == 'AMD64' and sys_platform == 'win32'",
|
||||||
|
@ -27599,7 +27587,7 @@ fn windows_amd64_required() -> Result<()> {
|
||||||
lock, @r#"
|
lock, @r#"
|
||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 2
|
||||||
requires-python = ">=3.12.[X], <3.13"
|
requires-python = "==3.12.*"
|
||||||
required-markers = [
|
required-markers = [
|
||||||
"platform_machine == 'x86' and sys_platform == 'win32'",
|
"platform_machine == 'x86' and sys_platform == 'win32'",
|
||||||
"platform_machine == 'AMD64' and sys_platform == 'win32'",
|
"platform_machine == 'AMD64' and sys_platform == 'win32'",
|
||||||
|
@ -28725,3 +28713,34 @@ fn lock_prefix_match() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Regression test for <https://github.com/astral-sh/uv/issues/14231>.
|
||||||
|
#[test]
|
||||||
|
fn test_tilde_equals_python_version() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "debug"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
dependencies = [
|
||||||
|
"anyio==4.2.0; python_full_version >= '3.11'",
|
||||||
|
"anyio==4.3.0; python_full_version ~= '3.10.0'",
|
||||||
|
]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -2909,16 +2909,16 @@ fn incompatible_narrowed_url_dependency() -> Result<()> {
|
||||||
"})?;
|
"})?;
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.pip_compile()
|
uv_snapshot!(context.filters(), context.pip_compile()
|
||||||
.arg("requirements.in"), @r###"
|
.arg("requirements.in"), @r"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: Requirements contain conflicting URLs for package `uv-public-pypackage`:
|
error: Requirements contain conflicting URLs for package `uv-public-pypackage`:
|
||||||
- git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389
|
|
||||||
- git+https://github.com/astral-test/uv-public-pypackage@test-branch
|
- git+https://github.com/astral-test/uv-public-pypackage@test-branch
|
||||||
"###
|
- git+https://github.com/astral-test/uv-public-pypackage@b270df1a2fb5d012294e9aaf05e7e0bab1e6a389
|
||||||
|
"
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -14679,10 +14679,7 @@ fn compile_derivation_chain() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([(r"/.*/src", "/[TMP]/src")])
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"/.*/src", "/[TMP]/src"),
|
|
||||||
])
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
uv_snapshot!(filters, context.pip_compile().arg("pyproject.toml"), @r###"
|
uv_snapshot!(filters, context.pip_compile().arg("pyproject.toml"), @r###"
|
||||||
|
@ -16345,7 +16342,7 @@ fn pep_751_compile_registry_wheel() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
|
@ -16394,7 +16391,7 @@ fn pep_751_compile_registry_sdist() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "source-distribution"
|
name = "source-distribution"
|
||||||
|
@ -16478,7 +16475,7 @@ fn pep_751_compile_directory() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
|
@ -16549,7 +16546,7 @@ fn pep_751_compile_git() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "uv-public-pypackage"
|
name = "uv-public-pypackage"
|
||||||
|
@ -16599,7 +16596,7 @@ fn pep_751_compile_url_wheel() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
|
@ -16663,7 +16660,7 @@ fn pep_751_compile_url_sdist() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
|
@ -16732,7 +16729,7 @@ fn pep_751_compile_path_wheel() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
|
@ -16770,7 +16767,7 @@ fn pep_751_compile_path_wheel() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o nested/pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o nested/pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
|
@ -16811,7 +16808,7 @@ fn pep_751_compile_path_sdist() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
|
@ -16850,7 +16847,7 @@ fn pep_751_compile_path_sdist() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o nested/pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o nested/pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
|
@ -16887,7 +16884,7 @@ fn pep_751_compile_preferences() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
|
@ -16928,7 +16925,7 @@ fn pep_751_compile_preferences() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
|
@ -16968,7 +16965,7 @@ fn pep_751_compile_preferences() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
|
@ -17007,7 +17004,7 @@ fn pep_751_compile_preferences() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
|
@ -17055,7 +17052,7 @@ fn pep_751_compile_warn() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml --emit-index-url
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml --emit-index-url
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
|
@ -17268,7 +17265,7 @@ fn pep_751_compile_no_emit_package() -> Result<()> {
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml --no-emit-package idna
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.txt --universal -o pylock.toml --no-emit-package idna
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
created-by = "uv"
|
created-by = "uv"
|
||||||
requires-python = ">=3.12.[X]"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
[[packages]]
|
[[packages]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
|
@ -17562,3 +17559,47 @@ fn git_path_transitive_dependency() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure that `--emit-index-annotation` plays nicely with `--annotation-style=line`.
|
||||||
|
#[test]
|
||||||
|
fn omit_python_patch_universal() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.11");
|
||||||
|
|
||||||
|
let requirements_in = context.temp_dir.child("requirements.in");
|
||||||
|
requirements_in.write_str("redis")?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.pip_compile()
|
||||||
|
.arg("requirements.in"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.in
|
||||||
|
redis==5.0.3
|
||||||
|
# via -r requirements.in
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.pip_compile()
|
||||||
|
.arg("requirements.in")
|
||||||
|
.arg("--universal"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
# This file was autogenerated by uv via the following command:
|
||||||
|
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --universal
|
||||||
|
async-timeout==4.0.3 ; python_full_version < '3.11.[X]'
|
||||||
|
# via redis
|
||||||
|
redis==5.0.3
|
||||||
|
# via -r requirements.in
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -342,10 +342,7 @@ dependencies = ["flask==1.0.x"]
|
||||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
requirements_txt.write_str("./path_dep")?;
|
requirements_txt.write_str("./path_dep")?;
|
||||||
|
|
||||||
let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
|
uv_snapshot!(context.filters(), context.pip_install()
|
||||||
.chain(context.filters())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
uv_snapshot!(filters, context.pip_install()
|
|
||||||
.arg("-r")
|
.arg("-r")
|
||||||
.arg("requirements.txt"), @r###"
|
.arg("requirements.txt"), @r###"
|
||||||
success: false
|
success: false
|
||||||
|
@ -1515,16 +1512,16 @@ fn install_editable_incompatible_constraint_url() -> Result<()> {
|
||||||
.arg("-e")
|
.arg("-e")
|
||||||
.arg(context.workspace_root.join("scripts/packages/black_editable"))
|
.arg(context.workspace_root.join("scripts/packages/black_editable"))
|
||||||
.arg("--constraint")
|
.arg("--constraint")
|
||||||
.arg("constraints.txt"), @r###"
|
.arg("constraints.txt"), @r"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 2
|
exit_code: 2
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: Requirements contain conflicting URLs for package `black`:
|
error: Requirements contain conflicting URLs for package `black`:
|
||||||
- [WORKSPACE]/scripts/packages/black_editable
|
- file://[WORKSPACE]/scripts/packages/black_editable (editable)
|
||||||
- https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl
|
- https://files.pythonhosted.org/packages/0f/89/294c9a6b6c75a08da55e9d05321d0707e9418735e3062b12ef0f54c33474/black-24.4.2-py3-none-any.whl
|
||||||
"###
|
"
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -4930,10 +4927,7 @@ fn no_build_isolation() -> Result<()> {
|
||||||
requirements_in.write_str("anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz")?;
|
requirements_in.write_str("anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz")?;
|
||||||
|
|
||||||
// We expect the build to fail, because `setuptools` is not installed.
|
// We expect the build to fail, because `setuptools` is not installed.
|
||||||
let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
|
uv_snapshot!(context.filters(), context.pip_install()
|
||||||
.chain(context.filters())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
uv_snapshot!(filters, context.pip_install()
|
|
||||||
.arg("-r")
|
.arg("-r")
|
||||||
.arg("requirements.in")
|
.arg("requirements.in")
|
||||||
.arg("--no-build-isolation"), @r###"
|
.arg("--no-build-isolation"), @r###"
|
||||||
|
@ -5001,10 +4995,7 @@ fn respect_no_build_isolation_env_var() -> Result<()> {
|
||||||
requirements_in.write_str("anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz")?;
|
requirements_in.write_str("anyio @ https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz")?;
|
||||||
|
|
||||||
// We expect the build to fail, because `setuptools` is not installed.
|
// We expect the build to fail, because `setuptools` is not installed.
|
||||||
let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
|
uv_snapshot!(context.filters(), context.pip_install()
|
||||||
.chain(context.filters())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
uv_snapshot!(filters, context.pip_install()
|
|
||||||
.arg("-r")
|
.arg("-r")
|
||||||
.arg("requirements.in")
|
.arg("requirements.in")
|
||||||
.env(EnvVars::UV_NO_BUILD_ISOLATION, "yes"), @r###"
|
.env(EnvVars::UV_NO_BUILD_ISOLATION, "yes"), @r###"
|
||||||
|
@ -8601,10 +8592,7 @@ fn install_build_isolation_package() -> Result<()> {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Running `uv pip install` should fail for iniconfig.
|
// Running `uv pip install` should fail for iniconfig.
|
||||||
let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
|
uv_snapshot!(context.filters(), context.pip_install()
|
||||||
.chain(context.filters())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
uv_snapshot!(filters, context.pip_install()
|
|
||||||
.arg("--no-build-isolation-package")
|
.arg("--no-build-isolation-package")
|
||||||
.arg("iniconfig")
|
.arg("iniconfig")
|
||||||
.arg(package.path()), @r###"
|
.arg(package.path()), @r###"
|
||||||
|
@ -8931,10 +8919,7 @@ fn missing_top_level() {
|
||||||
fn sklearn() {
|
fn sklearn() {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
|
uv_snapshot!(context.filters(), context.pip_install().arg("sklearn"), @r###"
|
||||||
.chain(context.filters())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
uv_snapshot!(filters, context.pip_install().arg("sklearn"), @r###"
|
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -8984,10 +8969,7 @@ fn resolve_derivation_chain() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([(r"/.*/src", "/[TMP]/src")])
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"/.*/src", "/[TMP]/src"),
|
|
||||||
])
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
uv_snapshot!(filters, context.pip_install()
|
uv_snapshot!(filters, context.pip_install()
|
||||||
|
@ -11486,3 +11468,25 @@ fn pep_751_dependency() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test that we show an error instead of panicking for conflicting arguments in different levels,
|
||||||
|
/// which are not caught by clap.
|
||||||
|
#[test]
|
||||||
|
fn conflicting_flags_clap_bug() {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.command()
|
||||||
|
.arg("pip")
|
||||||
|
.arg("--offline")
|
||||||
|
.arg("install")
|
||||||
|
.arg("--no-offline")
|
||||||
|
.arg("tqdm"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: `--offline` and `--no-offline` cannot be used together. Boolean flags on different levels are currently not supported (https://github.com/clap-rs/clap/issues/6049)
|
||||||
|
"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -4876,7 +4876,7 @@ fn exit_status_signal() -> Result<()> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_repeated() -> Result<()> {
|
fn run_repeated() -> Result<()> {
|
||||||
let context = TestContext::new_with_versions(&["3.13"]);
|
let context = TestContext::new_with_versions(&["3.13", "3.12"]);
|
||||||
|
|
||||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
pyproject_toml.write_str(indoc! { r#"
|
pyproject_toml.write_str(indoc! { r#"
|
||||||
|
@ -4923,22 +4923,25 @@ fn run_repeated() -> Result<()> {
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
// Re-running as a tool shouldn't require reinstalling `typing-extensions`, since the environment is cached.
|
// Re-running as a tool does require reinstalling `typing-extensions`, since the base venv is
|
||||||
|
// different.
|
||||||
uv_snapshot!(
|
uv_snapshot!(
|
||||||
context.filters(),
|
context.filters(),
|
||||||
context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r###"
|
context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r#"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ typing-extensions==4.10.0
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<string>", line 1, in <module>
|
File "<string>", line 1, in <module>
|
||||||
import typing_extensions; import iniconfig
|
import typing_extensions; import iniconfig
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
ModuleNotFoundError: No module named 'iniconfig'
|
ModuleNotFoundError: No module named 'iniconfig'
|
||||||
"###);
|
"#);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -4979,22 +4982,25 @@ fn run_without_overlay() -> Result<()> {
|
||||||
+ typing-extensions==4.10.0
|
+ typing-extensions==4.10.0
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
// Import `iniconfig` in the context of a `tool run` command, which should fail.
|
// Import `iniconfig` in the context of a `tool run` command, which should fail. Note that
|
||||||
|
// typing-extensions gets installed again, because the venv is not shared.
|
||||||
uv_snapshot!(
|
uv_snapshot!(
|
||||||
context.filters(),
|
context.filters(),
|
||||||
context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r###"
|
context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r#"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ typing-extensions==4.10.0
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<string>", line 1, in <module>
|
File "<string>", line 1, in <module>
|
||||||
import typing_extensions; import iniconfig
|
import typing_extensions; import iniconfig
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
ModuleNotFoundError: No module named 'iniconfig'
|
ModuleNotFoundError: No module named 'iniconfig'
|
||||||
"###);
|
"#);
|
||||||
|
|
||||||
// Re-running in the context of the project should reset the overlay.
|
// Re-running in the context of the project should reset the overlay.
|
||||||
uv_snapshot!(
|
uv_snapshot!(
|
||||||
|
|
|
@ -3,13 +3,14 @@ use assert_cmd::prelude::*;
|
||||||
use assert_fs::{fixture::ChildPath, prelude::*};
|
use assert_fs::{fixture::ChildPath, prelude::*};
|
||||||
use indoc::{formatdoc, indoc};
|
use indoc::{formatdoc, indoc};
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
|
|
||||||
use crate::common::{TestContext, download_to_disk, packse_index_url, uv_snapshot, venv_bin_path};
|
|
||||||
use predicates::prelude::predicate;
|
use predicates::prelude::predicate;
|
||||||
use tempfile::tempdir_in;
|
use tempfile::tempdir_in;
|
||||||
|
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
|
|
||||||
|
use crate::common::{TestContext, download_to_disk, packse_index_url, uv_snapshot, venv_bin_path};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sync() -> Result<()> {
|
fn sync() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
@ -1121,10 +1122,7 @@ fn sync_build_isolation_package() -> Result<()> {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Running `uv sync` should fail for iniconfig.
|
// Running `uv sync` should fail for iniconfig.
|
||||||
let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
|
uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r###"
|
||||||
.chain(context.filters())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
uv_snapshot!(filters, context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r###"
|
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -1214,10 +1212,7 @@ fn sync_build_isolation_extra() -> Result<()> {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Running `uv sync` should fail for the `compile` extra.
|
// Running `uv sync` should fail for the `compile` extra.
|
||||||
let filters = std::iter::once((r"exit code: 1", "exit status: 1"))
|
uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r###"
|
||||||
.chain(context.filters())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
uv_snapshot!(&filters, context.sync().arg("--extra").arg("compile"), @r###"
|
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -1238,7 +1233,7 @@ fn sync_build_isolation_extra() -> Result<()> {
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
// Running `uv sync` with `--all-extras` should also fail.
|
// Running `uv sync` with `--all-extras` should also fail.
|
||||||
uv_snapshot!(&filters, context.sync().arg("--all-extras"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--all-extras"), @r###"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -6984,10 +6979,7 @@ fn sync_derivation_chain() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([(r"/.*/src", "/[TMP]/src")])
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"/.*/src", "/[TMP]/src"),
|
|
||||||
])
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
uv_snapshot!(filters, context.sync(), @r###"
|
uv_snapshot!(filters, context.sync(), @r###"
|
||||||
|
@ -7050,10 +7042,7 @@ fn sync_derivation_chain_extra() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([(r"/.*/src", "/[TMP]/src")])
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"/.*/src", "/[TMP]/src"),
|
|
||||||
])
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
uv_snapshot!(filters, context.sync().arg("--extra").arg("wsgi"), @r###"
|
uv_snapshot!(filters, context.sync().arg("--extra").arg("wsgi"), @r###"
|
||||||
|
@ -7118,10 +7107,7 @@ fn sync_derivation_chain_group() -> Result<()> {
|
||||||
let filters = context
|
let filters = context
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([(r"/.*/src", "/[TMP]/src")])
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"/.*/src", "/[TMP]/src"),
|
|
||||||
])
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
uv_snapshot!(filters, context.sync().arg("--group").arg("wsgi"), @r###"
|
uv_snapshot!(filters, context.sync().arg("--group").arg("wsgi"), @r###"
|
||||||
|
@ -9989,3 +9975,54 @@ fn sync_url_with_query_parameters() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn read_only() -> Result<()> {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["iniconfig"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Prepared 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ iniconfig==2.0.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
assert!(context.temp_dir.child("uv.lock").exists());
|
||||||
|
|
||||||
|
// Remove the flock.
|
||||||
|
fs_err::remove_file(context.venv.child(".lock"))?;
|
||||||
|
|
||||||
|
// Make the virtual environment read and execute (but not write).
|
||||||
|
fs_err::set_permissions(&context.venv, std::fs::Permissions::from_mode(0o555))?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Audited 1 package in [TIME]
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -448,13 +448,13 @@ fn tool_install_suggest_other_packages_with_executable() {
|
||||||
uv_snapshot!(filters, context.tool_install()
|
uv_snapshot!(filters, context.tool_install()
|
||||||
.arg("fastapi==0.111.0")
|
.arg("fastapi==0.111.0")
|
||||||
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###"
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
No executables are provided by `fastapi`
|
No executables are provided by package `fastapi`; removing tool
|
||||||
However, an executable with the name `fastapi` is available via dependency `fastapi-cli`.
|
hint: An executable with the name `fastapi` is available via dependency `fastapi-cli`.
|
||||||
Did you mean `uv tool install fastapi-cli`?
|
Did you mean `uv tool install fastapi-cli`?
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 35 packages in [TIME]
|
Resolved 35 packages in [TIME]
|
||||||
|
@ -494,7 +494,7 @@ fn tool_install_suggest_other_packages_with_executable() {
|
||||||
+ uvicorn==0.29.0
|
+ uvicorn==0.29.0
|
||||||
+ watchfiles==0.21.0
|
+ watchfiles==0.21.0
|
||||||
+ websockets==12.0
|
+ websockets==12.0
|
||||||
"###);
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test installing a tool at a version
|
/// Test installing a tool at a version
|
||||||
|
@ -821,11 +821,11 @@ fn tool_install_remove_on_empty() -> Result<()> {
|
||||||
.arg(black.path())
|
.arg(black.path())
|
||||||
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
No executables are provided by `black`
|
No executables are provided by package `black`; removing tool
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
@ -839,7 +839,7 @@ fn tool_install_remove_on_empty() -> Result<()> {
|
||||||
- packaging==24.0
|
- packaging==24.0
|
||||||
- pathspec==0.12.1
|
- pathspec==0.12.1
|
||||||
- platformdirs==4.2.0
|
- platformdirs==4.2.0
|
||||||
"###);
|
");
|
||||||
|
|
||||||
// Re-request `black`. It should reinstall, without requiring `--force`.
|
// Re-request `black`. It should reinstall, without requiring `--force`.
|
||||||
uv_snapshot!(context.filters(), context.tool_install()
|
uv_snapshot!(context.filters(), context.tool_install()
|
||||||
|
@ -1649,18 +1649,18 @@ fn tool_install_no_entrypoints() {
|
||||||
.arg("iniconfig")
|
.arg("iniconfig")
|
||||||
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||||
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||||
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
No executables are provided by `iniconfig`
|
No executables are provided by package `iniconfig`; removing tool
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
Prepared 1 package in [TIME]
|
Prepared 1 package in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 1 package in [TIME]
|
||||||
+ iniconfig==2.0.0
|
+ iniconfig==2.0.0
|
||||||
"###);
|
");
|
||||||
|
|
||||||
// Ensure the tool environment is not created.
|
// Ensure the tool environment is not created.
|
||||||
tool_dir
|
tool_dir
|
||||||
|
@ -1682,7 +1682,6 @@ fn tool_install_uninstallable() {
|
||||||
.filters()
|
.filters()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain([
|
.chain([
|
||||||
(r"exit code: 1", "exit status: 1"),
|
|
||||||
(r"bdist\.[^/\\\s]+(-[^/\\\s]+)?", "bdist.linux-x86_64"),
|
(r"bdist\.[^/\\\s]+(-[^/\\\s]+)?", "bdist.linux-x86_64"),
|
||||||
(r"\\\.", ""),
|
(r"\\\.", ""),
|
||||||
(r"#+", "#"),
|
(r"#+", "#"),
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue