Compare commits

..

151 Commits
0.9.11 ... main

Author SHA1 Message Date
Charlie Marsh 0a83bf7dd5
Respect `--torch-backend` in `uv tool` commands (#17117)
## Summary

Like `uv pip`, these don't require a universal resolution, so
`--torch-backend` is easy to support.
2025-12-16 19:23:50 -05:00
Charlie Marsh e603761862
Support remote `pylock.toml` files (#17119)
## Summary

Closes https://github.com/astral-sh/uv/issues/17112.
2025-12-16 19:16:23 -05:00
Zanie Blue 4f6f56b070
Add ty to the README (#17139) 2025-12-16 09:39:59 -06:00
Zanie Blue 66f7093ad2
Move a couple README items into the FAQ (#17148) 2025-12-16 08:36:06 -06:00
Zanie Blue 60df92f9aa
Copy the Code of Conduct from Ruff (#17145) 2025-12-16 14:12:15 +00:00
konsti 0cee76417f
Bump version to 0.9.18 (#17141)
It's been a week.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-12-16 13:32:35 +00:00
jkipper af348c2a88
Ignore pyproject index username in lockfile comparison (#16995)
<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Pyproject.toml index url may contain a username while lockfile doesn't.
Treat it as the same index to prevent unintended package updates

Fixes #16436

---------

Co-authored-by: konstin <konstin@mailbox.org>
2025-12-16 10:47:50 +00:00
Diyor Khayrutdinov b58f543e5e
Support redirects in `uv publish` (#17130)
<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Follow redirects for `uv publish`. Related issue:
https://github.com/astral-sh/uv/issues/17126.

## Test Plan

<!-- How was it tested? -->
Added a unit test to test the custom redirect logic.

---------

Co-authored-by: konstin <konstin@mailbox.org>
2025-12-16 09:04:28 +00:00
Charlie Marsh 13e7ad62cb
Accept `--torch-backend` in `[tool.uv]` (#17116)
## Summary

I'd like to add `--torch-backend` to `uv tool`, so this PR lifts the
setting out of `[tool.uv.pip]`. Like other settings, if it's in
`[tool.uv.pip]`, it will take preference for `uv pip` operations.
2025-12-15 20:18:40 -05:00
Tomasz Kramkowski 94c97b6434
Add value hints to command line arguments to improve shell completion accuracy (#17080)
## Summary

This partially addresses #17076 by adding `value_hint` to various
arguments.

For cases where an option takes a path to either specifically a file or
a directory directory, `ValueHint::FilePath` and `ValueHint::DirPath`
are used respectively to try to limit the amount of noise presented by
completions in shells which support it.

For cases where a URL (and only a URL, not a path) can be supplied,
`ValueHint::Url` is used.

For cases where a python interpreter is to be specified,
`ValueHint::CommandName` is used which will tab complete from `$PATH` by
default, but will fall back to completing executable filenames if you
start typing a path.

Finally, for the many cases where there is no built in completion which
would make sense, and where default completion of a path would make no
sense (e.g. a package name, or version specifier, or date)
`ValueHint::Other` is used to explicitly disable completion.

## Test Plan

Manually tested a bunch of these. These _could_ be automated in the
sense that we could snapshot the completion from zsh but I've not
thought about how that could be done yet.
2025-12-15 18:29:32 +00:00
konsti af95677b9b
Update cargo shear (#17106)
Requires a companion PR that updates the GitHub Action.
2025-12-15 18:46:35 +01:00
konsti a5d50a20d2
Better rendering for multiline error messages (#17132)
Split out from https://github.com/astral-sh/uv/pull/17110

Indent multiline error messages properly, and add a test with a
multiline context and a context below since that combination isn't
captured atm.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-12-15 16:29:11 +00:00
samypr100 a768a9d111
Relax error when using uv add with `UV_GIT_LFS` set (#17127)
## Summary

Closes https://github.com/astral-sh/uv/issues/17083

Previously having `UV_GIT_LFS` set would cause an error when adding a
non-git requirement such as ```error: `requirement` did not resolve to a
Git repository, but a Git extension (`--lfs`) was provided.```

## Test Plan

Additional test has been added.
2025-12-15 08:26:14 -06:00
Tomasz Kramkowski d20948bec2
Support creating lock files on ExFAT on MacOS (#17115)
## Summary

Fix #16859 by falling back to simply creating the lock file and then
attempting to apply permissions in cases where the temporary lockfile
cannot be renamed without overwriting (persist_noclobber) due to lack of
underlying support from the filesystem.

I've also improved the error handling.

## Test Plan

Manually on MacOS with an ExFAT partition.

~~~ bash session
$ hdiutil create -size 1g -fs ExFAT -volname EXFATDISK exfat.dmg
$ hdiutil attach exfat.dmg
$ cd /Volumes/EXFATDISK
$ uv init --bare --cache-dir build/uv/cache -v 
~~~
2025-12-15 14:05:05 +00:00
Ben Beasley a2d64aa224
Update spdx dependency to 0.13 (#17129)
<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Updates the `spdx` dependency from 0.12.x to the latest release, 0.13.2.

https://github.com/EmbarkStudios/spdx/blob/0.13.2/CHANGELOG.md

Here in uv upstream, this just helps keep dependencies up to date; there
isn’t any other particular specific motivation or benefit. Downstream in
Fedora, this change allows me to avoid maintaining a `rust-spdx0.12`
compat package.
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->
`cargo nextest run -- --skip python_install::python_install_pyodide`
2025-12-14 13:25:06 -05:00
haruna c43315f4eb
Change exclude-newer type into optional string (#17121)
<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

fix: #17103 

## Test Plan

The following settings will be enabled for the schema.

```toml
[tool.uv]
exclude-newer = "P7D"
```
2025-12-13 13:42:01 -06:00
Charlie Marsh e77ee15204
Enforce latest-version in `@latest` requests (#17114)
## Summary

Given `uv tool install {name}@latest`, we make revalidation requests for
`{name}`, but we don't actually add a "latest" constraint when resolving
-- we just assume that since the package is unpinned, and we're fetching
the latest available versions, the resolver will select the latest
version.

However, imagine a package in which the latest version requires Python
3.13 or later, but prior versions support Python 3.9 and up. If we
happen to select Python 3.9 ahead of resolution, and the user requests
`{name}@latest`, we would backtrack to the non-latest version due to the
Python mismatch.

This PR modifies `uv tool install` and `uv tool run` to first determine
the latest version, then provide it as a constraint when resolving.
2025-12-13 10:39:01 -05:00
Zanie Blue ed37f3b432
Drop arm musl caveat from Docker documentation (#17111)
This works fine now

```
❯ docker run --rm -it ghcr.io/astral-sh/uv:alpine sh -c "uv python install 3.14"
Installed Python 3.14.2 in 2.77s
 + cpython-3.14.2-linux-aarch64-musl (python3.14)
```
2025-12-12 19:15:15 +00:00
konsti 3e80b10272
Avoid panics due to reads on failed requests (#17098)
Fixes https://github.com/astral-sh/uv/issues/17090, specifically the
panic, not the upstream bug.
2025-12-12 18:02:49 +01:00
konsti 7ad441a0bd
Better error handling for `uv publish` (#17096)
* Use `is_transient_network_error` as we do in all other cases, see also
https://github.com/astral-sh/uv/pull/16245
* Don't report success in the progress reporter if the upload failed
2025-12-12 18:02:37 +01:00
Zanie Blue 5a55bbe883
Include Docker images with the alpine version, e.g., `python3.x-alpine3.23` (#17100)
Closes https://github.com/astral-sh/uv/issues/17095

This also stabilizes the Alpine version for users that do not choose to
pin it. We could add this to the build matrix separately to avoid that,
but I think that's okay?
2025-12-12 14:17:15 +00:00
Tomasz Kramkowski 6ad80c5150
Refactor the Changelog for use in `report_dry_run` (#17039)
## Summary

Remove duplication in `report_dry_run` by making `Changelog` support
both local and remote dists. This is in support of #16653 and will form
a new basis for #16981.

This also involved refactoring `InstallLogger` and its implementations
to support dry run logging.

Additionally includes some minor refactoring in `SummaryInstallLogger`
and a fix to `InstalledVersion`.

See https://github.com/astral-sh/uv/compare/tk/dry-run-refactor for an
alternative approach (although obviously comes with some caveats).

## Test Plan

There are already quite a few tests which cover the output and they
pass. Manual testing was used to ensure styling stayed consistent.
2025-12-12 10:37:30 +00:00
Charlie Marsh 38ae414682
Initialize S3 signer once (#17092)
## Summary

Right now, we initialize the signer many times concurrently.
2025-12-11 20:36:41 +00:00
Matthew Mckee 6de869cc88
Speed up cache size command (#17015)
## Summary

`uv cache size` can be quite slow. Here i use
https://github.com/sharkdp/diskus to walk the cache directory with in
multiple threads.

Add cli option to set the number of threads and default to `
std:🧵:available_parallelism()` or 1.

## Test Plan

Added cli statement with info log test.

I believe this is a fair test, where i set cache dir to a large
directory.

```bash
matthew@matthew-main ~/develop/personal/uv                                                                                                                                                                                                                 [14:17:50]                                                                                                                                                                                                                                       [±cache-size-speed-up ✓▴]
> $ uv cache size --preview-features cache-size -H --cache-dir ~/develop/                                                                                                                                                                   [±cache-size-speed-up ✓▴]
75.7GiB

matthew@matthew-main ~/develop/personal/uv                                                                                                                                                                                                                 [14:18:24]
> $ hyperfine 'uv cache size --preview-features cache-size -H --cache-dir ~/develop/' 'target/debug/uv cache size --preview-features cache-size -H --cache-dir ~/develop/'                                                                  [±cache-size-speed-up ✓▴]
Benchmark 1: uv cache size --preview-features cache-size -H --cache-dir ~/develop/
  Time (mean ± σ):      1.059 s ±  0.014 s    [User: 0.171 s, System: 0.884 s]
  Range (min … max):    1.048 s …  1.097 s    10 runs

Benchmark 2: target/debug/uv cache size --preview-features cache-size -H --cache-dir ~/develop/
  Time (mean ± σ):     413.8 ms ±  17.1 ms    [User: 5789.2 ms, System: 1682.0 ms]
  Range (min … max):   386.3 ms … 441.6 ms    10 runs

Summary
  target/debug/uv cache size --preview-features cache-size -H --cache-dir ~/develop/ ran
    2.56 ± 0.11 times faster than uv cache size --preview-features cache-size -H --cache-dir ~/develop/  
```
2025-12-11 12:11:01 -05:00
Mathieu Kniewallner 59d73fdddf
docs(settings): better document `exclude-newer*` (#17079)
<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Following the changes in https://github.com/astral-sh/uv/pull/16814,
documentation for
[`--exclude-newer`](https://docs.astral.sh/uv/reference/cli/#uv-sync--exclude-newer)
and
[`--exclude-newer-package`](https://docs.astral.sh/uv/reference/cli/#uv-sync--exclude-newer-package)
arguments were updated, but not their settings counterparts, so this
just updates the settings ones to closely match the arguments ones.

## Test Plan

Ran documentation locally.
2025-12-11 09:23:48 -06:00
Charlie Marsh 4c1571fb76
Fix version reference in resolver example (#17085) 2025-12-11 15:53:48 +01:00
konsti ebdffaf728
CI Perf: fast-build (#16780)
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-12-11 13:51:37 +00:00
Eashwar Ranganathan 3bb7f67c71
Explicitly set EntryType for file entries in tar (#17043)
## Summary
This PR explicitly sets the entry type for files in an sdist. This
changes the entry type from `AREGTYPE` (the 'legacy' regular file type)
to `REGTYPE` (the 'normal' regular file type) in the generated tar.

This change works around a bug in the python `tarfile` module that
causes all entries after a certain point in the tar to be silently
ignored if any entry matches some very specific conditions. In `maturin`
this was very visible since the `PKG-INFO` was written at the very end
so `twine check` would loudly complain that the `PKG-INFO` was missing
and that the sdist was invalid. In `uv` the `PKG-INFO` is written at the
beginning so this issue is unlikely to be caught.

Note that this change does mean that sdists created with newer versions
of the uv build backend will not be byte-for-byte identical with sdists
from an older version.

See https://github.com/PyO3/maturin/issues/2855#issuecomment-3546501132

## Test Plan
This is the same as the change that was made in maturin to work around
the same issue

---------

Co-authored-by: konstin <konstin@mailbox.org>
2025-12-11 10:37:35 +00:00
konsti caac4814df
Add crate graph to contributing guide (#17062)
I share this regularly with people, we should document it.

---------

Co-authored-by: Tomasz Kramkowski <tom@astral.sh>
2025-12-10 09:01:00 -06:00
Zanie Blue a550743bed
Drop some non-integration exclude-newer tests (#17071)
Closes https://github.com/astral-sh/uv/issues/17070

Claude added these and they're unstable and just not useful imo.
2025-12-10 14:04:05 +00:00
Zanie Blue 94f1f02d85
Update the exclude newer duration tests to demonstrate package version changes (#17055)
Follows #16814 updating the test cases with Claude so that the
timestamps and durations are on the boundary of package versions so we
see actual version changes in the lockfile.
2025-12-10 07:47:44 -06:00
Ben Beasley 36806f8e66
Gate a few more tests on the pypi feature (#17059)
<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Gate a few more tests on the `pypi` feature. All of these fail in
offline environments because they try to communicate with PyPI.
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->
Applied as a patch to Fedora’s `uv` package, version 0.9.16.
2025-12-10 10:41:47 +01:00
Zanie Blue 2b5d65e61d
Bump version to 0.9.17 (#17058) 2025-12-09 16:36:00 -06:00
github-actions[bot] 81c99dd438
Sync latest Python releases (#17057)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-12-09 22:30:11 +00:00
Zanie Blue b931c6687c
Increase the size of binary build runners (#17016)
Summary from asking Claude to compare the runtime

```
  | Job                       | Before | After | Saved | Reduction |
  |---------------------------|--------|-------|-------|-----------|
  | macos-aarch64             | 19.1m  | 9.8m  | 9.2m  | 48%       |
  | macos-x86_64              | 15.1m  | 9.2m  | 5.9m  | 39%       |
  | linux-arm (aarch64)       | 18.6m  | 12.2m | 6.4m  | 34%       |
  | linux-arm (armv7)         | 17.4m  | 11.5m | 5.9m  | 34%       |
  | musllinux-cross (aarch64) | 19.3m  | 13.2m | 6.1m  | 32%       |
  | linux-arm (arm)           | 16.6m  | 11.4m | 5.2m  | 31%       |
  | musllinux-cross (armv7)   | 16.4m  | 10.8m | 5.6m  | 34%       |
```
The longest-running affected job went from 19.3m → 13.2m (saved 6.1
minutes, 32% faster).
2025-12-09 16:04:43 -06:00
Zanie Blue eca36eed08
Fix a typo (#17056) 2025-12-09 21:49:52 +00:00
William Woodruff 69910b4aab
Publish PyPI releases before crates.io artifacts (#16989)
## Summary

Closes #16987.

## Test Plan

We need a good way to dry-run this...

---------

Signed-off-by: William Woodruff <william@astral.sh>
2025-12-09 15:20:21 -06:00
konsti 8d2c2e8cdf
Better source-exclude reference docs (#16832)
Fixed https://github.com/astral-sh/uv/issues/16821

This is already explained in the guide, but it was missing from the
reference docs.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-12-09 21:14:28 +00:00
Niko Pikall b6686fbce3
Update UV_VERSION in docs for GitLab CI/CD (#17040)
<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->
## Summary
Update the `UV_VERSION`, such that a `copy-to-clipboard` action and
pasting into a `.gitlab-ci.yml` is not 4 minor versions behind, as it
happened to me a couple of times.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
I ran `mkdocs serve` and it worked (I literally only changed one
character)
<!-- How was it tested? -->
2025-12-09 14:56:27 -06:00
Charlie Marsh 4af2d2b922
Add `torch-tensorrt` and `torchao` to the PyTorch list (#17053)
## Summary

Closes https://github.com/astral-sh/uv/issues/17050.
2025-12-09 20:29:32 +00:00
Charlie Marsh 2502577c9d
Sort packages in GPU lists (#17054)
## Summary

No semantic changes; this is just bothering me.
2025-12-09 15:10:06 -05:00
Zanie Blue d0a6f5d13f
Add support for relative durations in `exclude-newer` (#16814)
Adds support for "friendly" durations like, 1 week, 7 days, 24 hours
using Jiff's parser. During resolution, we calculate this relative to
the current time and resolve it into a concrete timestamp for the
lockfile. If the span has not changed, e.g., to another relative value,
then locking again will not change the lockfile. The locked timestamp
will only be updated when the lockfile is invalidated, e.g., with
`--upgrade`. This prevents the lockfile from repeatedly churning when a
relative value is used.
2025-12-09 19:52:14 +00:00
Zanie Blue 7b6b02a7d1
Recommend `UV_NO_DEV` in Docker installs (#17030)
Closes https://github.com/astral-sh/uv/issues/17027

See also, https://github.com/astral-sh/uv-docker-example/pull/73
2025-12-09 12:12:08 -06:00
William Woodruff 0dd71f4382
Bump ambient-id to 0.0.7 (#17048) 2025-12-09 09:08:26 -08:00
F4RAN 38ce3b2919
Add hint for misplaced `--verbose` in `uv tool run` (#17020)
Resolves #16777

## Summary
When a command fails, users sometimes add --verbose after the package
name (e.g., uvx foo --verbose) instead of before it (e.g., uvx --verbose
foo). This adds a hint that suggests moving --verbose before the
command.
The hint appears when a verbose flag is detected in the subcommand
arguments and the command fails to resolve. It works for both uvx and uv
tool run.

## Test Plan
Tested by running:
uvx foo-does-not-exist --verbose - shows the hint
uv tool run foo-does-not-exist --verbose - shows the hint
The hint only appears when verbose flags are detected, and the message
shows the correct command format.

## Screenshot
<img width="920" height="34" alt="image"
src="https://github.com/user-attachments/assets/f6c303f6-b5e6-441f-8d8d-9f5e6ab87c87"
/>

Open to feedback and happy to make changes as needed! 💯

---------

Co-authored-by: Tomasz (Tom) Kramkowski <tom@astral.sh>
2025-12-09 10:15:06 -06:00
Zanie Blue a70ee58ae1
Move test support files out of `scripts/` into `test/`
(#17032)

It's been bothering me that we have a bunch of stub packages and such in
a `scripts` directory.
2025-12-09 10:06:05 -06:00
chisato 9774f8f1d4
Fix relocatable nushell activation script (#17036)
<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
Nushell activation now computes the venv root dynamically via path self
for relocatable venvs, while non-relocatable venvs still embed a quoted
absolute path

Still keep `activate.csh` (maybe delete is also an option)

close https://github.com/astral-sh/uv/issues/16973

<!-- How was it tested? -->
2025-12-09 14:39:55 +00:00
Zanie Blue 77df5887e4
Add a Claude hook for formatting (#17033)
Inspired by
https://github.com/oven-sh/bun/blob/main/.claude/hooks/pre-bash-zig-build.js

This has been driving me pretty crazy and the fix is easy enough.
2025-12-08 16:57:17 +00:00
Zanie Blue 4e1469b151
Remove now unused generate tests (#17031)
I missed this in #16969
2025-12-08 15:50:40 +00:00
Zanie Blue 5a6f2ea319
Generate reference documentation at publish-time and the JSON schema at release-time (#16969)
It'd be nice to avoid churn for contributors. This is a pretty frequent
cause of CI failures and I don't think we really need to have the
reference documentation committed.
2025-12-08 12:31:38 +00:00
Charlie Marsh 28a8194a67
Respect dropped (but explicit) indexes in dependency groups (#17012)
## Summary

There are a class of outcomes whereby an index might not be included in
"allowed indexes", but could still correctly appear in a lockfile. In
the linked case, we have two `default = true` indexes, and one of them
is also named. We omit the second `default = true` index from the list
of "allowed indexes", but since it's named, a dependency can reference
it explicitly. We handle this correctly for `project.dependencies`, but
the handling was incorrectly omitting dependency groups.

Closes https://github.com/astral-sh/uv/issues/16843.
2025-12-06 14:06:46 +00:00
Zanie Blue a63e5b62e3
Bump version to 0.9.16 (#17008) 2025-12-06 07:52:06 -06:00
Charlie Marsh ed19672f1f
Bump `astral-tl` to v0.7.11 (#17010)
## Summary

This was reverted due to a hang
(https://github.com/astral-sh/uv/issues/16937) which was then resolved
upstream (https://github.com/astral-sh/astral-tl/pull/16).
2025-12-06 07:32:55 -06:00
github-actions[bot] 9635258867
Sync latest Python releases (#16943)
Automated update for Python releases.

---------

Co-authored-by: jjhelmus <1050278+jjhelmus@users.noreply.github.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-12-06 00:03:57 +00:00
konsti c269619b1b
Help rustfmt a little (#17004)
Rustfmt gave up on that block due to the trailing functions. This
unblocks Rustfmt.
2025-12-05 13:35:18 -06:00
konsti eaa1882c51
Tweak language for build backend validation errors (#16720)
Validation errors can also come from files pulled in by
`pyproject.toml`, and `pyproject.toml` can be in a subdirectory.
2025-12-05 15:14:20 +00:00
Zanie Blue 8390b311f8
Update the versioning policy to retain the minor breaking scheme indefinitely (#16710) 2025-12-05 09:05:46 -06:00
konsti b73281d222
Error when built wheel is for the wrong platform (#16074)
Error when a built wheel is for the wrong platform. This can happen
especially when using `--python-platform` or `--python-version` with `uv
pip install`.

Fixes #16019
2025-12-05 16:04:53 +01:00
Zsolt Dollenstein 9f58280eb8
workspace metadata: enable preview warning (#16988) 2025-12-05 10:54:36 +00:00
Zanie Blue f6ad3dcd57
Regenerate the crates.io readmes on release (#16992)
Otherwise, they're stale!
2025-12-04 19:19:36 -06:00
Zsolt Dollenstein fb5de2228c
auth: use the globally constructed client builder (#16979)
## Summary

Instead of each subcommand instantiating its own `BaseClientBuilder`,
let's use the globally constructed one.


## Test Plan

Existing tests.
2025-12-04 14:04:33 -06:00
Zsolt Dollenstein 0c5391a7c7
Add a `uv auth helper --protocol bazel` command (#16886) 2025-12-04 18:56:57 +00:00
liam ee6e3be815
Add brew specific message for `uv self update` (#16838)
Resolves https://github.com/astral-sh/uv/issues/16833

`uv self update` could error with a better message if it knows the user
has installed it with brew. This diff adds an `InstallSource` we can use
the detect where a `uv` binary comes from and augment error messages
with better context. We're only adding brew for now, but it could easily
be extend with other detection heuristics.
2025-12-04 12:44:07 -06:00
samypr100 d3cd94ecaf
Adjust release script to handle uv-trampoline lockfile changes (#16971)
## Summary

Given `bump-workspace-crate-versions.py` will bump all crates, we also
need to update uv-trampoline lockfile references to those new versions
(for uv-static, uv-macros) after
https://github.com/astral-sh/uv/pull/16950.

## Test Plan

Ran release script manually and verify uv-trampoline lockfile is up to
date after the changes from bump-workspace-crate-versions.py
2025-12-04 11:45:18 -06:00
konsti 62bf92132b
Add a 5 min default timeout for deadlocks (#16342)
When a process is running and another calls `uv cache clean` or `uv
cache prune` we currently deadlock - sometimes until the CI timeout
(https://github.com/astral-sh/setup-uv/issues/588). To avoid this, we
add a default 5 min timeout waiting for a lock. 5 min balances allowing
in-progress builds to finish, especially with larger native
dependencies, while also giving timely errors for deadlocks on (remote)
systems.

Commit 1 is a refactoring.

This branch also fixes a problem with the logging where acquired and
released resources currently mismatch:

```
DEBUG Acquired lock for `https://github.com/tqdm/tqdm`
DEBUG Using existing Git source `https://github.com/tqdm/tqdm`
DEBUG Released lock at `C:\Users\Konsti\AppData\Local\uv\cache\git-v0\locks\16bb813afef8edd2`
```
2025-12-04 14:59:04 +01:00
konsti 2748dce860
Fix Pyston tags (#16972)
This was discovered by https://github.com/astral-sh/uv/pull/16074, where
the wrong tag now fails the Pyston integration test.

I'm not sure what the exact schema of the cache tag is, but since the
project is dead, I don't expect any new non-matching versions to follow.
2025-12-04 10:18:41 +01:00
Oshadha Gunawardena 2abe56a357
Clarify `--project` flag help text to indicate project discovery (#16965)
Clarify `--project` flag help text to indicate project discovery

  Update the help text for `--project` from "Run the command within
  the given project directory" to "Discover a project in the given
  directory" to better reflect its actual behavior.

  The `--project` flag affects file discovery (pyproject.toml, uv.toml,
  etc.) but does not change the working directory. Users should use
  `--directory` for changing the working directory.

  Fixes #16718
2025-12-03 12:15:36 -06:00
Tomasz Kramkowski 2f553bfc51
Add a stub `debug` subcommand to `uv pip` announcing its intentional absence (#16966)
## Summary

Inform users who encounter #16879 that `uv pip debug` is currently
intentionally not implemented.

## Test Plan

Added an integration test for the warning, ran the test suite.
2025-12-03 16:10:21 +00:00
Matthijs Kok 539b7368cd
Update Docker integration guide to prefer `COPY` over `ADD` for simple cases (#16883)
## Summary

Docker best practices recommend to use `COPY` when the additional
functionality of `ADD` is not used.

See:
- https://docs.docker.com/build/building/best-practices/#add-or-copy
-
https://www.docker.com/blog/docker-best-practices-understanding-the-differences-between-add-and-copy-instructions-in-dockerfiles/

## Test Plan

Docs only change
2025-12-03 15:16:42 +00:00
Charlie Marsh 99660a8574
Upgrade PyTorch documentation to latest versions (#16970)
## Summary

Point to PyTorch 2.9, Python 3.14, CUDA 12.8, etc.
2025-12-03 07:01:49 -08:00
Matthew Woolf 20ab80ad8f
Update pytorch.md to include information about supporting CUDA 13.0.x (#16957)
## Summary

This change updates the guide about integration with pytorch to include
the CUDA 13.0 variant.

## Test Plan

I generated the documentation and verified that it looked correct.
2025-12-03 06:47:57 -08:00
renovate[bot] 1d8252599a
Update Swatinem/rust-cache action to v2.8.2 (#16128)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [Swatinem/rust-cache](https://redirect.github.com/Swatinem/rust-cache)
| action | patch | `v2.8.0` -> `v2.8.2` |

---

### Release Notes

<details>
<summary>Swatinem/rust-cache (Swatinem/rust-cache)</summary>

###
[`v2.8.2`](https://redirect.github.com/Swatinem/rust-cache/releases/tag/v2.8.2)

[Compare
Source](https://redirect.github.com/Swatinem/rust-cache/compare/v2.8.1...v2.8.2)

##### What's Changed

- ci: address lint findings, add zizmor workflow by
[@&#8203;woodruffw](https://redirect.github.com/woodruffw) in
[#&#8203;262](https://redirect.github.com/Swatinem/rust-cache/pull/262)
- feat: Implement ability to disable adding job ID + rust environment
hashes to cache names by
[@&#8203;Ryan-Brice](https://redirect.github.com/Ryan-Brice) in
[#&#8203;279](https://redirect.github.com/Swatinem/rust-cache/pull/279)
- Don't overwrite env for cargo-metadata call by
[@&#8203;MaeIsBad](https://redirect.github.com/MaeIsBad) in
[#&#8203;285](https://redirect.github.com/Swatinem/rust-cache/pull/285)

##### New Contributors

- [@&#8203;woodruffw](https://redirect.github.com/woodruffw) made their
first contribution in
[#&#8203;262](https://redirect.github.com/Swatinem/rust-cache/pull/262)
- [@&#8203;Ryan-Brice](https://redirect.github.com/Ryan-Brice) made
their first contribution in
[#&#8203;279](https://redirect.github.com/Swatinem/rust-cache/pull/279)
- [@&#8203;MaeIsBad](https://redirect.github.com/MaeIsBad) made their
first contribution in
[#&#8203;285](https://redirect.github.com/Swatinem/rust-cache/pull/285)

**Full Changelog**:
<https://github.com/Swatinem/rust-cache/compare/v2.8.1...v2.8.2>

###
[`v2.8.1`](https://redirect.github.com/Swatinem/rust-cache/releases/tag/v2.8.1)

[Compare
Source](https://redirect.github.com/Swatinem/rust-cache/compare/v2.8.0...v2.8.1)

##### What's Changed

- Set empty `CARGO_ENCODED_RUSTFLAGS` in workspace metadata retrieval by
[@&#8203;ark0f](https://redirect.github.com/ark0f) in
[#&#8203;249](https://redirect.github.com/Swatinem/rust-cache/pull/249)
- chore(deps): update dependencies by
[@&#8203;reneleonhardt](https://redirect.github.com/reneleonhardt) in
[#&#8203;251](https://redirect.github.com/Swatinem/rust-cache/pull/251)
- chore: fix dependabot groups by
[@&#8203;reneleonhardt](https://redirect.github.com/reneleonhardt) in
[#&#8203;253](https://redirect.github.com/Swatinem/rust-cache/pull/253)
- Bump the prd-patch group with 2 updates by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;254](https://redirect.github.com/Swatinem/rust-cache/pull/254)
- chore(dependabot): regenerate and commit dist/ by
[@&#8203;reneleonhardt](https://redirect.github.com/reneleonhardt) in
[#&#8203;257](https://redirect.github.com/Swatinem/rust-cache/pull/257)
- Bump [@&#8203;types/node](https://redirect.github.com/types/node) from
22.16.3 to 24.2.1 in the dev-major group by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;255](https://redirect.github.com/Swatinem/rust-cache/pull/255)
- Bump typescript from 5.8.3 to 5.9.2 in the dev-minor group by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;256](https://redirect.github.com/Swatinem/rust-cache/pull/256)
- Bump actions/setup-node from 4 to 5 in the actions group by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;259](https://redirect.github.com/Swatinem/rust-cache/pull/259)
- Update README.md by
[@&#8203;Propfend](https://redirect.github.com/Propfend) in
[#&#8203;234](https://redirect.github.com/Swatinem/rust-cache/pull/234)
- Bump [@&#8203;types/node](https://redirect.github.com/types/node) from
24.2.1 to 24.3.0 in the dev-minor group by
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot] in
[#&#8203;258](https://redirect.github.com/Swatinem/rust-cache/pull/258)

##### New Contributors

- [@&#8203;ark0f](https://redirect.github.com/ark0f) made their first
contribution in
[#&#8203;249](https://redirect.github.com/Swatinem/rust-cache/pull/249)
- [@&#8203;reneleonhardt](https://redirect.github.com/reneleonhardt)
made their first contribution in
[#&#8203;251](https://redirect.github.com/Swatinem/rust-cache/pull/251)
- [@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot]
made their first contribution in
[#&#8203;254](https://redirect.github.com/Swatinem/rust-cache/pull/254)
- [@&#8203;Propfend](https://redirect.github.com/Propfend) made their
first contribution in
[#&#8203;234](https://redirect.github.com/Swatinem/rust-cache/pull/234)

**Full Changelog**:
<https://github.com/Swatinem/rust-cache/compare/v2...v2.8.1>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM, only on
Monday ( * 0-3 * * 1 ) (UTC), Automerge - At any time (no schedule
defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/uv).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xMzEuOSIsInVwZGF0ZWRJblZlciI6IjQyLjE5LjkiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImludGVybmFsIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 15:10:44 +01:00
konsti 05fa19c440
Use explicit credentials cache instead of global static (#16768)
Fixes https://github.com/astral-sh/uv/issues/16447

Passing this around explicitly uncovers some behaviors where we pass
e.g. the credentials store to reading the lockfile. The changes in this
PR should preserve the existing behavior for now, they only make the
locations we read from more explicit.

Labeling this PR as "Enhancement" instead of "Internal" in case this
changes behavior when it shouldn't have.
2025-12-03 14:51:25 +01:00
Charlie Marsh e00cc8c35f
Add bounds in `uv add --script` (#16954)
## Summary

Closes https://github.com/astral-sh/uv/issues/15544.
2025-12-03 07:37:04 -06:00
Charlie Marsh 932d7b8fce
Filter wheels from PEP 751 files based on `--no-binary` et al in `uv pip compile` (#16956)
## Summary

Like in `uv.lock`, we should omit artifacts that are filtered out by
`--no-binary` or by the target platform tags.

Closes https://github.com/astral-sh/uv/issues/13413.
2025-12-03 06:51:35 -06:00
Charlie Marsh 49b70e7225
Support `--target` and `--prefix` in `uv pip list`, `uv pip freeze`, and `uv pip show` (#16955)
## Summary

Closes https://github.com/astral-sh/uv/issues/15112.
2025-12-03 06:49:11 -06:00
Tomasz Kramkowski b1078fe595
Improve testsuite missing python version panic message (#16960)
## Summary

When the test suite panics due to a missing python version, it now
prints some helpful information to guide users to `cargo run python
install` and `CONTRIBUTING.md`

~~~
---- pip_sync::incompatible_build_constraint stdout ----

thread 'pip_sync::incompatible_build_constraint' (19737) panicked at
crates/uv/tests/it/common/mod.rs:1793:17:
Could not find Python 3.9 for test
Try `cargo run python install` first, or refer to CONTRIBUTING.md

~~~

## Test Plan

Uninstalled python3.9 and ran tests which depended on it.
2025-12-03 06:48:37 -06:00
Tomasz Kramkowski f01366bae8
Noisily allow redundant entries in `tool.uv.build-backend.module-name` (#16928)
## Summary

Fix #16906 by pruning modules or submodules which are already included
(either directly, or through a parent).

Generates warnings when this happens.

Example:

```bash session
$ uv build
Building source distribution (uv build backend)...
warning: Ignoring redundant module name(s): test_lib.bar test_lib test_lib.bar.baz test_lib.baz
Building wheel from source distribution (uv build backend)...
Successfully built dist/test-0.1.0.tar.gz
Successfully built dist/test-0.1.0-py3-none-any.whl
```

## Test Plan

Added some unit tests for the pruning function and one for the whole
build backend. Added an integration test for the warnings. Ran the full
test suite. Manually tested.

The unit test for the function doesn't cater for the fact that it
doesn't guarantee an order at the moment. I think this is fine.

---------

Co-authored-by: konsti <konstin@mailbox.org>
2025-12-03 10:05:28 +00:00
samypr100 ed63be5dab
chore(🧹): const env var usage cleanup (#16950)
## Summary

* Updates existing references to use EnvVars where usage was missing.
* Adds missing entries to env var usages, e.g. new env var declarations
in uv-trampoline, tests, etc.
* Note: this doesn't affect trampoline sizes as the end result is the
same
* Fixes versioning of `UV_HIDE_BUILD_OUTPUT`.

## Test Plan

Existing Tests. Compiled the trampolines locally to verify zero changes
(size, binary).

## Question

Will this complicate the crates publishing release process? I'm not
certain yet if it will be an issue for uv-trampoline (non-workspace
member) to reference a uv workspace member from a bump & release
perspective wrt lock files. If so, I'll revert the uv-trampoline changes
but keep the others.
2025-12-02 22:16:46 -08:00
Zanie Blue d2db06983a
Fix the changelog entry for `UV_HIDE_BUILD_OUTPUT` (#16948) 2025-12-03 01:48:01 +00:00
Zanie Blue 7954b34989
Revert "Temporarily drop `crates-publish` from the release for 0.9.15 re-run (#16945)" (#16947)
This reverts commit 89c411f0ae /
https://github.com/astral-sh/uv/pull/16945 restoring crates publish.
2025-12-02 19:46:55 -06:00
Zanie Blue 5eafae3327
Amend the 0.9.15 changelog (#16946)
See #16944
2025-12-02 18:57:55 -06:00
Zanie Blue 89c411f0ae
Temporarily drop `crates-publish` from the release for 0.9.15 re-run (#16945)
See https://github.com/astral-sh/uv/pull/16944

The `crates.io` publish succeeded and is not idempotent (i.e., it'll
fail on another publish attempt) so we will skip it for a re-run of the
release workflow.
2025-12-02 18:57:47 -06:00
William Woodruff 18a36528ea
Disable PEP 740 attestations for PyPI publishing (#16944)
## Summary

This broke the release and I haven't figured out why yet.

## Test Plan

Blame my past self.

Signed-off-by: William Woodruff <william@astral.sh>
2025-12-02 18:50:20 -06:00
Charlie Marsh eb65f9ff74
Add `UV_HIDE_BUILD_OUTPUT` to omit build logs (#16885)
## Summary

Closes #16804.
2025-12-02 16:43:01 -08:00
Zanie Blue e7af5838bb
Bump version to 0.9.15 (#16942) 2025-12-02 17:48:28 -06:00
Charlie Marsh 87adf14fdf
Allow reading requirements from scripts with HTTP(S) paths (#16891)
## Summary

Closes https://github.com/astral-sh/uv/issues/16890.
2025-12-02 23:42:44 +00:00
Zanie Blue 9fc07c8773
Add CPython 3.14.1 and 3.13.10 (#16941) 2025-12-02 23:36:44 +00:00
Zanie Blue d2162e27e6
Revert "Bump `astral-tl` to v0.7.10 (#16887)" (#16938)
This reverts commit 5f3d46c241 / #16887

Investigating https://github.com/astral-sh/uv/issues/16937
2025-12-02 17:06:09 -06:00
Zanie Blue 99c40f74c5
Link to the uv version in crates.io member READMEs (#16939)
Closes https://github.com/astral-sh/uv/issues/16931
2025-12-02 20:02:22 +00:00
William Woodruff e38cab64ce
Use our org-wide Renovate preset (#16935) 2025-12-02 18:28:55 +00:00
Zanie Blue e4d193a5f8
Fix `uv-trampoline-builder` builds from crates.io by moving bundled executables (#16922)
Closes https://github.com/astral-sh/uv/issues/16836
2025-12-02 07:50:39 -06:00
samypr100 fee7f9d093
Support Git LFS with opt-in (#16143)
## Summary

Follow up to https://github.com/astral-sh/uv/pull/15563
Closes https://github.com/astral-sh/uv/issues/13485

This is a first-pass at adding support for conditional support for Git
LFS between git sources, initial feedback welcome.

e.g.
```
[tool.uv.sources]
test-lfs-repo = { git = "https://github.com/zanieb/test-lfs-repo.git", lfs = true }
```

For context previously a user had to set `UV_GIT_LFS` to have uv fetch
lfs objects on git sources. This env var was all or nothing, meaning you
must always have it set to get consistent behavior and it applied to all
git sources. If you fetched lfs objects at a revision and then turned
off lfs (or vice versa), the git db, corresponding checkout lfs
artifacts would not be updated properly. Similarly, when git source
distributions were built, there would be no distinction between sources
with lfs and without lfs. Hence, it could corrupt the git, sdist, and
archive caches.

In order to support some sources being LFS enabled and other not, this
PR adds a stateful layer roughly similar to how `subdirectory` works but
for `lfs` since the git database, the checkouts and the corresponding
caching layers needed to be LFS aware (requested vs installed). The
caches also had to isolated and treated entirely separate when handling
LFS sources.

Summary
* Adds `lfs = true` or `lfs = false` to git sources in pyproject.toml
* Added `lfs=true` query param / fragments to most relevant url structs
(not parsed as user input)
  * In the case of uv add / uv tool, `--lfs` is supported instead
* `UV_GIT_LFS` environment variable support is still functional for
non-project entrypoints (e.g. uv pip)
* `direct-url.json` now has an custom `git_lfs` entry under VcsInfo
(note, this is not in the spec currently -- see caveats).
* git database and checkouts have an different cache key as the sources
should be treated effectively different for the same rev.
* sdists cache also differ in the cache key of a built distribution if
it was built using LFS enabled revisions to distinguish between non-LFS
same revisions. This ensures the strong assumption for archive-v0 that
an unpacked revision "doesn't change sources" stays valid.

Caveats
* `pylock.toml` import support has not been added via git_lfs=true,
going through the spec it wasn't clear to me it's something we'd support
outside of the env var (for now).
* direct-url struct was modified by adding a non-standard `git_lfs`
field under VcsInfo which may be undersirable although the PEP 610 does
say `Additional fields that would be necessary to support such VCS
SHOULD be prefixed with the VCS command name` which could be interpret
this change as ok.
* There will be a slight lockfile and cache churn for users that use
`UV_GIT_LFS` as all git lockfile entries will get a `lfs=true` fragment.
The cache version does not need an update, but LFS sources will get
their own namespace under git-v0 and sdist-v9/git hence a cache-miss
will occur once but this can be sufficient to label this as breaking for
workflows always setting `UV_GIT_LFS`.

## Test Plan

Some initial tests were added. More tests likely to follow as we reach
consensus on a final approach.

For IT test, we may want to move to use a repo under astral namespace in
order to test lfs functionality.

Manual testing was done for common pathological cases like killing LFS
fetch mid-way, uninstalling LFS after installing an sdist with it and
reinstalling, fetching LFS artifacts in different commits, etc.

PSA: Please ignore the docker build failures as its related to depot
OIDC issues.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
Co-authored-by: konstin <konstin@mailbox.org>
2025-12-02 12:23:51 +00:00
Zanie Blue 5947fb0c83
Support requirements without an extension (#16923)
Duplicate of #16889 since I merged it without realizing it was stacked.

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-12-02 11:02:24 +00:00
Zanie Blue 54f9932362
Bump timeout for macOS tests from 15m -> 20m (#16925)
This has been failing on main since
fbf925ee63
2025-12-02 05:01:43 -06:00
Charlie Marsh c8996d24a1
Cache source reads during resolution (#16888)
## Summary

If you have requirements files that are included multiple times, we can
avoid going back to disk. This also guards against accidental repeated
reads on standard input streams.
2025-12-02 09:38:56 +00:00
Charlie Marsh 2cdbf9e547
Add ROCm 6.4 to `--torch-backend=auto` (#16919)
## Summary

Closes https://github.com/astral-sh/uv/issues/16917.
2025-12-01 20:27:20 -05:00
William Woodruff 3347e196bb
Use `npm ci --ignore-scripts` in update_schemastore.py (#16915) 2025-12-01 23:36:55 +00:00
samypr100 23b8fc9d18
Add a Windows manifest to uv binaries (#16894)
## Summary

Currently we do not include a Windows manifest on the uv binary for
windows builds. This can cause problems such as the one in
https://github.com/astral-sh/uv/issues/16877 which can limit what uv can
do for some Windows operations (e.g. symlinks) that can have
restrictions imposed by the OS unbeknownst to us and make it none
obvious to isolate the issue.

Given we already do this for the `uv-trampoline`, we should also do it
for uv. In the case of uv, I opted for explicit entries in the manifest
rather than using the defaults embed_manifest crate provides which are
not appropriate in all general cases.

The manifest now includes declarations for:
* Explicit "system" codepage declaration to retain backwards compat with
previous uv releases. We should move to utf-8 codepage in the future to
align with `uv-trampoline`, but it's arguably a breaking change in rare
cases. We shouldn't have issues with using utf-8 as we don't really rely
on *A calls to begin with.
* Explicit Windows 10+ support to ensure the executables are not treated
as a legacy, preventing application compatibility layers being wrongly
applied to it all the way back to NT 6.0 (Windows Vista). Note, other
Windows compatibility entries do not imply support, rather they imply
awareness as a preventive measure.
* Long Path support to avoid Windows operations assuming
[MAX_PATH](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation)
applies. This still requires the system to have long paths enabled via
``HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem@LongPathsEnabled``
dword being set to ``1`` (see
[ref](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation#registry-setting-to-enable-long-paths)).
* Standard invoker execution levels for CLI applications to disable UAC
virtualization after including the manifest.

The resulting manifest is the following

```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
    xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
    <assemblyIdentity name="uv" type="win32" version="0.9.13.0"></assemblyIdentity>
    <asmv3:trustInfo>
        <asmv3:security>
            <asmv3:requestedPrivileges>
                <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"></asmv3:requestedExecutionLevel>
            </asmv3:requestedPrivileges>
        </asmv3:security>
    </asmv3:trustInfo>
    <asmv3:application>
        <asmv3:windowsSettings>
            <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
        </asmv3:windowsSettings>
    </asmv3:application>
    <ms_compatibility:compatibility xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" 
        xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <ms_compatibility:application xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1">
            <ms_compatibility:supportedOS xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"></ms_compatibility:supportedOS>
            <ms_compatibility:supportedOS xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"></ms_compatibility:supportedOS>
            <ms_compatibility:supportedOS xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"></ms_compatibility:supportedOS>
            <ms_compatibility:supportedOS xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"></ms_compatibility:supportedOS>
        </ms_compatibility:application>
    </ms_compatibility:compatibility>
</assembly>
```

For reference, here's `cargo`'s manifest from 1.91

```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" 
    xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
        <security>
            <requestedPrivileges>
                <requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
            </requestedPrivileges>
        </security>
    </trustInfo>
    <asmv3:application>
        <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings" 
            xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
            <ws2:longPathAware>true</ws2:longPathAware>
            <activeCodePage>UTF-8</activeCodePage>
        </asmv3:windowsSettings>
    </asmv3:application>
    <ms_compatibility:compatibility xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" 
        xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <ms_compatibility:application xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1">
            <ms_compatibility:supportedOS xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"></ms_compatibility:supportedOS>
            <ms_compatibility:supportedOS xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"></ms_compatibility:supportedOS>
            <ms_compatibility:supportedOS xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"></ms_compatibility:supportedOS>
            <ms_compatibility:supportedOS xmlns:ms_compatibility="urn:schemas-microsoft-com:compatibility.v1" Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"></ms_compatibility:supportedOS>
        </ms_compatibility:application>
    </ms_compatibility:compatibility>
</assembly>
```

Closes #16877

## Test Plan

Before changes on Windows 11 25H2 (without
SeCreateSymbolicLinkPrivilege)

```console
$ uv init
$ uv add jupyterlab-widgets==3.0.16 --link-mode=symlink
...
Resolved 2 packages in [TIME]
error: Failed to install: jupyterlab_widgets-3.0.16-py3-none-any.whl (jupyterlab-widgets==3.0.16)
  Caused by: failed to symlink file from [CACHE_DIR]\archive-v0\aQcqEjLJAkVwuSzohqymc\jupyterlab_widgets-3.0.16.data\data\share\jupyter\labextensions\@jupyter-widgets\jupyterlab-manager\static\packages_base_lib_index_js-webpack_sharing_consume_default_jquery_jquery.5dd13f8e980fa3c50bfe.js to [ROOT]\.venv\Lib\site-packages\jupyterlab_widgets-3.0.16.data\data\share\jupyter\labextensions\@jupyter-widgets\jupyterlab-manager\static\packages_base_lib_index_js-webpack_sharing_consume_default_jquery_jquery.5dd13f8e980fa3c50bfe.js: A required privilege is not held by the client. (os error 1314)
```

Before changes on Windows 11 25H2 (with SeCreateSymbolicLinkPrivilege)

```console
$ uv init
$ uv add jupyterlab-widgets==3.0.16 --link-mode=symlink
...
Resolved 2 packages in [TIME]
error: Failed to install: jupyterlab_widgets-3.0.16-py3-none-any.whl (jupyterlab-widgets==3.0.16)
  Caused by: failed to symlink file from [CACHE_DIR]\archive-v0\aQcqEjLJAkVwuSzohqymc\jupyterlab_widgets-3.0.16.data\data\share\jupyter\labextensions\@jupyter-widgets\jupyterlab-manager\static\packages_base_lib_index_js-webpack_sharing_consume_default_jquery_jquery.5dd13f8e980fa3c50bfe.js to [ROOT]\.venv\Lib\site-packages\jupyterlab_widgets-3.0.16.data\data\share\jupyter\labextensions\@jupyter-widgets\jupyterlab-manager\static\packages_base_lib_index_js-webpack_sharing_consume_default_jquery_jquery.5dd13f8e980fa3c50bfe.js: The parameter is incorrect. (os error 87)
```

After changes on Windows 11 25H2 (with or without
SeCreateSymbolicLinkPrivilege)

```console
$ uv init
$ uv add jupyterlab-widgets==3.0.16 --link-mode=symlink
...
Resolved 2 packages in [TIME]
Installed 1 package in [TIME]
 + jupyterlab-widgets==3.0.16
```
2025-12-01 14:02:35 -06:00
Zanie Blue 082be90177
Use 0o666 permissions for flock files instead of 0o777 (#16845)
This removes executable permissions while retaining global read / global
write.

It's been suggested we should use 0o644 instead, dropping the global
write permissions (i.e., just the owner can write), but since we're
taking an exclusive lock I don't think that would work and we'd regress
the issue that was solved by updating the permissions. I think we'll
need to revisit the locking scheme if that's the goal, but regardless,
this seems like a net improvement.
2025-12-01 12:09:43 -06:00
William Woodruff fbf925ee63
Enable PEP 740 attestations when publishing to PyPI (#16910) 2025-12-01 13:01:33 -05:00
Tomasz Kramkowski efa47adefb
Respect `NO_COLOR` and always show the command as a header when paging `uv help` output (#16908)
## Summary

Fix #16879 and address an additional issue where the pager prompt would
be bold regardless of NO_COLOR.

<img width="1437" height="483" alt="image"
src="https://github.com/user-attachments/assets/7234129a-364b-40c6-834a-57ac34212925"
/>

## Test Plan

Ran the test suite and manually verified against `less` 679 and `busybox` v1.34.1.

Checked with and without `NO_COLOR` on both "normal" `less`, `busybox` `less`, and `more`.
2025-12-01 18:00:43 +00:00
Zsolt Dollenstein 05814f9cd5
Bump version to 0.9.14 (#16909) 2025-12-01 11:52:15 -05:00
Zsolt Dollenstein 6b00d6522c
Attach subcommand to User-Agent string (#16837) 2025-12-01 10:29:54 -05:00
Sidharth Anil 5773b12fa9
Isolating test from accessing global git credential helper config (#16895)
## Summary
Resolves: https://github.com/astral-sh/uv/issues/1980

Added a utility function to TestContext called
**with_git_credential_helper_blocked** that isolates the test from
accessing the credential helper value defined in global/system git
config. It does so, by writing to a file .gitconfig in the temporary
home_dir that is created as part of the TestContext.

## Test Plan
Tested it by running the test
pip_install::install_git_private_https_pat_and_username, and making sure
it doesn't affect the keyring.

## Note:
The commit hash for the uv-private-package seems to have changed.
Kindly, ensure that the modification related to that is correct.
2025-12-01 12:41:21 +01:00
renovate[bot] 825ab78790
Update Rust crate rustls to v0.23.35 (#16902)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [rustls](https://redirect.github.com/rustls/rustls) |
workspace.dependencies | patch | `0.23.29` -> `0.23.35` |

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM, only on
Monday ( * 0-3 * * 1 ) (UTC), Automerge - At any time (no schedule
defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/uv).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 09:31:11 +01:00
renovate[bot] d0931e0ca9
Update Rust crate syn to v2.0.111 (#16903)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [syn](https://redirect.github.com/dtolnay/syn) |
workspace.dependencies | patch | `2.0.108` -> `2.0.111` |

---

### Release Notes

<details>
<summary>dtolnay/syn (syn)</summary>

###
[`v2.0.111`](https://redirect.github.com/dtolnay/syn/releases/tag/2.0.111)

[Compare
Source](https://redirect.github.com/dtolnay/syn/compare/2.0.110...2.0.111)

- Allow first argument of `braced!`, `bracketed!`, `parenthesized!` to
be an otherwise unused variable
([#&#8203;1946](https://redirect.github.com/dtolnay/syn/issues/1946))

###
[`v2.0.110`](https://redirect.github.com/dtolnay/syn/releases/tag/2.0.110)

[Compare
Source](https://redirect.github.com/dtolnay/syn/compare/2.0.109...2.0.110)

- Tweaks to improve build speed
([#&#8203;1939](https://redirect.github.com/dtolnay/syn/issues/1939),
thanks [@&#8203;dishmaker](https://redirect.github.com/dishmaker))
- Make `syn::ext::IdentExt::unraw` available without "parsing" feature
([#&#8203;1940](https://redirect.github.com/dtolnay/syn/issues/1940))
- Support parsing `syn::Meta` followed by `=>`
([#&#8203;1944](https://redirect.github.com/dtolnay/syn/issues/1944))

###
[`v2.0.109`](https://redirect.github.com/dtolnay/syn/releases/tag/2.0.109)

[Compare
Source](https://redirect.github.com/dtolnay/syn/compare/2.0.108...2.0.109)

- Tweaks to improve build speed
([#&#8203;1927](https://redirect.github.com/dtolnay/syn/issues/1927),
[#&#8203;1928](https://redirect.github.com/dtolnay/syn/issues/1928),
[#&#8203;1930](https://redirect.github.com/dtolnay/syn/issues/1930),
[#&#8203;1932](https://redirect.github.com/dtolnay/syn/issues/1932),
[#&#8203;1934](https://redirect.github.com/dtolnay/syn/issues/1934),
thanks [@&#8203;dishmaker](https://redirect.github.com/dishmaker))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM, only on
Monday ( * 0-3 * * 1 ) (UTC), Automerge - At any time (no schedule
defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/uv).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xOS45IiwidXBkYXRlZEluVmVyIjoiNDIuMTkuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 09:26:32 +01:00
renovate[bot] 6d8866a4f3
Update Rust crate mimalloc to v0.1.48 (#16900) 2025-11-30 21:52:04 -05:00
renovate[bot] 8a73958c4a
Update Rust crate open to v5.3.3 (#16901) 2025-11-30 21:50:36 -05:00
renovate[bot] 2c3b907dc0
Update Rust crate goblin to v0.10.4 (#16899) 2025-12-01 02:31:23 +00:00
renovate[bot] 0b70eba917
Update dependency astral-sh/uv to v0.9.13 (#16897) 2025-12-01 02:30:23 +00:00
Charlie Marsh 0ae54dbd8a
Use `UV_WORKING_DIR` for consistency (#16884)
## Summary

Closes https://github.com/astral-sh/uv/issues/16870.
2025-11-30 15:59:05 +00:00
Ben Berry-Allwood c29304aaca
Prefer updating existing `.zshenv` over creating a new one in `tool update-shell` (#16866) 2025-11-29 22:13:05 +00:00
Charlie Marsh 5f3d46c241
Bump `astral-tl` to v0.7.10 (#16887)
## Summary

Enables SIMD for HTML parsing.
2025-11-28 19:49:44 +00:00
Charlie Marsh 5498e4d6f6
Respect `-e` flags in `uv add` (#16882)
## Summary

Closes https://github.com/astral-sh/uv/issues/16872.
2025-11-28 10:03:05 -05:00
Charlie Marsh e2bda1173e
Allow earlier post releases with exclusive ordering (#16881)
## Summary

Given (e.g.) `<0.12.0.post2`, we need to omit pre-releases on `0.12.0`,
but include post-releases.

Closes https://github.com/astral-sh/uv/issues/16868.
2025-11-28 09:50:49 -05:00
konsti 0db41803cd
Update pubgrub to 0.3.3 (#16829)
Maintenance update.
2025-11-28 14:51:24 +01:00
konsti c67a0fdd7b
Support only rendering a specific packse template (#16874)
Support only updating a specific one of the three packse template, to
avoid re-build all three tests each time.
2025-11-28 10:15:37 +01:00
konsti f02b459d04
Support required environments in packse (#16873)
Companion change for https://github.com/astral-sh/packse/pull/293,
motivated by
https://github.com/astral-sh/uv/pull/16824#discussion_r2556176057
2025-11-27 15:17:16 +01:00
William Woodruff eaa4651df0
Use Bearer authentication for pyx publish test (#16864) 2025-11-26 17:05:48 +00:00
Zanie Blue 76d769d7a0
Use `--no-verify` to skip builds during crates.io publish (#16863)
Otherwise, this is quite slow.
2025-11-26 16:34:30 +00:00
renovate[bot] fa6afd5a71
Update aws-actions/configure-aws-credentials action to v5.1.1 (#16573)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[aws-actions/configure-aws-credentials](https://redirect.github.com/aws-actions/configure-aws-credentials)
| action | minor | `v5.0.0` -> `v5.1.1` |

---

### Release Notes

<details>
<summary>aws-actions/configure-aws-credentials
(aws-actions/configure-aws-credentials)</summary>

###
[`v5.1.1`](https://redirect.github.com/aws-actions/configure-aws-credentials/releases/tag/v5.1.1)

[Compare
Source](https://redirect.github.com/aws-actions/configure-aws-credentials/compare/v5.1.0...v5.1.1)

##### Miscellaneous Chores

- release 5.1.1
([56d6a58](56d6a583f0))
- various dependency updates

###
[`v5.1.0`](https://redirect.github.com/aws-actions/configure-aws-credentials/releases/tag/v5.1.0)

[Compare
Source](https://redirect.github.com/aws-actions/configure-aws-credentials/compare/v5.0.0...v5.1.0)

##### Features

- Add global timeout support
([#&#8203;1487](https://redirect.github.com/aws-actions/configure-aws-credentials/issues/1487))
([1584b8b](1584b8b0e2))
- add no-proxy support
([#&#8203;1482](https://redirect.github.com/aws-actions/configure-aws-credentials/issues/1482))
([dde9b22](dde9b22a8e))
- Improve debug logging in retry logic
([#&#8203;1485](https://redirect.github.com/aws-actions/configure-aws-credentials/issues/1485))
([97ef425](97ef425d73))

##### Bug Fixes

- properly expose getProxyForUrl (introduced in
[#&#8203;1482](https://redirect.github.com/aws-actions/configure-aws-credentials/issues/1482))
([#&#8203;1486](https://redirect.github.com/aws-actions/configure-aws-credentials/issues/1486))
([cea4298](cea42985ac))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM, only on
Monday ( * 0-3 * * 1 ) (UTC), Automerge - At any time (no schedule
defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/uv).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQyLjE5LjUiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImludGVybmFsIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-26 16:54:21 +01:00
my1e5 7ca92dcf66
Bump setup-uv action to v7 in docs (#16858)
## Summary

Bumps the setup-uv action to v7 in the GitHub Actions integration docs.

## Test Plan

Built the documentation.
2025-11-26 16:15:02 +01:00
Zanie Blue 735b87004c
Bump version to 0.9.13 (#16862) 2025-11-26 15:12:54 +00:00
Zanie Blue ca62066194
Revert "Allow `--with-requirements` to load extensionless inline-metadata scripts" (#16861)
Reverts https://github.com/astral-sh/uv/pull/16805 /
https://github.com/astral-sh/uv/pull/16744

This also invalidates

- https://github.com/astral-sh/uv/pull/16855
- #16857 

There's probably a way we can make this work, but detecting whether a
file is safe to read repeatedly is non-trivial, `is_file` returns `true`
for `/dev/stdin` on macOS so the approach from #16857 is not sufficient.
I spent a while trying to add `is_char_device` detection for macOS but
unfortunately that didn't work.
2025-11-26 14:57:45 +00:00
Charlie Marsh 4d747f6e86
Avoid eagerly reading input streams in `-r` (#16857)
## Summary

I think the comment should explain it.

Closes https://github.com/astral-sh/uv/issues/16856.
2025-11-25 22:55:08 -05:00
Nicola Soranzo 4bb219f8b9
Fix ``uv pip install -r /dev/stdin`` (#16855)
## Summary

Fix ``uv pip install -r /dev/stdin`` which was broken in uv 0.9.12 by
https://github.com/astral-sh/uv/pull/16805 .

Example of the issue:

```
$ echo "flask" | uv pip install -r /dev/stdin
warning: Requirements file `/dev/stdin` does not contain any dependencies
Audited in 8ms
```

Note that "upstream" ``pip install`` does support `-r /dev/stdin` and
doesn't support `-r -` .

## Test Plan

2 new tests added.
2025-11-26 03:13:32 +00:00
Charlie Marsh bfdee80f6c
Validate URL wheel tags against `Requires-Python` and required environments (#16824)
## Summary

Closes #16818.
2025-11-25 20:05:58 -05:00
Zanie Blue 17c1061676
Fix the links to uv in crates.io member READMEs (#16848) 2025-11-25 18:47:32 +00:00
Zanie Blue d735e27750
Drop unpublished crates from the uv crates.io README (#16847) 2025-11-25 18:46:02 +00:00
Zanie Blue 0fb1233363
Bump version to 0.9.12 (#16840) 2025-11-24 23:22:12 +00:00
William Woodruff 7b3199f07c
Collect and upload PEP 740 attestations during `uv publish` (#16731)
Co-authored-by: konsti <konstin@mailbox.org>
2025-11-24 16:47:15 -05:00
Zanie Blue 4b92f4fde4
Move the "Export" guide to the projects concept section (#16835)
I consider this a bit too advanced to be in the top-level guides
2025-11-24 10:39:52 -06:00
Zanie Blue 666059bd88
Add documentation for intermediate Docker layers in a workspace (#16787) 2025-11-24 15:22:27 +00:00
Zanie Blue 1a6238c835
Disable `test_simultaneous_multiple_create_delete_single_thread` on Windows (#16834)
Closes https://github.com/astral-sh/uv/issues/16096
2025-11-24 15:10:34 +00:00
renovate[bot] d6eb285f02
Update Rust crate indexmap to v2.12.1 (#16830)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [indexmap](https://redirect.github.com/indexmap-rs/indexmap) |
workspace.dependencies | patch | `2.12.0` -> `2.12.1` |

---

### Release Notes

<details>
<summary>indexmap-rs/indexmap (indexmap)</summary>

###
[`v2.12.1`](https://redirect.github.com/indexmap-rs/indexmap/blob/HEAD/RELEASES.md#2121-2025-11-20)

[Compare
Source](https://redirect.github.com/indexmap-rs/indexmap/compare/2.12.0...2.12.1)

- Simplified a lot of internals using `hashbrown`'s new bucket API.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM, only on
Monday ( * 0-3 * * 1 ) (UTC), Automerge - At any time (no schedule
defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/uv).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xNi4xIiwidXBkYXRlZEluVmVyIjoiNDIuMTYuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 12:14:30 +00:00
renovate[bot] 1fa6612c08
Update dependency astral-sh/uv to v0.9.11 (#16825)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [astral-sh/uv](https://redirect.github.com/astral-sh/uv) | uses-with |
patch | `0.9.10` -> `0.9.11` |

---

### Release Notes

<details>
<summary>astral-sh/uv (astral-sh/uv)</summary>

###
[`v0.9.11`](https://redirect.github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0911)

[Compare
Source](https://redirect.github.com/astral-sh/uv/compare/0.9.10...0.9.11)

Released on 2025-11-20.

##### Python

- Add CPython 3.15.0a2

See the [`python-build-standalone` release
notes](https://redirect.github.com/astral-sh/python-build-standalone/releases/tag/20251120)
for details.

##### Enhancements

- Add SBOM support to `uv export`
([#&#8203;16523](https://redirect.github.com/astral-sh/uv/pull/16523))
- Publish to `crates.io`
([#&#8203;16770](https://redirect.github.com/astral-sh/uv/pull/16770))

##### Preview features

- Add `uv workspace list --paths`
([#&#8203;16776](https://redirect.github.com/astral-sh/uv/pull/16776))
- Fix the preview warning on `uv workspace dir`
([#&#8203;16775](https://redirect.github.com/astral-sh/uv/pull/16775))

##### Bug fixes

- Fix `uv init` author serialization via `toml_edit` inline tables
([#&#8203;16778](https://redirect.github.com/astral-sh/uv/pull/16778))
- Fix status messages without TTY
([#&#8203;16785](https://redirect.github.com/astral-sh/uv/pull/16785))
- Preserve end-of-line comment whitespace when editing `pyproject.toml`
([#&#8203;16734](https://redirect.github.com/astral-sh/uv/pull/16734))
- Disable `always-authenticate` when running under Dependabot
([#&#8203;16773](https://redirect.github.com/astral-sh/uv/pull/16773))

##### Documentation

- Document the new behavior for free-threaded python versions
([#&#8203;16781](https://redirect.github.com/astral-sh/uv/pull/16781))
- Improve note about build system in publish guide
([#&#8203;16788](https://redirect.github.com/astral-sh/uv/pull/16788))
- Move do not upload publish note out of the guide into concepts
([#&#8203;16789](https://redirect.github.com/astral-sh/uv/pull/16789))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM, only on
Monday ( * 0-3 * * 1 ) (UTC), Automerge - At any time (no schedule
defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/uv).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xNi4xIiwidXBkYXRlZEluVmVyIjoiNDIuMTYuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 10:10:22 +01:00
renovate[bot] 5936e4324a
Update Rust crate clap to v4.5.53 (#16827)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [clap](https://redirect.github.com/clap-rs/clap) |
workspace.dependencies | patch | `4.5.51` -> `4.5.53` |

---

### Release Notes

<details>
<summary>clap-rs/clap (clap)</summary>

###
[`v4.5.53`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#4553---2025-11-19)

[Compare
Source](https://redirect.github.com/clap-rs/clap/compare/v4.5.52...v4.5.53)

##### Features

- Add `default_values_if`, `default_values_ifs`

###
[`v4.5.52`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#4552---2025-11-17)

[Compare
Source](https://redirect.github.com/clap-rs/clap/compare/v4.5.51...v4.5.52)

##### Fixes

- Don't panic when `args_conflicts_with_subcommands` conflicts with an
`ArgGroup`

</details>

---

### Configuration

📅 **Schedule**: Branch creation - Between 12:00 AM and 03:59 AM, only on
Monday ( * 0-3 * * 1 ) (UTC), Automerge - At any time (no schedule
defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/uv).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4xNi4xIiwidXBkYXRlZEluVmVyIjoiNDIuMTYuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 10:10:03 +01:00
Zanie Blue 7b8240dca9
Generate a README for crate members too (#16812)
We skip members with existing READMEs for now.

Follows #16809 and #16811
2025-11-21 15:44:05 -06:00
Zanie Blue ba46a448d4
Enumerate workspace members in the uv crate README (#16811) 2025-11-21 13:44:59 -06:00
Zanie Blue 1de0cbea94
Use the word "internal" in crate descriptions (#16810)
ref
https://github.com/astral-sh/uv/pull/16809#pullrequestreview-3494007588
2025-11-21 13:22:47 -06:00
Zanie Blue e550f960e8
Add a crates.io README for uv (#16809) 2025-11-21 13:05:26 -06:00
Charlie Marsh f7f159234f
Allow `--with-requirements` to load extensionless inline-metadata scripts (#16805)
Reverts astral-sh/uv#16802
2025-11-21 11:53:41 -05:00
Zanie Blue a8bf05d83b
Add manual release script (#16799)
Unfortunately I need this sometimes
2025-11-21 10:45:08 -06:00
Zanie Blue 563438f13d
Fix documentation links for crates (#16801)
Part of https://github.com/astral-sh/uv/issues/4392

We shouldn't link to PyPI, and dropping the workspace-level
documentation link should mean that we get the auto-generated `docs.rs`
links.
2025-11-21 10:44:58 -06:00
konsti 9b251c5667
Don't pass `DEFAULT_BACKEND` around (#16807)
This seem to have happened cause a function was refactored, and the
static wasn't inlined.
2025-11-21 15:29:47 +00:00
Charlie Marsh 985abdc555
Revert "Allow `--with-requirements` to load extensionless inline-metadata scripts" (#16802)
Reverts astral-sh/uv#16744. I'll un-revert and fix tomorrow.
2025-11-21 04:24:05 +00:00
liam f3cdfac93e
Allow `--with-requirements` to load extensionless inline-metadata scripts (#16744)
Resolves https://github.com/astral-sh/uv/issues/16732

This diff treats extensionless files that contain
[PEP 723](https://peps.python.org/pep-0723/) metadata as scripts when
resolving `--with-requirements`, so inline metadata works even when the
script doesn’t end in `.py`.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-11-20 21:33:41 -05:00
liam b086eabe5f
Prevent `uv export` from overwriting `pyproject.toml` (#16745)
Currently, it's possible for `uv export` to overwrite someones
`pyproject.toml`. This diff simply rejects project files passed in with
`-o`, so we avoid doing that.

---------

Co-authored-by: konstin <konstin@mailbox.org>
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-11-20 21:33:23 -05:00
Zanie Blue d3a9455998
Update the cargo install recommendation to use crates (#16800) 2025-11-20 20:05:46 -06:00
renovate[bot] 6e48fb130d
Update Rust crate reqsign to v0.18.1 (#16755) 2025-11-20 20:59:48 -05:00
584 changed files with 20673 additions and 16014 deletions

View File

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

15
.claude/settings.json Normal file
View File

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

View File

@ -3,7 +3,7 @@
dependencyDashboard: true,
suppressNotifications: ["prEditedNotification"],
extends: [
"config:recommended",
"github>astral-sh/renovate-config",
// For tool versions defined in GitHub Actions:
"customManagers:githubActionsVersions",
],
@ -11,7 +11,6 @@
schedule: ["* 0-3 * * 1"],
semanticCommits: "disabled",
separateMajorMinor: false,
prHourlyLimit: 10,
enabledManagers: ["github-actions", "pre-commit", "cargo", "custom.regex"],
cargo: {
// See https://docs.renovatebot.com/configuration-options/#rangestrategy

View File

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

View File

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

View File

@ -27,6 +27,8 @@ jobs:
outputs:
# Flag that is raised when any code is changed
code: ${{ steps.changed.outputs.code_any_changed }}
# Flag that is raised when uv.schema.json is changed (e.g., in a release PR)
schema: ${{ steps.changed.outputs.schema_changed }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
@ -40,10 +42,16 @@ jobs:
CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha || 'origin/main' }}...HEAD)
CODE_CHANGED=false
SCHEMA_CHANGED=false
while IFS= read -r file; do
# Generated markdown and JSON files are checked during test runs.
if [[ "${file}" =~ ^docs/ && ! "${file}" =~ ^docs/reference/(cli|settings).md && ! "${file}" =~ ^docs/reference/environment.md ]]; then
# Check if the schema file changed (e.g., in a release PR)
if [[ "${file}" == "uv.schema.json" ]]; then
echo "Detected schema change: ${file}"
SCHEMA_CHANGED=true
fi
if [[ "${file}" =~ ^docs/ ]]; then
echo "Skipping ${file} (matches docs/ pattern)"
continue
fi
@ -70,6 +78,7 @@ jobs:
done <<< "${CHANGED_FILES}"
echo "code_any_changed=${CODE_CHANGED}" >> "${GITHUB_OUTPUT}"
echo "schema_changed=${SCHEMA_CHANGED}" >> "${GITHUB_OUTPUT}"
lint:
timeout-minutes: 10
name: "lint"
@ -89,7 +98,7 @@ jobs:
- name: "Install uv"
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
version: "0.9.10"
version: "0.9.13"
- name: "rustfmt"
run: cargo fmt --all --check
@ -135,7 +144,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Check uv_build dependencies"
@ -167,7 +176,7 @@ jobs:
run: |
Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
workspaces: ${{ env.UV_WORKSPACE }}
@ -188,7 +197,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "cargo publish dry-run"
@ -204,11 +213,16 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Generate all"
run: cargo dev generate-all --mode check
run: cargo dev generate-all --mode dry-run
- name: "Check sysconfig mappings"
run: cargo dev generate-sysconfig-metadata --mode check
- name: "Check JSON schema"
if: ${{ needs.determine_changes.outputs.schema == 'true' }}
run: cargo dev generate-json-schema --mode check
cargo-shear:
timeout-minutes: 10
@ -219,7 +233,7 @@ jobs:
with:
persist-credentials: false
- name: "Install cargo shear"
uses: taiki-e/install-action@a416ddeedbd372e614cc1386e8b642692f66865e # v2.57.1
uses: taiki-e/install-action@d850aa816998e5cf15f67a78c7b933f2a5033f8a # v2.63.3
with:
tool: cargo-shear
- run: cargo shear
@ -241,14 +255,14 @@ jobs:
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Install Rust toolchain"
run: rustup show
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
version: "0.9.10"
version: "0.9.13"
- name: "Install required Python versions"
run: uv python install
@ -275,12 +289,13 @@ jobs:
UV_HTTP_RETRIES: 5
run: |
cargo nextest run \
--cargo-profile fast-build \
--features python-patch,native-auth,secret-service \
--workspace \
--status-level skip --failure-output immediate-final --no-fail-fast -j 20 --final-status-level slow
cargo-test-macos:
timeout-minutes: 15
timeout-minutes: 20
needs: determine_changes
# Only run macOS tests on main without opt-in
if: ${{ contains(github.event.pull_request.labels.*.name, 'test:macos') || github.ref == 'refs/heads/main' }}
@ -293,14 +308,14 @@ jobs:
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Install Rust toolchain"
run: rustup show
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
version: "0.9.10"
version: "0.9.13"
- name: "Install required Python versions"
run: uv python install
@ -316,8 +331,9 @@ jobs:
UV_HTTP_RETRIES: 5
run: |
cargo nextest run \
--cargo-profile fast-build \
--no-default-features \
--features python,python-managed,pypi,git,performance,crates-io,native-auth,apple-native \
--features python,python-managed,pypi,git,git-lfs,performance,crates-io,native-auth,apple-native \
--workspace \
--status-level skip --failure-output immediate-final --no-fail-fast -j 12 --final-status-level slow
@ -342,12 +358,12 @@ jobs:
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
version: "0.9.10"
version: "0.9.13"
- name: "Install required Python versions"
run: uv python install
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
workspaces: ${{ env.UV_WORKSPACE }}
@ -371,6 +387,7 @@ jobs:
shell: bash
run: |
cargo nextest run \
--cargo-profile fast-build \
--no-default-features \
--features python,pypi,python-managed,native-auth,windows-native \
--workspace \
@ -400,7 +417,7 @@ jobs:
run: |
Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
workspaces: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
@ -460,7 +477,7 @@ jobs:
- name: Copy Git Repo to Dev Drive
run: |
Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
workspaces: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
- name: "Install Rust toolchain"
@ -478,8 +495,8 @@ jobs:
working-directory: ${{ env.UV_WORKSPACE }}/crates/uv-trampoline
run: |
cargo build --target ${{ matrix.target-arch }}-pc-windows-msvc
cp target/${{ matrix.target-arch }}-pc-windows-msvc/debug/uv-trampoline-console.exe trampolines/uv-trampoline-${{ matrix.target-arch }}-console.exe
cp target/${{ matrix.target-arch }}-pc-windows-msvc/debug/uv-trampoline-gui.exe trampolines/uv-trampoline-${{ matrix.target-arch }}-gui.exe
cp target/${{ matrix.target-arch }}-pc-windows-msvc/debug/uv-trampoline-console.exe ../uv-trampoline-builder/trampolines/uv-trampoline-${{ matrix.target-arch }}-console.exe
cp target/${{ matrix.target-arch }}-pc-windows-msvc/debug/uv-trampoline-gui.exe ../uv-trampoline-builder/trampolines/uv-trampoline-${{ matrix.target-arch }}-gui.exe
- name: "Test new binaries"
working-directory: ${{ env.UV_WORKSPACE }}
run: |
@ -507,9 +524,17 @@ jobs:
persist-credentials: false
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
version: "0.9.10"
version: "0.9.13"
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Generate reference documentation"
run: |
cargo dev generate-options-reference
cargo dev generate-cli-reference
cargo dev generate-env-vars-reference
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
@ -536,18 +561,18 @@ jobs:
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Build"
run: cargo build
run: cargo build --profile no-debug
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: uv-linux-libc-${{ github.sha }}
path: |
./target/debug/uv
./target/debug/uvx
./target/no-debug/uv
./target/no-debug/uvx
retention-days: 1
build-binary-linux-aarch64:
@ -563,18 +588,18 @@ jobs:
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Build"
run: cargo build
run: cargo build --profile no-debug
- 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
./target/no-debug/uv
./target/no-debug/uvx
retention-days: 1
build-binary-linux-musl:
@ -595,18 +620,18 @@ jobs:
sudo apt-get install musl-tools
rustup target add x86_64-unknown-linux-musl
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Build"
run: cargo build --target x86_64-unknown-linux-musl --bin uv --bin uvx
run: cargo build --profile no-debug --target x86_64-unknown-linux-musl --bin uv --bin uvx
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: uv-linux-musl-${{ github.sha }}
path: |
./target/x86_64-unknown-linux-musl/debug/uv
./target/x86_64-unknown-linux-musl/debug/uvx
./target/x86_64-unknown-linux-musl/no-debug/uv
./target/x86_64-unknown-linux-musl/no-debug/uvx
retention-days: 1
build-binary-macos-aarch64:
@ -622,17 +647,17 @@ jobs:
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Build"
run: cargo build --bin uv --bin uvx
run: cargo build --profile no-debug --bin uv --bin uvx
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: uv-macos-aarch64-${{ github.sha }}
path: |
./target/debug/uv
./target/debug/uvx
./target/no-debug/uv
./target/no-debug/uvx
retention-days: 1
build-binary-macos-x86_64:
@ -648,17 +673,17 @@ jobs:
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Build"
run: cargo build --bin uv --bin uvx
run: cargo build --profile no-debug --bin uv --bin uvx
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: uv-macos-x86_64-${{ github.sha }}
path: |
./target/debug/uv
./target/debug/uvx
./target/no-debug/uv
./target/no-debug/uvx
retention-days: 1
build-binary-windows-x86_64:
@ -680,21 +705,21 @@ jobs:
run: |
Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
workspaces: ${{ env.UV_WORKSPACE }}
- name: "Build"
working-directory: ${{ env.UV_WORKSPACE }}
run: cargo build --bin uv --bin uvx
run: cargo build --profile no-debug --bin uv --bin uvx
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: uv-windows-x86_64-${{ github.sha }}
path: |
${{ env.UV_WORKSPACE }}/target/debug/uv.exe
${{ env.UV_WORKSPACE }}/target/debug/uvx.exe
${{ env.UV_WORKSPACE }}/target/no-debug/uv.exe
${{ env.UV_WORKSPACE }}/target/no-debug/uvx.exe
retention-days: 1
build-binary-windows-aarch64:
@ -717,7 +742,7 @@ jobs:
run: |
Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
workspaces: ${{ env.UV_WORKSPACE }}
@ -726,15 +751,15 @@ jobs:
- name: "Build"
working-directory: ${{ env.UV_WORKSPACE }}
run: cargo build --target aarch64-pc-windows-msvc
run: cargo build --profile no-debug --target aarch64-pc-windows-msvc
- name: "Upload binary"
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: uv-windows-aarch64-${{ github.sha }}
path: |
${{ env.UV_WORKSPACE }}/target/aarch64-pc-windows-msvc/debug/uv.exe
${{ env.UV_WORKSPACE }}/target/aarch64-pc-windows-msvc/debug/uvx.exe
${{ env.UV_WORKSPACE }}/target/aarch64-pc-windows-msvc/no-debug/uv.exe
${{ env.UV_WORKSPACE }}/target/aarch64-pc-windows-msvc/no-debug/uvx.exe
retention-days: 1
build-binary-msrv:
@ -758,11 +783,11 @@ jobs:
MSRV: ${{ steps.msrv.outputs.value }}
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- run: cargo +${MSRV} build
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- run: cargo +${MSRV} build --profile no-debug
env:
MSRV: ${{ steps.msrv.outputs.value }}
- run: ./target/debug/uv --version
- run: ./target/no-debug/uv --version
build-binary-freebsd:
needs: determine_changes
@ -775,7 +800,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Cross build"
run: |
# Install cross from `freebsd-firecracker`
@ -783,7 +808,7 @@ jobs:
chmod +x cross
mv cross /usr/local/bin/cross
cross build --target x86_64-unknown-freebsd
cross build --target x86_64-unknown-freebsd --profile no-debug
- name: Test in Firecracker VM
uses: acj/freebsd-firecracker-action@a5a3fc1709c5b5368141a5699f10259aca3cd965 # v0.6.0
@ -797,8 +822,8 @@ jobs:
cat <<EOF > $include_path
target
target/x86_64-unknown-freebsd
target/x86_64-unknown-freebsd/debug
target/x86_64-unknown-freebsd/debug/uv
target/x86_64-unknown-freebsd/no-debug
target/x86_64-unknown-freebsd/no-debug/uv
EOF
rsync -r -e "ssh" \
@ -808,7 +833,7 @@ jobs:
--exclude "*" \
. firecracker:
run-in-vm: |
mv target/x86_64-unknown-freebsd/debug/uv uv
mv target/x86_64-unknown-freebsd/no-debug/uv uv
chmod +x uv
./uv --version
@ -1848,7 +1873,7 @@ jobs:
run: chmod +x ./uv
- name: "Configure AWS credentials"
uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 # v5.0.0
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@ -2042,22 +2067,22 @@ jobs:
# Test the main path (`build_wheel`) through pip
./uv venv -v --seed
./uv run --no-project python -m pip install -v scripts/packages/built-by-uv --find-links crates/uv-build/dist --no-index --no-deps
./uv run --no-project python -m pip install -v test/packages/built-by-uv --find-links crates/uv-build/dist --no-index --no-deps
./uv run --no-project python -c "from built_by_uv import greet; print(greet())"
# Test both `build_wheel` and `build_sdist` through uv
./uv venv -c -v
./uv build -v --force-pep517 scripts/packages/built-by-uv --find-links crates/uv-build/dist --offline
./uv pip install -v scripts/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps
./uv build -v --force-pep517 test/packages/built-by-uv --find-links crates/uv-build/dist --offline
./uv pip install -v test/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps
./uv run --no-project python -c "from built_by_uv import greet; print(greet())"
# Test both `build_wheel` and `build_sdist` through the official `build`
rm -rf scripts/packages/built-by-uv/dist/
rm -rf test/packages/built-by-uv/dist/
./uv venv -c -v
./uv pip install build
# Add the uv binary to PATH for `build` to find
PATH="$(pwd):$PATH" UV_OFFLINE=1 UV_FIND_LINKS=crates/uv-build/dist ./uv run --no-project python -m build -v --installer uv scripts/packages/built-by-uv
./uv pip install -v scripts/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps
PATH="$(pwd):$PATH" UV_OFFLINE=1 UV_FIND_LINKS=crates/uv-build/dist ./uv run --no-project python -m build -v --installer uv test/packages/built-by-uv
./uv pip install -v test/packages/built-by-uv/dist/*.tar.gz --find-links crates/uv-build/dist --offline --no-deps
./uv run --no-project python -c "from built_by_uv import greet; print(greet())"
cache-test-ubuntu:
@ -2906,7 +2931,7 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Install Rust toolchain"
run: rustup show
@ -2921,8 +2946,8 @@ jobs:
sudo apt-get update
sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev
cargo run --bin uv -- venv --cache-dir .cache
cargo run --bin uv -- pip compile scripts/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
cargo run --bin uv -- pip compile scripts/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
cargo run --bin uv -- pip compile test/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
cargo run --bin uv -- pip compile test/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
- name: "Build benchmarks"
run: cargo codspeed build --profile profiling -p uv-bench
@ -2946,7 +2971,7 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Install Rust toolchain"
run: rustup show
@ -2961,8 +2986,8 @@ jobs:
sudo apt-get update
sudo apt-get install -y libsasl2-dev libldap2-dev libkrb5-dev
cargo run --bin uv -- venv --cache-dir .cache
cargo run --bin uv -- pip compile scripts/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
cargo run --bin uv -- pip compile scripts/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
cargo run --bin uv -- pip compile test/requirements/jupyter.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
cargo run --bin uv -- pip compile test/requirements/airflow.in --universal --exclude-newer 2024-08-08 --cache-dir .cache
- name: "Build benchmarks"
run: cargo codspeed build --profile profiling -p uv-bench

View File

@ -27,6 +27,7 @@ jobs:
# - uses: rust-lang/crates-io-auth-action@v1
# id: auth
- name: Publish workspace crates
run: cargo publish --workspace
# Note `--no-verify` is safe because we do a publish dry-run elsewhere in CI
run: cargo publish --workspace --no-verify
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_TOKEN }}

View File

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

View File

@ -18,8 +18,7 @@ jobs:
environment:
name: release
permissions:
# For PyPI's trusted publishing.
id-token: write
id-token: write # For PyPI's trusted publishing
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
@ -37,8 +36,7 @@ jobs:
environment:
name: release
permissions:
# For PyPI's trusted publishing.
id-token: write
id-token: write # For PyPI's trusted publishing
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0

View File

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

5
.gitignore vendored
View File

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

View File

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

View File

@ -3,6 +3,193 @@
<!-- prettier-ignore-start -->
## 0.9.18
Released on 2025-12-16.
### Enhancements
- Add value hints to command line arguments to improve shell completion accuracy ([#17080](https://github.com/astral-sh/uv/pull/17080))
- Improve error handling in `uv publish` ([#17096](https://github.com/astral-sh/uv/pull/17096))
- Improve rendering of multiline error messages ([#17132](https://github.com/astral-sh/uv/pull/17132))
- Support redirects in `uv publish` ([#17130](https://github.com/astral-sh/uv/pull/17130))
- Include Docker images with the alpine version, e.g., `python3.x-alpine3.23` ([#17100](https://github.com/astral-sh/uv/pull/17100))
### Configuration
- Accept `--torch-backend` in `[tool.uv]` ([#17116](https://github.com/astral-sh/uv/pull/17116))
### Performance
- Speed up `uv cache size` ([#17015](https://github.com/astral-sh/uv/pull/17015))
- Initialize S3 signer once ([#17092](https://github.com/astral-sh/uv/pull/17092))
### Bug fixes
- Avoid panics due to reads on failed requests ([#17098](https://github.com/astral-sh/uv/pull/17098))
- Enforce latest-version in `@latest` requests ([#17114](https://github.com/astral-sh/uv/pull/17114))
- Explicitly set `EntryType` for file entries in tar ([#17043](https://github.com/astral-sh/uv/pull/17043))
- Ignore `pyproject.toml` index username in lockfile comparison ([#16995](https://github.com/astral-sh/uv/pull/16995))
- Relax error when using `uv add` with `UV_GIT_LFS` set ([#17127](https://github.com/astral-sh/uv/pull/17127))
- Support file locks on ExFAT on macOS ([#17115](https://github.com/astral-sh/uv/pull/17115))
- Change schema for `exclude-newer` into optional string ([#17121](https://github.com/astral-sh/uv/pull/17121))
### Documentation
- Drop arm musl caveat from Docker documentation ([#17111](https://github.com/astral-sh/uv/pull/17111))
- Fix version reference in resolver example ([#17085](https://github.com/astral-sh/uv/pull/17085))
- Better documentation for `exclude-newer*` ([#17079](https://github.com/astral-sh/uv/pull/17079))
## 0.9.17
Released on 2025-12-09.
### Enhancements
- Add `torch-tensorrt` and `torchao` to the PyTorch list ([#17053](https://github.com/astral-sh/uv/pull/17053))
- Add hint for misplaced `--verbose` in `uv tool run` ([#17020](https://github.com/astral-sh/uv/pull/17020))
- Add support for relative durations in `exclude-newer` (a.k.a., dependency cooldowns) ([#16814](https://github.com/astral-sh/uv/pull/16814))
- Add support for relocatable nushell activation script ([#17036](https://github.com/astral-sh/uv/pull/17036))
### Bug fixes
- Respect dropped (but explicit) indexes in dependency groups ([#17012](https://github.com/astral-sh/uv/pull/17012))
### Documentation
- Improve `source-exclude` reference docs ([#16832](https://github.com/astral-sh/uv/pull/16832))
- Recommend `UV_NO_DEV` in Docker installs ([#17030](https://github.com/astral-sh/uv/pull/17030))
- Update `UV_VERSION` in docs for GitLab CI/CD ([#17040](https://github.com/astral-sh/uv/pull/17040))
## 0.9.16
Released on 2025-12-06.
### Python
- Add CPython 3.14.2
- Add CPython 3.13.11
### Enhancements
- Add a 5m default timeout to acquiring file locks to fail faster on deadlock ([#16342](https://github.com/astral-sh/uv/pull/16342))
- Add a stub `debug` subcommand to `uv pip` announcing its intentional absence ([#16966](https://github.com/astral-sh/uv/pull/16966))
- Add bounds in `uv add --script` ([#16954](https://github.com/astral-sh/uv/pull/16954))
- Add brew specific message for `uv self update` ([#16838](https://github.com/astral-sh/uv/pull/16838))
- Error when built wheel is for the wrong platform ([#16074](https://github.com/astral-sh/uv/pull/16074))
- Filter wheels from PEP 751 files based on `--no-binary` et al in `uv pip compile` ([#16956](https://github.com/astral-sh/uv/pull/16956))
- Support `--target` and `--prefix` in `uv pip list`, `uv pip freeze`, and `uv pip show` ([#16955](https://github.com/astral-sh/uv/pull/16955))
- Tweak language for build backend validation errors ([#16720](https://github.com/astral-sh/uv/pull/16720))
- Use explicit credentials cache instead of global static ([#16768](https://github.com/astral-sh/uv/pull/16768))
- Enable SIMD in HTML parsing ([#17010](https://github.com/astral-sh/uv/pull/17010))
### Preview features
- Fix missing preview warning in `uv workspace metadata` ([#16988](https://github.com/astral-sh/uv/pull/16988))
- Add a `uv auth helper --protocol bazel` command ([#16886](https://github.com/astral-sh/uv/pull/16886))
### Bug fixes
- Fix Pyston wheel compatibility tags ([#16972](https://github.com/astral-sh/uv/pull/16972))
- Allow redundant entries in `tool.uv.build-backend.module-name` but emit warnings ([#16928](https://github.com/astral-sh/uv/pull/16928))
- Fix infinite loop in non-attribute re-treats during HTML parsing ([#17010](https://github.com/astral-sh/uv/pull/17010))
### Documentation
- Clarify `--project` flag help text to indicate project discovery ([#16965](https://github.com/astral-sh/uv/pull/16965))
- Regenerate the crates.io READMEs on release ([#16992](https://github.com/astral-sh/uv/pull/16992))
- Update Docker integration guide to prefer `COPY` over `ADD` for simple cases ([#16883](https://github.com/astral-sh/uv/pull/16883))
- Update PyTorch documentation to include information about supporting CUDA 13.0.x ([#16957](https://github.com/astral-sh/uv/pull/16957))
- Update the versioning policy ([#16710](https://github.com/astral-sh/uv/pull/16710))
- Upgrade PyTorch documentation to latest versions ([#16970](https://github.com/astral-sh/uv/pull/16970))
## 0.9.15
Released on 2025-12-02.
### Python
- Add CPython 3.14.1
- Add CPython 3.13.10
### Enhancements
- Add ROCm 6.4 to `--torch-backend=auto` ([#16919](https://github.com/astral-sh/uv/pull/16919))
- Add a Windows manifest to uv binaries ([#16894](https://github.com/astral-sh/uv/pull/16894))
- Add LFS toggle to Git sources ([#16143](https://github.com/astral-sh/uv/pull/16143))
- Cache source reads during resolution ([#16888](https://github.com/astral-sh/uv/pull/16888))
- Allow reading requirements from scripts without an extension ([#16923](https://github.com/astral-sh/uv/pull/16923))
- Allow reading requirements from scripts with HTTP(S) paths ([#16891](https://github.com/astral-sh/uv/pull/16891))
### Configuration
- Add `UV_HIDE_BUILD_OUTPUT` to omit build logs ([#16885](https://github.com/astral-sh/uv/pull/16885))
### Bug fixes
- Fix `uv-trampoline-builder` builds from crates.io by moving bundled executables ([#16922](https://github.com/astral-sh/uv/pull/16922))
- Respect `NO_COLOR` and always show the command as a header when paging `uv help` output ([#16908](https://github.com/astral-sh/uv/pull/16908))
- Use `0o666` permissions for flock files instead of `0o777` ([#16845](https://github.com/astral-sh/uv/pull/16845))
- Revert "Bump `astral-tl` to v0.7.10 (#16887)" to narrow down a regression causing hangs in metadata retrieval ([#16938](https://github.com/astral-sh/uv/pull/16938))
### Documentation
- Link to the uv version in crates.io member READMEs ([#16939](https://github.com/astral-sh/uv/pull/16939))
## 0.9.14
Released on 2025-12-01.
### Performance
- Bump `astral-tl` to v0.7.10 to enable SIMD for HTML parsing ([#16887](https://github.com/astral-sh/uv/pull/16887))
### Bug fixes
- Allow earlier post releases with exclusive ordering ([#16881](https://github.com/astral-sh/uv/pull/16881))
- Prefer updating existing `.zshenv` over creating a new one in `tool update-shell` ([#16866](https://github.com/astral-sh/uv/pull/16866))
- Respect `-e` flags in `uv add` ([#16882](https://github.com/astral-sh/uv/pull/16882))
### Enhancements
- Attach subcommand to User-Agent string ([#16837](https://github.com/astral-sh/uv/pull/16837))
- Prefer `UV_WORKING_DIR` over `UV_WORKING_DIRECTORY` for consistency ([#16884](https://github.com/astral-sh/uv/pull/16884))
## 0.9.13
Released on 2025-11-26.
### Bug fixes
- Revert "Allow `--with-requirements` to load extensionless inline-metadata scripts" to fix reading of requirements files from streams ([#16861](https://github.com/astral-sh/uv/pull/16861))
- Validate URL wheel tags against `Requires-Python` and required environments ([#16824](https://github.com/astral-sh/uv/pull/16824))
### Documentation
- Drop unpublished crates from the uv crates.io README ([#16847](https://github.com/astral-sh/uv/pull/16847))
- Fix the links to uv in crates.io member READMEs ([#16848](https://github.com/astral-sh/uv/pull/16848))
## 0.9.12
Released on 2025-11-24.
### Enhancements
- Allow `--with-requirements` to load extensionless inline-metadata scripts ([#16744](https://github.com/astral-sh/uv/pull/16744))
- Collect and upload PEP 740 attestations during `uv publish` ([#16731](https://github.com/astral-sh/uv/pull/16731))
- Prevent `uv export` from overwriting `pyproject.toml` ([#16745](https://github.com/astral-sh/uv/pull/16745))
### Documentation
- Add a crates.io README for uv ([#16809](https://github.com/astral-sh/uv/pull/16809))
- Add documentation for intermediate Docker layers in a workspace ([#16787](https://github.com/astral-sh/uv/pull/16787))
- Enumerate workspace members in the uv crate README ([#16811](https://github.com/astral-sh/uv/pull/16811))
- Fix documentation links for crates ([#16801](https://github.com/astral-sh/uv/pull/16801))
- Generate a crates.io README for uv workspace members ([#16812](https://github.com/astral-sh/uv/pull/16812))
- Move the "Export" guide to the projects concept section ([#16835](https://github.com/astral-sh/uv/pull/16835))
- Update the cargo install recommendation to use crates ([#16800](https://github.com/astral-sh/uv/pull/16800))
- Use the word "internal" in crate descriptions ([#16810](https://github.com/astral-sh/uv/pull/16810))
## 0.9.11
Released on 2025-11-20.
@ -316,25 +503,25 @@ There are no breaking changes to [`uv_build`](https://docs.astral.sh/uv/concepts
### Breaking changes
- **Python 3.14 is now the default stable version**
The default Python version has changed from 3.13 to 3.14. This applies to Python version installation when no Python version is requested, e.g., `uv python install`. By default, uv will use the system Python version if present, so this may not cause changes to general use of uv. For example, if Python 3.13 is installed already, then `uv venv` will use that version. If no Python versions are installed on a machine and automatic downloads are enabled, uv will now use 3.14 instead of 3.13, e.g., for `uv venv` or `uvx python`. This change will not affect users who are using a `.python-version` file to pin to a specific Python version.
- **Allow use of free-threaded variants in Python 3.14+ without explicit opt-in** ([#16142](https://github.com/astral-sh/uv/pull/16142))
Previously, free-threaded variants of Python were considered experimental and required explicit opt-in (i.e., with `3.14t`) for usage. Now uv will allow use of free-threaded Python 3.14+ interpreters without explicit selection. The GIL-enabled build of Python will still be preferred, e.g., when performing an installation with `uv python install 3.14`. However, e.g., if a free-threaded interpreter comes before a GIL-enabled build on the `PATH`, it will be used. This change does not apply to free-threaded Python 3.13 interpreters, which will continue to require opt-in.
- **Use Python 3.14 stable Docker images** ([#16150](https://github.com/astral-sh/uv/pull/16150))
Previously, the Python 3.14 images had an `-rc` suffix, e.g., `python:3.14-rc-alpine` or
`python:3.14-rc-trixie`. Now, the `-rc` suffix has been removed to match the stable
[upstream images](https://hub.docker.com/_/python). The `-rc` images tags will no longer be
updated. This change should not break existing workflows.
- **Upgrade Alpine Docker image to Alpine 3.22**
Previously, the `uv:alpine` Docker image was based on Alpine 3.21. Now, this image is based on Alpine 3.22. The previous image can be recovered with `uv:alpine3.21` and will continue to be updated until a future release.
- **Upgrade Debian Docker images to Debian 13 "Trixie"**
Previously, the `uv:debian` and `uv:debian-slim` Docker images were based on Debian 12 "Bookworm". Now, these images are based on Debian 13 "Trixie". The previous images can be recovered with `uv:bookworm` and `uv:bookworm-slim` and will continue to be updated until a future release.
- **Fix incorrect output path when a trailing `/` is used in `uv build`** ([#15133](https://github.com/astral-sh/uv/pull/15133))
When using `uv build` in a workspace, the artifacts are intended to be written to a `dist` directory in the workspace root. A bug caused workspace root determination to fail when the input path included a trailing `/` causing the `dist` directory to be placed in the child directory. This bug has been fixed in this release. For example, `uv build child/` is used, the output path will now be in `<workspace root>/dist/` rather than `<workspace root>/child/dist/`.
### Python

125
CODE_OF_CONDUCT.md Normal file
View File

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

View File

@ -86,6 +86,13 @@ cargo test --package <package> --test <test> -- <test_name> -- --exact
cargo insta review
```
### Git and Git LFS
A subset of uv tests require both [Git](https://git-scm.com) and [Git LFS](https://git-lfs.com/) to
execute properly.
These tests can be disabled by turning off either `git` or `git-lfs` uv features.
### Local testing
You can invoke your development version of uv with `cargo run -- <args>`. For example:
@ -95,6 +102,15 @@ cargo run -- venv
cargo run -- pip install requests
```
## Crate structure
Rust does not allow circular dependencies between crates. To visualize the crate hierarchy, install
[cargo-depgraph](https://github.com/jplatte/cargo-depgraph) and graphviz, then run:
```shell
cargo depgraph --dedup-transitive-deps --workspace-only | dot -Tpng > graph.png
```
## Running inside a Docker container
Source distributions can run arbitrary code on build and can make unwanted modifications to your
@ -120,7 +136,7 @@ Please refer to Ruff's
it applies to uv, too.
We provide diverse sets of requirements for testing and benchmarking the resolver in
`scripts/requirements` and for the installer in `scripts/requirements/compiled`.
`test/requirements` and for the installer in `test/requirements/compiled`.
You can use `scripts/benchmark` to benchmark predefined workloads between uv versions and with other
tools, e.g., from the `scripts/benchmark` directory:
@ -131,7 +147,7 @@ uv run resolver \
--poetry \
--benchmark \
resolve-cold \
../scripts/requirements/trio.in
../test/requirements/trio.in
```
### Analyzing concurrency
@ -141,7 +157,7 @@ visualize parallel requests and find any spots where uv is CPU-bound. Example us
`uv-dev` respectively:
```shell
RUST_LOG=uv=info TRACING_DURATIONS_FILE=target/traces/jupyter.ndjson cargo run --features tracing-durations-export --profile profiling -- pip compile scripts/requirements/jupyter.in
RUST_LOG=uv=info TRACING_DURATIONS_FILE=target/traces/jupyter.ndjson cargo run --features tracing-durations-export --profile profiling -- pip compile test/requirements/jupyter.in
```
```shell

278
Cargo.lock generated
View File

@ -45,9 +45,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "ambient-id"
version = "0.0.6"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36b48a3b1ad866e5034859be45edd1ebba2f097289c8a34b61623c76f10480f3"
checksum = "b8cad022ed72ad2176498be1c097bb46e598193e92f3491ea0766980edeee168"
dependencies = [
"astral-reqwest-middleware",
"reqwest",
@ -198,9 +198,9 @@ dependencies = [
[[package]]
name = "astral-pubgrub"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf544aa6f110fc4bfdffcc68b1ebeb1b39ce6188e3b9e057d7a5ed4fa865e7be"
checksum = "d6cb15b4f5096a3a1b41fdc2736a1c33d87c78f34d3c1ec2b669e766edadd559"
dependencies = [
"astral-version-ranges",
"indexmap",
@ -248,9 +248,12 @@ dependencies = [
[[package]]
name = "astral-tl"
version = "0.7.9"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b5af1203c9c635c62edcbdaa36ee54b17f84809f7769912d356c35f9a6cd7"
checksum = "d90933ffb0f97e2fc2e0de21da9d3f20597b804012d199843a6fe7c2810d28f3"
dependencies = [
"memchr",
]
[[package]]
name = "astral-tokio-tar"
@ -749,9 +752,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.51"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
dependencies = [
"clap_builder",
"clap_derive",
@ -759,9 +762,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.51"
version = "4.5.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
dependencies = [
"anstream",
"anstyle",
@ -1035,6 +1038,15 @@ dependencies = [
"itertools 0.10.5",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
@ -1249,7 +1261,17 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.59.0",
"windows-sys 0.61.0",
]
[[package]]
name = "diskus"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec07379c016f78e7ddcd953663b9ed17928ff384928d34d824ed7e463bd3d908"
dependencies = [
"crossbeam-channel",
"rayon",
]
[[package]]
@ -1314,6 +1336,12 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "embed-manifest"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94cdc65b1cf9e871453ce2f86f5aaec24ff2eaa36a1fa3e02e441dddc3613b99"
[[package]]
name = "encode_unicode"
version = "1.0.0"
@ -1776,9 +1804,9 @@ dependencies = [
[[package]]
name = "goblin"
version = "0.10.3"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51876e3748c4a347fe65b906f2b1ae46a1e55a497b22c94c1f4f2c469ff7673a"
checksum = "4db6758c546e6f81f265638c980e5e84dfbda80cfd8e89e02f83454c8e8124bd"
dependencies = [
"log",
"plain",
@ -1831,9 +1859,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
@ -2158,12 +2186,12 @@ checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf"
[[package]]
name = "indexmap"
version = "2.12.0"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
"equivalent",
"hashbrown 0.16.0",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
@ -2329,7 +2357,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.52.0",
"windows-sys 0.61.0",
]
[[package]]
@ -2426,9 +2454,9 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libmimalloc-sys"
version = "0.1.43"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d"
checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870"
dependencies = [
"cc",
"libc",
@ -2617,9 +2645,9 @@ dependencies = [
[[package]]
name = "mimalloc"
version = "0.1.47"
version = "0.1.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40"
checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8"
dependencies = [
"libmimalloc-sys",
]
@ -2667,7 +2695,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08"
dependencies = [
"windows-sys 0.60.2",
"windows-sys 0.61.0",
]
[[package]]
@ -2860,9 +2888,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]]
name = "open"
version = "5.3.2"
version = "5.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95"
checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc"
dependencies = [
"is-wsl",
"libc",
@ -3434,9 +3462,9 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.10.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
"either",
"rayon-core",
@ -3444,9 +3472,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.1"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@ -3563,9 +3591,9 @@ dependencies = [
[[package]]
name = "reqsign"
version = "0.18.0"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9a80170eaab619a5dfa6428b0596c0cb85734bfa36b717a764a16abc3456a7b"
checksum = "ea386ba750000b6e59f760a08bdcca9461809b95e6f8f209ce5724056802824f"
dependencies = [
"reqsign-aws-v4",
"reqsign-command-execute-tokio",
@ -3576,9 +3604,9 @@ dependencies = [
[[package]]
name = "reqsign-aws-v4"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50993dfb45a89b82dba66b2251984baad70e1b3c502db980f077f095615a26e"
checksum = "4510c2a3e42b653cf788d560a3d54b0ae4cc315a62aaba773554f18319c0db0b"
dependencies = [
"anyhow",
"async-trait",
@ -3598,9 +3626,9 @@ dependencies = [
[[package]]
name = "reqsign-command-execute-tokio"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84d74ecba4eca9afdd6bebf86d71e442dd4acab3fcec4461f3b96b33cf6a16b5"
checksum = "38b53d033600f533135afec8e97be99c80fcf8177f6285da6c7300955d5377a1"
dependencies = [
"async-trait",
"reqsign-core",
@ -3609,9 +3637,9 @@ dependencies = [
[[package]]
name = "reqsign-core"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f2f07d63648c81c8dbccc19e8e10ef8d57daafb8174e4c2a75f14f33fe8c5ec"
checksum = "39da118ccf3bdb067ac6cc40136fec99bc5ba418cbd388dc88e4ce0e5d0b1423"
dependencies = [
"anyhow",
"async-trait",
@ -3631,9 +3659,9 @@ dependencies = [
[[package]]
name = "reqsign-file-read-tokio"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "262eb485bb6e8213b13ef10e86ef8613539fb03daa2123b57d96675f784b15b6"
checksum = "669ea66036266a9ac371d2e63cc7d345e69994da0168b4e6f3487fe21e126f76"
dependencies = [
"anyhow",
"async-trait",
@ -3643,17 +3671,19 @@ dependencies = [
[[package]]
name = "reqsign-http-send-reqwest"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ff9bb6507b23175dbda8a91ae1a0ad2317471f6ee117e500d1cf6b9ed1eeb0b"
checksum = "46186bce769674f9200ad01af6f2ca42de3e819ddc002fff1edae135bfb6cd9c"
dependencies = [
"anyhow",
"async-trait",
"bytes",
"futures-channel",
"http",
"http-body-util",
"reqsign-core",
"reqwest",
"wasm-bindgen-futures",
]
[[package]]
@ -3894,9 +3924,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.29"
version = "0.23.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1"
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
dependencies = [
"once_cell",
"ring",
@ -3930,9 +3960,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
version = "0.103.4"
version = "0.103.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
dependencies = [
"ring",
"rustls-pki-types",
@ -4367,9 +4397,9 @@ dependencies = [
[[package]]
name = "spdx"
version = "0.12.0"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cf87c0efffc158b9dde4d6e0567a43e4383adc4c949e687a2039732db2f23a"
checksum = "35107b1c818f4e9cb9e6c4444ca560ba03b4ee1288dcecc6d7830c2023a7609e"
dependencies = [
"smallvec",
]
@ -4498,9 +4528,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.108"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
"proc-macro2",
"quote",
@ -5376,7 +5406,7 @@ dependencies = [
[[package]]
name = "uv"
version = "0.9.11"
version = "0.9.18"
dependencies = [
"anstream",
"anyhow",
@ -5391,13 +5421,14 @@ dependencies = [
"clap",
"console 0.16.1",
"ctrlc",
"diskus",
"dotenvy",
"dunce",
"embed-manifest",
"filetime",
"flate2",
"fs-err",
"futures",
"h2",
"http",
"ignore",
"indexmap",
@ -5494,7 +5525,7 @@ dependencies = [
[[package]]
name = "uv-auth"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"arcstr",
@ -5537,7 +5568,7 @@ dependencies = [
[[package]]
name = "uv-bench"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"codspeed-criterion-compat",
@ -5564,7 +5595,7 @@ dependencies = [
[[package]]
name = "uv-bin-install"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"astral-reqwest-middleware",
"astral-reqwest-retry",
@ -5588,7 +5619,7 @@ dependencies = [
[[package]]
name = "uv-build"
version = "0.9.11"
version = "0.9.18"
dependencies = [
"anstream",
"anyhow",
@ -5600,7 +5631,7 @@ dependencies = [
[[package]]
name = "uv-build-backend"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"astral-version-ranges",
"base64 0.22.1",
@ -5616,7 +5647,7 @@ dependencies = [
"schemars",
"serde",
"sha2",
"spdx 0.12.0",
"spdx 0.13.2",
"tar",
"tempfile",
"thiserror 2.0.17",
@ -5640,7 +5671,7 @@ dependencies = [
[[package]]
name = "uv-build-frontend"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anstream",
"fs-err",
@ -5657,6 +5688,7 @@ dependencies = [
"tokio",
"toml_edit 0.23.7",
"tracing",
"uv-auth",
"uv-cache-key",
"uv-configuration",
"uv-distribution",
@ -5677,7 +5709,7 @@ dependencies = [
[[package]]
name = "uv-cache"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"clap",
"fs-err",
@ -5687,6 +5719,7 @@ dependencies = [
"same-file",
"serde",
"tempfile",
"thiserror 2.0.17",
"tracing",
"uv-cache-info",
"uv-cache-key",
@ -5702,7 +5735,7 @@ dependencies = [
[[package]]
name = "uv-cache-info"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"fs-err",
@ -5719,7 +5752,7 @@ dependencies = [
[[package]]
name = "uv-cache-key"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"hex",
"memchr",
@ -5731,7 +5764,7 @@ dependencies = [
[[package]]
name = "uv-cli"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anstream",
"anyhow",
@ -5763,7 +5796,7 @@ dependencies = [
[[package]]
name = "uv-client"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"astral-reqwest-middleware",
@ -5826,7 +5859,7 @@ dependencies = [
[[package]]
name = "uv-configuration"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"clap",
@ -5855,14 +5888,14 @@ dependencies = [
[[package]]
name = "uv-console"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"console 0.16.1",
]
[[package]]
name = "uv-dev"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anstream",
"anyhow",
@ -5911,7 +5944,7 @@ dependencies = [
[[package]]
name = "uv-dirs"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"assert_fs",
"etcetera",
@ -5923,7 +5956,7 @@ dependencies = [
[[package]]
name = "uv-dispatch"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"futures",
@ -5955,7 +5988,7 @@ dependencies = [
[[package]]
name = "uv-distribution"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"astral-reqwest-middleware",
@ -5985,6 +6018,7 @@ dependencies = [
"uv-distribution-filename",
"uv-distribution-types",
"uv-extract",
"uv-flags",
"uv-fs",
"uv-git",
"uv-git-types",
@ -6003,7 +6037,7 @@ dependencies = [
[[package]]
name = "uv-distribution-filename"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"insta",
"memchr",
@ -6020,7 +6054,7 @@ dependencies = [
[[package]]
name = "uv-distribution-types"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"arcstr",
"astral-version-ranges",
@ -6060,7 +6094,7 @@ dependencies = [
[[package]]
name = "uv-extract"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"astral-tokio-tar",
"astral_async_zip",
@ -6090,14 +6124,14 @@ dependencies = [
[[package]]
name = "uv-flags"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"bitflags 2.9.4",
]
[[package]]
name = "uv-fs"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"backon",
"dunce",
@ -6112,20 +6146,23 @@ dependencies = [
"schemars",
"serde",
"tempfile",
"thiserror 2.0.17",
"tokio",
"tracing",
"uv-static",
"windows 0.59.0",
]
[[package]]
name = "uv-git"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"astral-reqwest-middleware",
"cargo-util",
"dashmap",
"fs-err",
"owo-colors",
"reqwest",
"thiserror 2.0.17",
"tokio",
@ -6138,23 +6175,25 @@ dependencies = [
"uv-redacted",
"uv-static",
"uv-version",
"uv-warnings",
"which",
]
[[package]]
name = "uv-git-types"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"serde",
"thiserror 2.0.17",
"tracing",
"url",
"uv-redacted",
"uv-static",
]
[[package]]
name = "uv-globfilter"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anstream",
"fs-err",
@ -6171,7 +6210,7 @@ dependencies = [
[[package]]
name = "uv-install-wheel"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"assert_fs",
@ -6211,7 +6250,7 @@ dependencies = [
[[package]]
name = "uv-installer"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"async-channel",
@ -6252,7 +6291,7 @@ dependencies = [
[[package]]
name = "uv-keyring"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"async-trait",
"byteorder",
@ -6269,7 +6308,7 @@ dependencies = [
[[package]]
name = "uv-logging"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"jiff",
"owo-colors",
@ -6279,7 +6318,7 @@ dependencies = [
[[package]]
name = "uv-macros"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"proc-macro2",
"quote",
@ -6289,7 +6328,7 @@ dependencies = [
[[package]]
name = "uv-metadata"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"astral_async_zip",
"fs-err",
@ -6306,7 +6345,7 @@ dependencies = [
[[package]]
name = "uv-normalize"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"rkyv",
"schemars",
@ -6316,7 +6355,7 @@ dependencies = [
[[package]]
name = "uv-once-map"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"dashmap",
"futures",
@ -6325,14 +6364,14 @@ dependencies = [
[[package]]
name = "uv-options-metadata"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"serde",
]
[[package]]
name = "uv-pep440"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"astral-version-ranges",
"indoc",
@ -6346,7 +6385,7 @@ dependencies = [
[[package]]
name = "uv-pep508"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"arcstr",
"astral-version-ranges",
@ -6375,7 +6414,7 @@ dependencies = [
[[package]]
name = "uv-performance-memory-allocator"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"mimalloc",
"tikv-jemallocator",
@ -6383,7 +6422,7 @@ dependencies = [
[[package]]
name = "uv-platform"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"fs-err",
"goblin",
@ -6400,7 +6439,7 @@ dependencies = [
[[package]]
name = "uv-platform-tags"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"insta",
"memchr",
@ -6413,7 +6452,7 @@ dependencies = [
[[package]]
name = "uv-preview"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"bitflags 2.9.4",
"thiserror 2.0.17",
@ -6422,14 +6461,16 @@ dependencies = [
[[package]]
name = "uv-publish"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"ambient-id",
"anstream",
"astral-reqwest-middleware",
"astral-reqwest-retry",
"astral-tokio-tar",
"async-compression",
"base64 0.22.1",
"fastrand",
"fs-err",
"futures",
"glob",
@ -6457,14 +6498,15 @@ dependencies = [
"uv-redacted",
"uv-static",
"uv-warnings",
"wiremock",
]
[[package]]
name = "uv-pypi-types"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"hashbrown 0.16.0",
"hashbrown 0.16.1",
"indexmap",
"insta",
"itertools 0.14.0",
@ -6493,7 +6535,7 @@ dependencies = [
[[package]]
name = "uv-python"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"assert_fs",
@ -6555,7 +6597,7 @@ dependencies = [
[[package]]
name = "uv-redacted"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"ref-cast",
"schemars",
@ -6566,7 +6608,7 @@ dependencies = [
[[package]]
name = "uv-requirements"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"configparser",
@ -6601,7 +6643,7 @@ dependencies = [
[[package]]
name = "uv-requirements-txt"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"assert_fs",
@ -6634,7 +6676,7 @@ dependencies = [
[[package]]
name = "uv-resolver"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"arcstr",
"astral-pubgrub",
@ -6644,7 +6686,7 @@ dependencies = [
"either",
"fs-err",
"futures",
"hashbrown 0.16.0",
"hashbrown 0.16.1",
"indexmap",
"insta",
"itertools 0.14.0",
@ -6699,7 +6741,7 @@ dependencies = [
[[package]]
name = "uv-scripts"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"fs-err",
"indoc",
@ -6723,7 +6765,7 @@ dependencies = [
[[package]]
name = "uv-settings"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"clap",
"fs-err",
@ -6758,12 +6800,13 @@ dependencies = [
[[package]]
name = "uv-shell"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"fs-err",
"nix",
"same-file",
"temp-env",
"tempfile",
"tracing",
"uv-fs",
@ -6774,7 +6817,7 @@ dependencies = [
[[package]]
name = "uv-small-str"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"arcstr",
"rkyv",
@ -6784,7 +6827,7 @@ dependencies = [
[[package]]
name = "uv-state"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"fs-err",
"tempfile",
@ -6793,14 +6836,14 @@ dependencies = [
[[package]]
name = "uv-static"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"uv-macros",
]
[[package]]
name = "uv-tool"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"fs-err",
"pathdiff",
@ -6829,7 +6872,7 @@ dependencies = [
[[package]]
name = "uv-torch"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"clap",
"either",
@ -6849,7 +6892,7 @@ dependencies = [
[[package]]
name = "uv-trampoline-builder"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"assert_cmd",
@ -6866,7 +6909,7 @@ dependencies = [
[[package]]
name = "uv-types"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"dashmap",
@ -6888,11 +6931,11 @@ dependencies = [
[[package]]
name = "uv-version"
version = "0.9.11"
version = "0.9.18"
[[package]]
name = "uv-virtualenv"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"console 0.16.1",
"fs-err",
@ -6914,16 +6957,19 @@ dependencies = [
[[package]]
name = "uv-warnings"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anstream",
"anyhow",
"indoc",
"insta",
"owo-colors",
"rustc-hash",
]
[[package]]
name = "uv-workspace"
version = "0.0.1"
version = "0.0.8"
dependencies = [
"anyhow",
"assert_fs",

View File

@ -11,72 +11,71 @@ resolver = "2"
edition = "2024"
rust-version = "1.89"
homepage = "https://pypi.org/project/uv/"
documentation = "https://pypi.org/project/uv/"
repository = "https://github.com/astral-sh/uv"
authors = ["uv"]
license = "MIT OR Apache-2.0"
[workspace.dependencies]
uv-auth = { version = "0.0.1", path = "crates/uv-auth" }
uv-bin-install = { version = "0.0.1", path = "crates/uv-bin-install" }
uv-build-backend = { version = "0.0.1", path = "crates/uv-build-backend" }
uv-build-frontend = { version = "0.0.1", path = "crates/uv-build-frontend" }
uv-cache = { version = "0.0.1", path = "crates/uv-cache" }
uv-cache-info = { version = "0.0.1", path = "crates/uv-cache-info" }
uv-cache-key = { version = "0.0.1", path = "crates/uv-cache-key" }
uv-cli = { version = "0.0.1", path = "crates/uv-cli" }
uv-client = { version = "0.0.1", path = "crates/uv-client" }
uv-configuration = { version = "0.0.1", path = "crates/uv-configuration" }
uv-console = { version = "0.0.1", path = "crates/uv-console" }
uv-dirs = { version = "0.0.1", path = "crates/uv-dirs" }
uv-dispatch = { version = "0.0.1", path = "crates/uv-dispatch" }
uv-distribution = { version = "0.0.1", path = "crates/uv-distribution" }
uv-distribution-filename = { version = "0.0.1", path = "crates/uv-distribution-filename" }
uv-distribution-types = { version = "0.0.1", path = "crates/uv-distribution-types" }
uv-extract = { version = "0.0.1", path = "crates/uv-extract" }
uv-flags = { version = "0.0.1", path = "crates/uv-flags" }
uv-fs = { version = "0.0.1", path = "crates/uv-fs", features = ["serde", "tokio"] }
uv-git = { version = "0.0.1", path = "crates/uv-git" }
uv-git-types = { version = "0.0.1", path = "crates/uv-git-types" }
uv-globfilter = { version = "0.0.1", path = "crates/uv-globfilter" }
uv-install-wheel = { version = "0.0.1", path = "crates/uv-install-wheel", default-features = false }
uv-installer = { version = "0.0.1", path = "crates/uv-installer" }
uv-keyring = { version = "0.0.1", path = "crates/uv-keyring" }
uv-logging = { version = "0.0.1", path = "crates/uv-logging" }
uv-macros = { version = "0.0.1", path = "crates/uv-macros" }
uv-metadata = { version = "0.0.1", path = "crates/uv-metadata" }
uv-normalize = { version = "0.0.1", path = "crates/uv-normalize" }
uv-once-map = { version = "0.0.1", path = "crates/uv-once-map" }
uv-options-metadata = { version = "0.0.1", path = "crates/uv-options-metadata" }
uv-performance-memory-allocator = { version = "0.0.1", path = "crates/uv-performance-memory-allocator" }
uv-pep440 = { version = "0.0.1", path = "crates/uv-pep440", features = ["tracing", "rkyv", "version-ranges"] }
uv-pep508 = { version = "0.0.1", path = "crates/uv-pep508", features = ["non-pep508-extensions"] }
uv-platform = { version = "0.0.1", path = "crates/uv-platform" }
uv-platform-tags = { version = "0.0.1", path = "crates/uv-platform-tags" }
uv-preview = { version = "0.0.1", path = "crates/uv-preview" }
uv-publish = { version = "0.0.1", path = "crates/uv-publish" }
uv-pypi-types = { version = "0.0.1", path = "crates/uv-pypi-types" }
uv-python = { version = "0.0.1", path = "crates/uv-python" }
uv-redacted = { version = "0.0.1", path = "crates/uv-redacted" }
uv-requirements = { version = "0.0.1", path = "crates/uv-requirements" }
uv-requirements-txt = { version = "0.0.1", path = "crates/uv-requirements-txt" }
uv-resolver = { version = "0.0.1", path = "crates/uv-resolver" }
uv-scripts = { version = "0.0.1", path = "crates/uv-scripts" }
uv-settings = { version = "0.0.1", path = "crates/uv-settings" }
uv-shell = { version = "0.0.1", path = "crates/uv-shell" }
uv-small-str = { version = "0.0.1", path = "crates/uv-small-str" }
uv-state = { version = "0.0.1", path = "crates/uv-state" }
uv-static = { version = "0.0.1", path = "crates/uv-static" }
uv-tool = { version = "0.0.1", path = "crates/uv-tool" }
uv-torch = { version = "0.0.1", path = "crates/uv-torch" }
uv-trampoline-builder = { version = "0.0.1", path = "crates/uv-trampoline-builder" }
uv-types = { version = "0.0.1", path = "crates/uv-types" }
uv-version = { version = "0.9.11", path = "crates/uv-version" }
uv-virtualenv = { version = "0.0.1", path = "crates/uv-virtualenv" }
uv-warnings = { version = "0.0.1", path = "crates/uv-warnings" }
uv-workspace = { version = "0.0.1", path = "crates/uv-workspace" }
uv-auth = { version = "0.0.8", path = "crates/uv-auth" }
uv-bin-install = { version = "0.0.8", path = "crates/uv-bin-install" }
uv-build-backend = { version = "0.0.8", path = "crates/uv-build-backend" }
uv-build-frontend = { version = "0.0.8", path = "crates/uv-build-frontend" }
uv-cache = { version = "0.0.8", path = "crates/uv-cache" }
uv-cache-info = { version = "0.0.8", path = "crates/uv-cache-info" }
uv-cache-key = { version = "0.0.8", path = "crates/uv-cache-key" }
uv-cli = { version = "0.0.8", path = "crates/uv-cli" }
uv-client = { version = "0.0.8", path = "crates/uv-client" }
uv-configuration = { version = "0.0.8", path = "crates/uv-configuration" }
uv-console = { version = "0.0.8", path = "crates/uv-console" }
uv-dirs = { version = "0.0.8", path = "crates/uv-dirs" }
uv-dispatch = { version = "0.0.8", path = "crates/uv-dispatch" }
uv-distribution = { version = "0.0.8", path = "crates/uv-distribution" }
uv-distribution-filename = { version = "0.0.8", path = "crates/uv-distribution-filename" }
uv-distribution-types = { version = "0.0.8", path = "crates/uv-distribution-types" }
uv-extract = { version = "0.0.8", path = "crates/uv-extract" }
uv-flags = { version = "0.0.8", path = "crates/uv-flags" }
uv-fs = { version = "0.0.8", path = "crates/uv-fs", features = ["serde", "tokio"] }
uv-git = { version = "0.0.8", path = "crates/uv-git" }
uv-git-types = { version = "0.0.8", path = "crates/uv-git-types" }
uv-globfilter = { version = "0.0.8", path = "crates/uv-globfilter" }
uv-install-wheel = { version = "0.0.8", path = "crates/uv-install-wheel", default-features = false }
uv-installer = { version = "0.0.8", path = "crates/uv-installer" }
uv-keyring = { version = "0.0.8", path = "crates/uv-keyring" }
uv-logging = { version = "0.0.8", path = "crates/uv-logging" }
uv-macros = { version = "0.0.8", path = "crates/uv-macros" }
uv-metadata = { version = "0.0.8", path = "crates/uv-metadata" }
uv-normalize = { version = "0.0.8", path = "crates/uv-normalize" }
uv-once-map = { version = "0.0.8", path = "crates/uv-once-map" }
uv-options-metadata = { version = "0.0.8", path = "crates/uv-options-metadata" }
uv-performance-memory-allocator = { version = "0.0.8", path = "crates/uv-performance-memory-allocator" }
uv-pep440 = { version = "0.0.8", path = "crates/uv-pep440", features = ["tracing", "rkyv", "version-ranges"] }
uv-pep508 = { version = "0.0.8", path = "crates/uv-pep508", features = ["non-pep508-extensions"] }
uv-platform = { version = "0.0.8", path = "crates/uv-platform" }
uv-platform-tags = { version = "0.0.8", path = "crates/uv-platform-tags" }
uv-preview = { version = "0.0.8", path = "crates/uv-preview" }
uv-publish = { version = "0.0.8", path = "crates/uv-publish" }
uv-pypi-types = { version = "0.0.8", path = "crates/uv-pypi-types" }
uv-python = { version = "0.0.8", path = "crates/uv-python" }
uv-redacted = { version = "0.0.8", path = "crates/uv-redacted" }
uv-requirements = { version = "0.0.8", path = "crates/uv-requirements" }
uv-requirements-txt = { version = "0.0.8", path = "crates/uv-requirements-txt" }
uv-resolver = { version = "0.0.8", path = "crates/uv-resolver" }
uv-scripts = { version = "0.0.8", path = "crates/uv-scripts" }
uv-settings = { version = "0.0.8", path = "crates/uv-settings" }
uv-shell = { version = "0.0.8", path = "crates/uv-shell" }
uv-small-str = { version = "0.0.8", path = "crates/uv-small-str" }
uv-state = { version = "0.0.8", path = "crates/uv-state" }
uv-static = { version = "0.0.8", path = "crates/uv-static" }
uv-tool = { version = "0.0.8", path = "crates/uv-tool" }
uv-torch = { version = "0.0.8", path = "crates/uv-torch" }
uv-trampoline-builder = { version = "0.0.8", path = "crates/uv-trampoline-builder" }
uv-types = { version = "0.0.8", path = "crates/uv-types" }
uv-version = { version = "0.9.18", path = "crates/uv-version" }
uv-virtualenv = { version = "0.0.8", path = "crates/uv-virtualenv" }
uv-warnings = { version = "0.0.8", path = "crates/uv-warnings" }
uv-workspace = { version = "0.0.8", path = "crates/uv-workspace" }
ambient-id = { version = "0.0.6", default-features = false, features = ["astral-reqwest-middleware"] }
ambient-id = { version = "0.0.7", default-features = false, features = ["astral-reqwest-middleware"] }
anstream = { version = "0.6.15" }
anyhow = { version = "1.0.89" }
arcstr = { version = "1.2.0" }
@ -104,11 +103,14 @@ ctrlc = { version = "3.4.5" }
cyclonedx-bom = { version = "0.8.0" }
dashmap = { version = "6.1.0" }
data-encoding = { version = "2.6.0" }
diskus = { version = "0.9.0", default-features = false }
dotenvy = { version = "0.15.7" }
dunce = { version = "1.0.5" }
either = { version = "1.13.0" }
encoding_rs_io = { version = "0.1.7" }
embed-manifest = { version = "1.5.0" }
etcetera = { version = "0.11.0" }
fastrand = { version = "2.3.0" }
flate2 = { version = "1.0.33", default-features = false, features = ["zlib-rs"] }
fs-err = { version = "3.0.0", features = ["tokio"] }
futures = { version = "0.3.30" }
@ -141,7 +143,7 @@ percent-encoding = { version = "2.3.1" }
petgraph = { version = "0.8.0" }
proc-macro2 = { version = "1.0.86" }
procfs = { version = "0.17.0", default-features = false, features = ["flate2"] }
pubgrub = { version = "0.3.2" , package = "astral-pubgrub" }
pubgrub = { version = "0.3.3" , package = "astral-pubgrub" }
quote = { version = "1.0.37" }
rayon = { version = "1.10.0" }
ref-cast = { version = "1.0.24" }
@ -168,7 +170,7 @@ serde-untagged = { version = "0.1.6" }
serde_json = { version = "1.0.128" }
sha2 = { version = "0.10.8" }
smallvec = { version = "1.13.2" }
spdx = { version = "0.12.0" }
spdx = { version = "0.13.0" }
syn = { version = "2.0.77" }
sys-info = { version = "0.9.1" }
tar = { version = "0.4.43" }
@ -176,8 +178,8 @@ target-lexicon = { version = "0.13.0" }
tempfile = { version = "3.14.0" }
textwrap = { version = "0.16.1" }
thiserror = { version = "2.0.0" }
astral-tl = { version = "0.7.9" }
tokio = { version = "1.40.0", features = ["fs", "io-util", "macros", "process", "rt", "signal", "sync"] }
astral-tl = { version = "0.7.11" }
tokio = { version = "1.40.0", features = ["fs", "io-util", "macros", "process", "rt", "signal", "sync", "time"] }
tokio-stream = { version = "0.1.16" }
tokio-util = { version = "0.7.12", features = ["compat", "io"] }
toml = { version = "0.9.2", features = ["fast_hash"] }
@ -223,9 +225,6 @@ test-log = { version = "0.2.16", features = ["trace"], default-features = false
tokio-rustls = { version = "0.26.2", default-features = false }
whoami = { version = "1.6.0" }
[workspace.metadata.cargo-shear]
ignored = ["flate2", "xz2", "h2", "uv-performance-memory-allocator"]
[workspace.lints.rust]
unsafe_code = "warn"
unreachable_pub = "warn"
@ -311,12 +310,21 @@ strip = false
debug = "full"
lto = false
# Profile for fast test execution: Skip debug info generation, and
# apply basic optimization, which speed up build and running tests.
[profile.fast-build]
inherits = "dev"
opt-level = 1
debug = 0
strip = "debuginfo"
# Profile for faster builds: Skip debug info generation, for faster
# builds of smaller binaries.
[profile.no-debug]
inherits = "dev"
debug = 0
strip = "debuginfo"
# Profile to build a minimally sized binary for uv-build
[profile.minimal-size]
inherits = "release"

View File

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

View File

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

View File

@ -1,11 +1,10 @@
[package]
name = "uv-auth"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

13
crates/uv-auth/README.md Normal file
View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-auth
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-auth).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -11,8 +11,8 @@ use url::Url;
use uv_once_map::OnceMap;
use uv_redacted::DisplaySafeUrl;
use crate::Realm;
use crate::credentials::{Authentication, Username};
use crate::{Credentials, Realm};
type FxOnceMap<K, V> = OnceMap<K, V, BuildHasherDefault<FxHasher>>;
@ -33,6 +33,7 @@ impl Display for FetchUrl {
}
}
#[derive(Debug)] // All internal types are redacted.
pub struct CredentialsCache {
/// A cache per realm and username
realms: RwLock<FxHashMap<(Realm, Username), Arc<Authentication>>>,
@ -58,6 +59,27 @@ impl CredentialsCache {
}
}
/// Populate the global authentication store with credentials on a URL, if there are any.
///
/// Returns `true` if the store was updated.
pub fn store_credentials_from_url(&self, url: &DisplaySafeUrl) -> bool {
if let Some(credentials) = Credentials::from_url(url) {
trace!("Caching credentials for {url}");
self.insert(url, Arc::new(Authentication::from(credentials)));
true
} else {
false
}
}
/// Populate the global authentication store with credentials on a URL, if there are any.
///
/// Returns `true` if the store was updated.
pub fn store_credentials(&self, url: &DisplaySafeUrl, credentials: Credentials) {
trace!("Caching credentials for {url}");
self.insert(url, Arc::new(Authentication::from(credentials)));
}
/// Return the credentials that should be used for a realm and username, if any.
pub(crate) fn get_realm(
&self,

View File

@ -1,12 +1,5 @@
use std::sync::{Arc, LazyLock};
use tracing::trace;
use uv_redacted::DisplaySafeUrl;
use crate::credentials::Authentication;
pub use access_token::AccessToken;
use cache::CredentialsCache;
pub use cache::CredentialsCache;
pub use credentials::{Credentials, Username};
pub use index::{AuthPolicy, Index, Indexes};
pub use keyring::KeyringProvider;
@ -29,32 +22,3 @@ mod pyx;
mod realm;
mod service;
mod store;
// TODO(zanieb): Consider passing a cache explicitly throughout
/// Global authentication cache for a uv invocation
///
/// This is used to share credentials across uv clients.
pub(crate) static CREDENTIALS_CACHE: LazyLock<CredentialsCache> =
LazyLock::new(CredentialsCache::default);
/// Populate the global authentication store with credentials on a URL, if there are any.
///
/// Returns `true` if the store was updated.
pub fn store_credentials_from_url(url: &DisplaySafeUrl) -> bool {
if let Some(credentials) = Credentials::from_url(url) {
trace!("Caching credentials for {url}");
CREDENTIALS_CACHE.insert(url, Arc::new(Authentication::from(credentials)));
true
} else {
false
}
}
/// Populate the global authentication store with credentials on a URL, if there are any.
///
/// Returns `true` if the store was updated.
pub fn store_credentials(url: &DisplaySafeUrl, credentials: Credentials) {
trace!("Caching credentials for {url}");
CREDENTIALS_CACHE.insert(url, Arc::new(Authentication::from(credentials)));
}

View File

@ -17,13 +17,13 @@ use crate::credentials::Authentication;
use crate::providers::{HuggingFaceProvider, S3EndpointProvider};
use crate::pyx::{DEFAULT_TOLERANCE_SECS, PyxTokenStore};
use crate::{
AccessToken, CREDENTIALS_CACHE, CredentialsCache, KeyringProvider,
AccessToken, CredentialsCache, KeyringProvider,
cache::FetchUrl,
credentials::{Credentials, Username},
index::{AuthPolicy, Indexes},
realm::Realm,
};
use crate::{Index, TextCredentialStore, TomlCredentialError};
use crate::{Index, TextCredentialStore};
/// Cached check for whether we're running in Dependabot.
static IS_DEPENDABOT: LazyLock<bool> =
@ -65,49 +65,55 @@ impl NetrcMode {
/// Strategy for loading text-based credential files.
enum TextStoreMode {
Automatic(LazyLock<Option<TextCredentialStore>>),
Automatic(tokio::sync::OnceCell<Option<TextCredentialStore>>),
Enabled(TextCredentialStore),
Disabled,
}
impl Default for TextStoreMode {
fn default() -> Self {
// TODO(zanieb): Reconsider this pattern. We're just mirroring the [`NetrcMode`]
// implementation for now.
Self::Automatic(LazyLock::new(|| {
let path = TextCredentialStore::default_file()
.inspect_err(|err| {
warn!("Failed to determine credentials file path: {}", err);
})
.ok()?;
match TextCredentialStore::read(&path) {
Ok((store, _lock)) => {
debug!("Loaded credential file {}", path.display());
Some(store)
}
Err(TomlCredentialError::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
debug!("No credentials file found at {}", path.display());
None
}
Err(err) => {
warn!(
"Failed to load credentials from {}: {}",
path.display(),
err
);
None
}
}
}))
Self::Automatic(tokio::sync::OnceCell::new())
}
}
impl TextStoreMode {
async fn load_default_store() -> Option<TextCredentialStore> {
let path = TextCredentialStore::default_file()
.inspect_err(|err| {
warn!("Failed to determine credentials file path: {}", err);
})
.ok()?;
match TextCredentialStore::read(&path).await {
Ok((store, _lock)) => {
debug!("Loaded credential file {}", path.display());
Some(store)
}
Err(err)
if err
.as_io_error()
.is_some_and(|err| err.kind() == std::io::ErrorKind::NotFound) =>
{
debug!("No credentials file found at {}", path.display());
None
}
Err(err) => {
warn!(
"Failed to load credentials from {}: {}",
path.display(),
err
);
None
}
}
}
/// Get the parsed credential store, if enabled.
fn get(&self) -> Option<&TextCredentialStore> {
async fn get(&self) -> Option<&TextCredentialStore> {
match self {
Self::Automatic(lock) => lock.as_ref(),
// TODO(zanieb): Reconsider this pattern. We're just mirroring the [`NetrcMode`]
// implementation for now.
Self::Automatic(lock) => lock.get_or_init(Self::load_default_store).await.as_ref(),
Self::Enabled(store) => Some(store),
Self::Disabled => None,
}
@ -123,6 +129,15 @@ enum TokenState {
Initialized(Option<AccessToken>),
}
#[derive(Clone)]
enum S3CredentialState {
/// The S3 credential state has not yet been initialized.
Uninitialized,
/// The S3 credential state has been initialized, with either a signer or `None` if
/// no S3 endpoint is configured.
Initialized(Option<Arc<Authentication>>),
}
/// A middleware that adds basic authentication to requests.
///
/// Uses a cache to propagate credentials from previously seen requests and
@ -131,7 +146,8 @@ pub struct AuthMiddleware {
netrc: NetrcMode,
text_store: TextStoreMode,
keyring: Option<KeyringProvider>,
cache: Option<CredentialsCache>,
/// Global authentication cache for a uv invocation to share credentials across uv clients.
cache: Arc<CredentialsCache>,
/// Auth policies for specific URLs.
indexes: Indexes,
/// Set all endpoints as needing authentication. We never try to send an
@ -143,21 +159,31 @@ pub struct AuthMiddleware {
pyx_token_store: Option<PyxTokenStore>,
/// Tokens to use for persistent credentials.
pyx_token_state: Mutex<TokenState>,
/// Cached S3 credentials to avoid running the credential helper multiple times.
s3_credential_state: Mutex<S3CredentialState>,
preview: Preview,
}
impl Default for AuthMiddleware {
fn default() -> Self {
Self::new()
}
}
impl AuthMiddleware {
pub fn new() -> Self {
Self {
netrc: NetrcMode::default(),
text_store: TextStoreMode::default(),
keyring: None,
cache: None,
// TODO(konsti): There shouldn't be a credential cache without that in the initializer.
cache: Arc::new(CredentialsCache::default()),
indexes: Indexes::new(),
only_authenticated: false,
base_client: None,
pyx_token_store: None,
pyx_token_state: Mutex::new(TokenState::Uninitialized),
s3_credential_state: Mutex::new(S3CredentialState::Uninitialized),
preview: Preview::default(),
}
}
@ -205,7 +231,14 @@ impl AuthMiddleware {
/// Configure the [`CredentialsCache`] to use.
#[must_use]
pub fn with_cache(mut self, cache: CredentialsCache) -> Self {
self.cache = Some(cache);
self.cache = Arc::new(cache);
self
}
/// Configure the [`CredentialsCache`] to use from an existing [`Arc`].
#[must_use]
pub fn with_cache_arc(mut self, cache: Arc<CredentialsCache>) -> Self {
self.cache = cache;
self
}
@ -238,17 +271,9 @@ impl AuthMiddleware {
self
}
/// Get the configured authentication store.
///
/// If not set, the global store is used.
/// Global authentication cache for a uv invocation to share credentials across uv clients.
fn cache(&self) -> &CredentialsCache {
self.cache.as_ref().unwrap_or(&CREDENTIALS_CACHE)
}
}
impl Default for AuthMiddleware {
fn default() -> Self {
Self::new()
&self.cache
}
}
@ -665,13 +690,26 @@ impl AuthMiddleware {
return Some(credentials);
}
if let Some(credentials) = S3EndpointProvider::credentials_for(url, self.preview)
.map(Authentication::from)
.map(Arc::new)
{
debug!("Found S3 credentials for {url}");
self.cache().fetches.done(key, Some(credentials.clone()));
return Some(credentials);
if S3EndpointProvider::is_s3_endpoint(url, self.preview) {
let mut s3_state = self.s3_credential_state.lock().await;
// If the S3 credential state is uninitialized, initialize it.
let credentials = match &*s3_state {
S3CredentialState::Uninitialized => {
trace!("Initializing S3 credentials for {url}");
let signer = S3EndpointProvider::create_signer();
let credentials = Arc::new(Authentication::from(signer));
*s3_state = S3CredentialState::Initialized(Some(credentials.clone()));
Some(credentials)
}
S3CredentialState::Initialized(credentials) => credentials.clone(),
};
if let Some(credentials) = credentials {
debug!("Found S3 credentials for {url}");
self.cache().fetches.done(key, Some(credentials.clone()));
return Some(credentials);
}
}
// If this is a known URL, authenticate it via the token store.
@ -729,9 +767,16 @@ impl AuthMiddleware {
Some(credentials)
// Text credential store support.
} else if let Some(credentials) = self.text_store.get().and_then(|text_store| {
} else if let Some(credentials) = self.text_store.get().await.and_then(|text_store| {
debug!("Checking text store for credentials for {url}");
text_store.get_credentials(url, credentials.as_ref().and_then(|credentials| credentials.username())).cloned()
text_store
.get_credentials(
url,
credentials
.as_ref()
.and_then(|credentials| credentials.username()),
)
.cloned()
}) {
debug!("Found credentials in plaintext store for {url}");
Some(credentials)
@ -747,10 +792,16 @@ impl AuthMiddleware {
if let Some(index) = index {
// N.B. The native store performs an exact look up right now, so we use the root
// URL of the index instead of relying on prefix-matching.
debug!("Checking native store for credentials for index URL {}{}", display_username, index.root_url);
debug!(
"Checking native store for credentials for index URL {}{}",
display_username, index.root_url
);
native_store.fetch(&index.root_url, username).await
} else {
debug!("Checking native store for credentials for URL {}{}", display_username, url);
debug!(
"Checking native store for credentials for URL {}{}",
display_username, url
);
native_store.fetch(url, username).await
}
// TODO(zanieb): We should have a realm fallback here too
@ -771,10 +822,18 @@ impl AuthMiddleware {
// always authenticate.
if let Some(username) = credentials.and_then(|credentials| credentials.username()) {
if let Some(index) = index {
debug!("Checking keyring for credentials for index URL {}@{}", username, index.url);
keyring.fetch(DisplaySafeUrl::ref_cast(&index.url), Some(username)).await
debug!(
"Checking keyring for credentials for index URL {}@{}",
username, index.url
);
keyring
.fetch(DisplaySafeUrl::ref_cast(&index.url), Some(username))
.await
} else {
debug!("Checking keyring for credentials for full URL {}@{}", username, url);
debug!(
"Checking keyring for credentials for full URL {}@{}",
username, url
);
keyring.fetch(url, Some(username)).await
}
} else if matches!(auth_policy, AuthPolicy::Always) {
@ -783,12 +842,16 @@ impl AuthMiddleware {
"Checking keyring for credentials for index URL {} without username due to `authenticate = always`",
index.url
);
keyring.fetch(DisplaySafeUrl::ref_cast(&index.url), None).await
keyring
.fetch(DisplaySafeUrl::ref_cast(&index.url), None)
.await
} else {
None
}
} else {
debug!("Skipping keyring fetch for {url} without username; use `authenticate = always` to force");
debug!(
"Skipping keyring fetch for {url} without username; use `authenticate = always` to force"
);
None
}
}
@ -798,9 +861,9 @@ impl AuthMiddleware {
Some(credentials)
} else {
None
}
.map(Authentication::from)
.map(Arc::new);
};
let credentials = credentials.map(Authentication::from).map(Arc::new);
// Register the fetch for this key
self.cache().fetches.done(key, credentials.clone());

View File

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

View File

@ -5,7 +5,7 @@ use fs_err as fs;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use uv_fs::{LockedFile, with_added_extension};
use uv_fs::{LockedFile, LockedFileError, LockedFileMode, with_added_extension};
use uv_preview::{Preview, PreviewFeatures};
use uv_redacted::DisplaySafeUrl;
@ -28,7 +28,7 @@ pub enum AuthBackend {
}
impl AuthBackend {
pub fn from_settings(preview: Preview) -> Result<Self, TomlCredentialError> {
pub async fn from_settings(preview: Preview) -> Result<Self, TomlCredentialError> {
// If preview is enabled, we'll use the system-native store
if preview.is_enabled(PreviewFeatures::NATIVE_AUTH) {
return Ok(Self::System(KeyringProvider::native()));
@ -36,12 +36,16 @@ impl AuthBackend {
// Otherwise, we'll use the plaintext credential store
let path = TextCredentialStore::default_file()?;
match TextCredentialStore::read(&path) {
match TextCredentialStore::read(&path).await {
Ok((store, lock)) => Ok(Self::TextStore(store, lock)),
Err(TomlCredentialError::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
Err(err)
if err
.as_io_error()
.is_some_and(|err| err.kind() == std::io::ErrorKind::NotFound) =>
{
Ok(Self::TextStore(
TextCredentialStore::default(),
TextCredentialStore::lock(&path)?,
TextCredentialStore::lock(&path).await?,
))
}
Err(err) => Err(err),
@ -69,6 +73,8 @@ pub enum AuthScheme {
pub enum TomlCredentialError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
LockedFile(#[from] LockedFileError),
#[error("Failed to parse TOML credential file: {0}")]
ParseError(#[from] toml::de::Error),
#[error("Failed to serialize credentials to TOML")]
@ -83,6 +89,21 @@ pub enum TomlCredentialError {
TokenNotUnicode(#[from] std::string::FromUtf8Error),
}
impl TomlCredentialError {
pub fn as_io_error(&self) -> Option<&std::io::Error> {
match self {
Self::Io(err) => Some(err),
Self::LockedFile(err) => err.as_io_error(),
Self::ParseError(_)
| Self::SerializeError(_)
| Self::BasicAuthError(_)
| Self::BearerAuthError(_)
| Self::CredentialsDirError
| Self::TokenNotUnicode(_) => None,
}
}
}
#[derive(Debug, Error)]
pub enum BasicAuthError {
#[error("`username` is required with `scheme = basic`")]
@ -233,12 +254,12 @@ impl TextCredentialStore {
}
/// Acquire a lock on the credentials file at the given path.
pub fn lock(path: &Path) -> Result<LockedFile, TomlCredentialError> {
pub async fn lock(path: &Path) -> Result<LockedFile, TomlCredentialError> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let lock = with_added_extension(path, ".lock");
Ok(LockedFile::acquire_blocking(lock, "credentials store")?)
Ok(LockedFile::acquire(lock, LockedFileMode::Exclusive, "credentials store").await?)
}
/// Read credentials from a file.
@ -269,8 +290,8 @@ impl TextCredentialStore {
/// Returns [`TextCredentialStore`] and a [`LockedFile`] to hold if mutating the store.
///
/// If the store will not be written to following the read, the lock can be dropped.
pub fn read<P: AsRef<Path>>(path: P) -> Result<(Self, LockedFile), TomlCredentialError> {
let lock = Self::lock(path.as_ref())?;
pub async fn read<P: AsRef<Path>>(path: P) -> Result<(Self, LockedFile), TomlCredentialError> {
let lock = Self::lock(path.as_ref()).await?;
let store = Self::from_file(path)?;
Ok((store, lock))
}
@ -450,8 +471,8 @@ mod tests {
assert!(store.get_credentials(&url, None).is_none());
}
#[test]
fn test_file_operations() {
#[tokio::test]
async fn test_file_operations() {
let mut temp_file = NamedTempFile::new().unwrap();
writeln!(
temp_file,
@ -487,7 +508,7 @@ password = "pass2"
store
.write(
temp_output.path(),
TextCredentialStore::lock(temp_file.path()).unwrap(),
TextCredentialStore::lock(temp_file.path()).await.unwrap(),
)
.unwrap();

View File

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

13
crates/uv-bench/README.md Normal file
View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-bench
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-bench).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -59,7 +59,10 @@ fn setup(manifest: Manifest) -> impl Fn(bool) {
.build()
.unwrap();
let cache = Cache::from_path("../../.cache").init().unwrap();
let cache = Cache::from_path("../../.cache")
.init_no_wait()
.expect("No cache contention when running benchmarks")
.unwrap();
let interpreter = PythonEnvironment::from_root("../../.venv", &cache)
.unwrap()
.into_interpreter();
@ -131,7 +134,7 @@ mod resolver {
);
static TAGS: LazyLock<Tags> = LazyLock::new(|| {
Tags::from_env(&PLATFORM, (3, 11), "cpython", (3, 11), false, false).unwrap()
Tags::from_env(&PLATFORM, (3, 11), "cpython", (3, 11), false, false, false).unwrap()
});
pub(crate) async fn resolve(

View File

@ -1,11 +1,10 @@
[package]
name = "uv-bin-install"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
@ -24,6 +23,7 @@ uv-extract = { workspace = true }
uv-pep440 = { workspace = true }
uv-platform = { workspace = true }
uv-redacted = { workspace = true }
fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true }
reqwest = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-bin-install
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-bin-install).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -19,7 +19,7 @@ use tracing::debug;
use url::Url;
use uv_distribution_filename::SourceDistExtension;
use uv_cache::{Cache, CacheBucket, CacheEntry};
use uv_cache::{Cache, CacheBucket, CacheEntry, Error as CacheError};
use uv_client::{BaseClient, is_transient_network_error};
use uv_extract::{Error as ExtractError, stream};
use uv_pep440::Version;
@ -135,6 +135,9 @@ pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Cache(#[from] CacheError),
#[error("Failed to detect platform")]
Platform(#[from] uv_platform::Error),

View File

@ -1,11 +1,10 @@
[package]
name = "uv-build-backend"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-build-backend
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-build-backend).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -1,3 +1,4 @@
use itertools::Itertools;
mod metadata;
mod serde_verbatim;
mod settings;
@ -7,8 +8,10 @@ mod wheel;
pub use metadata::{PyProjectToml, check_direct_build};
pub use settings::{BuildBackendSettings, WheelDataIncludes};
pub use source_dist::{build_source_dist, list_source_dist};
use uv_warnings::warn_user_once;
pub use wheel::{build_editable, build_wheel, list_wheel, metadata};
use std::collections::HashSet;
use std::ffi::OsStr;
use std::io;
use std::path::{Path, PathBuf};
@ -29,9 +32,9 @@ use crate::settings::ModuleName;
pub enum Error {
#[error(transparent)]
Io(#[from] io::Error),
#[error("Invalid pyproject.toml")]
Toml(#[from] toml::de::Error),
#[error("Invalid pyproject.toml")]
#[error("Invalid metadata format in: {}", _0.user_display())]
Toml(PathBuf, #[source] toml::de::Error),
#[error("Invalid project metadata")]
Validation(#[from] ValidationError),
#[error("Invalid module name: {0}")]
InvalidModuleName(String, #[source] IdentifierParseError),
@ -191,6 +194,60 @@ fn check_metadata_directory(
Ok(())
}
/// Returns the list of module names without names which would be included twice
///
/// In normal cases it should do nothing:
///
/// * `["aaa"] -> ["aaa"]`
/// * `["aaa", "bbb"] -> ["aaa", "bbb"]`
///
/// Duplicate elements are removed:
///
/// * `["aaa", "aaa"] -> ["aaa"]`
/// * `["bbb", "aaa", "bbb"] -> ["aaa", "bbb"]`
///
/// Names with more specific paths are removed in favour of more general paths:
///
/// * `["aaa.foo", "aaa"] -> ["aaa"]`
/// * `["bbb", "aaa", "bbb.foo", "ccc.foo", "ccc.foo.bar", "aaa"] -> ["aaa", "bbb.foo", "ccc.foo"]`
///
/// This does not preserve the order of the elements.
fn prune_redundant_modules(mut names: Vec<String>) -> Vec<String> {
names.sort();
let mut pruned = Vec::with_capacity(names.len());
for name in names {
if let Some(last) = pruned.last() {
if name == *last {
continue;
}
// This is a more specific (narrow) module name than what came before
if name
.strip_prefix(last)
.is_some_and(|suffix| suffix.starts_with('.'))
{
continue;
}
}
pruned.push(name);
}
pruned
}
/// Wraps [`prune_redundant_modules`] with a conditional warning when modules are ignored
fn prune_redundant_modules_warn(names: &[String], show_warnings: bool) -> Vec<String> {
let pruned = prune_redundant_modules(names.to_vec());
if show_warnings && names.len() != pruned.len() {
let mut pruned: HashSet<_> = pruned.iter().collect();
let ignored: Vec<_> = names.iter().filter(|name| !pruned.remove(name)).collect();
let s = if ignored.len() == 1 { "" } else { "s" };
warn_user_once!(
"Ignoring redundant module name{s} in `tool.uv.build-backend.module-name`: `{}`",
ignored.into_iter().join("`, `")
);
}
pruned
}
/// Returns the source root and the module path(s) with the `__init__.py[i]` below to it while
/// checking the project layout and names.
///
@ -213,6 +270,7 @@ fn find_roots(
relative_module_root: &Path,
module_name: Option<&ModuleName>,
namespace: bool,
show_warnings: bool,
) -> Result<(PathBuf, Vec<PathBuf>), Error> {
let relative_module_root = uv_fs::normalize_path(relative_module_root);
// Check that even if a path contains `..`, we only include files below the module root.
@ -231,8 +289,8 @@ fn find_roots(
ModuleName::Name(name) => {
vec![name.split('.').collect::<PathBuf>()]
}
ModuleName::Names(names) => names
.iter()
ModuleName::Names(names) => prune_redundant_modules_warn(names, show_warnings)
.into_iter()
.map(|name| name.split('.').collect::<PathBuf>())
.collect(),
}
@ -250,9 +308,9 @@ fn find_roots(
let modules_relative = if let Some(module_name) = module_name {
match module_name {
ModuleName::Name(name) => vec![module_path_from_module_name(&src_root, name)?],
ModuleName::Names(names) => names
.iter()
.map(|name| module_path_from_module_name(&src_root, name))
ModuleName::Names(names) => prune_redundant_modules_warn(names, show_warnings)
.into_iter()
.map(|name| module_path_from_module_name(&src_root, &name))
.collect::<Result<_, _>>()?,
}
} else {
@ -420,19 +478,20 @@ mod tests {
fn build(source_root: &Path, dist: &Path) -> Result<BuildResults, Error> {
// Build a direct wheel, capture all its properties to compare it with the indirect wheel
// latest and remove it since it has the same filename as the indirect wheel.
let (_name, direct_wheel_list_files) = list_wheel(source_root, MOCK_UV_VERSION)?;
let direct_wheel_filename = build_wheel(source_root, dist, None, MOCK_UV_VERSION)?;
let (_name, direct_wheel_list_files) = list_wheel(source_root, MOCK_UV_VERSION, false)?;
let direct_wheel_filename = build_wheel(source_root, dist, None, MOCK_UV_VERSION, false)?;
let direct_wheel_path = dist.join(direct_wheel_filename.to_string());
let direct_wheel_contents = wheel_contents(&direct_wheel_path);
let direct_wheel_hash = sha2::Sha256::digest(fs_err::read(&direct_wheel_path)?);
fs_err::remove_file(&direct_wheel_path)?;
// Build a source distribution.
let (_name, source_dist_list_files) = list_source_dist(source_root, MOCK_UV_VERSION)?;
let (_name, source_dist_list_files) =
list_source_dist(source_root, MOCK_UV_VERSION, false)?;
// TODO(konsti): This should run in the unpacked source dist tempdir, but we need to
// normalize the path.
let (_name, wheel_list_files) = list_wheel(source_root, MOCK_UV_VERSION)?;
let source_dist_filename = build_source_dist(source_root, dist, MOCK_UV_VERSION)?;
let (_name, wheel_list_files) = list_wheel(source_root, MOCK_UV_VERSION, false)?;
let source_dist_filename = build_source_dist(source_root, dist, MOCK_UV_VERSION, false)?;
let source_dist_path = dist.join(source_dist_filename.to_string());
let source_dist_contents = sdist_contents(&source_dist_path);
@ -446,7 +505,13 @@ mod tests {
source_dist_filename.name.as_dist_info_name(),
source_dist_filename.version
));
let wheel_filename = build_wheel(&sdist_top_level_directory, dist, None, MOCK_UV_VERSION)?;
let wheel_filename = build_wheel(
&sdist_top_level_directory,
dist,
None,
MOCK_UV_VERSION,
false,
)?;
let wheel_contents = wheel_contents(&dist.join(wheel_filename.to_string()));
// Check that direct and indirect wheels are identical.
@ -534,7 +599,7 @@ mod tests {
/// platform-independent deterministic builds.
#[test]
fn built_by_uv_building() {
let built_by_uv = Path::new("../../scripts/packages/built-by-uv");
let built_by_uv = Path::new("../../test/packages/built-by-uv");
let src = TempDir::new().unwrap();
for dir in [
"src",
@ -597,7 +662,7 @@ mod tests {
// Check that the source dist is reproducible across platforms.
assert_snapshot!(
format!("{:x}", sha2::Sha256::digest(fs_err::read(&source_dist_path).unwrap())),
@"871d1f859140721b67cbeaca074e7a2740c88c38028d0509eba87d1285f1da9e"
@"bb74bff575b135bb39e5c9bce56349441fb0923bb8857e32a5eaf34ec1843967"
);
// Check both the files we report and the actual files
assert_snapshot!(format_file_list(build.source_dist_list_files, src.path()), @r"
@ -756,7 +821,7 @@ mod tests {
// Build a wheel from a source distribution
let output_dir = TempDir::new().unwrap();
build_source_dist(src.path(), output_dir.path(), "0.5.15").unwrap();
build_source_dist(src.path(), output_dir.path(), "0.5.15", false).unwrap();
let sdist_tree = TempDir::new().unwrap();
let source_dist_path = output_dir.path().join("pep_pep639_license-1.0.0.tar.gz");
let sdist_reader = BufReader::new(File::open(&source_dist_path).unwrap());
@ -767,6 +832,7 @@ mod tests {
output_dir.path(),
None,
"0.5.15",
false,
)
.unwrap();
let wheel = output_dir
@ -831,6 +897,7 @@ mod tests {
output_dir.path(),
Some(&metadata_dir.path().join(&dist_info_dir)),
"0.5.15",
false,
)
.unwrap();
let wheel = output_dir
@ -1414,4 +1481,114 @@ mod tests {
simple_namespace_part-1.0.0.dist-info/WHEEL
");
}
/// `prune_redundant_modules` should remove modules which are already
/// included (either directly or via their parent)
#[test]
fn test_prune_redundant_modules() {
fn check(input: &[&str], expect: &[&str]) {
let input = input.iter().map(|s| (*s).to_string()).collect();
let expect: Vec<_> = expect.iter().map(|s| (*s).to_string()).collect();
assert_eq!(prune_redundant_modules(input), expect);
}
// Basic cases
check(&[], &[]);
check(&["foo"], &["foo"]);
check(&["foo", "bar"], &["bar", "foo"]);
// Deshadowing
check(&["foo", "foo.bar"], &["foo"]);
check(&["foo.bar", "foo"], &["foo"]);
check(
&["foo.bar.a", "foo.bar.b", "foo.bar", "foo", "foo.bar.a.c"],
&["foo"],
);
check(
&["bar.one", "bar.two", "baz", "bar", "baz.one"],
&["bar", "baz"],
);
// Potential false positives
check(&["foo", "foobar"], &["foo", "foobar"]);
check(
&["foo", "foobar", "foo.bar", "foobar.baz"],
&["foo", "foobar"],
);
check(&["foo.bar", "foo.baz"], &["foo.bar", "foo.baz"]);
check(&["foo", "foo", "foo.bar", "foo.bar"], &["foo"]);
// Everything
check(
&[
"foo.inner",
"foo.inner.deeper",
"foo",
"bar",
"bar.sub",
"bar.sub.deep",
"foobar",
"baz.baz.bar",
"baz.baz",
"qux",
],
&["bar", "baz.baz", "foo", "foobar", "qux"],
);
}
/// A package with duplicate module names.
#[test]
fn duplicate_module_names() {
let src = TempDir::new().unwrap();
let pyproject_toml = indoc! {r#"
[project]
name = "duplicate"
version = "1.0.0"
[tool.uv.build-backend]
module-name = ["foo", "foo", "bar.baz", "bar.baz.submodule"]
[build-system]
requires = ["uv_build>=0.5.15,<0.6.0"]
build-backend = "uv_build"
"#
};
fs_err::write(src.path().join("pyproject.toml"), pyproject_toml).unwrap();
fs_err::create_dir_all(src.path().join("src").join("foo")).unwrap();
File::create(src.path().join("src").join("foo").join("__init__.py")).unwrap();
fs_err::create_dir_all(src.path().join("src").join("bar").join("baz")).unwrap();
File::create(
src.path()
.join("src")
.join("bar")
.join("baz")
.join("__init__.py"),
)
.unwrap();
let dist = TempDir::new().unwrap();
let build = build(src.path(), dist.path()).unwrap();
assert_snapshot!(build.source_dist_contents.join("\n"), @r"
duplicate-1.0.0/
duplicate-1.0.0/PKG-INFO
duplicate-1.0.0/pyproject.toml
duplicate-1.0.0/src
duplicate-1.0.0/src/bar
duplicate-1.0.0/src/bar/baz
duplicate-1.0.0/src/bar/baz/__init__.py
duplicate-1.0.0/src/foo
duplicate-1.0.0/src/foo/__init__.py
");
assert_snapshot!(build.wheel_contents.join("\n"), @r"
bar/
bar/baz/
bar/baz/__init__.py
duplicate-1.0.0.dist-info/
duplicate-1.0.0.dist-info/METADATA
duplicate-1.0.0.dist-info/RECORD
duplicate-1.0.0.dist-info/WHEEL
foo/
foo/__init__.py
");
}
}

View File

@ -154,8 +154,11 @@ impl PyProjectToml {
&self.project.version
}
pub(crate) fn parse(contents: &str) -> Result<Self, Error> {
Ok(toml::from_str(contents)?)
pub(crate) fn parse(path: &Path) -> Result<Self, Error> {
let contents = fs_err::read_to_string(path)?;
let pyproject_toml =
toml::from_str(&contents).map_err(|err| Error::Toml(path.to_path_buf(), err))?;
Ok(pyproject_toml)
}
pub(crate) fn readme(&self) -> Option<&Readme> {
@ -949,7 +952,7 @@ mod tests {
requires = ["uv_build>=0.4.15,<0.5.0"]
build-backend = "uv_build"
"#;
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
let temp_dir = TempDir::new().unwrap();
let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap();
@ -1034,7 +1037,7 @@ mod tests {
"#
};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap();
assert_snapshot!(metadata.core_metadata_format(), @r###"
@ -1128,7 +1131,7 @@ mod tests {
"#
};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap();
assert_snapshot!(metadata.core_metadata_format(), @r"
@ -1220,7 +1223,7 @@ mod tests {
"#
};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
let metadata = pyproject_toml.to_metadata(temp_dir.path()).unwrap();
assert_snapshot!(metadata.core_metadata_format(), @r###"
@ -1281,7 +1284,7 @@ mod tests {
#[test]
fn build_system_valid() {
let contents = extend_project("");
let pyproject_toml = PyProjectToml::parse(&contents).unwrap();
let pyproject_toml: PyProjectToml = toml::from_str(&contents).unwrap();
assert_snapshot!(
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
@""
@ -1299,7 +1302,7 @@ mod tests {
requires = ["uv_build"]
build-backend = "uv_build"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
assert_snapshot!(
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
@r###"`build_system.requires = ["uv_build"]` is missing an upper bound on the `uv_build` version such as `<0.5`. Without bounding the `uv_build` version, the source distribution will break when a future, breaking version of `uv_build` is released."###
@ -1317,7 +1320,7 @@ mod tests {
requires = ["uv_build>=0.4.15,<0.5.0", "wheel"]
build-backend = "uv_build"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
assert_snapshot!(
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
@"Expected a single uv requirement in `build-system.requires`, found ``"
@ -1335,7 +1338,7 @@ mod tests {
requires = ["setuptools"]
build-backend = "uv_build"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
assert_snapshot!(
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
@"Expected a single uv requirement in `build-system.requires`, found ``"
@ -1353,7 +1356,7 @@ mod tests {
requires = ["uv_build>=0.4.15,<0.5.0"]
build-backend = "setuptools"
"#};
let pyproject_toml = PyProjectToml::parse(contents).unwrap();
let pyproject_toml: PyProjectToml = toml::from_str(contents).unwrap();
assert_snapshot!(
pyproject_toml.check_build_system("0.4.15+test").join("\n"),
@r###"The value for `build_system.build-backend` should be `"uv_build"`, not `"setuptools"`"###
@ -1364,7 +1367,7 @@ mod tests {
fn minimal() {
let contents = extend_project("");
let metadata = PyProjectToml::parse(&contents)
let metadata = toml::from_str::<PyProjectToml>(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap();
@ -1383,15 +1386,14 @@ mod tests {
"#
});
let err = PyProjectToml::parse(&contents).unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
Caused by: TOML parse error at line 4, column 10
let err = toml::from_str::<PyProjectToml>(&contents).unwrap_err();
assert_snapshot!(format_err(err), @r#"
TOML parse error at line 4, column 10
|
4 | readme = { path = "Readme.md" }
| ^^^^^^^^^^^^^^^^^^^^^^
data did not match any variant of untagged enum Readme
"###);
"#);
}
#[test]
@ -1401,7 +1403,7 @@ mod tests {
"#
});
let err = PyProjectToml::parse(&contents)
let err = toml::from_str::<PyProjectToml>(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
@ -1423,14 +1425,14 @@ mod tests {
"#
});
let err = PyProjectToml::parse(&contents)
let err = toml::from_str::<PyProjectToml>(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
assert_snapshot!(format_err(err), @r"
Invalid project metadata
Caused by: `project.description` must be a single line
"###);
");
}
#[test]
@ -1441,14 +1443,14 @@ mod tests {
"#
});
let err = PyProjectToml::parse(&contents)
let err = toml::from_str::<PyProjectToml>(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
assert_snapshot!(format_err(err), @r"
Invalid project metadata
Caused by: When `project.license-files` is defined, `project.license` must be an SPDX expression string
"###);
");
}
#[test]
@ -1457,7 +1459,7 @@ mod tests {
license = "MIT OR Apache-2.0"
"#
});
let metadata = PyProjectToml::parse(&contents)
let metadata = toml::from_str::<PyProjectToml>(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap();
@ -1475,13 +1477,13 @@ mod tests {
license = "MIT XOR Apache-2"
"#
});
let err = PyProjectToml::parse(&contents)
let err = toml::from_str::<PyProjectToml>(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
// TODO(konsti): We mess up the indentation in the error.
assert_snapshot!(format_err(err), @r"
Invalid pyproject.toml
Invalid project metadata
Caused by: `project.license` is not a valid SPDX expression: MIT XOR Apache-2
Caused by: MIT XOR Apache-2
^^^ unknown term
@ -1495,18 +1497,18 @@ mod tests {
"#
});
let err = PyProjectToml::parse(&contents)
let err = toml::from_str::<PyProjectToml>(&contents)
.unwrap()
.to_metadata(Path::new("/do/not/read"))
.unwrap_err();
assert_snapshot!(format_err(err), @r###"
Invalid pyproject.toml
assert_snapshot!(format_err(err), @r"
Invalid project metadata
Caused by: Dynamic metadata is not supported
"###);
");
}
fn script_error(contents: &str) -> String {
let err = PyProjectToml::parse(contents)
let err = toml::from_str::<PyProjectToml>(contents)
.unwrap()
.to_entry_points()
.unwrap_err();

View File

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

View File

@ -24,9 +24,9 @@ pub fn build_source_dist(
source_tree: &Path,
source_dist_directory: &Path,
uv_version: &str,
show_warnings: bool,
) -> Result<SourceDistFilename, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
let filename = SourceDistFilename {
name: pyproject_toml.name().clone(),
version: pyproject_toml.version().clone(),
@ -34,7 +34,7 @@ pub fn build_source_dist(
};
let source_dist_path = source_dist_directory.join(filename.to_string());
let writer = TarGzWriter::new(&source_dist_path)?;
write_source_dist(source_tree, writer, uv_version)?;
write_source_dist(source_tree, writer, uv_version, show_warnings)?;
Ok(filename)
}
@ -42,9 +42,9 @@ pub fn build_source_dist(
pub fn list_source_dist(
source_tree: &Path,
uv_version: &str,
show_warnings: bool,
) -> Result<(SourceDistFilename, FileList), Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
let filename = SourceDistFilename {
name: pyproject_toml.name().clone(),
version: pyproject_toml.version().clone(),
@ -52,7 +52,7 @@ pub fn list_source_dist(
};
let mut files = FileList::new();
let writer = ListWriter::new(&mut files);
write_source_dist(source_tree, writer, uv_version)?;
write_source_dist(source_tree, writer, uv_version, show_warnings)?;
Ok((filename, files))
}
@ -61,6 +61,7 @@ fn source_dist_matcher(
source_tree: &Path,
pyproject_toml: &PyProjectToml,
settings: BuildBackendSettings,
show_warnings: bool,
) -> Result<(GlobDirFilter, GlobSet), Error> {
// File and directories to include in the source directory
let mut include_globs = Vec::new();
@ -75,6 +76,7 @@ fn source_dist_matcher(
&settings.module_root,
settings.module_name.as_ref(),
settings.namespace,
show_warnings,
)?;
for module_relative in modules_relative {
// The wheel must not include any files included by the source distribution (at least until we
@ -182,9 +184,9 @@ fn write_source_dist(
source_tree: &Path,
mut writer: impl DirectoryWriter,
uv_version: &str,
show_warnings: bool,
) -> Result<SourceDistFilename, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
for warning in pyproject_toml.check_build_system(uv_version) {
warn_user_once!("{warning}");
}
@ -218,7 +220,7 @@ fn write_source_dist(
)?;
let (include_matcher, exclude_matcher) =
source_dist_matcher(source_tree, &pyproject_toml, settings)?;
source_dist_matcher(source_tree, &pyproject_toml, settings, show_warnings)?;
let mut files_visited = 0;
for entry in WalkDir::new(source_tree)
@ -297,6 +299,10 @@ impl TarGzWriter {
impl DirectoryWriter for TarGzWriter {
fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error> {
let mut header = Header::new_gnu();
// Work around bug in Python's std tar module
// https://github.com/python/cpython/issues/141707
// https://github.com/astral-sh/uv/pull/17043#issuecomment-3636841022
header.set_entry_type(EntryType::Regular);
header.set_size(bytes.len() as u64);
// Reasonable default to avoid 0o000 permissions, the user's umask will be applied on
// unpacking.
@ -310,6 +316,10 @@ impl DirectoryWriter for TarGzWriter {
fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error> {
let metadata = fs_err::metadata(file)?;
let mut header = Header::new_gnu();
// Work around bug in Python's std tar module
// https://github.com/python/cpython/issues/141707
// https://github.com/astral-sh/uv/pull/17043#issuecomment-3636841022
header.set_entry_type(EntryType::Regular);
// Preserve the executable bit, especially for scripts
#[cfg(unix)]
let executable_bit = {

View File

@ -29,9 +29,9 @@ pub fn build_wheel(
wheel_dir: &Path,
metadata_directory: Option<&Path>,
uv_version: &str,
show_warnings: bool,
) -> Result<WheelFilename, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
for warning in pyproject_toml.check_build_system(uv_version) {
warn_user_once!("{warning}");
}
@ -58,6 +58,7 @@ pub fn build_wheel(
&filename,
uv_version,
wheel_writer,
show_warnings,
)?;
Ok(filename)
@ -67,9 +68,9 @@ pub fn build_wheel(
pub fn list_wheel(
source_tree: &Path,
uv_version: &str,
show_warnings: bool,
) -> Result<(WheelFilename, FileList), Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
for warning in pyproject_toml.check_build_system(uv_version) {
warn_user_once!("{warning}");
}
@ -87,7 +88,14 @@ pub fn list_wheel(
let mut files = FileList::new();
let writer = ListWriter::new(&mut files);
write_wheel(source_tree, &pyproject_toml, &filename, uv_version, writer)?;
write_wheel(
source_tree,
&pyproject_toml,
&filename,
uv_version,
writer,
show_warnings,
)?;
Ok((filename, files))
}
@ -97,6 +105,7 @@ fn write_wheel(
filename: &WheelFilename,
uv_version: &str,
mut wheel_writer: impl DirectoryWriter,
show_warnings: bool,
) -> Result<(), Error> {
let settings = pyproject_toml
.settings()
@ -132,6 +141,7 @@ fn write_wheel(
&settings.module_root,
settings.module_name.as_ref(),
settings.namespace,
show_warnings,
)?;
let mut files_visited = 0;
@ -259,9 +269,9 @@ pub fn build_editable(
wheel_dir: &Path,
metadata_directory: Option<&Path>,
uv_version: &str,
show_warnings: bool,
) -> Result<WheelFilename, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
for warning in pyproject_toml.check_build_system(uv_version) {
warn_user_once!("{warning}");
}
@ -295,6 +305,7 @@ pub fn build_editable(
&settings.module_root,
settings.module_name.as_ref(),
settings.namespace,
show_warnings,
)?;
wheel_writer.write_bytes(
@ -321,8 +332,7 @@ pub fn metadata(
metadata_directory: &Path,
uv_version: &str,
) -> Result<String, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
let pyproject_toml = PyProjectToml::parse(&source_tree.join("pyproject.toml"))?;
for warning in pyproject_toml.check_build_system(uv_version) {
warn_user_once!("{warning}");
}
@ -830,7 +840,7 @@ mod test {
#[test]
fn test_prepare_metadata() {
let metadata_dir = TempDir::new().unwrap();
let built_by_uv = Path::new("../../scripts/packages/built-by-uv");
let built_by_uv = Path::new("../../test/packages/built-by-uv");
metadata(built_by_uv, metadata_dir.path(), "1.0.0+test").unwrap();
let mut files: Vec<_> = WalkDir::new(metadata_dir.path())

View File

@ -1,11 +1,10 @@
[package]
name = "uv-build-frontend"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
@ -17,6 +16,7 @@ doctest = false
workspace = true
[dependencies]
uv-auth = { workspace = true }
uv-cache-key = { workspace = true }
uv-configuration = { workspace = true }
uv-distribution = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-build-frontend
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-build-frontend).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -28,7 +28,7 @@ use tokio::io::AsyncBufReadExt;
use tokio::process::Command;
use tokio::sync::{Mutex, Semaphore};
use tracing::{Instrument, debug, info_span, instrument, warn};
use uv_auth::CredentialsCache;
use uv_cache_key::cache_digest;
use uv_configuration::{BuildKind, BuildOutput, SourceStrategy};
use uv_distribution::BuildRequires;
@ -36,7 +36,7 @@ use uv_distribution_types::{
ConfigSettings, ExtraBuildRequirement, ExtraBuildRequires, IndexLocations, Requirement,
Resolution,
};
use uv_fs::LockedFile;
use uv_fs::{LockedFile, LockedFileMode};
use uv_fs::{PythonExt, Simplified};
use uv_normalize::PackageName;
use uv_pep440::Version;
@ -292,6 +292,7 @@ impl SourceBuild {
mut environment_variables: FxHashMap<OsString, OsString>,
level: BuildOutput,
concurrent_builds: usize,
credentials_cache: &CredentialsCache,
preview: Preview,
) -> Result<Self, Error> {
let temp_dir = build_context.cache().venv_dir()?;
@ -302,7 +303,6 @@ impl SourceBuild {
source.to_path_buf()
};
let default_backend: Pep517Backend = DEFAULT_BACKEND.clone();
// Check if we have a PEP 517 build backend.
let (pep517_backend, project) = Self::extract_pep517_backend(
&source_tree,
@ -311,7 +311,7 @@ impl SourceBuild {
locations,
source_strategy,
workspace_cache,
&default_backend,
credentials_cache,
)
.await
.map_err(|err| *err)?;
@ -383,7 +383,6 @@ impl SourceBuild {
let resolved_requirements = Self::get_resolved_requirements(
build_context,
source_build_context,
&default_backend,
&pep517_backend,
extra_build_dependencies,
build_stack,
@ -455,6 +454,7 @@ impl SourceBuild {
&environment_variables,
&modified_path,
&temp_dir,
credentials_cache,
)
.await?;
}
@ -493,12 +493,16 @@ impl SourceBuild {
"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();
source_tree_lock = LockedFile::acquire(
lock_path,
LockedFileMode::Exclusive,
self.source_tree.to_string_lossy(),
)
.await
.inspect_err(|err| {
warn!("Failed to acquire build lock: {err}");
})
.ok();
}
Ok(source_tree_lock)
}
@ -506,13 +510,12 @@ impl SourceBuild {
async fn get_resolved_requirements(
build_context: &impl BuildContext,
source_build_context: SourceBuildContext,
default_backend: &Pep517Backend,
pep517_backend: &Pep517Backend,
extra_build_dependencies: Vec<Requirement>,
build_stack: &BuildStack,
) -> Result<Resolution, Error> {
Ok(
if pep517_backend.requirements == default_backend.requirements
if pep517_backend.requirements == DEFAULT_BACKEND.requirements
&& extra_build_dependencies.is_empty()
{
let mut resolution = source_build_context.default_resolution.lock().await;
@ -520,7 +523,7 @@ impl SourceBuild {
resolved_requirements.clone()
} else {
let resolved_requirements = build_context
.resolve(&default_backend.requirements, build_stack)
.resolve(&DEFAULT_BACKEND.requirements, build_stack)
.await
.map_err(|err| {
Error::RequirementsResolve("`setup.py` build", err.into())
@ -560,7 +563,7 @@ impl SourceBuild {
locations: &IndexLocations,
source_strategy: SourceStrategy,
workspace_cache: &WorkspaceCache,
default_backend: &Pep517Backend,
credentials_cache: &CredentialsCache,
) -> Result<(Pep517Backend, Option<Project>), Box<Error>> {
match fs::read_to_string(source_tree.join("pyproject.toml")) {
Ok(toml) => {
@ -589,6 +592,7 @@ impl SourceBuild {
locations,
source_strategy,
workspace_cache,
credentials_cache,
)
.await
.map_err(Error::Lowering)?;
@ -658,7 +662,7 @@ impl SourceBuild {
}
}
default_backend.clone()
DEFAULT_BACKEND.clone()
};
Ok((backend, pyproject_toml.project))
}
@ -674,7 +678,7 @@ impl SourceBuild {
// the default backend, to match `build`. `pip` uses `setup.py` directly in this
// case, but plans to make PEP 517 builds the default in the future.
// See: https://github.com/pypa/pip/issues/9175.
Ok((default_backend.clone(), None))
Ok((DEFAULT_BACKEND.clone(), None))
}
Err(err) => Err(Box::new(err.into())),
}
@ -961,6 +965,7 @@ async fn create_pep517_build_environment(
environment_variables: &FxHashMap<OsString, OsString>,
modified_path: &OsString,
temp_dir: &TempDir,
credentials_cache: &CredentialsCache,
) -> Result<(), Error> {
// Write the hook output to a file so that we can read it back reliably.
let outfile = temp_dir
@ -1055,6 +1060,7 @@ async fn create_pep517_build_environment(
locations,
source_strategy,
workspace_cache,
credentials_cache,
)
.await
.map_err(Error::Lowering)?;

View File

@ -1,11 +1,10 @@
[package]
name = "uv-build"
version = "0.9.11"
version = "0.9.18"
description = "A Python build backend"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

View File

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

View File

@ -44,6 +44,7 @@ fn main() -> Result<()> {
&env::current_dir()?,
&sdist_directory,
uv_version::version(),
false,
)?;
// Tell the build frontend about the name of the artifact we built
writeln!(&mut std::io::stdout(), "{filename}").context("stdout is closed")?;
@ -56,6 +57,7 @@ fn main() -> Result<()> {
&wheel_directory,
metadata_directory.as_deref(),
uv_version::version(),
false,
)?;
// Tell the build frontend about the name of the artifact we built
writeln!(&mut std::io::stdout(), "{filename}").context("stdout is closed")?;
@ -68,6 +70,7 @@ fn main() -> Result<()> {
&wheel_directory,
metadata_directory.as_deref(),
uv_version::version(),
false,
)?;
// Tell the build frontend about the name of the artifact we built
writeln!(&mut std::io::stdout(), "{filename}").context("stdout is closed")?;

View File

@ -1,11 +1,10 @@
[package]
name = "uv-cache-info"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-cache-info
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-cache-info).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -1,11 +1,10 @@
[package]
name = "uv-cache-key"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-cache-key
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-cache-key).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -139,8 +139,18 @@ impl std::fmt::Display for CanonicalUrl {
/// `https://github.com/pypa/package.git#subdirectory=pkg_b` would map to different
/// [`CanonicalUrl`] values, but the same [`RepositoryUrl`], since they map to the same
/// resource.
///
/// The additional information it holds should only be used to discriminate between
/// sources that hold the exact same commit in their canonical representation,
/// but may differ in the contents such as when Git LFS is enabled.
///
/// A different cache key will be computed when Git LFS is enabled.
/// When Git LFS is `false` or `None`, the cache key remains unchanged.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct RepositoryUrl(DisplaySafeUrl);
pub struct RepositoryUrl {
repo_url: DisplaySafeUrl,
with_lfs: Option<bool>,
}
impl RepositoryUrl {
pub fn new(url: &DisplaySafeUrl) -> Self {
@ -161,19 +171,31 @@ impl RepositoryUrl {
url.set_fragment(None);
url.set_query(None);
Self(url)
Self {
repo_url: url,
with_lfs: None,
}
}
pub fn parse(url: &str) -> Result<Self, DisplaySafeUrlError> {
Ok(Self::new(&DisplaySafeUrl::parse(url)?))
}
#[must_use]
pub fn with_lfs(mut self, lfs: Option<bool>) -> Self {
self.with_lfs = lfs;
self
}
}
impl CacheKey for RepositoryUrl {
fn cache_key(&self, state: &mut CacheKeyHasher) {
// `as_str` gives the serialisation of a url (which has a spec) and so insulates against
// possible changes in how the URL crate does hashing.
self.0.as_str().cache_key(state);
self.repo_url.as_str().cache_key(state);
if let Some(true) = self.with_lfs {
1u8.cache_key(state);
}
}
}
@ -181,7 +203,10 @@ impl Hash for RepositoryUrl {
fn hash<H: Hasher>(&self, state: &mut H) {
// `as_str` gives the serialisation of a url (which has a spec) and so insulates against
// possible changes in how the URL crate does hashing.
self.0.as_str().hash(state);
self.repo_url.as_str().hash(state);
if let Some(true) = self.with_lfs {
1u8.hash(state);
}
}
}
@ -189,13 +214,13 @@ impl Deref for RepositoryUrl {
type Target = Url;
fn deref(&self) -> &Self::Target {
&self.0
&self.repo_url
}
}
impl std::fmt::Display for RepositoryUrl {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
std::fmt::Display::fmt(&self.repo_url, f)
}
}
@ -283,6 +308,14 @@ mod tests {
)?,
);
// Two URLs should _not_ be considered equal if they differ in Git LFS enablement.
assert_ne!(
CanonicalUrl::parse(
"git+https://github.com/pypa/sample-namespace-packages.git#lfs=true"
)?,
CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?,
);
// Two URLs should _not_ be considered equal if they request different commit tags.
assert_ne!(
CanonicalUrl::parse(
@ -378,6 +411,76 @@ mod tests {
)?,
);
// Two URLs should be considered equal if they map to the same repository, even if they
// differ in Git LFS enablement.
assert_eq!(
RepositoryUrl::parse(
"git+https://github.com/pypa/sample-namespace-packages.git#lfs=true"
)?,
RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?,
);
Ok(())
}
#[test]
fn repository_url_with_lfs() -> Result<(), DisplaySafeUrlError> {
let mut hasher = CacheKeyHasher::new();
RepositoryUrl::parse("https://example.com/pypa/sample-namespace-packages.git@2.0.0")?
.cache_key(&mut hasher);
let repo_url_basic = hasher.finish();
let mut hasher = CacheKeyHasher::new();
RepositoryUrl::parse(
"https://user:foo@example.com/pypa/sample-namespace-packages.git@2.0.0#foo=bar",
)?
.cache_key(&mut hasher);
let repo_url_with_fragments = hasher.finish();
assert_eq!(
repo_url_basic, repo_url_with_fragments,
"repository urls should have the exact cache keys as fragments are removed",
);
let mut hasher = CacheKeyHasher::new();
RepositoryUrl::parse(
"https://user:foo@example.com/pypa/sample-namespace-packages.git@2.0.0#foo=bar",
)?
.with_lfs(None)
.cache_key(&mut hasher);
let git_url_with_fragments = hasher.finish();
assert_eq!(
repo_url_with_fragments, git_url_with_fragments,
"both structs should have the exact cache keys as fragments are still removed",
);
let mut hasher = CacheKeyHasher::new();
RepositoryUrl::parse(
"https://user:foo@example.com/pypa/sample-namespace-packages.git@2.0.0#foo=bar",
)?
.with_lfs(Some(false))
.cache_key(&mut hasher);
let git_url_with_fragments_and_lfs_false = hasher.finish();
assert_eq!(
git_url_with_fragments, git_url_with_fragments_and_lfs_false,
"both structs should have the exact cache keys as lfs false should not influence them",
);
let mut hasher = CacheKeyHasher::new();
RepositoryUrl::parse(
"https://user:foo@example.com/pypa/sample-namespace-packages.git@2.0.0#foo=bar",
)?
.with_lfs(Some(true))
.cache_key(&mut hasher);
let git_url_with_fragments_and_lfs_true = hasher.finish();
assert_ne!(
git_url_with_fragments, git_url_with_fragments_and_lfs_true,
"both structs should have different cache keys as one has Git LFS enabled",
);
Ok(())
}
}

View File

@ -1,11 +1,10 @@
[package]
name = "uv-cache"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
@ -35,5 +34,6 @@ rustc-hash = { workspace = true }
same-file = { workspace = true }
serde = { workspace = true, features = ["derive"] }
tempfile = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
walkdir = { workspace = true }

13
crates/uv-cache/README.md Normal file
View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-cache
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-cache).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

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

View File

@ -10,7 +10,7 @@ use rustc_hash::FxHashMap;
use tracing::{debug, trace, warn};
use uv_cache_info::Timestamp;
use uv_fs::{LockedFile, Simplified, cachedir, directories};
use uv_fs::{LockedFile, LockedFileError, LockedFileMode, Simplified, cachedir, directories};
use uv_normalize::PackageName;
use uv_pypi_types::ResolutionMetadata;
@ -35,6 +35,17 @@ mod wheel;
/// Must be kept in-sync with the version in [`CacheBucket::to_str`].
pub const ARCHIVE_VERSION: u8 = 0;
/// Error locking a cache entry or shard
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] io::Error),
#[error("Could not make the path absolute")]
Absolute(#[source] io::Error),
#[error("Could not acquire lock")]
Acquire(#[from] LockedFileError),
}
/// A [`CacheEntry`] which may or may not exist yet.
#[derive(Debug, Clone)]
pub struct CacheEntry(PathBuf);
@ -80,9 +91,14 @@ impl CacheEntry {
}
/// Acquire the [`CacheEntry`] as an exclusive lock.
pub async fn lock(&self) -> Result<LockedFile, io::Error> {
pub async fn lock(&self) -> Result<LockedFile, Error> {
fs_err::create_dir_all(self.dir())?;
LockedFile::acquire(self.path(), self.path().display()).await
Ok(LockedFile::acquire(
self.path(),
LockedFileMode::Exclusive,
self.path().display(),
)
.await?)
}
}
@ -109,9 +125,14 @@ impl CacheShard {
}
/// Acquire the cache entry as an exclusive lock.
pub async fn lock(&self) -> Result<LockedFile, io::Error> {
pub async fn lock(&self) -> Result<LockedFile, Error> {
fs_err::create_dir_all(self.as_ref())?;
LockedFile::acquire(self.join(".lock"), self.display()).await
Ok(LockedFile::acquire(
self.join(".lock"),
LockedFileMode::Exclusive,
self.display(),
)
.await?)
}
/// Return the [`CacheShard`] as a [`PathBuf`].
@ -182,7 +203,7 @@ impl Cache {
}
/// Acquire a lock that allows removing entries from the cache.
pub fn with_exclusive_lock(self) -> Result<Self, io::Error> {
pub async fn with_exclusive_lock(self) -> Result<Self, LockedFileError> {
let Self {
root,
refresh,
@ -198,8 +219,12 @@ impl Cache {
),
);
}
let lock_file =
LockedFile::acquire_blocking(root.join(".lock"), root.simplified_display())?;
let lock_file = LockedFile::acquire(
root.join(".lock"),
LockedFileMode::Exclusive,
root.simplified_display(),
)
.await?;
Ok(Self {
root,
@ -220,7 +245,11 @@ impl Cache {
lock_file,
} = self;
match LockedFile::acquire_no_wait(root.join(".lock"), root.simplified_display()) {
match LockedFile::acquire_no_wait(
root.join(".lock"),
LockedFileMode::Exclusive,
root.simplified_display(),
) {
Some(lock_file) => Ok(Self {
root,
refresh,
@ -372,10 +401,8 @@ impl Cache {
self.temp_dir.is_some()
}
/// Initialize the [`Cache`].
pub fn init(self) -> Result<Self, io::Error> {
let root = &self.root;
/// Populate the cache scaffold.
fn create_base_files(root: &PathBuf) -> io::Result<()> {
// Create the cache directory, if it doesn't exist.
fs_err::create_dir_all(root)?;
@ -421,29 +448,66 @@ impl Cache {
.join(".git"),
)?;
Ok(())
}
/// Initialize the [`Cache`].
pub async fn init(self) -> Result<Self, Error> {
let root = &self.root;
Self::create_base_files(root)?;
// Block cache removal operations from interfering.
let lock_file = match LockedFile::acquire_shared_blocking(
let lock_file = match LockedFile::acquire(
root.join(".lock"),
LockedFileMode::Shared,
root.simplified_display(),
) {
)
.await
{
Ok(lock_file) => Some(Arc::new(lock_file)),
Err(err) if err.kind() == io::ErrorKind::Unsupported => {
Err(err)
if err
.as_io_error()
.is_some_and(|err| err.kind() == io::ErrorKind::Unsupported) =>
{
warn!(
"Shared locking is not supported by the current platform or filesystem, \
reduced parallel process safety with `uv cache clean` and `uv cache prune`."
reduced parallel process safety with `uv cache clean` and `uv cache prune`."
);
None
}
Err(err) => return Err(err),
Err(err) => return Err(err.into()),
};
Ok(Self {
root: std::path::absolute(root)?,
root: std::path::absolute(root).map_err(Error::Absolute)?,
lock_file,
..self
})
}
/// Initialize the [`Cache`], assuming that there are no other uv processes running.
pub fn init_no_wait(self) -> Result<Option<Self>, Error> {
let root = &self.root;
Self::create_base_files(root)?;
// Block cache removal operations from interfering.
let Some(lock_file) = LockedFile::acquire_no_wait(
root.join(".lock"),
LockedFileMode::Shared,
root.simplified_display(),
) else {
return Ok(None);
};
Ok(Some(Self {
root: std::path::absolute(root).map_err(Error::Absolute)?,
lock_file: Some(Arc::new(lock_file)),
..self
}))
}
/// Clear the cache, removing all entries.
pub fn clear(self, reporter: Box<dyn CleanReporter>) -> Result<Removal, io::Error> {
// Remove everything but `.lock`, Windows does not allow removal of a locked file
@ -478,7 +542,7 @@ impl Cache {
/// Remove a package from the cache.
///
/// Returns the number of entries removed from the cache.
pub fn remove(&self, name: &PackageName) -> Result<Removal, io::Error> {
pub fn remove(&self, name: &PackageName) -> io::Result<Removal> {
// Collect the set of referenced archives.
let references = self.find_archive_references()?;

View File

@ -15,7 +15,7 @@ pub enum WheelCache<'a> {
Path(&'a DisplaySafeUrl),
/// An editable dependency, which we key by URL.
Editable(&'a DisplaySafeUrl),
/// A Git dependency, which we key by URL and SHA.
/// A Git dependency, which we key by URL (including LFS state), SHA.
///
/// Note that this variant only exists for source distributions; wheels can't be delivered
/// through Git.

View File

@ -1,11 +1,10 @@
[package]
name = "uv-cli"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

13
crates/uv-cli/README.md Normal file
View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-cli
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-cli).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,11 +1,10 @@
[package]
name = "uv-client"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-client
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-client).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -28,7 +28,7 @@ use tracing::{debug, trace};
use url::ParseError;
use url::Url;
use uv_auth::{AuthMiddleware, Credentials, Indexes, PyxTokenStore};
use uv_auth::{AuthMiddleware, Credentials, CredentialsCache, Indexes, PyxTokenStore};
use uv_configuration::{KeyringProviderType, TrustedHost};
use uv_fs::Simplified;
use uv_pep508::MarkerEnvironment;
@ -50,7 +50,7 @@ pub const DEFAULT_RETRIES: u32 = 3;
/// Maximum number of redirects to follow before giving up.
///
/// This is the default used by [`reqwest`].
const DEFAULT_MAX_REDIRECTS: u32 = 10;
pub const DEFAULT_MAX_REDIRECTS: u32 = 10;
/// Selectively skip parts or the entire auth middleware.
#[derive(Debug, Clone, Copy, Default)]
@ -78,6 +78,8 @@ pub struct BaseClientBuilder<'a> {
markers: Option<&'a MarkerEnvironment>,
platform: Option<&'a Platform>,
auth_integration: AuthIntegration,
/// Global authentication cache for a uv invocation to share credentials across uv clients.
credentials_cache: Arc<CredentialsCache>,
indexes: Indexes,
timeout: Duration,
extra_middleware: Option<ExtraMiddleware>,
@ -89,6 +91,8 @@ pub struct BaseClientBuilder<'a> {
cross_origin_credential_policy: CrossOriginCredentialsPolicy,
/// Optional custom reqwest client to use instead of creating a new one.
custom_client: Option<Client>,
/// uv subcommand in which this client is being used
subcommand: Option<Vec<String>>,
}
/// The policy for handling HTTP redirects.
@ -100,6 +104,8 @@ pub enum RedirectPolicy {
BypassMiddleware,
/// Handle redirects manually, re-triggering our custom middleware for each request.
RetriggerMiddleware,
/// No redirect for non-cloneable (e.g., streaming) requests with custom redirect logic.
NoRedirect,
}
impl RedirectPolicy {
@ -107,6 +113,7 @@ impl RedirectPolicy {
match self {
Self::BypassMiddleware => reqwest::redirect::Policy::default(),
Self::RetriggerMiddleware => reqwest::redirect::Policy::none(),
Self::NoRedirect => reqwest::redirect::Policy::none(),
}
}
}
@ -136,6 +143,7 @@ impl Default for BaseClientBuilder<'_> {
markers: None,
platform: None,
auth_integration: AuthIntegration::default(),
credentials_cache: Arc::new(CredentialsCache::default()),
indexes: Indexes::new(),
timeout: Duration::from_secs(30),
extra_middleware: None,
@ -143,11 +151,12 @@ impl Default for BaseClientBuilder<'_> {
redirect_policy: RedirectPolicy::default(),
cross_origin_credential_policy: CrossOriginCredentialsPolicy::Secure,
custom_client: None,
subcommand: None,
}
}
}
impl BaseClientBuilder<'_> {
impl<'a> BaseClientBuilder<'a> {
pub fn new(
connectivity: Connectivity,
native_tls: bool,
@ -166,9 +175,7 @@ impl BaseClientBuilder<'_> {
..Self::default()
}
}
}
impl<'a> BaseClientBuilder<'a> {
/// Use a custom reqwest client instead of creating a new one.
///
/// This allows you to provide your own reqwest client with custom configuration.
@ -276,6 +283,26 @@ impl<'a> BaseClientBuilder<'a> {
self
}
#[must_use]
pub fn subcommand(mut self, subcommand: Vec<String>) -> Self {
self.subcommand = Some(subcommand);
self
}
pub fn credentials_cache(&self) -> &CredentialsCache {
&self.credentials_cache
}
/// See [`CredentialsCache::store_credentials_from_url`].
pub fn store_credentials_from_url(&self, url: &DisplaySafeUrl) -> bool {
self.credentials_cache.store_credentials_from_url(url)
}
/// See [`CredentialsCache::store_credentials`].
pub fn store_credentials(&self, url: &DisplaySafeUrl, credentials: Credentials) {
self.credentials_cache.store_credentials(url, credentials);
}
pub fn is_native_tls(&self) -> bool {
self.native_tls
}
@ -324,6 +351,7 @@ impl<'a> BaseClientBuilder<'a> {
dangerous_client,
raw_dangerous_client,
timeout,
credentials_cache: self.credentials_cache.clone(),
}
}
@ -350,6 +378,7 @@ impl<'a> BaseClientBuilder<'a> {
raw_client: existing.raw_client.clone(),
raw_dangerous_client: existing.raw_dangerous_client.clone(),
timeout: existing.timeout,
credentials_cache: existing.credentials_cache.clone(),
}
}
@ -358,7 +387,7 @@ impl<'a> BaseClientBuilder<'a> {
let mut user_agent_string = format!("uv/{}", version());
// Add linehaul metadata.
let linehaul = LineHaul::new(self.markers, self.platform);
let linehaul = LineHaul::new(self.markers, self.platform, self.subcommand.clone());
if let Ok(output) = serde_json::to_string(&linehaul) {
let _ = write!(user_agent_string, " {output}");
}
@ -554,6 +583,7 @@ impl<'a> BaseClientBuilder<'a> {
match self.auth_integration {
AuthIntegration::Default => {
let mut auth_middleware = AuthMiddleware::new()
.with_cache_arc(self.credentials_cache.clone())
.with_base_client(base_client)
.with_indexes(self.indexes.clone())
.with_keyring(self.keyring.to_provider())
@ -565,6 +595,7 @@ impl<'a> BaseClientBuilder<'a> {
}
AuthIntegration::OnlyAuthenticated => {
let mut auth_middleware = AuthMiddleware::new()
.with_cache_arc(self.credentials_cache.clone())
.with_base_client(base_client)
.with_indexes(self.indexes.clone())
.with_keyring(self.keyring.to_provider())
@ -608,6 +639,8 @@ pub struct BaseClient {
allow_insecure_host: Vec<TrustedHost>,
/// The number of retries to attempt on transient errors.
retries: u32,
/// Global authentication cache for a uv invocation to share credentials across uv clients.
credentials_cache: Arc<CredentialsCache>,
}
#[derive(Debug, Clone, Copy)]
@ -659,6 +692,10 @@ impl BaseClient {
}
builder.build_with_max_retries(self.retries)
}
pub fn credentials_cache(&self) -> &CredentialsCache {
&self.credentials_cache
}
}
/// Wrapper around [`ClientWithMiddleware`] that manages redirects.
@ -695,6 +732,7 @@ impl RedirectClientWithMiddleware {
match self.redirect_policy {
RedirectPolicy::BypassMiddleware => self.client.execute(req).await,
RedirectPolicy::RetriggerMiddleware => self.execute_with_redirect_handling(req).await,
RedirectPolicy::NoRedirect => self.client.execute(req).await,
}
}

View File

@ -5,6 +5,7 @@ use std::fmt::{Display, Formatter};
use std::ops::Deref;
use std::path::PathBuf;
use uv_cache::Error as CacheError;
use uv_distribution_filename::{WheelFilename, WheelFilenameError};
use uv_normalize::PackageName;
use uv_redacted::DisplaySafeUrl;
@ -337,6 +338,9 @@ pub enum ErrorKind {
#[error("Failed to write to the client cache")]
CacheWrite(#[source] std::io::Error),
#[error("Failed to acquire lock on the client cache")]
CacheLock(#[source] CacheError),
#[error(transparent)]
Io(std::io::Error),

View File

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

View File

@ -12,6 +12,7 @@ use uv_version::version;
pub struct Installer {
pub name: Option<String>,
pub version: Option<String>,
pub subcommand: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
@ -63,7 +64,11 @@ pub struct LineHaul {
impl LineHaul {
/// Initializes Linehaul information based on PEP 508 markers.
#[instrument(name = "linehaul", skip_all)]
pub fn new(markers: Option<&MarkerEnvironment>, platform: Option<&Platform>) -> Self {
pub fn new(
markers: Option<&MarkerEnvironment>,
platform: Option<&Platform>,
subcommand: Option<Vec<String>>,
) -> Self {
// https://github.com/pypa/pip/blob/24.0/src/pip/_internal/network/session.py#L87
let looks_like_ci = [
EnvVars::BUILD_BUILDID,
@ -123,6 +128,7 @@ impl LineHaul {
installer: Option::from(Installer {
name: Some("uv".to_string()),
version: Some(version().to_string()),
subcommand,
}),
python: markers.map(|markers| markers.python_full_version().version.to_string()),
implementation: Option::from(Implementation {

View File

@ -15,7 +15,7 @@ use tokio::sync::{Mutex, Semaphore};
use tracing::{Instrument, debug, info_span, instrument, trace, warn};
use url::Url;
use uv_auth::{Indexes, PyxTokenStore};
use uv_auth::{CredentialsCache, Indexes, PyxTokenStore};
use uv_cache::{Cache, CacheBucket, CacheEntry, WheelCache};
use uv_configuration::IndexStrategy;
use uv_configuration::KeyringProviderType;
@ -148,8 +148,30 @@ impl<'a> RegistryClientBuilder<'a> {
self
}
pub fn build(self) -> RegistryClient {
self.index_locations.cache_index_credentials();
/// Add all authenticated sources to the cache.
pub fn cache_index_credentials(&mut self) {
for index in self.index_locations.known_indexes() {
if let Some(credentials) = index.credentials() {
trace!(
"Read credentials for index {}",
index
.name
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| index.url.to_string())
);
if let Some(root_url) = index.root_url() {
self.base_client_builder
.store_credentials(&root_url, credentials.clone());
}
self.base_client_builder
.store_credentials(index.raw_url(), credentials);
}
}
}
pub fn build(mut self) -> RegistryClient {
self.cache_index_credentials();
let index_urls = self.index_locations.index_urls();
// Build a base client
@ -180,8 +202,8 @@ impl<'a> RegistryClientBuilder<'a> {
}
/// Share the underlying client between two different middleware configurations.
pub fn wrap_existing(self, existing: &BaseClient) -> RegistryClient {
self.index_locations.cache_index_credentials();
pub fn wrap_existing(mut self, existing: &BaseClient) -> RegistryClient {
self.cache_index_credentials();
let index_urls = self.index_locations.index_urls();
// Wrap in any relevant middleware and handle connectivity.
@ -269,6 +291,10 @@ impl RegistryClient {
self.timeout
}
pub fn credentials_cache(&self) -> &CredentialsCache {
self.client.uncached().credentials_cache()
}
/// Return the appropriate index URLs for the given [`PackageName`].
fn index_urls_for(
&self,
@ -513,7 +539,7 @@ impl RegistryClient {
#[cfg(windows)]
let _lock = {
let lock_entry = cache_entry.with_file(format!("{package_name}.lock"));
lock_entry.lock().await.map_err(ErrorKind::CacheWrite)?
lock_entry.lock().await.map_err(ErrorKind::CacheLock)?
};
let result = if matches!(index, IndexUrl::Path(_)) {
@ -1005,7 +1031,7 @@ impl RegistryClient {
#[cfg(windows)]
let _lock = {
let lock_entry = cache_entry.with_file(format!("{}.lock", filename.stem()));
lock_entry.lock().await.map_err(ErrorKind::CacheWrite)?
lock_entry.lock().await.map_err(ErrorKind::CacheLock)?
};
let response_callback = async |response: Response| {
@ -1089,7 +1115,7 @@ impl RegistryClient {
#[cfg(windows)]
let _lock = {
let lock_entry = cache_entry.with_file(format!("{}.lock", filename.stem()));
lock_entry.lock().await.map_err(ErrorKind::CacheWrite)?
lock_entry.lock().await.map_err(ErrorKind::CacheLock)?
};
// Attempt to fetch via a range request.

View File

@ -11,7 +11,7 @@ use uv_redacted::DisplaySafeUrl;
#[tokio::test]
async fn remote_metadata_with_and_without_cache() -> Result<()> {
let cache = Cache::temp()?.init()?;
let cache = Cache::temp()?.init().await?;
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build();
// The first run is without cache (the tempdir is empty), the second has the cache from the

View File

@ -84,7 +84,7 @@ async fn ssl_env_vars() -> Result<()> {
}
let (server_task, addr) = start_https_user_agent_server(&standalone_server_cert).await?;
let url = DisplaySafeUrl::from_str(&format!("https://{addr}"))?;
let cache = Cache::temp()?.init()?;
let cache = Cache::temp()?.init().await?;
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build();
let res = client
.cached_client()
@ -142,7 +142,7 @@ async fn ssl_env_vars() -> Result<()> {
}
let (server_task, addr) = start_https_user_agent_server(&standalone_server_cert).await?;
let url = DisplaySafeUrl::from_str(&format!("https://{addr}"))?;
let cache = Cache::temp()?.init()?;
let cache = Cache::temp()?.init().await?;
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build();
let res = client
.cached_client()
@ -171,7 +171,7 @@ async fn ssl_env_vars() -> Result<()> {
}
let (server_task, addr) = start_https_user_agent_server(&standalone_server_cert).await?;
let url = DisplaySafeUrl::from_str(&format!("https://{addr}"))?;
let cache = Cache::temp()?.init()?;
let cache = Cache::temp()?.init().await?;
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build();
let res = client
.cached_client()
@ -194,7 +194,7 @@ async fn ssl_env_vars() -> Result<()> {
}
let (server_task, addr) = start_https_user_agent_server(&standalone_server_cert).await?;
let url = DisplaySafeUrl::from_str(&format!("https://{addr}"))?;
let cache = Cache::temp()?.init()?;
let cache = Cache::temp()?.init().await?;
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build();
let res = client
.cached_client()
@ -259,7 +259,7 @@ async fn ssl_env_vars() -> Result<()> {
}
let (server_task, addr) = start_https_mtls_user_agent_server(&ca_cert, &server_cert).await?;
let url = DisplaySafeUrl::from_str(&format!("https://{addr}"))?;
let cache = Cache::temp()?.init()?;
let cache = Cache::temp()?.init().await?;
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build();
let res = client
.cached_client()
@ -283,7 +283,7 @@ async fn ssl_env_vars() -> Result<()> {
}
let (server_task, addr) = start_https_mtls_user_agent_server(&ca_cert, &server_cert).await?;
let url = DisplaySafeUrl::from_str(&format!("https://{addr}"))?;
let cache = Cache::temp()?.init()?;
let cache = Cache::temp()?.init().await?;
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build();
let res = client
.cached_client()

View File

@ -20,7 +20,7 @@ async fn test_user_agent_has_version() -> Result<()> {
let (server_task, addr) = start_http_user_agent_server().await?;
// Initialize uv-client
let cache = Cache::temp()?.init()?;
let cache = Cache::temp()?.init().await?;
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build();
// Send request to our dummy server
@ -57,7 +57,70 @@ async fn test_user_agent_has_version() -> Result<()> {
assert_json_snapshot!(&linehaul.installer, @r#"
{
"name": "uv",
"version": "[VERSION]"
"version": "[VERSION]",
"subcommand": null
}
"#);
});
// Wait for the server task to complete, to be a good citizen.
let _ = server_task.await?;
Ok(())
}
#[tokio::test]
async fn test_user_agent_has_subcommand() -> Result<()> {
// Initialize dummy http server
let (server_task, addr) = start_http_user_agent_server().await?;
// Initialize uv-client
let cache = Cache::temp()?.init().await?;
let client = RegistryClientBuilder::new(
BaseClientBuilder::default().subcommand(vec!["foo".to_owned(), "bar".to_owned()]),
cache,
)
.build();
// Send request to our dummy server
let url = DisplaySafeUrl::from_str(&format!("http://{addr}"))?;
let res = client
.cached_client()
.uncached()
.for_host(&url)
.get(Url::from(url))
.send()
.await?;
// Check the HTTP status
assert!(res.status().is_success());
// Check User Agent
let body = res.text().await?;
let (uv_version, uv_linehaul) = body
.split_once(' ')
.expect("Failed to split User-Agent header");
// Deserializing Linehaul
let linehaul: LineHaul = serde_json::from_str(uv_linehaul)?;
// Assert linehaul user agent
let filters = vec![(version(), "[VERSION]")];
with_settings!({
filters => filters
}, {
// Assert uv version
assert_snapshot!(uv_version, @"uv/[VERSION]");
// Assert linehaul json
assert_json_snapshot!(&linehaul.installer, @r#"
{
"name": "uv",
"version": "[VERSION]",
"subcommand": [
"foo",
"bar"
]
}
"#);
});
@ -89,7 +152,7 @@ async fn test_user_agent_has_linehaul() -> Result<()> {
})?;
// Initialize uv-client
let cache = Cache::temp()?.init()?;
let cache = Cache::temp()?.init().await?;
let mut builder =
RegistryClientBuilder::new(BaseClientBuilder::default(), cache).markers(&markers);
@ -152,11 +215,12 @@ async fn test_user_agent_has_linehaul() -> Result<()> {
assert_json_snapshot!(&linehaul, {
".distro" => "[distro]",
".ci" => "[ci]"
}, @r###"
}, @r#"
{
"installer": {
"name": "uv",
"version": "[VERSION]"
"version": "[VERSION]",
"subcommand": null
},
"python": "3.12.2",
"implementation": {
@ -174,7 +238,7 @@ async fn test_user_agent_has_linehaul() -> Result<()> {
"rustc_version": null,
"ci": "[ci]"
}
"###);
"#);
});
// Assert distro

View File

@ -1,11 +1,10 @@
[package]
name = "uv-configuration"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-configuration
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-configuration).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

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

View File

@ -1,11 +1,10 @@
[package]
name = "uv-console"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-console
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-console).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -1,13 +1,12 @@
[package]
name = "uv-dev"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
publish = false
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
@ -80,4 +79,4 @@ performance-memory-allocator = ["dep:uv-performance-memory-allocator"]
render = ["poloto", "resvg", "tagu"]
[package.metadata.cargo-shear]
ignored = ["flate2", "uv-extract", "uv-performance-memory-allocator"]
ignored = ["uv-performance-memory-allocator"]

13
crates/uv-dev/README.md Normal file
View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-dev
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-dev).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -18,7 +18,7 @@ pub(crate) struct CompileArgs {
}
pub(crate) async fn compile(args: CompileArgs) -> anyhow::Result<()> {
let cache = Cache::try_from(args.cache_args)?.init()?;
let cache = Cache::try_from(args.cache_args)?.init().await?;
let interpreter = if let Some(python) = args.python {
python

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ pub(crate) async fn list_packages(
args: ListPackagesArgs,
environment: EnvironmentOptions,
) -> Result<()> {
let cache = Cache::try_from(args.cache_args)?.init()?;
let cache = Cache::try_from(args.cache_args)?.init().await?;
let client = RegistryClientBuilder::new(
BaseClientBuilder::default().timeout(environment.http_timeout),
cache,

View File

@ -22,7 +22,7 @@ pub(crate) async fn validate_zip(
args: ValidateZipArgs,
environment: EnvironmentOptions,
) -> Result<()> {
let cache = Cache::try_from(args.cache_args)?.init()?;
let cache = Cache::try_from(args.cache_args)?.init().await?;
let client = RegistryClientBuilder::new(
BaseClientBuilder::default().timeout(environment.http_timeout),
cache,

View File

@ -23,7 +23,7 @@ pub(crate) async fn wheel_metadata(
args: WheelMetadataArgs,
environment: EnvironmentOptions,
) -> Result<()> {
let cache = Cache::try_from(args.cache_args)?.init()?;
let cache = Cache::try_from(args.cache_args)?.init().await?;
let client = RegistryClientBuilder::new(
BaseClientBuilder::default().timeout(environment.http_timeout),
cache,

View File

@ -1,11 +1,10 @@
[package]
name = "uv-dirs"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

13
crates/uv-dirs/README.md Normal file
View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-dirs
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-dirs).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -1,11 +1,10 @@
[package]
name = "uv-dispatch"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-dispatch
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-dispatch).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -492,6 +492,7 @@ impl BuildContext for BuildDispatch<'_> {
environment_variables,
build_output,
self.concurrency.builds,
self.client.credentials_cache(),
self.preview,
)
.boxed_local()
@ -504,6 +505,7 @@ impl BuildContext for BuildDispatch<'_> {
source: &'data Path,
subdirectory: Option<&'data Path>,
output_dir: &'data Path,
sources: SourceStrategy,
build_kind: BuildKind,
version_id: Option<&'data str>,
) -> Result<Option<DistFilename>, BuildDispatchError> {
@ -532,6 +534,7 @@ impl BuildContext for BuildDispatch<'_> {
&output_dir,
None,
uv_version::version(),
sources == SourceStrategy::Enabled,
)?;
DistFilename::WheelFilename(wheel)
}
@ -540,6 +543,7 @@ impl BuildContext for BuildDispatch<'_> {
&source_tree,
&output_dir,
uv_version::version(),
sources == SourceStrategy::Enabled,
)?;
DistFilename::SourceDistFilename(source_dist)
}
@ -549,6 +553,7 @@ impl BuildContext for BuildDispatch<'_> {
&output_dir,
None,
uv_version::version(),
sources == SourceStrategy::Enabled,
)?;
DistFilename::WheelFilename(wheel)
}

View File

@ -1,11 +1,10 @@
[package]
name = "uv-distribution-filename"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-distribution-filename
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-distribution-filename).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -1,11 +1,10 @@
[package]
name = "uv-distribution-types"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-distribution-types
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-distribution-types).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -8,7 +8,6 @@ use std::sync::{Arc, LazyLock, RwLock};
use itertools::Either;
use rustc_hash::{FxHashMap, FxHashSet};
use thiserror::Error;
use tracing::trace;
use url::{ParseError, Url};
use uv_auth::RealmRef;
use uv_cache_key::CanonicalUrl;
@ -440,26 +439,6 @@ impl<'a> IndexLocations {
}
}
/// Add all authenticated sources to the cache.
pub fn cache_index_credentials(&self) {
for index in self.known_indexes() {
if let Some(credentials) = index.credentials() {
trace!(
"Read credentials for index {}",
index
.name
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| index.url.to_string())
);
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
uv_auth::store_credentials(index.raw_url(), credentials);
}
}
}
/// Return the Simple API cache control header for an [`IndexUrl`], if configured.
pub fn simple_api_cache_control_for(&self, url: &IndexUrl) -> Option<&str> {
for index in &self.indexes {

View File

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

View File

@ -7,7 +7,7 @@ use thiserror::Error;
use uv_cache_key::{CacheKey, CacheKeyHasher};
use uv_distribution_filename::DistExtension;
use uv_fs::{CWD, PortablePath, PortablePathBuf, relative_to};
use uv_git_types::{GitOid, GitReference, GitUrl, GitUrlParseError, OidParseError};
use uv_git_types::{GitLfs, GitOid, GitReference, GitUrl, GitUrlParseError, OidParseError};
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::VersionSpecifiers;
use uv_pep508::{
@ -350,6 +350,13 @@ impl Display for Requirement {
if let Some(subdirectory) = subdirectory {
writeln!(f, "#subdirectory={}", subdirectory.display())?;
}
if git.lfs().enabled() {
writeln!(
f,
"{}lfs=true",
if subdirectory.is_some() { "&" } else { "#" }
)?;
}
}
RequirementSource::Path { url, .. } => {
write!(f, " @ {url}")?;
@ -436,6 +443,9 @@ impl CacheKey for Requirement {
} else {
0u8.cache_key(state);
}
if git.lfs().enabled() {
1u8.cache_key(state);
}
url.cache_key(state);
}
RequirementSource::Path {
@ -765,6 +775,13 @@ impl Display for RequirementSource {
if let Some(subdirectory) = subdirectory {
writeln!(f, "#subdirectory={}", subdirectory.display())?;
}
if git.lfs().enabled() {
writeln!(
f,
"{}lfs=true",
if subdirectory.is_some() { "&" } else { "#" }
)?;
}
}
Self::Path { url, .. } => {
write!(f, "{url}")?;
@ -856,6 +873,11 @@ impl From<RequirementSource> for RequirementSourceWire {
.append_pair("subdirectory", &subdirectory);
}
// Persist lfs=true in the distribution metadata only when explicitly enabled.
if git.lfs().enabled() {
url.query_pairs_mut().append_pair("lfs", "true");
}
// Put the requested reference in the query.
match git.reference() {
GitReference::Branch(branch) => {
@ -932,6 +954,7 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
let mut reference = GitReference::DefaultBranch;
let mut subdirectory: Option<PortablePathBuf> = None;
let mut lfs = GitLfs::Disabled;
for (key, val) in repository.query_pairs() {
match &*key {
"tag" => reference = GitReference::Tag(val.into_owned()),
@ -940,6 +963,7 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
"subdirectory" => {
subdirectory = Some(PortablePathBuf::from(val.as_ref()));
}
"lfs" => lfs = GitLfs::from(val.eq_ignore_ascii_case("true")),
_ => {}
}
}
@ -959,13 +983,22 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
let path = format!("{}@{}", url.path(), rev);
url.set_path(&path);
}
let mut frags: Vec<String> = Vec::new();
if let Some(subdirectory) = subdirectory.as_ref() {
url.set_fragment(Some(&format!("subdirectory={subdirectory}")));
frags.push(format!("subdirectory={subdirectory}"));
}
// Preserve that we're using Git LFS in the Verbatim Url representations
if lfs.enabled() {
frags.push("lfs=true".to_string());
}
if !frags.is_empty() {
url.set_fragment(Some(&frags.join("&")));
}
let url = VerbatimUrl::from_url(url);
Ok(Self::Git {
git: GitUrl::from_fields(repository, reference, precise)?,
git: GitUrl::from_fields(repository, reference, precise, lfs)?,
subdirectory: subdirectory.map(Box::<Path>::from),
url,
})

View File

@ -1,7 +1,7 @@
use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use uv_git_types::GitReference;
use uv_git_types::{GitLfs, GitReference};
use uv_normalize::ExtraName;
use uv_pep508::{MarkerEnvironment, MarkerTree, UnnamedRequirement};
use uv_pypi_types::{Hashes, ParsedUrl};
@ -75,6 +75,7 @@ impl UnresolvedRequirement {
rev: Option<&str>,
tag: Option<&str>,
branch: Option<&str>,
lfs: Option<bool>,
marker: Option<MarkerTree>,
) -> Self {
#[allow(clippy::manual_map)]
@ -107,6 +108,11 @@ impl UnresolvedRequirement {
} else {
git
};
let git = if let Some(lfs) = lfs {
git.with_lfs(GitLfs::from(lfs))
} else {
git
};
RequirementSource::Git {
git,
subdirectory,
@ -129,6 +135,9 @@ impl UnresolvedRequirement {
if let Some(git_reference) = git_reference {
git.url = git.url.with_reference(git_reference);
}
if let Some(lfs) = lfs {
git.url = git.url.with_lfs(GitLfs::from(lfs));
}
VerbatimParsedUrl {
parsed_url: ParsedUrl::Git(git),
verbatim: requirement.url.verbatim,

View File

@ -1,11 +1,10 @@
[package]
name = "uv-distribution"
version = "0.0.1"
description = "This is a component crate of uv"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
@ -25,6 +24,7 @@ uv-configuration = { workspace = true }
uv-distribution-filename = { workspace = true }
uv-distribution-types = { workspace = true }
uv-extract = { workspace = true }
uv-flags = { workspace = true }
uv-fs = { workspace = true, features = ["tokio"] }
uv-git = { workspace = true }
uv-git-types = { workspace = true }

View File

@ -0,0 +1,13 @@
<!-- This file is generated. DO NOT EDIT -->
# uv-distribution
This crate is an internal component of [uv](https://crates.io/crates/uv). The Rust API exposed here
is unstable and will have frequent breaking changes.
This version (0.0.8) is a component of [uv 0.9.18](https://crates.io/crates/uv/0.9.18). The source
can be found [here](https://github.com/astral-sh/uv/blob/0.9.18/crates/uv-distribution).
See uv's
[crate versioning policy](https://docs.astral.sh/uv/reference/policies/versioning/#crate-versioning)
for details on versioning.

View File

@ -385,6 +385,27 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
.boxed_local()
.await?;
// Check that the wheel is compatible with its install target.
//
// When building a build dependency for a cross-install, the build dependency needs
// to install and run on the host instead of the target. In this case the `tags` are already
// for the host instead of the target, so this check passes.
if !built_wheel.filename.is_compatible(tags) {
return if tags.is_cross() {
Err(Error::BuiltWheelIncompatibleTargetPlatform {
filename: built_wheel.filename,
python_platform: tags.python_platform().clone(),
python_version: tags.python_version(),
})
} else {
Err(Error::BuiltWheelIncompatibleHostPlatform {
filename: built_wheel.filename,
python_platform: tags.python_platform().clone(),
python_version: tags.python_version(),
})
};
}
// Acquire the advisory lock.
#[cfg(windows)]
let _lock = {
@ -395,7 +416,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
built_wheel.target.file_name().unwrap().to_str().unwrap()
),
);
lock_entry.lock().await.map_err(Error::CacheWrite)?
lock_entry.lock().await.map_err(Error::CacheLock)?
};
// If the wheel was unzipped previously, respect it. Source distributions are
@ -554,7 +575,11 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
pyproject_toml: &PyProjectToml,
) -> Result<Option<RequiresDist>, Error> {
self.builder
.source_tree_requires_dist(path, pyproject_toml)
.source_tree_requires_dist(
path,
pyproject_toml,
self.client.unmanaged.credentials_cache(),
)
.await
}
@ -574,7 +599,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
#[cfg(windows)]
let _lock = {
let lock_entry = wheel_entry.with_file(format!("{}.lock", filename.stem()));
lock_entry.lock().await.map_err(Error::CacheWrite)?
lock_entry.lock().await.map_err(Error::CacheLock)?
};
// Create an entry for the HTTP cache.
@ -745,7 +770,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
#[cfg(windows)]
let _lock = {
let lock_entry = wheel_entry.with_file(format!("{}.lock", filename.stem()));
lock_entry.lock().await.map_err(Error::CacheWrite)?
lock_entry.lock().await.map_err(Error::CacheLock)?
};
// Create an entry for the HTTP cache.
@ -947,7 +972,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
#[cfg(windows)]
let _lock = {
let lock_entry = wheel_entry.with_file(format!("{}.lock", filename.stem()));
lock_entry.lock().await.map_err(Error::CacheWrite)?
lock_entry.lock().await.map_err(Error::CacheLock)?
};
// Determine the last-modified time of the wheel.

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