Compare commits

...

819 Commits
0.8.8 ... 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
Zanie Blue 8d8aabb884
Add read permissions to `publish-crates` job (#16797) 2025-11-20 16:38:19 -06:00
Zanie Blue f2e92b4bfb
Increase walltime timeout by 5m (#16796)
It is failing while caching the dependencies
2025-11-20 22:36:38 +00:00
Charlie Marsh c5c44168e0
Cache Dependabot lookup (#16795)
## Summary

Small nit, but I wanted to avoid doing this access in the hot path.
(Probably not important in practice.)

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-11-20 16:24:16 -06:00
Zanie Blue 4be1e0a83c
Bump version to 0.9.11 (#16794) 2025-11-20 16:24:01 -06:00
Zanie Blue dfe89047bb
Publish to `crates.io` (#16770) 2025-11-20 21:26:44 +00:00
github-actions[bot] e799a088a5
Sync latest Python releases (#16792)
Automated update for Python releases.

---------

Co-authored-by: jjhelmus <1050278+jjhelmus@users.noreply.github.com>
Co-authored-by: Jonathan Helmus <jjhelmus@gmail.com>
2025-11-20 20:27:40 +00:00
Zanie Blue 5eda329e5a
Improve note about build system in publish guide (#16788)
Addresses
https://github.com/astral-sh/uv/issues/5605#issuecomment-3549958048
2025-11-20 13:59:59 -06:00
Zanie Blue aebd7578bb
Add `uv workspace list --paths` (#16776)
I initially thought I didn't need this, but in some contexts, the
workspace member name is not useful at all and I just want to iterate
over the paths without composing with `uv workspace dir --package
<name>`
2025-11-20 13:44:57 -06:00
liam 79bfa2b4cd
Preserve end-of-line comment whitespace when editing `pyproject.toml` (#16734)
Resolves https://github.com/astral-sh/uv/issues/16719

`uv add` collapses multiple spaces before inline comments in
`[project.dependencies]`, causing unrelated diffs and moving comments
onto the wrong columns. This diff captures the exact whitespace padding
that preceded each end-of-line comment when parsing the array and reuses
it when formatting.

---------

Co-authored-by: konstin <konstin@mailbox.org>
2025-11-20 20:15:27 +01:00
liam 5b4446f086
Fix `uv init` author serialization via `toml_edit` inline tables (#16778) 2025-11-20 14:10:12 -05:00
Zanie Blue 75bd2ea0c5
Move do not upload publish note out of the guide into concepts (#16789)
This feels a little out of place here and it seems nice to be able to
link to it.
2025-11-20 12:33:15 -06:00
Tom Schafer fd7e6d0a05
Add SBOM export support (#16523)
Co-authored-by: Will Rollason <william.rollason@snyk.io>
2025-11-20 12:52:31 -05:00
pythonweb2 7d8634bf35
Document the new behavior for free-threaded python versions (#16781)
## Summary

I noticed that after first installing the free-threaded version, then
the gil version of 3.14, I wasn't able
to install greenlet, because it doesn't ship with wheels for the
free-threaded version (I think it isn't
safe for it to use that interpreter). I noticed that the change made in
3.14 wasn't updated in the docs.

## Test Plan

N/A

---------

Co-authored-by: Wade Roberts <wade.roberts@centralsquare.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-11-20 08:58:49 -06:00
Ryan Blue 4a867dc60b
Fix status messages without TTY (#16785)
<!--
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

#12175 changed the behavior of `on_request_complete` when stderr is not
a tty to output `Downloading`/`Uploading` (via `Direction::as_str`).
This fixes it to output `Downloaded`/`Uploaded` again.

## Test Plan

Tested locally to verify new output.

Old:
```
$ uv sync --no-cache 2>&1 | tee /dev/null
Using CPython 3.14.0
Creating virtual environment at: .venv
Resolved 12 packages in 19ms
Downloading numpy (15.8MiB)
Downloading matplotlib (9.4MiB)
Downloading fonttools (4.6MiB)
Downloading pillow (6.7MiB)
Downloading kiwisolver (1.4MiB)
 Downloading kiwisolver
 Downloading fonttools
 Downloading pillow
 Downloading matplotlib
 Downloading numpy
```
New:
```
$ uv sync --no-cache 2>&1 | tee /dev/null
Using CPython 3.14.0
Creating virtual environment at: .venv
Resolved 12 packages in 3ms
Downloading numpy (15.8MiB)
Downloading fonttools (4.6MiB)
Downloading matplotlib (9.4MiB)
Downloading kiwisolver (1.4MiB)
Downloading pillow (6.7MiB)
 Downloaded kiwisolver
 Downloaded pillow
 Downloaded fonttools
 Downloaded matplotlib
 Downloaded numpy
```
2025-11-20 15:14:42 +01:00
Zanie Blue fc0cf90795
Fix the preview warning on `uv workspace dir` (#16775) 2025-11-19 13:20:25 -06:00
Zanie Blue 3ac43e8d15
Disable always-authenticate when running under Dependabot (#16773)
Dependabot appears to run a proxy which intercepts all requests and adds
credentials — credentials are _not_ provided via the CLI or environment
variables and there's no way for a user to do so. This means that when
`authenticate = "always"` is used (or when the index URL is on a pyx
domain), uv will fail even though Dependabot may intercept the request
and add credentials.

See
https://github.com/dependabot/dependabot-core/#private-registry-credential-management
2025-11-18 15:43:44 -06:00
renovate[bot] 0e88114882
Update dependency astral-sh/uv to v0.9.10 (#16753)
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.8` -> `0.9.10` |

---

### Release Notes

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

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

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

Released on 2025-11-17.

##### Enhancements

- Add support for `SSL_CERT_DIR`
([#&#8203;16473](https://redirect.github.com/astral-sh/uv/pull/16473))
- Enforce UTF‑8-encoded license files during `uv build`
([#&#8203;16699](https://redirect.github.com/astral-sh/uv/pull/16699))
- Error when a `project.license-files` glob matches nothing
([#&#8203;16697](https://redirect.github.com/astral-sh/uv/pull/16697))
- `pip install --target` (and `sync`) install Python if necessary
([#&#8203;16694](https://redirect.github.com/astral-sh/uv/pull/16694))
- Account for `python_downloads_json_url` in pre-release Python version
warnings
([#&#8203;16737](https://redirect.github.com/astral-sh/uv/pull/16737))
- Support HTTP/HTTPS URLs in `uv python --python-downloads-json-url`
([#&#8203;16542](https://redirect.github.com/astral-sh/uv/pull/16542))

##### Preview features

- Add support for `--upgrade` in `uv python install`
([#&#8203;16676](https://redirect.github.com/astral-sh/uv/pull/16676))
- Fix handling of `python install --default` for pre-release Python
versions
([#&#8203;16706](https://redirect.github.com/astral-sh/uv/pull/16706))
- Add `uv workspace list` to list workspace members
([#&#8203;16691](https://redirect.github.com/astral-sh/uv/pull/16691))

##### Bug fixes

- Don't check file URLs for ambiguously parsed credentials
([#&#8203;16759](https://redirect.github.com/astral-sh/uv/pull/16759))

##### Documentation

- Add a "storage" reference document
([#&#8203;15954](https://redirect.github.com/astral-sh/uv/pull/15954))

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

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

Released on 2025-11-12.

##### Deprecations

- Deprecate use of `--project` in `uv init`
([#&#8203;16674](https://redirect.github.com/astral-sh/uv/pull/16674))

##### Enhancements

- Add iOS support to Python interpreter discovery
([#&#8203;16686](https://redirect.github.com/astral-sh/uv/pull/16686))
- Reject ambiguously parsed URLs
([#&#8203;16622](https://redirect.github.com/astral-sh/uv/pull/16622))
- Allow explicit values in `uv version --bump`
([#&#8203;16555](https://redirect.github.com/astral-sh/uv/pull/16555))
- Warn on use of managed pre-release Python versions when a stable
version is available
([#&#8203;16619](https://redirect.github.com/astral-sh/uv/pull/16619))
- Allow signing trampolines on Windows by using `.rcdata` to store
metadata
([#&#8203;15068](https://redirect.github.com/astral-sh/uv/pull/15068))
- Add `--only-emit-workspace` and similar variants to `uv export`
([#&#8203;16681](https://redirect.github.com/astral-sh/uv/pull/16681))

##### Preview features

- Add `uv workspace dir` command
([#&#8203;16678](https://redirect.github.com/astral-sh/uv/pull/16678))
- Add `uv workspace metadata` command
([#&#8203;16516](https://redirect.github.com/astral-sh/uv/pull/16516))

##### Configuration

- Add `UV_NO_DEFAULT_GROUPS` environment variable
([#&#8203;16645](https://redirect.github.com/astral-sh/uv/pull/16645))

##### Bug fixes

- Remove `torch-model-archiver` and `torch-tb-profiler` from PyTorch
backend
([#&#8203;16655](https://redirect.github.com/astral-sh/uv/pull/16655))
- Fix Pixi environment detection
([#&#8203;16585](https://redirect.github.com/astral-sh/uv/pull/16585))

##### Documentation

- Fix `CMD` path in FastAPI Dockerfile
([#&#8203;16701](https://redirect.github.com/astral-sh/uv/pull/16701))

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNzMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE3My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 18:46:15 +01:00
William Woodruff f78ddf05c4
Add an integration test for publishing to pyx (#16740) 2025-11-18 12:13:57 -05:00
William Woodruff 512c0ca5ed
Open PRs as drafts for sync-python-releases (#16763)
## Summary

This is a little goofy, but it saves us a click: when automation PRs are
opened as drafts, they don't need to be cycled through closed/opened to
force the CI to run. Instead, once undrafted the CI runs.

See #16505 for an example of the closed/opened cycle hack this avoids.

## Test Plan

No functional changes besides CI automation.

Signed-off-by: William Woodruff <william@astral.sh>
2025-11-17 15:04:10 -06:00
Zanie Blue cda7fc3fda
Revert "Fix CMD path in FastAPI Dockerfile (#16701)" (#16752)
This reverts commit 92230ba679 from #16701

See https://github.com/astral-sh/uv/pull/16701#issuecomment-3538541379
2025-11-17 13:17:27 -06:00
konsti 44f5a14f40
Bump version to 0.9.10 (#16762)
Motivated by https://github.com/astral-sh/uv/pull/16759

Doesn't contain https://github.com/astral-sh/uv/pull/16752, but that one
doesn't seem critical
2025-11-17 16:29:14 +00:00
Zanie Blue 07e03ee776
Add `uv workspace list` to list workspace members (#16691)
I'm a little wary here, in the sense that it might be silly to have a
command that does something so simple that's covered by `uv workspace
metadata`? but I think this could be stabilized much faster than `uv
workspace metadata` and makes it easier to write scripts against
workspace members.

---------

Co-authored-by: liam <liam@scalzulli.com>
2025-11-17 09:35:50 -06:00
Zsolt Dollenstein 6f525f9462
Add a "storage" reference document (#15954)
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-11-17 09:38:14 -05:00
konsti 2d75aca8e3
Don't check file URLs for ambiguously parsed URLs (#16759)
Fixes https://github.com/astral-sh/uv/issues/16756
Follow-up for https://github.com/astral-sh/uv/pull/16622

I noticed that rustfmt couldn't handle the check, so I moved the code
around in the first two commits.
2025-11-17 14:16:13 +00:00
renovate[bot] 163729ecc3
Update Rust crate indicatif to v0.18.3 (#16754)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [indicatif](https://redirect.github.com/console-rs/indicatif) |
workspace.dependencies | patch | `0.18.2` -> `0.18.3` |

---

### Release Notes

<details>
<summary>console-rs/indicatif (indicatif)</summary>

###
[`v0.18.3`](https://redirect.github.com/console-rs/indicatif/releases/tag/0.18.3)

[Compare
Source](https://redirect.github.com/console-rs/indicatif/compare/0.18.2...0.18.3)

#### What's Changed

- Add ProgressBar::set\_elapsed by
[@&#8203;sunshowers](https://redirect.github.com/sunshowers) in
[#&#8203;742](https://redirect.github.com/console-rs/indicatif/pull/742)

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNzMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE3My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 13:23:12 +01:00
samypr100 bf99f0a195
Add support for SSL_CERT_DIR (#16473)
## Summary

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

Adds support for the standard
[SSL_CERT_DIR](https://docs.openssl.org/3.6/man3/SSL_CTX_load_verify_locations)
which has gained recent proper support from
[rustls-native-certs](https://github.com/rustls/rustls-native-certs/pull/187)
in v0.8.2.

In addition, this PR clarifies documentation around `SSL_CERT_FILE` and
`SSL_CERT_DIR` when used in combination with `UV_NATIVE_TLS` as
mentioned in
https://github.com/astral-sh/uv/issues/16412#issuecomment-3434927201

## Test Plan

Manually tested with custom cert chains in multiple directories and
loading them via SSL_CERT_DIR. We didn't have tests for `SSL_CERT_FILE`
or `SSL_CERT_DIR` environment variables so I added a basic one using our
own test-only certificate generation and dummy https server. I also
moved some things around for better reuse.
2025-11-16 11:48:31 -06:00
Meitar Reihan b9826778b9
Support http/https URLs in `uv python --python-downloads-json-url` (#16542)
continuation PR based on #14687

---------

Co-authored-by: Geoffrey Thomas <geofft@ldpreload.com>
Co-authored-by: Aria Desires <aria.desires@gmail.com>
2025-11-14 17:51:24 -05:00
Meitar Reihan 7f4d8c67a8
Account for `python_downloads_json_url` on Pre-release Python version warnings (#16737)
Solves #16711
2025-11-14 15:12:35 -06:00
konsti 4e4235648a
Use crates.io async_zip fork (#16742)
Migrate to our fork on crates.io.
2025-11-14 20:15:54 +01:00
konsti 56b0db3359
Use crates.io pubgrub fork (#16725)
Migrate pubgrub and version-ranges from a Git dependency to our fork on
crates.io.
2025-11-14 17:56:37 +00:00
konsti 181262bdbd
Use crates.io reqwest-middleware fork (#16738)
Requires forking async_http_range_reader too and a new ambient-id
release.
2025-11-14 18:43:25 +01:00
liam 1a14d595fd
Error when a `project.license-files` glob matches nothing (#16697)
Resolves https://github.com/astral-sh/uv/issues/16693

[`PEP 639`](https://peps.python.org/pep-0639/#add-license-files-key)
requires build tools to error if any user-specified
`project.license-files` glob fails to match a file, but uv currently
allows the build to succeed and produces empty `.dist-info/licenses/`
directories.

This PR enforces the spec by tracking matches for each glob during
metadata generation, raising a clear
validation error when one is unmatched.
2025-11-14 11:02:04 +01:00
Zanie Blue f5ce5b47c8
Add support for `--upgrade` in `uv python install` (#16676)
This allows us to suggest `uv python install --upgrade 3.14` as the
canonical way to get the latest patch version of a given Python
regardless of whether it is installed already. Currently, you can do `uv
python upgrade 3.14` and it will install it, but I'd like to remove that
behavior as I find it very surprising.
2025-11-13 09:55:48 -06:00
liam e28dc62358
Enforce UTF‑8-encoded license files during `uv build` (#16699)
I noticed this when working on
https://github.com/astral-sh/uv/pull/16697.

[PEP 639](https://peps.python.org/pep-0639/#add-license-files-key)
expects tools to ship license texts as UTF‑8, but previously `uv build`
would quietly include any binary blob listed under
`project.license-files`.

I have no clue what is going on with `rustfmt` for this file, but it
seems that when I add the check, it wants to reformat a bunch of
surrounding stuff.

The relevant part to look at is:

```rust
for license_file in &license_files {
    let file_path = root.join(license_file);
    let bytes = fs_err::read(&file_path)?;
    if str::from_utf8(&bytes).is_err() {
        return Err(ValidationError::LicenseFileNotUtf8(license_file.clone()).into());
    }
}
```

where we validate all collected license files before proceeding.

---------

Co-authored-by: konstin <konstin@mailbox.org>
2025-11-13 12:49:59 +00:00
CatBraaain c167146f8c
refactor: update deprecated renovate schedule syntax (#16717)
<!--
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?
-->

Related issue: none
This is a minor change; no issue is associated. Please let me know if
one is required.

## Summary
Update deprecated renovate schedule syntax
```diff
- schedule: ["before 4am on Monday"],
+ schedule: ["* 0-3 * * 1"],
```

<!-- What's the purpose of the change? What does it do, and why? -->
## Rationale
- Deprecated: Renovate plans to remove the @breejs/later library, so
cron syntax is recommended.
- Time range: at least 3-4 hours recommended for any schedule.

References:
- [Deprecated later
syntax](https://docs.renovatebot.com/key-concepts/scheduling/#deprecated-breejslater-syntax)
- [Schedule
options](https://docs.renovatebot.com/configuration-options/#schedule)


## Test Plan
<!-- How was it tested? -->
- Cron syntax verified on
[crontab.guru](https://crontab.guru/#*_0-3_*_*_1)
- Behavior in Renovate not testable directly
2025-11-13 10:52:19 +01:00
Mikayla Thompson 88811553e4
`pip install --target` (and `sync`) install python if necessary (#16694)
## Summary

As described in https://github.com/astral-sh/uv/issues/12229, `pip
install` with `--target` or `--prefix` seem like they should install the
necessary python version if it doesn't exist, but they currently don't.

Most minimal reproduction is something like:
```
> uv python uninstall 3.13
...
> uv pip install anyio --target target-dir --python 3.13
error: No interpreter found for Python 3.13 in virtual environments, managed installations, or search path
```

This also fails without `--target`, but a venv is expected in that case,
so the with `--target`/`--prefix` is the only version that needs to be
fixed. The same mechanism occurs for `uv pip sync` as well.

## Test Plan

Added tests for install and sync that failed before fix and now pass.

---------

Signed-off-by: Mikayla Thompson <mrt@mikayla.codes>
2025-11-12 15:42:52 -07:00
Oshadha Gunawardena aec42540a1
Fix handling of `python install --default` for pre-release Python versions (#16706)
## Summary

Fixes `--default` not creating default executable links for pre-release
Python versions.

When using `--default` with a pre-release version like `3.15.0a1`, the
code was checking `matches_installation()` against the download request
instead of the original user request. This caused the check to fail
since the download request doesn't match pre-release versions the same
way.

Changed it to use `installation.satisfies(&first_request.request)` when
`--default` is used, which checks against the original user request.

Fixes #16696

## Test Plan

Added `python_install_default_prerelease` test that installs Python 3.15
with `--default` and verifies all three executable links (`python3.15`,
`python3`, `python`) are created. The test skips gracefully if 3.15
isn't available.

All existing tests pass.
2025-11-12 12:33:58 -06:00
Zanie Blue 4fac4cb7ed
Bump version to 0.9.9 (#16708) 2025-11-12 18:14:44 +00:00
Zsolt Dollenstein e96354a6dd
Always attach linehaul data (#16705) 2025-11-12 17:10:15 +00:00
Charlie Marsh 2c0d166260
Add `only-emit-workspace` and similar variants to `uv export` (#16681)
## Summary

This is useful for scenarios in which you want to emit the workspace
dependencies, but nothing else.
2025-11-12 16:51:45 +00:00
William Woodruff ae1edef9c0
Reject ambiguously parsed URLs (#16622)
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-11-12 16:27:57 +00:00
bigmoonbit 82c612704a
chore: fix typo in crates/uv-virtualenv/README.md (#16700)
<!--
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 typo in crates/uv-virtualenv/README.md


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

## Test Plan

<!-- How was it tested? -->

No need.

Signed-off-by: bigmoonbit <bigmoonbit@outlook.com>
2025-11-12 09:45:37 -06:00
Zsolt Dollenstein c16a5fd630
Fix shell-specific test snapshots (#16688)
## Summary

Two tests were failing when run with `SHELL=fish`:
1. `create_venv_current_working_directory` failed because the "activate"
filter didn't apply properly when the venv was in the CWD. This PR fixes
the filter.
2. `tool_install_warn_path` failed because the messages are different
between fish and bash. This PR hardcodes `SHELL=fish`.

## Test Plan

CI
2025-11-12 07:55:33 -06:00
liam d32cc638d1
Deny stdout/stderr printing in `uv` crate via clippy (#16695)
Follow-up from https://github.com/astral-sh/uv/pull/16690, in `uv` every
command should be using `write!(...)/writeln!(...)` with the `Printer`
abstraction instead of bypassing control with the standard printing
functions. This lint ensures that.
2025-11-12 07:54:52 -06:00
Zsolt Dollenstein 2de987ed37
Skip python_install_emulated_macos except on ARM64 macos with rosetta (#16687)
## Summary

This test isn't useful on non-arm64 macs, and it outright fails if
rosetta isn't installed.

## Test Plan

Run it on my rosetta-stripped macbook
2025-11-12 07:48:16 -06:00
liam 1b38b47a3f
Warn on managed prerelease interpreters when a stable build is available (#16619)
Resolves https://github.com/astral-sh/uv/issues/16616

This PR detects managed prerelease interpreters during discovery and
warns when a matching stable build is available, wiring the new helper
into `PythonInstallation::find`, `find_best`, and `find_or_download`.
2025-11-12 07:45:31 -06:00
jspablo 92230ba679
Fix CMD path in FastAPI Dockerfile (#16701)
<!--
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 suggestion for uv when used with FastAPI and Docker.

## Test Plan

The Dockerfile sets `/app` as workdir, later the CMD command points
again to `app/main.py` instead of only `main.py`, causing the following
error:
```
Path does not exist app/main.py
```
2025-11-12 07:39:14 -06:00
konsti e36bf634ba
Add iOS support for Python discovery (#16686)
iOS support exist nominally
(https://github.com/astral-sh/uv/pull/15640), but Python discovery
currently fails.
2025-11-12 11:03:27 +00:00
Zanie Blue 63df2cdfe9
Minor fixups to `uv workspace metadata` (#16692) 2025-11-11 20:41:26 +00:00
Mikayla Thompson 00e2a0e54d
Replace println with Printer for all `dir` commands & workspace dir followups (#16690)
## Summary

1. Discussed in review of #16678 that println should be replaced by
using `printer`. The `println` pattern was pretty consistent across all
the `dir` commands, so I've updated all of them in this PR (there are
some usages of `println` outside of `uv/src/commands` that I didn't
touch -- the use cases there seemed more complex and nuanced).
2. I missed two comments in the previous PR before merging, so updates
from those are in here as well.

## Test Plan

No behavior changes, existing tests for all commands pass.

---------

Signed-off-by: Mikayla Thompson <mrt@mikayla.codes>
2025-11-11 14:36:31 -06:00
Mikayla Thompson b81060674e
`workspace dir` command (#16678)
Addresses https://github.com/astral-sh/uv/issues/13636

Prints the path to the workspace root by default, and any of the child
packages if requested.

I looped it into the same preview flag as `workspace metadata`, given
how closely related they are.

## Summary

```
─> uv workspace dir
/Users/mikayla/code/uv/dev-envs

─> uv workspace dir --package foo-proj
/Users/mikayla/code/uv/dev-envs/foo-proj

─> uv workspace dir --package bar-proj
error: Package `bar-proj` not found in workspace.
```

## Test Plan

Unit tests added.

---------

Signed-off-by: Mikayla Thompson <mrt@mikayla.codes>
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-11-11 12:30:39 -07:00
konsti 92c2bfcca0
Remove unnecessary `DisplaySafeUrl::from` (#16689)
For #16622
2025-11-11 19:12:20 +00:00
liam 63ab247765
Allow explicit values with `uv version --bump` (#16555)
Resolves https://github.com/astral-sh/uv/issues/16427

This PR updates `uv version --bump` so you can pin the exact number
you’re targeting, i.e. `--bump patch=10` or `--bump dev=42`. The
command-line interface now parses those `component=value` flags, and the
bump logic actually sets the version to the number you asked for.
2025-11-11 12:27:32 -05:00
Zanie Blue 3ccad58166
Add `uv workspace metadata` (#16516)
This adds the scaffolding for a `uv workspace metadata` command, as an
equivalent to `cargo metadata`, for integration with downstream tools. I
didn't do much here beyond emit the workspace root path and the paths of
the workspace members. I explored doing a bit more in #16638, but I
think we're actually going to want to come up with a fairly
comprehensive schema like `cargo metadata` has. I've started exploring
that too, but I don't have a concrete proposal to share yet.

I don't want this to be a top-level command because I think people would
expect `uv metadata <PACKAGE>` to show metadata about arbitrary packages
(this has been requested several times). I also think we can do other
things in the workspace namespace to make trivial integrations simpler,
like `uv workspace list` (enumerate members) and `uv workspace dir`
(show the path to the workspace root).

I don't expect this to be stable at all to start. I've both gated it
with preview and hidden it from the help. The intent is to merge so we
can iterate on it as we figure out what integrations need.
2025-11-11 15:46:01 +00:00
Charlie Marsh 5b517bb966
Remove Git-based dependency for tl (#16679)
## Summary

I published our own fork, since our PR had been lingering for over a
year: https://crates.io/crates/astral-tl
2025-11-11 08:41:13 -06:00
Mikayla Thompson f22af0f88a
Deprecate `--project` arg for init (#16674)
Addresses https://github.com/astral-sh/uv/issues/15790

## Summary

After discussion, the functionality of `--project` vs `--directory` was
quite unclear in this case, so deprecating `--project` for `init` is
probably the clearest behavior option. This is a breaking change, so it
requires being under preview before being rolled out fully.

Included in the PR now:
- new feature flag (`init --project` is deprecated if `--preview` or
`--preview-features deprecate-project-for-init` are provided)
- tests (for `--directory` behavior, as well as the current warning and
future error)
- documentation updated in docs/concepts/projects/init.md

---------

Signed-off-by: Mikayla Thompson <mrt@mikayla.codes>
2025-11-10 16:33:08 -07:00
Mathieu Kniewallner 9a21897f3d
feat(cli): expose `UV_NO_DEFAULT_GROUPS` environment variable (#16645)
<!--
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

Similarly to #16529 that adds `UV_NO_GROUP`, this adds
`UV_NO_DEFAULT_GROUPS` that does the same as `--no-default-groups`. This
can be useful on the CI, to disable default groups on a job without
having to set the argument in all commands that could trigger a sync
(for instance
[here](8757b318e9/.github/workflows/main.yml (L105-L116))).

## Test Plan

Snapshot tests.

---------

Co-authored-by: samypr100 <3933065+samypr100@users.noreply.github.com>
2025-11-10 14:43:48 -06:00
Charlie Marsh c1c1950dce
Add support for the Simple index API top-level route (#16656)
## Summary

At present, we only have support for the detail routes (e.g.,
`https://pypi.org/simple/requests`), but not the top-level index route
(e.g., `https://pypi.org/simple/`). I need this for some downstream work
so pulling it into its own PR.
2025-11-10 14:50:19 -05:00
Charlie Marsh ce4a47a2e0
Remove `torch-model-archiver` and `torch-tb-profiler` from PyTorch backend (#16655)
## Summary

These are present on the PyTorch index, but only at very old versions.
The PyPI versions are newer, and seemingly these don't need to be built
against CUDA, etc.

Closes https://github.com/astral-sh/uv/issues/16651.
2025-11-10 10:26:12 -05:00
Zanie Blue 60196bc3ce
Remove stale comment (#16667)
This happens in `from_settings` instead
2025-11-10 14:55:04 +00:00
renovate[bot] 59cbc9fe3e
Update Rust crate quote to v1.0.42 (#16659)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [quote](https://redirect.github.com/dtolnay/quote) |
workspace.dependencies | patch | `1.0.41` -> `1.0.42` |

---

### Release Notes

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

###
[`v1.0.42`](https://redirect.github.com/dtolnay/quote/releases/tag/1.0.42)

[Compare
Source](https://redirect.github.com/dtolnay/quote/compare/1.0.41...1.0.42)

- Tweaks to improve build speed
([#&#8203;305](https://redirect.github.com/dtolnay/quote/issues/305),
[#&#8203;306](https://redirect.github.com/dtolnay/quote/issues/306),
[#&#8203;307](https://redirect.github.com/dtolnay/quote/issues/307),
[#&#8203;308](https://redirect.github.com/dtolnay/quote/issues/308),
thanks [@&#8203;dishmaker](https://redirect.github.com/dishmaker))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 12:33:37 +01:00
renovate[bot] 4f6162f988
Update dependency astral-sh/uv to v0.9.8 (#16657)
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.7` -> `0.9.8` |

---

### Release Notes

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

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

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

Released on 2025-11-07.

##### Enhancements

- Accept multiple packages in `uv export`
([#&#8203;16603](https://redirect.github.com/astral-sh/uv/pull/16603))
- Accept multiple packages in `uv sync`
([#&#8203;16543](https://redirect.github.com/astral-sh/uv/pull/16543))
- Add a `uv cache size` command
([#&#8203;16032](https://redirect.github.com/astral-sh/uv/pull/16032))
- Add prerelease guidance for build-system resolution failures
([#&#8203;16550](https://redirect.github.com/astral-sh/uv/pull/16550))
- Allow Python requests to include `+gil` to require a GIL-enabled
interpreter
([#&#8203;16537](https://redirect.github.com/astral-sh/uv/pull/16537))
- Avoid pluralizing 'retry' for single value
([#&#8203;16535](https://redirect.github.com/astral-sh/uv/pull/16535))
- Enable first-class dependency exclusions
([#&#8203;16528](https://redirect.github.com/astral-sh/uv/pull/16528))
- Fix inclusive constraints on available package versions in resolver
errors
([#&#8203;16629](https://redirect.github.com/astral-sh/uv/pull/16629))
- Improve `uv init` error for invalid directory names
([#&#8203;16554](https://redirect.github.com/astral-sh/uv/pull/16554))
- Show help on `uv build -h`
([#&#8203;16632](https://redirect.github.com/astral-sh/uv/pull/16632))
- Include the Python variant suffix in "Using Python ..." messages
([#&#8203;16536](https://redirect.github.com/astral-sh/uv/pull/16536))
- Log most recently modified file for cache-keys
([#&#8203;16338](https://redirect.github.com/astral-sh/uv/pull/16338))
- Update Docker builds to use nightly Rust toolchain with musl v1.2.5
([#&#8203;16584](https://redirect.github.com/astral-sh/uv/pull/16584))

##### Configuration

- Expose `UV_NO_GROUP` as an environment variable
([#&#8203;16529](https://redirect.github.com/astral-sh/uv/pull/16529))
- Add `UV_NO_SOURCES` as an environment variable
([#&#8203;15883](https://redirect.github.com/astral-sh/uv/pull/15883))

##### Bug fixes

- Allow `--check` and `--locked` to be used together in `uv lock`
([#&#8203;16538](https://redirect.github.com/astral-sh/uv/pull/16538))
- Allow for unnormalized names in the METADATA file
([#&#8203;16547](https://redirect.github.com/astral-sh/uv/issues/16547))
([#&#8203;16548](https://redirect.github.com/astral-sh/uv/pull/16548))
- Fix missing value\_type for `default-groups` in schema
([#&#8203;16575](https://redirect.github.com/astral-sh/uv/pull/16575))
- Respect multi-GPU outputs in `nvidia-smi`
([#&#8203;15460](https://redirect.github.com/astral-sh/uv/pull/15460))
- Fix DNS lookup errors in Docker containers
([#&#8203;8450](https://redirect.github.com/astral-sh/uv/issues/8450))

##### Documentation

- Fix typo in uv tool list doc
([#&#8203;16625](https://redirect.github.com/astral-sh/uv/pull/16625))
- Note `uv pip list` name normalization in docs
([#&#8203;13210](https://redirect.github.com/astral-sh/uv/pull/13210))

##### Other changes

- Update Rust toolchain to 1.91 and MSRV to 1.89
([#&#8203;16531](https://redirect.github.com/astral-sh/uv/pull/16531))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 12:23:14 +01:00
renovate[bot] d8ccf89193
Update Rust crate jiff to v0.2.16 (#16658)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [jiff](https://redirect.github.com/BurntSushi/jiff) |
workspace.dependencies | patch | `0.2.15` -> `0.2.16` |

---

### Release Notes

<details>
<summary>BurntSushi/jiff (jiff)</summary>

###
[`v0.2.16`](https://redirect.github.com/BurntSushi/jiff/blob/HEAD/CHANGELOG.md#0216-2025-11-07)

[Compare
Source](https://redirect.github.com/BurntSushi/jiff/compare/0.2.15...0.2.16)

\===================
This release contains a number of enhancements and bug fixes that have
accrued
over the last few months. Most are small polishes. A couple of the bug
fixes
apply to panics that could occur when parsing invalid `TZ` strings or
invalid
`strptime` format strings.

Also, parsing into a `Span` should now be much faster (for both the ISO
8601
and "friendly" duration formats).

Enhancements:

- [#&#8203;298](https://redirect.github.com/BurntSushi/jiff/issues/298):
  Add Serde helpers for (de)serializing `std::time::Duration` values.
- [#&#8203;396](https://redirect.github.com/BurntSushi/jiff/issues/396):
Add `Sub` and `Add` trait implementations for `Zoned` (in addition to
the
  already existing trait implementations for `&Zoned`).
- [#&#8203;397](https://redirect.github.com/BurntSushi/jiff/pull/397):
Add `BrokenDownTime::set_meridiem` and ensure it overrides the hour when
  formatting.
- [#&#8203;409](https://redirect.github.com/BurntSushi/jiff/pull/409):
Switch dependency on `serde` to `serde_core`. This should help speed up
  compilation times in some cases.
- [#&#8203;430](https://redirect.github.com/BurntSushi/jiff/pull/430):
Add new `Zoned::series` API, making it consistent with the same API on
other
  datetime types.
- [#&#8203;432](https://redirect.github.com/BurntSushi/jiff/pull/432):
When `lenient` mode is enabled for `strftime`, Jiff will no longer error
when
  the formatting string contains invalid UTF-8.
- [#&#8203;432](https://redirect.github.com/BurntSushi/jiff/pull/432):
Formatting of `%y` and `%g` no longer fails based on the specific year
value.
- [#&#8203;432](https://redirect.github.com/BurntSushi/jiff/pull/432):
Parsing of `%s` is now a bit more consistent with other fields.
Moreover,
`BrokenDownTime::{to_timestamp,to_zoned}` will now prefer timestamps
parsed
  with `%s` over any other fields that have been parsed.
- [#&#8203;433](https://redirect.github.com/BurntSushi/jiff/pull/433):
Allow parsing just a `%s` into a `Zoned` via the `Etc/Unknown` time
zone.

Bug fixes:

- [#&#8203;386](https://redirect.github.com/BurntSushi/jiff/issues/386):
Fix a bug where `2087-12-31T23:00:00Z` in the `Africa/Casablanca` time
zone
could not be round-tripped (because its offset was calculated
incorrectly as
  a result of not handling "permanent DST" POSIX time zones).
- [#&#8203;407](https://redirect.github.com/BurntSushi/jiff/issues/407):
Fix a panic that occurred when parsing an empty string as a POSIX time
zone.
- [#&#8203;410](https://redirect.github.com/BurntSushi/jiff/issues/410):
  Fix a panic that could occur when parsing `%:` via `strptime` APIs.
- [#&#8203;414](https://redirect.github.com/BurntSushi/jiff/pull/414):
Update some parts of the documentation to indicate that
`TimeZone::unknown()`
is a fallback for `TimeZone::system()` (instead of the `jiff 0.1`
behavior of
  using `TimeZone::UTC`).
- [#&#8203;423](https://redirect.github.com/BurntSushi/jiff/issues/423):
  Fix a panicking bug when reading malformed TZif data.
- [#&#8203;426](https://redirect.github.com/BurntSushi/jiff/issues/426):
  Fix a panicking bug when parsing century (`%C`) via `strptime`.
- [#&#8203;445](https://redirect.github.com/BurntSushi/jiff/pull/445):
  Fixed bugs with parsing durations like `-9223372036854775808s`
  and `-PT9223372036854775808S`.

Performance:

- [#&#8203;445](https://redirect.github.com/BurntSushi/jiff/pull/445):
Parsing into `Span` or `SignedDuration` is now a fair bit faster in some
cases.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 12:22:32 +01:00
liam c826f2b042
Fix pixi environment detection by recognizing Conda prefixes with `conda-meta/pixi` (#16585)
Resolves https://github.com/astral-sh/uv/issues/16295

This PR updates the Conda base-environment heuristic to recognize
Pixi-managed environments by checking for the `conda-meta/pixi` marker
file. Pixi default environments now resolve as isolated child
environments instead of system installations, restoring the expected uv
pip behavior without the `--system` flag.
2025-11-09 22:16:17 -06:00
Zanie Blue 2d9fe7ca70
Pin lock futures (#16643)
These futures are quite large (~16,000 bytes) and adding new fields to
the `ResolverOptions` in another pull request caused a lint error from
Clippy.
2025-11-09 10:37:16 -06:00
Pavel Logan Dikov caf49f845f
Use `.rcdata` to store trampoline type + path to python binary (#15068)
`.rsrc` is the idiomatic way of storing metadata and non-code resources
in PE
binaries. This should make the resulting binaries more robust as they
are no longer
dependent on the exact location of a certain magic number.

Addresses: #15022

## Test Plan

Existing integration test for `uv-trampoline-builder` + addition to
ensure robustness
to code signing.

---------

Co-authored-by: samypr100 <3933065+samypr100@users.noreply.github.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-11-09 08:12:40 -06:00
github-actions[bot] 1b7faafd7a
Sync latest Python releases (#16505)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-11-08 10:16:21 -06:00
Zanie Blue 5983a8876b
Refactor to remove some cruft from `ExcludeNewer` propagation (#16641)
I think using a wire here is less convoluted.
2025-11-08 09:44:17 -06:00
Zanie Blue e0ae518e95
Move attestations to the proper release in the changelog (#16644) 2025-11-08 09:43:09 -06:00
Zanie Blue 85c5d32284
Bump version to 0.9.8 (#16636) 2025-11-07 11:45:22 -06:00
Zanie Blue bfecc9902e
Fix inclusive constraints on available package versions in resolver errors (#16629)
Closes https://github.com/astral-sh/uv/issues/16626
2025-11-07 09:23:37 -06:00
Zanie Blue 5e181d36ef
Show help on `uv build -h` (#16632) 2025-11-07 16:19:40 +01:00
Zanie Blue 08229ca73b
Add expectation for externally managed marker in x86-64 macOS system test (#16628)
This is failing with

```
Run python3 scripts/check_system_python.py --uv ./uv
INFO: Checking that `pylint` isn't installed.
WARNING: Package(s) not found: pylint
INFO: Installing the package `pylint`.
DEBUG uv 0.9.7 (2cd1400fb 2025-11-06)
DEBUG Acquired shared lock for `/Users/runner/.cache/uv`
DEBUG Searching for default Python interpreter in search path or managed installations
DEBUG Found `cpython-3.14.0-macos-x86_64-none` at `/usr/local/bin/python3` (first executable in the search path)
Using Python 3.14.0 environment at: /usr/local/opt/python@3.14/Frameworks/Python.framework/Versions/3.14
DEBUG Released lock at `/Users/runner/.cache/uv/.lock`
error: The interpreter at /usr/local/opt/python@3.14/Frameworks/Python.framework/Versions/3.14 is externally managed, and indicates the following:

  To install Python packages system-wide, try brew install
  xyz, where xyz is the package you are trying to
  install.

  If you wish to install a Python library that isn't in Homebrew,
  use a virtual environment:

  python3 -m venv path/to/venv
  source path/to/venv/bin/activate
  python3 -m pip install xyz

  If you wish to install a Python application that isn't in Homebrew,
  it may be easiest to use 'pipx install xyz', which will manage a
  virtual environment for you. You can install pipx with

  brew install pipx

  You may restore the old behavior of pip by passing
  the '--break-system-packages' flag to pip, or by adding
  'break-system-packages = true' to your pip.conf file. The latter
  will permanently disable this error.

  If you disable this error, we STRONGLY recommend that you additionally
  pass the '--user' flag to pip, or set 'user = true' in your pip.conf
  file. Failure to do this can result in a broken Homebrew installation.

  Read more about this behavior here: <https://peps.python.org/pep-0668/>
```
2025-11-06 23:53:46 -06:00
Charlie Marsh 4740d267e9
Remove fast path from `uv-git` fetch (#16607)
## Summary

Now that we perform this fast-path in
`crates/uv-distribution/src/source/mod.rs`, I _think_ the fast-path here
is no longer used? In my testing, we only actually took this path when
the fast-path _already_ failed (and thus it would fail again, wasting
time).
2025-11-06 22:18:05 -06:00
Haaris Rahman e5c10cd45d
Fix typo in uv tool list doc (#16625)
<!--
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


Fixed a typo in the docs.

Regenerated the docs, with the fix.


<!-- What's the purpose of the change? What does it do, and why? -->
2025-11-06 22:06:17 -06:00
Charlie Marsh 5fe8af114b
Accept multiple packages in `uv export` (#16603)
## Summary

Closes https://github.com/astral-sh/uv/issues/16503.
2025-11-05 22:52:22 +00:00
konsti 148b694b6b
Make `augment_requirement` a method (#16592)
Fixing the pattern so we can be consistent in
https://github.com/astral-sh/uv/pull/16143#discussion_r2479028440
2025-11-04 19:53:59 +01:00
samypr100 3068fa89e8
Docker builds target rust toolchain with bundled musl v1.2.5 (#16584)
## Summary

Addresses https://github.com/astral-sh/uv/issues/8450 for docker builds

## Test Plan

* Ubuntu Tests [CI
Run](https://github.com/samypr100/uv/actions/runs/19054484993/job/54421786118)
with `nightly-2025-11-02` set in root `rust-toolchain.toml`
* Manually test functionality using locally built binaries from
cargo-zigbuild
2025-11-04 13:53:04 -05:00
Charlie Marsh 9a6eafc043
Accept multiple packages in `uv sync` (#16543)
## Summary

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

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
Co-authored-by: konsti <konstin@mailbox.org>
2025-11-04 14:17:58 +00:00
Zanie Blue 60a811e715
Allow Python requests to include `+gil` to require a GIL-enabled interpreter (#16537)
Addresses
https://github.com/astral-sh/uv/pull/16142#issuecomment-3390586957

Nobody seems to have a good idea about how to spell this. "not
free-threaded" would be the most technically correct, but I think "gil"
will be more intuitive.
2025-11-03 13:35:52 -06:00
renovate[bot] a87a3bbae4
Update Rust crate tracing-tree to v0.4.1 (#16570) 2025-11-03 15:58:03 +00:00
renovate[bot] 5c426a1fd6
Update Rust crate zeroize to v1.8.2 (#16572) 2025-11-03 10:45:28 -05:00
renovate[bot] e6a06f0c5c
Update Rust crate unicode-width to v0.2.2 (#16571) 2025-11-03 10:44:58 -05:00
Les Freire 86e7b2e97a
Fix missing value_type for `default-groups` in schema (#16575)
## Summary

Fix incomplete value_type attribute for default-groups field in the
ToolUv struct schema definition. The value_type was missing its value,
which should be str | list[str] to reflect that default-groups can
accept either the literal "all" or a list of group names. (#16574)
2025-11-03 08:55:20 -05:00
renovate[bot] 0fc68f7d49
Update Rust crate toml_edit to v0.23.7 (#16569) 2025-11-03 02:38:05 +00:00
renovate[bot] aa63d26f5b
Update Rust crate toml to v0.9.8 (#16568) 2025-11-03 02:29:14 +00:00
renovate[bot] e5cb2ee01b
Update Rust crate tikv-jemallocator to v0.6.1 (#16567) 2025-11-02 21:07:49 -05:00
renovate[bot] b2550c20f4
Update Rust crate schemars to v1.0.5 (#16565) 2025-11-03 02:03:08 +00:00
renovate[bot] 0272472a21
Update Rust crate syn to v2.0.108 (#16566) 2025-11-03 01:59:31 +00:00
renovate[bot] eed777f022
Update Rust crate indicatif to v0.18.2 (#16561) 2025-11-02 20:52:02 -05:00
renovate[bot] 2a691a55d2
Update Rust crate proc-macro2 to v1.0.103 (#16563) 2025-11-03 01:37:22 +00:00
renovate[bot] 6d9ed5fe34
Update Rust crate regex-automata to v0.4.13 (#16564) 2025-11-03 01:35:43 +00:00
renovate[bot] 0023aaba21
Update Rust crate indoc to v2.0.7 (#16562) 2025-11-03 01:34:07 +00:00
renovate[bot] 8f4083a746
Update dependency astral-sh/uv to v0.9.7 (#16557) 2025-11-02 20:04:43 -05:00
renovate[bot] b2ddef9a57
Update Rust crate cargo-util to v0.2.24 (#16558) 2025-11-02 20:04:38 -05:00
renovate[bot] c6a3fc1728
Update Rust crate ignore to v0.4.25 (#16560) 2025-11-02 20:04:27 -05:00
renovate[bot] 580f08ff94
Update Rust crate ctrlc to v3.5.1 (#16559) 2025-11-02 20:03:59 -05:00
liam 77fecc5c27
Improve `uv init` error for invalid directory names (#16554)
Resolves https://github.com/astral-sh/uv/issues/16433

When `uv init` infers a project name from the working directory,
directories with characters outside the PEP 503 rules produced the
generic “Not a valid package or extra name” message that didn’t explain
the source of the problem. This change intercepts that failure, reports
whether the current or explicit target directory caused it, and tells
the user to supply an explicit `--name`.
2025-11-02 19:26:09 -05:00
Michael Richter 485503ee65
Remove fs2 dependency and update Rust to 1.89 (#15764)
<!--
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? --> 

This PR removes the crate fs2 and updates Rust version to 1.89.

*Why?*

Crate fs2 is unmaintained for a long time now and has unfixed issues.
Especially it doesn't build on AIX, which is the reason I started fixing
it.

*How?*

I removed fs2 and replaced it by std:fs:File methods.

## Test Plan

<!-- How was it tested? -->
- I built it on Windows and AIX only.
- I did not test the artifacts.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-11-02 16:33:53 -05:00
Charlie Marsh 6da135a66a
Respect multi-GPU outputs in nvidia-smi (#15460)
## Summary

This initially included `NVIDIA_VISIBLE_DEVICES` masking, though it's
now omitted for simplicity.

Closes https://github.com/astral-sh/uv/issues/14647.
2025-11-02 21:21:44 +00:00
renovate[bot] 040521802c
Update Rust crate clap to v4.5.51 (#16364) 2025-11-02 21:05:26 +00:00
chisato 8b479efd2f
Add a `uv cache size` command (#16032)
## Summary

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

Implement `uv cache size` to output the cache directory size in raw
bytes by default, with a `--human` option for human-readable output.

close #15821

<!-- How was it tested? -->

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-11-02 20:44:28 +00:00
Gabe Joseph 17c9656df2
Note `uv pip list` name normalization in docs (#13210)
Related to https://github.com/astral-sh/uv/issues/13209

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-11-02 20:39:30 +00:00
Blair Allan 4b61e65d6c
Add `UV_NO_SOURCES` as an environment variable (#15883)
## Summary

This is an enhancement that makes the cli flag `--no-sources` an
environment variable - "UV_NO_SOURCES"

Why is this a relevant change? 

When working across different environments, in our case remote vs local,
we often have our packages hosted in a artifact registry but when
developing locally we build our packages from github. This results in us
using the uv.tool.sources table quite a bit however this then also
forces us to use `--no-sources` for all our remote work.

This change enables us to set an environment variable once and to never
have to type --no-sources after every uv run command again.

## Test Plan

Expanded on the current --no-sources tests, to test when
UV_NO_SOURCES=true/false the behaviour is the same as the flag.
Additionally ensured that the cli overrides the env variable.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-11-02 20:25:19 +00:00
konsti 944ff6c685
Log most recently modified file for cache-keys (#16338)
For https://github.com/astral-sh/uv/issues/16336. We previously weren't
telling the user which file is responsible for rebuilding.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-11-02 18:52:07 +00:00
liam 857827da14
Add prerelease guidance for build-system resolution failures (#16550)
Resolves https://github.com/astral-sh/uv/issues/16496

This PR updates the resolver so `build-system` dependency failures
surface prerelease hints even when prerelease selection is fixed. When a
build dependency only has prerelease candidates, or the requested
version explicitly includes a prerelease marker, we now emit a tailored
hint explaining that build environments can’t auto-enable prereleases
and describing how to opt in.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-11-02 18:38:09 +00:00
Ben Beasley 45c1907ede
Update the spdx dependency to version 0.12 (#16552) 2025-11-02 13:21:29 -05:00
renovate[bot] 95b1556012
Update Rust crate tokio-util to v0.7.16 (#15726)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [tokio-util](https://tokio.rs)
([source](https://redirect.github.com/tokio-rs/tokio)) |
workspace.dependencies | patch | `0.7.15` -> `0.7.16` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>tokio-rs/tokio (tokio-util)</summary>

###
[`v0.7.16`](https://redirect.github.com/tokio-rs/tokio/compare/tokio-util-0.7.15...tokio-util-0.7.16)

[Compare
Source](https://redirect.github.com/tokio-rs/tokio/compare/tokio-util-0.7.15...tokio-util-0.7.16)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuOTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 17:23:28 +00:00
Christian Asch 2784856e10
Allow for unnormalized names in the `METADATA` file (#16547) (#16548)
## Summary

Deserialize project name into both a String and a ProjectName, this way
we can keep using the normalized name elsewhere while respecting the
original name from the `pyproject.toml` file

This PR addresses issue #16547

## Test Plan

I added a new test for this, and I ran the test suite in the
`metadata.rs` file.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-11-01 21:58:49 +00:00
Zanie Blue 101d0e2892
Allow `--check` and `--locked` to be used together in `uv lock` (#16538)
See https://github.com/astral-sh/uv/issues/16517#issuecomment-3473747461
2025-10-31 13:11:34 -05:00
Zanie Blue 1e2ec4a50c
Include the Python variant suffix in "Using Python ..." messages (#16536)
See https://github.com/astral-sh/uv/issues/16253#issuecomment-3393383573
2025-10-31 13:11:25 -05:00
Charlie Marsh 82aa0d0ef5
Avoid pluralizing 'retry' for single value (#16535) 2025-10-31 10:28:51 -04:00
Charlie Marsh 5c71b5c124
Enable first-class dependency exclusions (#16528)
## Summary

This PR adds an `exclude-dependencies` setting that allows users to omit
a dependency during resolution. It's effectively a formalized version of
the `flask ; python_version < '0'` hack that we've suggested to users in
various issues.

Closes #12616.
2025-10-31 10:14:12 -04:00
samypr100 7978122837
Update Rust toolchain to 1.91 and MSRV to 1.89 (#16531)
## Summary

Updates Rust Toolchain to
[1.91](https://blog.rust-lang.org/2025/10/30/Rust-1.91.0/) and bumps
MSRV to [1.89](https://blog.rust-lang.org/2025/08/07/Rust-1.89.0/) per
versioning policy. New clippy rule [implicit
clone](https://rust-lang.github.io/rust-clippy/master/index.html#implicit_clone)
resulted in some minor changes (some with improvements).

Updates trampoline to `nightly-2025-06-23` which is roughly 1.89~. The
trampoline binaries do not need to be regenerated as there should be no
changes.
2025-10-30 22:34:59 -05:00
Charlie Marsh 7cf1646a44
Expose `UV_NO_GROUP` as an environment variable (#16529)
## Summary

Closes https://github.com/astral-sh/uv/issues/11619.
2025-10-30 22:34:14 -05:00
Aria Desires d71ae61c0b
Fix github attestations of releases (#16530) 2025-10-30 22:31:19 -04:00
Zanie Blue 0adb444806
Bump version to 0.9.7 (#16524) 2025-10-30 16:47:51 -05:00
Zanie Blue 97341c6910
Drop terminal coloring from `uv auth token` output (#16504)
It's too common to set `FORCE_COLOR` in CI which then breaks consumption
of the token.

This is actually specific to `pyx.dev`, as we print passwords without
coloring.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-10-30 20:10:05 +00:00
Alexander Vandenbulcke c156b1d50d
Don't use `UV_LOCKED` to enable `--check` flag (#16521)
Env var UV_LOCKED should only be used to enable `--locked` for the `uv
lock` command. Previously `--check` was also enabled by specifying
UV_LOCKED.
2025-10-30 15:37:27 -04:00
Zanie Blue a1610c794e
Add Windows x86-32 emulation support to interpreter architecture checks (#13475)
Closes https://github.com/astral-sh/uv/issues/13471

Refactors the logic there a bit too.

---------

Co-authored-by: Aria Desires <aria.desires@gmail.com>
2025-10-30 10:50:26 -04:00
Clemens Brunner c7aaa8b7ef
Improve readability of progress bars (#16509)
<!--
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

Improve readability of progress bars by drawing the right portions in
dimmed black instead of dimmed green. This makes it much easier to
distinguish from the left (finished) part, which is drawn in green.

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

## Test Plan

Tested locally, looks as follows:

<img width="1100" height="731" alt="white"
src="https://github.com/user-attachments/assets/2a396e96-27ef-41ed-8b03-a0de2061af12"
/>
<img width="1100" height="731" alt="black"
src="https://github.com/user-attachments/assets/85d03a3a-a1dc-4389-9e51-fd486e60e067"
/>
2025-10-30 14:16:28 +00:00
samypr100 f3d3203734
Add uv release artifact attestations (#11357)
## Summary

Similar to https://github.com/astral-sh/uv/pull/8685, this adds
attestations for uv release artifacts.

The changes on this PR would add attestations for
* `dist-manifest.json`
* `uv-installer.ps1`
* `uv-installer.sh`
* All `*.tar.gz` and `*.zip` uv binary files

## Test Plan

~(clarifying note: I'm aware this file is managed cargo dist and this
will not work without allow-dirty at this time)~

~Currently cargo dist targets generation in `build_local_artifacts`
which is not used here, plus we'd ideally want to attest the GH
downloads / artifacts.~ (edit: fixed by
https://github.com/axodotdev/cargo-dist/pull/2000)

At a glance, this release workflow seems to work successfully:

e.g. Example Run:
https://github.com/samypr100/uv/actions/runs/13229100555
e.g. Example Release:
https://github.com/samypr100/uv/releases/tag/0.5.29

---------

Co-authored-by: Aria Desires <aria.desires@gmail.com>
2025-10-29 20:33:37 -04:00
Zanie Blue 2652244655
Bump version to 0.9.6 (#16500)
Signed-off-by: William Woodruff <william@astral.sh>
Co-authored-by: William Woodruff <william@astral.sh>
2025-10-29 14:08:49 -05:00
renovate[bot] 19372ff2a7
Update Rust crate etcetera to 0.11.0 (#16501)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [etcetera](https://redirect.github.com/lunacookies/etcetera) |
workspace.dependencies | minor | `0.10.0` -> `0.11.0` |

---

### Release Notes

<details>
<summary>lunacookies/etcetera (etcetera)</summary>

###
[`v0.11.0`](https://redirect.github.com/lunacookies/etcetera/releases/tag/v0.11.0)

[Compare
Source](https://redirect.github.com/lunacookies/etcetera/compare/v0.10.0...v0.11.0)

#### What's Changed

- chore(README): fix documentation link by
[@&#8203;hasezoey](https://redirect.github.com/hasezoey) in
[#&#8203;34](https://redirect.github.com/lunacookies/etcetera/pull/34)
- Small docs fix that confused me when I was looking at example on
docs.rs by [@&#8203;Ac5000](https://redirect.github.com/Ac5000) in
[#&#8203;38](https://redirect.github.com/lunacookies/etcetera/pull/38)
- crate: use std::env::home\_dir, bump to edition 2024, raise MSRV to
1.87 by
[@&#8203;utkarshgupta137](https://redirect.github.com/utkarshgupta137)
in
[#&#8203;39](https://redirect.github.com/lunacookies/etcetera/pull/39)

#### New Contributors

- [@&#8203;hasezoey](https://redirect.github.com/hasezoey) made their
first contribution in
[#&#8203;34](https://redirect.github.com/lunacookies/etcetera/pull/34)
- [@&#8203;Ac5000](https://redirect.github.com/Ac5000) made their first
contribution in
[#&#8203;38](https://redirect.github.com/lunacookies/etcetera/pull/38)

**Full Changelog**:
<https://github.com/lunacookies/etcetera/compare/v0.10.0...v0.11.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 19:20:31 +01:00
konsti de96aa13f2
Use std `home_dir` instead of `home` crate (#16483)
The `home_dir` function in std was deprecated for some years for reading
`HOME` on Windows. It has recently been fixed and undeprecated:
https://github.com/rust-lang/rust/pull/132515

Conversely, the Cargo maintainers want us to move away from the home
crate (https://github.com/rust-lang/cargo/tree/master/crates/home):

> Note: This has been fixed in Rust 1.85 to no longer use the HOME
environment variable on Windows. If you are still using this crate for
the purpose of getting a home directory, you are strongly encouraged to
switch to using the standard library's home_dir instead. It is planned
to have the deprecation notice removed in 1.87.
>
> This crate further provides two functions, cargo_home and rustup_home,
which are the canonical way to determine the location that Cargo and
rustup store their data.
>
> See rust-lang/rust#43321.
>
> > This crate is maintained by the Cargo team, primarily for use by
Cargo and Rustup and not intended for external use. This crate may make
major changes to its APIs or be deprecated without warning.

When https://github.com/lunacookies/etcetera/pull/36 merges, we can
remove the home crate from our dependency tree.
2025-10-29 18:49:08 +01:00
twilligon db1d34e91b
Support GitHub Gist URLs via HTTP redirects in `uv run` (#16451)
## Summary

Extend the existing GitHub Gist URL support from #15058 to handle URLs
that redirect to Gists.

`reqwest` already handled generic URL redirects for us (note this
redirects directly to the `.py` on `gist.githubusercontent.com`):

~/git/uv $ uv run
c28a4bf0cb/hello.py
    hello world!

But running a URL that redirected to a Gist's "main page" (a bit.ly link
leading to a Gist, etc.) did not:

~/git/uv $ uv run
https://httpbin.org/redirect-to?url=https://gist.github.com/twilligon/4d878a4d9550a4f1df258cde1f058699
      File "/tmp/scriptNodt3Q.py", line 87
        <title>hello.py · GitHub</title>

But if we have `reqwest` follow redirects *before* `resolve_gist_url`,
we can handle this fine:

~/git/uv $ target/debug/uv run
https://httpbin.org/redirect-to?url=https://gist.github.com/twilligon/4d878a4d9550a4f1df258cde1f058699
    hello world!

## Test Plan

I'd write an automated test but that'd require network access since
wiremock doesn't seem to support mocking specific hostnames like
`gist.github.com`. As manual tests go, I basically did the above,
testing with several redirectors to both generic and Gist URLs.
2025-10-29 12:30:22 -04:00
William Woodruff c6d0b412a0
Limit `uv auth login pyx.dev` retries to 60s (#16498)
## Summary

Without this, a user who does `uv auth login ...` will retry against the
service's status endpoint forever. This probably isn't what they
intended (they probably walked away from their machine), so we end their
login initiation session after 60 retries. Since we do a retry every
second, this gives them no less than a minute to complete a login (which
should be more than enough).

## Test Plan

We don't have browser-negotiated login tests at the moment in CI, but
I've tested this locally:

```console
% ./target/debug/uv auth login pyx.dev
Logging in with https://api.pyx.dev/auth/cli/login/REDACTED
error: Login session timed out
```

(That took well over a minute, so 60s is a lower bound assuming a very
optimal network roundtrip on each poll.)

---------

Signed-off-by: William Woodruff <william@astral.sh>
2025-10-29 10:39:23 -05:00
William Woodruff 2d54f329e5
Fix a small clippy warning (#16499)
## Summary

Missed this with another PR.

## Test Plan

NFC.

Signed-off-by: William Woodruff <william@astral.sh>
2025-10-29 10:38:27 -05:00
Charlie Marsh c4f5741e7d
Add an empty group with `uv add -r` (#16490)
## Summary

`uv add -r requirements.txt --group foo` will now create `foo` even if
`requirements.txt` is empty.

Closes https://github.com/astral-sh/uv/issues/16361.
2025-10-29 11:31:03 -04:00
William Woodruff da659fee48
Merge commit from fork
* feat: reject ZIP archives with improbable filenames

Signed-off-by: William Woodruff <william@astral.sh>

* use my PR for async_zip temporarily

Signed-off-by: William Woodruff <william@astral.sh>

* update snapshot

Signed-off-by: William Woodruff <william@astral.sh>

* two more tests

Signed-off-by: William Woodruff <william@astral.sh>

* update rev

Signed-off-by: William Woodruff <william@astral.sh>

---------

Signed-off-by: William Woodruff <william@astral.sh>
2025-10-29 11:11:06 -04:00
github-actions[bot] 41cd3d1926
Sync latest Python releases (#16486)
Automated update for Python releases.

Co-authored-by: jjhelmus <1050278+jjhelmus@users.noreply.github.com>
2025-10-29 10:10:49 -05:00
Charlie Marsh a759612bc8
Show package list with `pip freeze --quiet` (#16491)
## Summary

Closes https://github.com/astral-sh/uv/issues/16178.
2025-10-29 11:00:00 -04:00
konsti 61c67bebcb
Set opt-level 1 for fast build profile (#16481)
Test cases:

```
touch crates/uv-resolver/src/resolver/mod.rs && time cargo nextest run --cargo-profile dev --no-fail-fast -- --skip python_install
touch crates/uv-resolver/src/resolver/mod.rs && time cargo nextest run --cargo-profile fast-build --no-fail-fast -- --skip python_install
```

On my machine, we go from 7.x s (dev) to 8.x s (dev + opt-level 1) for
compilation, and 6.x s for the combined `fast-build` profile. With
opt-level 1, we go from 84s for the first line to 64s for the second
line.
2025-10-28 13:31:18 +01:00
konsti cfa1de311e
Add `--no-create-gitignore` to `uv build` (#16369)
Fixes #16332
2025-10-28 07:25:31 -05:00
konsti c279b4ab54
Only run debug assertion tests when debug assertions are active (#16479)
These tests currently fail when running tests in release mode.
2025-10-28 12:21:58 +00:00
renovate[bot] fce1db39f2
Update Rust crate globset to v0.4.18 (#16460)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[globset](https://redirect.github.com/BurntSushi/ripgrep/tree/master/crates/globset)
([source](https://redirect.github.com/BurntSushi/ripgrep/tree/HEAD/crates/globset))
| workspace.dependencies | patch | `0.4.17` -> `0.4.18` |

---

### Release Notes

<details>
<summary>BurntSushi/ripgrep (globset)</summary>

###
[`v0.4.18`](https://redirect.github.com/BurntSushi/ripgrep/compare/globset-0.4.17...globset-0.4.18)

[Compare
Source](https://redirect.github.com/BurntSushi/ripgrep/compare/globset-0.4.17...globset-0.4.18)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTYuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE1Ni4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 07:18:46 -05:00
renovate[bot] 570c84bf57
Update Rust crate flate2 to v1.1.5 (#16459)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [flate2](https://redirect.github.com/rust-lang/flate2-rs) |
workspace.dependencies | patch | `1.1.4` -> `1.1.5` |

---

### Release Notes

<details>
<summary>rust-lang/flate2-rs (flate2)</summary>

###
[`v1.1.5`](https://redirect.github.com/rust-lang/flate2-rs/releases/tag/1.1.5)

[Compare
Source](https://redirect.github.com/rust-lang/flate2-rs/compare/1.1.4...1.1.5)

This bugfix release fixes
[#&#8203;508](https://redirect.github.com/rust-lang/flate2-rs/issues/508),
as flush didn't always work anymore in conjunction with `miniz_oxide`.

#### What's Changed

- Revert flush change by
[@&#8203;fintelia](https://redirect.github.com/fintelia) in
[#&#8203;509](https://redirect.github.com/rust-lang/flate2-rs/pull/509)

**Full Changelog**:
<https://github.com/rust-lang/flate2-rs/compare/1.1.4...1.1.5>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTYuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE1Ni4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 07:18:31 -05:00
liam 3fb3c9af03
Clarify system Python discovery logging order (#16463)
Resolves https://github.com/astral-sh/uv/issues/16453

When `--system` is used, the debug log now reports interpreter discovery
sources in the same order they are probed, prioritising the PATH ahead
of managed installs on every platform. I also added a few unit tests
that use `DiscoveryPreferences::sources`, ensuring the log strings stay
aligned with the actual discovery sequence for both System and
OnlySystem preferences.
2025-10-28 07:18:13 -05:00
Hugo van Kemenade 399094aedc
Follow the style guide in the style guide (#16466)
<!--
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? -->

> 4. Hyphenate compound words, e.g., use "platform-specific" not
"platform specific".

Applies to "command line arguments" -> "command-line arguments".
2025-10-27 15:46:39 -05:00
konsti 804f1ff808
Add `--clear` to `uv build` to remove old build artifacts (#16371)
Add `uv build --clear` that behaves like `rm -r ./dist && uv build` to
clear artifacts from previous builds. This avoids accidentally
publishing the wrong artifacts and removes accumulated old builds.

See https://github.com/astral-sh/uv/issues/10293#issuecomment-3405904013
Fixes #10293
2025-10-27 18:15:17 +00:00
elle fe5d944afa
Change reporter length to `Option` (#16448)
## Summary

Changes the `length` parameter to `PythonDownloadReporter::new` to an
`Option<u64>` to resolve the issue of setting a single-item reporter
length.

Resolves: #15404

## Test Plan

- build test
- ran test suite
2025-10-27 17:17:14 +01:00
Matthijs Kok 659b4873b6
Update docs for maturin build backend init template (#16469)
I forgot to update the docs in
https://github.com/astral-sh/uv/pull/16449
Sorry!
2025-10-27 11:35:39 +01:00
Matthijs Kok 85c01b29d5
Update maturin build backend init template (#16449)
## Summary

Upgrades to the latest Rust edition and pyo3 version. 
Change initialized module to "Inline Declaration" format.
https://pyo3.rs/v0.27.1/module.html#inline-declaration

The output of `maturin new` is also updating to the new declarative
format

342239a95a

## Test Plan

Updated the relevant snapshot tests and to confirm ran
`cargo nextest run --all-features --no-fail-fast maturin`

Also used the updated template to generate a library in a different
project with
```
cargo run -- init --lib --build-backend maturin --name try-template ../_OTHER_PROJECT_/backend/try-template
```
and the generated workspace member functioned as expected.
2025-10-27 10:58:17 +01:00
renovate[bot] 424e588213
Update Rust crate doc-comment to v0.3.4 (#16458) 2025-10-26 22:26:36 -04:00
renovate[bot] 66b69488f7
Update Rust crate criterion to v4.0.5 (#16457) 2025-10-26 22:26:18 -04:00
renovate[bot] e9b0478dab
Update dependency astral-sh/uv to v0.9.5 (#16456) 2025-10-26 22:26:15 -04:00
Basil Sh. b8a1b13feb
docs: installation.md add info about installing via MacPorts (#16039) 2025-10-26 22:07:21 -04:00
Zanie Blue e2eea6d7db
Fix root of `uv tree` when `--package` is used with circular dependencies (#15908)
Closes #15907

Best viewed with
https://github.com/astral-sh/uv/pull/15908/files?diff=unified&w=1

When `--package` is used, just use those as the roots rather than
calculating them. I'm not sure if there will be undesirable
side-effects, but it's the naive solution.
2025-10-26 22:01:00 -04:00
SADIK KUZU 175be60727
Upgrade setup-python action to version 6 (#16450)
## Summary

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

This pull request updates the GitHub Actions workflow documentation to
use the latest version of the `actions/setup-python` action. This
ensures compatibility with recent improvements and bug fixes in the
action.

Workflow version updates:

* Updated the `uses: actions/setup-python` step from version `v5` to
`v6` in two separate workflow job examples in
`docs/guides/integration/github.md`.
[[1]](diffhunk://#diff-e864b910728c865e8e16ddb7892761fc2ef4838f2bf256eb1e20c35b24edd9fbL96-R96)
[[2]](diffhunk://#diff-e864b910728c865e8e16ddb7892761fc2ef4838f2bf256eb1e20c35b24edd9fbL119-R119)
2025-10-26 21:54:20 -04:00
liam ae6607d5dc
Deterministically order `--find-links` distributions (#16446)
Made to address this comment:
https://github.com/astral-sh/uv/pull/16103#discussion_r2437498249

This PR sorts the distributions collected by
`FlatIndexClient::read_from_directory` (used for `--find-links`) so
results are ordered deterministically by filename and index.
2025-10-26 21:53:32 -04:00
Brian Dentino ce7808d0cb
Update docs to reflect new signal forwarding semantics (#16430)
<!--
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? -->
I am a new uv user and I was reading the docs to better understand the
project scope & best practices. The section on [signal
forwarding](https://docs.astral.sh/uv/concepts/projects/run/#signal-handling)
with `uv run` caught my eye because I've used tools that use SIGHUP to
trigger config reloads or SIGUSR1/2 to enable debugging/profiling/etc so
I was a little concerned about using a runner that might block those
signals. After some searching in issues/PRs, I found that this behavior
was actually [changed earlier this
year](https://github.com/astral-sh/uv/pull/13017) to forward additional
signals (awesome!) and thought I would update the docs and save the next
person/llm from thinking their tool won't work as expected if it uses
custom signal handling.

Thanks for all your great work!

P.S. If you think it makes more sense to explicitly list all forwarded
signals as opposed to just the exclusions, I'm happy to edit.
2025-10-24 14:52:08 -05:00
Zanie Blue 3ff3ae2062
Pin the maturin version in the release pipeline (#16439)
This should avoid hitting the GitHub API to determine the latest
release, and more generally we should not automatically fetch the latest
version of Maturin in our release pipeline as it opens us to
supply-chain attacks.
2025-10-24 13:16:30 -05:00
Zanie Blue 2baee75a94
Restore DockerHub images and annotations (#16441)
- **Revert "Drop publish of extended images to DockerHub temporarily
(#16355)"**
- **Revert "Drop annotations from DockerHub uv images (#16356)"**
2025-10-24 13:16:24 -05:00
William Woodruff 7df01bcbde
ci: re-enable zizmor (#16437)
## Summary

The latest version should fix our rate-limiting issues.

## Test Plan

See what happens in CI.

Signed-off-by: William Woodruff <william@astral.sh>
2025-10-24 10:31:45 -05:00
Zanie Blue 17181fef07
Do not error when a virtual environment directory cannot be removed due to a busy error (#16394)
Closes https://github.com/astral-sh/uv/issues/16218

This occurs when using a mounted file system in Docker.

We're almost always removing a virtual environment to replace it, and
removing the parent directory isn't necessary for that.
2025-10-23 15:08:53 -05:00
konsti 491293362f
Don't panic in `uv export --frozen` when the lockfile is outdated (#16407)
Provide a good error message when the discovered workspace members
mismatch with the locked workspace members in `uv export --frozen`,
instead of panicking.

Fixes #16406
2025-10-23 15:07:14 -05:00
Zanie Blue 940a3f63ce
Use the Python download cache during `python_upgrade` tests (#16421) 2025-10-23 10:48:45 -05:00
Zanie Blue 1fbc1c7ff4
Check for matching Python implementation during `uv python upgrade` (#16420)
Closes https://github.com/astral-sh/uv/issues/16416
2025-10-23 10:48:34 -05:00
github-actions[bot] 00bf80bfda
Upgrade GraalPy to 25.0.1 (#16401)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-10-23 15:16:37 +00:00
Zanie Blue 0959763a82
Improve hint on `pip install --system` when externally managed (#16392)
I had some qualms with https://github.com/astral-sh/uv/pull/16318

1. The snapshot was specific to uv's managed interpreter due to
inclusion of the externally-managed output. This will break downstream
distros. We either need to filter the message, or, as done here, install
a managed interpreter.
2. It had a custom filter for the interpreter path, which we shouldn't
need. If needed, we should fix that in the test context.
3. We already had an implicit hint to create a virtual environment. The
change in styling of the new hint hint following it is confusing. It's
also confusing to hint creating a virtual environment when `--system`
was used.
4. There were unresolved requested changes to the language for the
message / it didn't fit stylistic with our existing ones.
5. The message was also very confusing for a uv-managed interpreter,
which is both a "system" Python interpreter (in that it's global) and
the opposite of a "system" interpreter per UV_PYTHON_PREFERENCE.

Also problematic, but not addressed here, is that there are other
commands that display an externally-managed message, e.g., `uv pip
sync`, but #16318 was just limited to `pip install`. We should not just
implement this in one place — I'll open a tracking issue to consolidate
and reuse the logic.
2025-10-21 17:59:01 +00:00
Charles-Meldhine Madi Mnemoi 225bffbb6c
Running `uv lock --check` with outdated lockfile will print that `--check` was passed, instead of `--locked` (#16322)
Hello,

# Summary

This PR fixes the confusing error message when running `uv lock --check`
with an outdated lockfile.

Previously, the error message incorrectly stated that `--locked` was
provided, even when the user used `--check`.

Now, the error message correctly indicates which flag was used: either
`--check` or `--locked`.

This closes #14105.

# Test plan

- I updated the existing integration test (`check_outdated_lock` in
`lock.rs`) to verify the new error message includes the correct flag.
- I ran existing tests to ensure I have no introduced regressions for
other commands.
2025-10-21 12:46:10 -05:00
William Woodruff 509a1e8ff6
CHANGELOG: include security note + entry for 0.9.5 (#16393) 2025-10-21 13:33:28 -04:00
William Woodruff d5f39331a7
Bump version to 0.9.5 (#16389) 2025-10-21 12:14:22 -04:00
konsti 1a53701d1d
Deactivate zizmor due to GitHub ratelimiting (#16390) 2025-10-21 15:36:20 +00:00
William Woodruff ae450662d1
deps: bump astral-tokio-tar to 0.5.6 (#16387) 2025-10-21 10:55:58 -04:00
konsti 38412a675e
Remove outdated aarch64 musl note (#16385)
We now build aarch64 musl Python distributions. I've also removed the
rye mention since it's now officially deprecated.
2025-10-21 16:08:37 +02:00
Samuel Rigaud 2fc5fe2ac0
fix small typos (#16357)
## Summary

I tried to fix minor typos in the project

---------

Co-authored-by: Samuel Rigaud <rigaud@gmail.com>
Co-authored-by: konstin <konstin@mailbox.org>
2025-10-21 12:11:59 +00:00
Jordan Borean 17155c4cca
Fix backtick escaping for PowerShell (#16307)
## Summary
Fixes the logic for escaping a double quoted string in PowerShell by not
escaping a backslash but escaping the Unicode double quote variants that
PowerShell treats the same as the ASCII double quotes.

<img width="685" height="99" alt="image"
src="https://github.com/user-attachments/assets/ac1368c2-d915-4e49-b67f-ac71ee0f7d46"
/>

## Test Plan
There does not seem to be any tests for this. I'm fairly new to rust but
happy to add some if someone can point me in the right direction.

---------

Co-authored-by: Aria Desires <aria.desires@gmail.com>
2025-10-21 10:16:21 +00:00
Andrei Berenda 51e8da2d1c
Move parsing http retries to EnvironmentOptions (#16284)
## Summary
- Move  parsing `UV_HTTP_RETRIES` to `EnvironmentOptions`

Relates https://github.com/astral-sh/uv/issues/14720

## Test Plan

- Tests with existing tests
2025-10-21 11:14:37 +02:00
Charles-Meldhine Madi Mnemoi 29cec24d5c
fix: Add a hint on `uv pip install` failure if the `--system` flag is used to select an externally managed interpreter (#16318)
Hello,

# Summary
This PR makes the error message clearer when you try to install packages
into an externally managed Python environment with the `--system` flag.
Now, instead of just failing, the error explains that you're hitting
this because you explicitly used `--system`.

This closes #15639.

# Test plan

- I added a new integration test (`install_with_system_interpreter` in
`pip_install.rs`) that checks the error message includes the hint.
- I tried to run `uv pip install --system -r requirements.txt` to see
the actual error message in action, but had permission issues.
2025-10-21 07:46:39 +00:00
github-actions[bot] ed65c2482c
Sync latest Python releases (#16246)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-10-21 00:41:53 +00:00
eun2ce e0fe38eabb
Improve 403 Forbidden error message to indicate package may not exist (#16353)
Fixes #16340

## Summary

Some package registries (PyTorch, corporate PyPI mirrors) return `403
Forbidden` when a package is not found, instead of `404 Not Found`. The
previous error message incorrectly suggested this was always an
authentication issue, causing confusion for users.

This PR updates the error hint to clarify that a 403 error could
indicate either missing authentication credentials OR that the package
doesn't exist on the index.

## Test Plan

- Updated existing snapshot test in `crates/uv/tests/it/edit.rs` to
reflect the new error message
- The change is purely a message improvement with no behavioral changes

## Changes

### Before

hint: An index URL (https://example.com/simple) could not be queried due
to a lack of valid authentication credentials (403 Forbidden).

### After

hint: An index URL (https://example.com/simple) returned a 403 Forbidden
error. This could indicate missing authentication credentials, or the
package may not exist on this index.


## Files Changed

- `crates/uv-resolver/src/pubgrub/report.rs` - Updated error message
- `crates/uv/tests/it/edit.rs` - Updated snapshot test expectation

---------

Co-authored-by: eun2ce <eun2ce@eun2ceui-MacBookPro.local>
Co-authored-by: konstin <konstin@mailbox.org>
2025-10-20 11:43:18 +00:00
Parham MohammadAlizadeh ed3f99a119
Add required environment marker example to hint (#16244)
## Summary
fixes issue #15938 
- show platform wheel hint with a concrete
`tool.uv.required-environments` example so users know how to configure
compatibility
- add `WheelTagHint::suggest_environment_marker` to pick a sensible
environment marker based on the available wheel tags
- update the `sync_required_environment_hint` integration snapshot to
expect the new multi-line hint

## Test Plan

cargo test --package uv --test it --
sync::sync_required_environment_hint
2025-10-20 13:08:10 +02:00
renovate[bot] 2b0407e277
Update dependency astral-sh/uv to v0.9.4 (#16363)
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.2` -> `0.9.4` |

---

### Release Notes

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

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

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

Released on 2025-10-17.

##### Enhancements

- Add CUDA 13.0 support
([#&#8203;16321](https://redirect.github.com/astral-sh/uv/pull/16321))
- Add auto-detection for Intel GPU on Windows
([#&#8203;16280](https://redirect.github.com/astral-sh/uv/pull/16280))
- Implement display of RFC 9457 HTTP error contexts
([#&#8203;16199](https://redirect.github.com/astral-sh/uv/pull/16199))

##### Bug fixes

- Avoid obfuscating pyx tokens in `uv auth token` output
([#&#8203;16345](https://redirect.github.com/astral-sh/uv/pull/16345))

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

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

Released on 2025-10-14.

##### Python

- Add CPython 3.15.0a1
- Add CPython 3.13.9

##### Enhancements

- Obfuscate secret token values in logs
([#&#8203;16164](https://redirect.github.com/astral-sh/uv/pull/16164))

##### Bug fixes

- Fix workspace with relative pathing
([#&#8203;16296](https://redirect.github.com/astral-sh/uv/pull/16296))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 10:33:57 +02:00
renovate[bot] 1bdd85b4e8
Update Rust crate globset to v0.4.17 (#16365)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[globset](https://redirect.github.com/BurntSushi/ripgrep/tree/master/crates/globset)
([source](https://redirect.github.com/BurntSushi/ripgrep/tree/HEAD/crates/globset))
| workspace.dependencies | patch | `0.4.16` -> `0.4.17` |

---

### Release Notes

<details>
<summary>BurntSushi/ripgrep (globset)</summary>

###
[`v0.4.17`](https://redirect.github.com/BurntSushi/ripgrep/compare/globset-0.4.16...globset-0.4.17)

[Compare
Source](https://redirect.github.com/BurntSushi/ripgrep/compare/globset-0.4.16...globset-0.4.17)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 10:32:48 +02:00
renovate[bot] 152461bc43
Update Rust crate ignore to v0.4.24 (#16367)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[ignore](https://redirect.github.com/BurntSushi/ripgrep/tree/master/crates/ignore)
([source](https://redirect.github.com/BurntSushi/ripgrep/tree/HEAD/crates/ignore))
| workspace.dependencies | patch | `0.4.23` -> `0.4.24` |

---

### Release Notes

<details>
<summary>BurntSushi/ripgrep (ignore)</summary>

###
[`v0.4.24`](https://redirect.github.com/BurntSushi/ripgrep/compare/ignore-0.4.23...ignore-0.4.24)

[Compare
Source](https://redirect.github.com/BurntSushi/ripgrep/compare/ignore-0.4.23...ignore-0.4.24)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 10:32:02 +02:00
renovate[bot] c494c2c0b0
Update Rust crate goblin to v0.10.3 (#16366) 2025-10-20 01:32:05 +00:00
konsti 2428a1664e
Document metadata consistency expectation (#15683)
An opinionated write-up on why Python packaging needs metadata
consistency, and that we need to extend metadata to accommodate ML and
scientific users.

I didn't add a paragraph related to CUDA or accelerators in general and
wheel variants, as this is currently support neither by wheel tags nor
by PEP 508 markers, so it's not a strict metadata consistency concern,
plus this would get outdated quickly as wheel variants progress.
2025-10-19 14:29:47 +02:00
Björn Dahlgren 57f56467d9
Fix typo in MissingTopLevel warning (#16351)
<!--
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? -->
The warning shown when `egg-info` lacks `top_level.txt` incorrectly
warns about missing `top-level.txt`

ee2649feaa/crates/uv-install-wheel/src/uninstall.rs (L179-L188)

ee2649feaa/crates/uv/src/commands/pip/operations.rs (L687-L693)

The non fatal warning with incorrect filename was introduced in
https://github.com/astral-sh/uv/pull/6881 which changed previous fatal
error https://github.com/astral-sh/uv/issues/6872 with correct
`top_level.txt` output.

## Test Plan

<!-- How was it tested? -->
Updated unit test to reflect change in warning
2025-10-19 11:32:50 +02:00
Zanie Blue 88f519a3bf
Drop annotations from DockerHub uv images (#16356) 2025-10-18 14:58:06 -05:00
Zanie Blue 70cfc4020a
Drop publish of extended images to DockerHub temporarily (#16355)
To workaround https://github.com/astral-sh/uv/issues/16350
2025-10-18 14:47:48 -05:00
Zanie Blue ee2649feaa
Bump version to 0.9.4 (#16347) 2025-10-17 16:02:02 -05:00
David Peter b50c1be100
Only add actual schema in schemastore PRs (#16346)
## Summary

Last time I ran the `update_schemastore.py` script in ty, due to what I
assume was a `npm` version mismatch, the `package-lock.json` file was
updated while running `npm install` in the `schemastore`. Due to the use
of `git commit -a`, it was accidentally included in the commit for the
semi-automated schemastore PR. The solution here is to only add the
actual file that we want to commit.

Same as https://github.com/astral-sh/ty/pull/1391

## Test Plan

I did a dry-run of this script (by commenting out the final `push`) and
verified that the commit did include the schema, but not the updated
`package-lock.json` file.
2025-10-17 19:35:53 +00:00
Zanie Blue 7b3a9c2859
Avoid obfuscating pyx tokens in `uv auth token` output (#16345) 2025-10-17 19:19:05 +00:00
Yu, Guangye de9f299b80
Add auto-detection for Intel GPU on Windows (#16280)
This PR enables `--torch-backend=auto` to automatically detect Intel
GPUs. It follows up on
[#14386](https://github.com/astral-sh/uv/pull/14386).
On Windows, detection is implemented by querying the
`Win32_VideoController` class via the [WMI
crate](https://github.com/ohadravid/wmi-rs/tree/v0.16.0).

Currently, Intel GPUs (XPU) do not depend on specific driver or toolkit
versions to determine which PyTorch wheel to use.
2025-10-16 16:56:07 -04:00
Mark Dodgson c12e8bb343
Implement RFC9457 compliant messaging (#16199)
<!--
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

HTTP1.1 [RFC 9112 -
HTTP/1.1](https://www.rfc-editor.org/rfc/rfc9112.html#name-status-line)
section 4 defines the response status code to optionally include a text
description (human readable) of the reason for the status code.

[RFC9113 - HTTP/2](https://www.rfc-editor.org/rfc/rfc9113) is the HTTP2
protocol standard and the response status only considers the [status
code](https://www.rfc-editor.org/rfc/rfc9113#name-response-pseudo-header-fiel)
and not the reason phrase, and as such important information can be lost
in helping the client determine a route cause of a failure.

As per discussion on this
[PR](https://github.com/astral-sh/uv/pull/15979) the current feeling is
that implementing the RFC9457 standard might be the preferred route.
This PR makes those changes to aid the discussion which has also been
moved to the [PEP
board](https://discuss.python.org/t/block-download-of-components-when-violating-policy/104021/1)

## Test Plan

Pulling components that violate our policy over HTTP2 and without any
RFC9457 implementation the following message is presented to the user:
<img width="1482" height="104" alt="image"
src="https://github.com/user-attachments/assets/0afcd0d8-ca67-4f94-a6c2-131e3b6d8dcc"
/>


With the RFC9457 standard implemented, below you can see the advantage
in the extra context as to why the component has been blocked:
<img width="2171" height="127" alt="image"
src="https://github.com/user-attachments/assets/25bb5465-955d-4a76-9f30-5477fc2c866f"
/>

---------

Co-authored-by: konstin <konstin@mailbox.org>
2025-10-16 19:53:49 +00:00
Zanie Blue 0a2b160400
Migrate from deprecated `macos-13` runner to `macos-15-intel` (#16316) 2025-10-16 09:07:18 -05:00
Charlie Marsh bf81a5bf0c
Add CUDA 13.0 support (#16321)
## Summary

Closes https://github.com/astral-sh/uv/issues/16319.
2025-10-15 15:10:08 -04:00
konsti 766bd951cb
Use `uv_build` in `make_project` tests (#16298)
Skip downloading and running setuptools.
2025-10-15 20:45:32 +02:00
konsti 52cc3c8b94
Add missing `UV_TEST_NO_HTTP_RETRY_DELAY` check and better logging (#16313)
The `install_http_retries` test goes from 15s to 0.3s. Additionally, we
log the retry delay.
2025-10-15 20:45:21 +02:00
Zanie Blue 83635a6c45
Update changelog to escape GitHub bad behavior (#16315) 2025-10-15 08:45:05 -05:00
Zanie Blue 9db7d38cf7
Bump version to 0.9.3 (#16305) 2025-10-14 18:19:49 -05:00
Zanie Blue d1413a60d8
Add CPython 3.15.0a1 and 3.13.9 (#16304) 2025-10-14 18:05:44 -05:00
Mark Dodgson b151e0ea3d
Fix workspace with relative pathing (#16296)
<!--
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

Fixes #16285 

## Test Plan

<!-- How was it tested? -->
2025-10-14 19:15:19 +02:00
Geoffrey Thomas 8eada1685c
Fix comments on python_no_transparent_upgrade_with_venv_patch_specification (#16294)
I think this was originally intended to behave as the comments
specified, but the behavior changed in discussions in #13312 somewhere.
2025-10-14 07:32:47 -05:00
renovate[bot] f9fbf41f69
Update Rust crate ref-cast to v1.0.25 (#16272) 2025-10-13 10:59:44 -04:00
renovate[bot] 45e8b38bb6
Update Rust crate thiserror to v2.0.17 (#16276) 2025-10-13 10:59:09 -04:00
renovate[bot] ae273f35d3
Update Rust crate serde to v1.0.228 (#16275) 2025-10-13 10:59:01 -04:00
Zanie Blue 15829bb30a
Obfuscate Bearer Token values in logs (#16164)
Sometimes a credential's `Debug` formatted value appears in tracing logs
- make sure the credential doesn't appear there.

## Test plan

Added a test case + ran
```
uv pip install --default-index $PYX_API_URL/$SOME_INDEX $SOME_PACKAGE -vv
```
With an authenticated uv client and confirmed the tokens are obfuscated.

---------

Co-authored-by: Zsolt Dollenstein <zsol.zsol@gmail.com>
2025-10-13 08:28:20 -05:00
renovate[bot] 01d43382be
Update Rust crate rkyv to v0.8.12 (#16274) 2025-10-13 02:57:25 +00:00
renovate[bot] e3690e702f
Update Rust crate regex-automata to v0.4.12 (#16273) 2025-10-13 02:55:24 +00:00
renovate[bot] 15f1dc6269
Update Rust crate petgraph to v0.8.3 (#16270) 2025-10-13 02:42:49 +00:00
renovate[bot] 3837b92cc1
Update Rust crate owo-colors to v4.2.3 (#16269) 2025-10-13 02:40:07 +00:00
renovate[bot] dd89bdaab0
Update Rust crate quote to v1.0.41 (#16271) 2025-10-13 02:36:45 +00:00
renovate[bot] cd859ccd17
Update Rust crate memchr to v2.7.6 (#16268) 2025-10-12 22:21:43 -04:00
renovate[bot] c741e4bfb5
Update Rust crate fs-err to v3.1.3 (#16265) 2025-10-12 22:21:23 -04:00
renovate[bot] 7d5795e2a5
Update Rust crate flate2 to v1.1.4 (#16264) 2025-10-12 22:21:17 -04:00
renovate[bot] 1b91b31763
Update Rust crate goblin to v0.10.2 (#16266) 2025-10-13 02:00:35 +00:00
renovate[bot] 2898e5c346
Update Rust crate bytecheck to v0.8.2 (#16262) 2025-10-13 01:33:17 +00:00
renovate[bot] 3e6e1eddf5
Update Rust crate anstream to v0.6.21 (#16261) 2025-10-13 01:32:30 +00:00
renovate[bot] a2360c1753
Update Rust crate criterion to v4.0.4 (#16263) 2025-10-13 01:26:45 +00:00
Andrei Berenda c7d3d549e2
Move parsing concurrency env variable to EnvironmentOptions (#16223)
## Summary
- Move parsing `UV_CONCURRENT_INSTALLS`, `UV_CONCURRENT_BUILDS` and
`UV_CONCURRENT_DOWNLOADS` to `EnvironmentOptions`

Relates https://github.com/astral-sh/uv/issues/14720

## Test Plan

- Tests with existing tests
- Add test with invalid parsing
2025-10-12 21:22:30 -04:00
renovate[bot] d536d3f27d
Update dependency astral-sh/uv to v0.9.2 (#16259) 2025-10-13 01:13:59 +00:00
William Woodruff 141369ce73
Bump version to 0.9.2 (#16238)
Co-authored-by: Geoffrey Thomas <geofft@ldpreload.com>
2025-10-10 14:20:14 -04:00
github-actions[bot] d54a5bb5cb
Sync latest Python releases (#16229)
Add python-build-standalone 20251010 for CPython 3.12.12, 3.11.14, 3.10.19 and 3.9.24.

Co-authored-by: Geoffrey Thomas <geofft@ldpreload.com>
2025-10-10 13:35:37 -04:00
Harshith VH b4168e665e
Add uv tool list --show-python (#15814)
<!--
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?
-->

Closes #15312 
Closes https://github.com/astral-sh/uv/issues/16237

---------

Co-authored-by: pythonweb2 <32141163+pythonweb2@users.noreply.github.com>
Co-authored-by: Wade Roberts <wade.roberts@centralsquare.com>
2025-10-10 12:33:26 -05:00
William Woodruff 6fb00a9936
publish: don't infer check URLs for pyx uploads (#16234) 2025-10-10 12:51:02 -04:00
konsti d45acaebc8
Fix typo in resolver internal docs (#16232) 2025-10-10 15:46:47 +00:00
samypr100 d5dd43aa18
Missing added_in on new env vars (#16217)
## Summary

Adds the version for environment variables added in
https://github.com/astral-sh/uv/pull/16040 and
https://github.com/astral-sh/uv/pull/16125. as these were in-flight
before documentation versioning was added.

Adds ability to emit a compiler error when added in is missing for
improved reporting to the developer.

e.g. example for the ones fixed in this PR

```shell
error: missing #[attr_added_in("x.y.z")] on `UV_UPLOAD_HTTP_TIMEOUT`
       note: env vars for an upcoming release should be annotated with `#[attr_added_in("next release")]`
   --> crates\uv-static\src\env_vars.rs:593:15
    |
593 |     pub const UV_UPLOAD_HTTP_TIMEOUT: &'static str = "UV_UPLOAD_HTTP_TIMEOUT";
    |               ^^^^^^^^^^^^^^^^^^^^^^

error: missing #[attr_added_in("x.y.z")] on `UV_WORKING_DIRECTORY`
       note: env vars for an upcoming release should be annotated with `#[attr_added_in("next release")]`
    --> crates\uv-static\src\env_vars.rs:1087:15
     |
1087 |     pub const UV_WORKING_DIRECTORY: &'static str = "UV_WORKING_DIRECTORY";
     |               ^^^^^^^^^^^^^^^^^^^^
error: could not compile `uv-static` (lib) due to 2 previous errors
```
2025-10-10 08:55:09 -05:00
Skyler Hawthorne ea5a09215b
fix recompiling every time in uv-python (#16214)
Cargo is currently recompiling uv-python on every invocation because the
minified JSON output file is getting a mod time newer than the last run.
Instead, set the mod time of the output file to the same as the input
file.
2025-10-09 16:10:08 -05:00
Zanie Blue 9887ef5bd7
Bump version to 0.9.1 (#16212)
Archives the 0.8.x changelog too.
2025-10-09 18:28:59 +00:00
Alyssa Coghlan 628eedea36
Fix pylock.toml config conflict error messages (#16211)
## Summary

When specifying constraints or overrides in combination with
`pylock.toml` as an input to `uv pip install`,
the error messages are not currently correct.

## Test Plan

No testing, it's a straightforward error string fix.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-10-09 17:38:18 +00:00
Andrei Berenda a58d031157
Add `UV_UPLOAD_HTTP_TIMEOUT` and respect `UV_HTTP_TIMEOUT` in uploads (#16040)
## Summary
- Move parsing `UV_HTTP_TIMEOUT`, `UV_REQUEST_TIMEOUT` and
`HTTP_TIMEOUT` to `EnvironmentOptions`
- Add new env varialbe `UV_UPLOAD_HTTP_TIMEOUT`

Relates https://github.com/astral-sh/uv/issues/14720

## Test Plan

Tests with existing tests
2025-10-09 12:28:30 -05:00
Zanie Blue 84d6a913ac Ignore pre-release Python versions when a patch version is requested (#16210)
I think this is ostensively breaking, though I think the impact would be
small given this will go out in 0.9.1 and should only affect people
using pre-release Python versions.

When `3.14.0` is requested (opposed to `3.14`), we treat this as a
request for a final / stable version and ignore pre-releases.

I think this is a fairly clean way to allow users to explicitly request
the stable version.

Closes https://github.com/astral-sh/uv/issues/16175
Follows #16208
2025-10-09 12:24:42 -05:00
Zanie Blue fb7d5361b0 Fix handling of Python requests with pre-releases in ranges 2025-10-09 12:24:42 -05:00
Assad Yousuf 24ebdf02c0
Preserve comments on version bump (#16141)
## Summary
Fixes [1633](https://github.com/astral-sh/uv/issues/16133). Preserves
comments preceding "version = ..." line when uv version --bump is ran

## Test Plan
Added IT test

---------

Co-authored-by: konsti <konstin@mailbox.org>
2025-10-09 16:07:00 +00:00
konsti 787d035d5e
Document why uv discards upper bounds on `requires-python` (#15927)
We're regularly get questions about this. The DPO thread is the best
ressource, but it's also a long read, so I summarized some points for
uv's decision.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-10-09 17:15:03 +02:00
konsti 3e6fe1da86
Allow missing `Scripts` directory (#16206)
With the new Python install manager, the `Scripts` directory reported by
Python may not exist.

See https://github.com/astral-sh/uv/pull/16205 for a failing CI run:
https://github.com/astral-sh/uv/actions/runs/18377230241/job/52354460636?pr=16205#step:4:15

Fixes https://github.com/astral-sh/uv/issues/16204
2025-10-09 16:34:40 +02:00
konsti f0fbda1001
Update codspeed to v4 (#16139)
Simplifies the codspeed setup.
2025-10-09 15:57:05 +02:00
konsti 40397ddb4b
Log Python choice in `uv init` (#16182)
Example output:

```
$ uv-debug init foo -v
  DEBUG uv 0.9.0+6 (d3324baf68 2025-10-08)
  DEBUG Acquired shared lock for `/home/konsti/.cache/uv`
  DEBUG No Python version file found in ancestors of working directory: /home/konsti/projects/foo
  DEBUG Checking for Python environment at: `foo/.venv`
  DEBUG Searching for default Python interpreter in managed installations or search path
  DEBUG Searching for managed installations at `/home/konsti/.local/share/uv/python`
  DEBUG Found managed installation `cpython-3.14.0-linux-x86_64-gnu`
  DEBUG Found `cpython-3.14.0-linux-x86_64-gnu` at `/home/konsti/.local/share/uv/python/cpython-3.14.0-linux-x86_64-gnu/bin/python3.14` (managed installations)
  DEBUG Using Python version `>=3.14` from default interpreter
  DEBUG `git rev-parse --is-inside-work-tree` failed but didn't contain `not a git repository` in stderr for `/home/konsti/projects/foo`
  DEBUG No Python version file found in ancestors of working directory: /home/konsti/projects/foo
  DEBUG Writing Python versions to `/home/konsti/projects/foo/.python-version`
  Initialized project `foo` at `/home/konsti/projects/foo`
  DEBUG Released lock at `/home/konsti/.cache/uv/.lock`
```

First commit is refactoring, second commit is the actual change.
2025-10-09 13:55:22 +00:00
Zanie Blue c96abc93f2
Pin a uv version in CI (#16207)
To reduce rate-limiting by avoiding a latest version fetch
2025-10-09 13:54:28 +00:00
konsti 5ee728b3e3
Retry all h2 errors (#16038)
The h2 errors, a specific type nested in reqwest errors, all look like
they shouldn't happen in regular operations and should be retried. This
covers all `io::Error`s going through h2 (i.e., only HTTP 2
connections).

Fixes https://github.com/astral-sh/uv/issues/15916
2025-10-09 13:53:14 +00:00
konsti d3dc451ee9
Update pypa/gh-action-pypi-publish to release tag (#16176)
Renovate has been complaining.
2025-10-09 15:34:43 +02:00
Mitchell Berendhuysen 40204f06d1
Replace `fs_err` alias (#16201) 2025-10-09 15:18:38 +02:00
samypr100 f81c6b9a62
document uv version for environment variables (#15196)
## Summary

As new environment variables get introduced (e.g. `UV_EDITABLE`) I
thought it would useful to start tracking which release they were
introduced. I think its a common workflow to navigate to the [env var
documentation](https://docs.astral.sh/uv/reference/environment) to know
what the env var for something is but then end up in a situation where
one is using an environment variable with the wrong version of uv and
not notice immediately that its not compatible and therefore ignored.

## Test Plan

Existing tests.

The versions in `since` have all been manually reviewed to the best of
my ability for correctness.
2025-10-08 12:31:12 -05:00
Zanie Blue 1051709792
Treat deleted Windows registry keys as equivalent to missing ones (#16194)
I noticed this flaked in a snapshot.

We may want to encode this in a helper at some point.
2025-10-08 12:24:36 -05:00
Zanie Blue a681fe9976
Retry on Rocky Linux Python install (#16190)
I'm not sure why these mirrors have been flaky lately.

e.g.
https://github.com/astral-sh/uv/actions/runs/18346934817/job/52256348387?pr=16182
2025-10-08 12:24:27 -05:00
Zanie Blue 3c30e11a4d
Add a system test for Rocky Linux 10 (#16191) 2025-10-08 12:24:10 -05:00
Zanie Blue 9cec60bcc4
Include the RFC in the docs for our `Credentials` variants (#16162) 2025-10-08 16:26:30 +00:00
tison aadf103855
Upgrade reqsign to 0.18.0 to remove chrono deps (#16172)
This follows up https://github.com/astral-sh/uv/pull/15925.

cc @BurntSushi @charliermarsh

Signed-off-by: tison <wander4096@gmail.com>
2025-10-08 10:31:53 -04:00
Shunsuke Tsuchiya 39e2e3e74b
Support `UV_WORKING_DIRECTORY` for setting `--directory` (#16125)
<!--
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? -->

This pull request enables the `--directory` option to accept environment
variable: `UV_DIRECTORY`

### Motivation

Currently, the `--project` option already supports environment
variables, but --directory does not.

The motivation for this change is the same as for the --project option.
When using this option, it’s likely that the project root and the
directory containing the uv project differ. In such cases, allowing
environment variables makes it easier to avoid repeatedly specifying the
directory in commands or task runners.

### Other PRs

- PR for create `--project` option:
https://github.com/astral-sh/uv/pull/12327

## Test Plan

<!-- How was it tested? -->

### no auto testing

As with the --project option, no auto tests are included for this
change.
This is because the implementation relies on Clap’s built-in attribute
functionality, and testing such behavior would effectively mean testing
a third-party crate, which would be redundant.

As long as the compiler accepts it, things should work as expected.

### testing manually

i tested manually like [previous pull
request](https://github.com/astral-sh/uv/pull/12327)

```shell
$ cargo build --locked
./target/debug/uv init uv_directory

$ mkdir uv_directory

$ UV_DIRECTORY=uv_directory ./target/debug/uv sync
Using CPython 3.14.0rc3
Creating virtual environment at: .venv
Resolved 1 package in 15ms
Audited in 0.04ms

$ UV_DIRECTORY=uv_directory ./target/debug/uv run main.py
Hello from uv-directory!

$ ./target/debug/uv run main.py
error: Failed to spawn: `main.py`
  Caused by: No such file or directory (os error 2)
```

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-10-08 08:46:11 -05:00
Ruben Arts 2e180f5c66
Add missing fields to the Cargo package manifests (#16179)
<!--
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?
-->

Hey devs, great tool as always, you're doing amazing work. 

## Summary

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

Adds the following fields to the `[package]` table of `Cargo.toml` files
where they were missing:
```toml
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
```

Most crates already had these fields, this just aligns the rest for
consistency.

This also resolves the warnings from `cargo-deny` when using `uv` crates
as dependencies in Pixi.
## Test Plan
No tests needed, this only updates metadata.
2025-10-08 12:01:52 +02:00
konsti 0d11e52ea4
Use correct tag comment for Vampire/setup-wsl v6.0.0 (#16177)
Renovate complained that it was the wrong tag.

For the actual CI, it's a comment change only.
2025-10-08 06:56:03 +00:00
github-actions[bot] 4c4fce4284
Sync latest Python releases (#16169)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-10-08 04:26:32 +00:00
Samuel Dion-Girardeau 783b48c13f
Fix minor typo in changelog (#16173)
Noticed from the release notes -- might want to edit those as well.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-10-08 04:17:10 +00:00
Zanie Blue 09b004c7bc
Updates to the 0.9.0 changelog (#16168) 2025-10-07 19:08:29 -05:00
Zanie Blue 39b6886536
Bump version to 0.9.0 (#16161)
Co-authored-by: konsti <konstin@mailbox.org>
2025-10-07 23:17:42 +00:00
samypr100 593678055f
Publish Python 3.14 Docker Images (#16150)
## Summary

Tentative PR to drop the `-rc` suffix from the published images.

Do not merge this yet until the formal release
[upstream](https://hub.docker.com/_/python) :)
2025-10-07 18:10:07 -05:00
samypr100 2f7bb7e8ca
Promote trixie and alpine 3.22 as default tags (#15352)
## Summary

Semi related to https://github.com/astral-sh/uv/issues/15270, except
there's no removal.

Makes Alpine 3.22 and debian trixie the default tags instead of removing
bookworm and alpine 3.21 to minimize churn.

~~This PR is pending https://github.com/astral-sh/uv/pull/15351 merged
first.~~ Merged

## Test Plan

No functional changes made besides changing tag pointers.
2025-10-07 17:26:37 -05:00
Zanie Blue df0a12d461
Fix `uv python upgrade` replacement of installed binaries on pre-release to stable (#16159) 2025-10-07 20:29:42 +00:00
Zanie Blue 3a507e69b2
Ban pre-release versions in `uv python upgrade` requests (#16160) 2025-10-07 15:07:09 -05:00
github-actions[bot] 37b3557dab
Sync latest Python releases (#16157)
Pick up python-build-standalone tag 20251007 to add 3.14.0 final
and 3.13.8, and adjust tests.

Also, fix matching version requests with prereleases: `uvx
python@3.14.0rc3` should not be satisfied by 3.14.0 final.

Co-authored-by: Geoffrey Thomas <geofft@ldpreload.com>
2025-10-07 19:07:18 +00:00
Zanie Blue 61b8eae08a
Fix `uv python upgrade / install` output when there is a no-op for one request (#16158) 2025-10-07 18:39:28 +00:00
Skyler Hawthorne 9214855109
fix compile error on missing uv-python metadata (#16154)
The `uv-python` crate reads the checked-in download metadata to write
out a minified version to be statically included into the binary.

Sometimes, the resulting `download-metadata-minified.json` can go
missing, such as when `uv` is installed via `cargo` as a git source, and
there is an update; `cargo` will do a hard reset on the git repo it
stores in the cache, causing it to be deleted. In these instances, the
`uv-python` crate does not currently re-run its build script, because as
it happens, there was no change to the `download-metadata.json` since it
was last compiled. This causes a build error like:

```
            Updating git repository `https://github.com/astral-sh/uv`
          Installing uv v0.8.24 (https://github.com/astral-sh/uv?tag=0.8.24#252f8873)
            Updating crates.io index
           Compiling uv-version v0.8.24 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-version)
           Compiling uv-git v0.0.1 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-git)
           Compiling uv-build-backend v0.1.0 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-build-backend)
           Compiling uv-configuration v0.0.1 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-configuration)
           Compiling uv-client v0.0.1 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-client)
           Compiling uv-extract v0.0.1 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-extract)
           Compiling uv-workspace v0.0.1 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-workspace)
           Compiling uv-python v0.0.1 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-python)
           Compiling uv-requirements-txt v0.0.1 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-requirements-txt)
           Compiling uv-bin-install v0.0.1 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-bin-install)
           Compiling uv-publish v0.1.0 (/home/skhawtho/.cargo/git/checkouts/uv-c9e40703e19509a8/252f887/crates/uv-publish)
        error: couldn't read `crates/uv-python/src/download-metadata-minified.json`: No such file or directory (os error 2)
           --> crates/uv-python/src/downloads.rs:795:45
            |
        795 | const BUILTIN_PYTHON_DOWNLOADS_JSON: &str = include_str!("download-metadata-minified.json");
            |                                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

        error: could not compile `uv-python` (lib) due to 1 previous error
        error: failed to compile `uv v0.8.24 (https://github.com/astral-sh/uv?tag=0.8.24#252f8873)`, intermediate artifacts can be found at `/home/skhawtho/.cache/cargo`.
        To reuse those artifacts with a future compilation, set the environment variable `CARGO_TARGET_DIR` to that path.
```

This fixes the issue by also adding a `rerun-if-changed` on the
resulting minified JSON file.

Separately, this revision also changes the output directory of the
minified file to the build script's output directory via the `OUT_DIR`
environment variable, so as not to pollute the source code.
2025-10-07 12:33:04 -05:00
chance 111ed6fc94
Update `uv pip compile` args in layout.md (#16155)
<!--
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

Corrects example command for generating pylock.toml from requirements.in

## Test Plan

Local preview
2025-10-07 12:32:02 -05:00
Zanie Blue 83f738074d
Allow use of free-threaded variants in Python 3.14+ without explicit opt-in (#16142)
Closes https://github.com/astral-sh/uv/issues/15739
Closes #12445
2025-10-07 11:24:05 -05:00
liam 7d63ef114a
Surface pinned-version hint when `uv tool upgrade` can’t move the tool (#16081)
Resolves https://github.com/astral-sh/uv/issues/15665

`uv tool upgrade` already respects version pins, but when a tool was
installed with an exact version the command quietly becomes a no-op even
though users expect it to upgrade the executable. This change tweaks the
upgrade flow to detect that situation by inspecting the stored receipt
and, whenever the tool stays pinned, emit a concise hint explaining why
the version didn’t move and how to reinstall without the pin.

The message still appears if the run only refreshed supporting packages,
so users aren’t misled by dependency churn that leaves the tool itself
untouched.

I also added an integration test for the scenario end to end by
installing `babel==2.6.0`, attempting an upgrade, and asserting that the
hint is shown alongside the dependency updates.
2025-10-07 11:18:39 -05:00
konsti 73e62c0c17
Don't warn when dependency is constraint by other dependency (#16149)
Currently, `uv lock --resolution lowest-direct` warns above the setup
below, as we visit the unbounded `anyio[trio]` first.

```toml
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "anyio[trio]",
    "anyio>=4"
]
```
2025-10-07 17:59:01 +02:00
Zanie Blue 252f887338
Bump version to 0.8.24 (#16146) 2025-10-07 03:03:01 +00:00
renovate[bot] 4795aad408
Update crate-ci/typos action to v1.37.2 (#16130)
> [!NOTE]
> Mend has cancelled [the proposed
renaming](https://redirect.github.com/renovatebot/renovate/discussions/37842)
of the Renovate GitHub app being renamed to `mend[bot]`.
> 
> This notice will be removed on 2025-10-07.

<hr>

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [crate-ci/typos](https://redirect.github.com/crate-ci/typos) | action
| minor | `v1.36.3` -> `v1.37.2` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>crate-ci/typos (crate-ci/typos)</summary>

###
[`v1.37.2`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.37.1...v1.37.2)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.2...HEAD

[1.37.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.1...v1.37.2

[1.37.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.0...v1.37.1

[1.37.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.3...v1.37.0

[1.36.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.2...v1.36.3

[1.36.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.1...v1.36.2

[1.36.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.0...v1.36.1

[1.36.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.8...v1.36.0

[1.35.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.7...v1.35.8

[1.35.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.6...v1.35.7

[1.35.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.5...v1.35.6

[1.35.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.4...v1.35.5

[1.35.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...v1.35.4

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.25...v1.13.26

[1.13.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.24...v1.13.25

[1.13.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.23...v1.13.24

[1.13.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.22...v1.13.23

[1.13.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.21...v1.13.22

[1.13.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.20...v1.13.21

[1.13.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.19...v1.13.20

[1.13.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.18...v1.13.19

[1.13.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.17...v1.13.18

[1.13.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.16...v1.13.17

[1.13.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.15...v1.13.16

[1.13.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.14...v1.13.15

[1.13.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.13...v1.13.14

[1.13.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.12...v1.13.13

[1.13.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.11...v1.13.12

[1.13.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.10...v1.13.11

[1.13.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.9...v1.13.10

[1.13.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.8...v1.13.9

[1.13.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.7...v1.13.8

[1.13.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.6...v1.13.7

[1.13.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.5...v1.13.6

[1.13.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.4...v1.13.5

[1.13.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.3...v1.13.4

[1.13.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.2...v1.13.3

[1.13.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.1...v1.13.2

[1.13.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.0...v1.13.1

[1.13.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.14...v1.13.0

[1.12.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.13...v1.12.14

[1.12.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.12...v1.12.13

[1.12.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.11...v1.12.12

[1.12.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.10...v1.12.11

[1.12.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.9...v1.12.10

[1.12.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.8...v1.12.9

[1.12.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.7...v1.12.8

[1.12.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.6...v1.12.7

[1.12.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.5...v1.12.6

[1.12.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.4...v1.12.5

[1.12.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.3...v1.12.4

[1.12.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.2...v1.12.3

[1.12.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.1...v1.12.2

[1.12.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.0...v1.12.1

[1.12.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.5...v1.12.0

[1.11.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.4...v1.11.5

[1.11.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.3...v1.11.4

[1.11.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.2...v1.11.3

[1.11.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.1...v1.11.2

[1.11.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.0...v1.11.1

[1.11.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.3...v1.11.0

[1.10.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.2...v1.10.3

[1.10.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.1...v1.10.2

[1.10.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.0...v1.10.1

[1.10.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.9.0...v1.10.0

[1.9.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.1...v1.9.0

[1.8.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.0...v1.8.1

[1.8.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.3...v1.8.0

[1.7.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.2...v1.7.3

[1.7.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.1...v1.7.2

[1.7.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.0...v1.7.1

[1.7.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.6.0...v1.7.0

[1.6.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.5.0...v1.6.0

[1.5.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.1...v1.5.0

[1.4.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.0...v1.4.1

[1.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.9...v1.4.0

[1.3.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.8...v1.3.9

[1.3.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.7...v1.3.8

[1.3.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.6...v1.3.7

[1.3.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.5...v1.3.6

[1.3.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.4...v1.3.5

[1.3.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.3...v1.3.4

[1.3.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.2...v1.3.3

[1.3.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.1...v1.3.2

[1.3.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.0...v1.3.1

[1.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.1...v1.3.0

[1.2.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.0...v1.2.1

[1.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.9...v1.2.0

[1.1.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.8...v1.1.9

[1.1.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.7...v1.1.8

[1.1.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.6...v1.1.7

[1.1.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.5...v1.1.6

[1.1.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.4...v1.1.5

[1.1.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.3...v1.1.4

[1.1.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.2...v1.1.3

[1.1.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.1...v1.1.2

[1.1.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.0...v1.1.1

[1.1.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.11...v1.1.0

[1.0.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.10...v1.0.11

[1.0.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.9...v1.0.10

[1.0.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.8...v1.0.9

[1.0.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.7...v1.0.8

[1.0.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.6...v1.0.7

[1.0.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.5...v1.0.6

[1.0.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.4...v1.0.5

[1.0.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.3...v1.0.4

[1.0.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.2...v1.0.3

[1.0.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.1...v1.0.2

[1.0.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.0...v1.0.1

[1.0.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.4.0...v1.0.0

[0.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.3.0...v0.4.0

[0.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.2.0...v0.3.0

[0.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.1.4...v0.2.0

###
[`v1.37.1`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.37.0...v1.37.1)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.2...HEAD

[1.37.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.1...v1.37.2

[1.37.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.0...v1.37.1

[1.37.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.3...v1.37.0

[1.36.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.2...v1.36.3

[1.36.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.1...v1.36.2

[1.36.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.0...v1.36.1

[1.36.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.8...v1.36.0

[1.35.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.7...v1.35.8

[1.35.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.6...v1.35.7

[1.35.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.5...v1.35.6

[1.35.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.4...v1.35.5

[1.35.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...v1.35.4

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.25...v1.13.26

[1.13.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.24...v1.13.25

[1.13.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.23...v1.13.24

[1.13.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.22...v1.13.23

[1.13.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.21...v1.13.22

[1.13.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.20...v1.13.21

[1.13.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.19...v1.13.20

[1.13.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.18...v1.13.19

[1.13.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.17...v1.13.18

[1.13.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.16...v1.13.17

[1.13.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.15...v1.13.16

[1.13.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.14...v1.13.15

[1.13.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.13...v1.13.14

[1.13.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.12...v1.13.13

[1.13.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.11...v1.13.12

[1.13.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.10...v1.13.11

[1.13.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.9...v1.13.10

[1.13.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.8...v1.13.9

[1.13.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.7...v1.13.8

[1.13.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.6...v1.13.7

[1.13.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.5...v1.13.6

[1.13.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.4...v1.13.5

[1.13.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.3...v1.13.4

[1.13.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.2...v1.13.3

[1.13.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.1...v1.13.2

[1.13.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.0...v1.13.1

[1.13.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.14...v1.13.0

[1.12.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.13...v1.12.14

[1.12.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.12...v1.12.13

[1.12.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.11...v1.12.12

[1.12.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.10...v1.12.11

[1.12.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.9...v1.12.10

[1.12.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.8...v1.12.9

[1.12.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.7...v1.12.8

[1.12.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.6...v1.12.7

[1.12.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.5...v1.12.6

[1.12.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.4...v1.12.5

[1.12.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.3...v1.12.4

[1.12.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.2...v1.12.3

[1.12.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.1...v1.12.2

[1.12.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.0...v1.12.1

[1.12.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.5...v1.12.0

[1.11.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.4...v1.11.5

[1.11.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.3...v1.11.4

[1.11.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.2...v1.11.3

[1.11.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.1...v1.11.2

[1.11.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.0...v1.11.1

[1.11.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.3...v1.11.0

[1.10.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.2...v1.10.3

[1.10.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.1...v1.10.2

[1.10.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.0...v1.10.1

[1.10.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.9.0...v1.10.0

[1.9.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.1...v1.9.0

[1.8.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.0...v1.8.1

[1.8.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.3...v1.8.0

[1.7.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.2...v1.7.3

[1.7.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.1...v1.7.2

[1.7.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.0...v1.7.1

[1.7.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.6.0...v1.7.0

[1.6.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.5.0...v1.6.0

[1.5.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.1...v1.5.0

[1.4.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.0...v1.4.1

[1.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.9...v1.4.0

[1.3.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.8...v1.3.9

[1.3.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.7...v1.3.8

[1.3.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.6...v1.3.7

[1.3.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.5...v1.3.6

[1.3.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.4...v1.3.5

[1.3.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.3...v1.3.4

[1.3.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.2...v1.3.3

[1.3.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.1...v1.3.2

[1.3.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.0...v1.3.1

[1.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.1...v1.3.0

[1.2.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.0...v1.2.1

[1.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.9...v1.2.0

[1.1.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.8...v1.1.9

[1.1.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.7...v1.1.8

[1.1.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.6...v1.1.7

[1.1.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.5...v1.1.6

[1.1.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.4...v1.1.5

[1.1.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.3...v1.1.4

[1.1.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.2...v1.1.3

[1.1.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.1...v1.1.2

[1.1.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.0...v1.1.1

[1.1.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.11...v1.1.0

[1.0.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.10...v1.0.11

[1.0.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.9...v1.0.10

[1.0.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.8...v1.0.9

[1.0.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.7...v1.0.8

[1.0.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.6...v1.0.7

[1.0.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.5...v1.0.6

[1.0.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.4...v1.0.5

[1.0.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.3...v1.0.4

[1.0.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.2...v1.0.3

[1.0.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.1...v1.0.2

[1.0.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.0...v1.0.1

[1.0.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.4.0...v1.0.0

[0.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.3.0...v0.4.0

[0.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.2.0...v0.3.0

[0.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.1.4...v0.2.0

###
[`v1.37.0`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.36.3...v1.37.0)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.2...HEAD

[1.37.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.1...v1.37.2

[1.37.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.0...v1.37.1

[1.37.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.3...v1.37.0

[1.36.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.2...v1.36.3

[1.36.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.1...v1.36.2

[1.36.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.0...v1.36.1

[1.36.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.8...v1.36.0

[1.35.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.7...v1.35.8

[1.35.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.6...v1.35.7

[1.35.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.5...v1.35.6

[1.35.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.4...v1.35.5

[1.35.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...v1.35.4

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]: h

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xMzEuOSIsInVwZGF0ZWRJblZlciI6IjQxLjEzMS45IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 20:45:38 +00:00
Zanie Blue 6f7d0dc7b4
Emit a message on `cache clean` and `prune` when lock is held (#16138)
Closes https://github.com/astral-sh/uv/issues/16112

```
❯ cargo run -q cache clean
Cache is currently in-use, waiting for other uv processes to finish (use `--force` to override)
```

Otherwise, `-v` is required to see we're waiting on a lock.

I'd rather do this than elevate the exclusive lock log message to
something more verbose because this allows us to use a more specific
message as appropriate for the situation. We also previously reduced the
verbosity of arbitrary locks, e.g., see
https://github.com/astral-sh/uv/issues/7489
2025-10-06 16:49:24 +00:00
Zanie Blue 7e4edf0fb4
Add `--force` flag for `uv cache prune` (#16137)
Matching #15992 

See https://github.com/astral-sh/setup-uv/issues/588
2025-10-06 11:36:58 -05:00
konsti 3bed81866f
Remove unused version.rs (#16135)
This file does not have a `mod version;`, I assume it's a remnant of the
`uv version` -> `uv self version` transition.
2025-10-06 09:58:20 -05:00
renovate[bot] 68d6ad813e
Update pre-commit dependencies (#16127)
> [!NOTE]
> Mend has cancelled [the proposed
renaming](https://redirect.github.com/renovatebot/renovate/discussions/37842)
of the Renovate GitHub app being renamed to `mend[bot]`.
> 
> This notice will be removed on 2025-10-07.

<hr>

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit)
| repository | patch | `v0.13.2` -> `v0.13.3` |
| [crate-ci/typos](https://redirect.github.com/crate-ci/typos) |
repository | minor | `v1.36.3` -> `v1.37.2` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

<details>
<summary>astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit)</summary>

###
[`v0.13.3`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.13.3)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.13.2...v0.13.3)

See: <https://github.com/astral-sh/ruff/releases/tag/0.13.3>

</details>

<details>
<summary>crate-ci/typos (crate-ci/typos)</summary>

###
[`v1.37.2`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.37.1...v1.37.2)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.2...HEAD

[1.37.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.1...v1.37.2

[1.37.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.0...v1.37.1

[1.37.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.3...v1.37.0

[1.36.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.2...v1.36.3

[1.36.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.1...v1.36.2

[1.36.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.0...v1.36.1

[1.36.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.8...v1.36.0

[1.35.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.7...v1.35.8

[1.35.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.6...v1.35.7

[1.35.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.5...v1.35.6

[1.35.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.4...v1.35.5

[1.35.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...v1.35.4

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.25...v1.13.26

[1.13.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.24...v1.13.25

[1.13.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.23...v1.13.24

[1.13.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.22...v1.13.23

[1.13.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.21...v1.13.22

[1.13.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.20...v1.13.21

[1.13.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.19...v1.13.20

[1.13.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.18...v1.13.19

[1.13.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.17...v1.13.18

[1.13.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.16...v1.13.17

[1.13.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.15...v1.13.16

[1.13.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.14...v1.13.15

[1.13.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.13...v1.13.14

[1.13.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.12...v1.13.13

[1.13.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.11...v1.13.12

[1.13.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.10...v1.13.11

[1.13.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.9...v1.13.10

[1.13.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.8...v1.13.9

[1.13.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.7...v1.13.8

[1.13.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.6...v1.13.7

[1.13.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.5...v1.13.6

[1.13.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.4...v1.13.5

[1.13.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.3...v1.13.4

[1.13.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.2...v1.13.3

[1.13.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.1...v1.13.2

[1.13.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.0...v1.13.1

[1.13.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.14...v1.13.0

[1.12.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.13...v1.12.14

[1.12.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.12...v1.12.13

[1.12.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.11...v1.12.12

[1.12.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.10...v1.12.11

[1.12.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.9...v1.12.10

[1.12.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.8...v1.12.9

[1.12.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.7...v1.12.8

[1.12.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.6...v1.12.7

[1.12.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.5...v1.12.6

[1.12.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.4...v1.12.5

[1.12.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.3...v1.12.4

[1.12.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.2...v1.12.3

[1.12.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.1...v1.12.2

[1.12.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.0...v1.12.1

[1.12.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.5...v1.12.0

[1.11.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.4...v1.11.5

[1.11.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.3...v1.11.4

[1.11.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.2...v1.11.3

[1.11.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.1...v1.11.2

[1.11.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.0...v1.11.1

[1.11.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.3...v1.11.0

[1.10.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.2...v1.10.3

[1.10.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.1...v1.10.2

[1.10.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.0...v1.10.1

[1.10.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.9.0...v1.10.0

[1.9.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.1...v1.9.0

[1.8.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.0...v1.8.1

[1.8.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.3...v1.8.0

[1.7.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.2...v1.7.3

[1.7.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.1...v1.7.2

[1.7.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.0...v1.7.1

[1.7.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.6.0...v1.7.0

[1.6.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.5.0...v1.6.0

[1.5.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.1...v1.5.0

[1.4.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.0...v1.4.1

[1.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.9...v1.4.0

[1.3.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.8...v1.3.9

[1.3.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.7...v1.3.8

[1.3.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.6...v1.3.7

[1.3.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.5...v1.3.6

[1.3.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.4...v1.3.5

[1.3.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.3...v1.3.4

[1.3.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.2...v1.3.3

[1.3.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.1...v1.3.2

[1.3.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.0...v1.3.1

[1.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.1...v1.3.0

[1.2.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.0...v1.2.1

[1.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.9...v1.2.0

[1.1.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.8...v1.1.9

[1.1.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.7...v1.1.8

[1.1.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.6...v1.1.7

[1.1.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.5...v1.1.6

[1.1.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.4...v1.1.5

[1.1.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.3...v1.1.4

[1.1.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.2...v1.1.3

[1.1.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.1...v1.1.2

[1.1.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.0...v1.1.1

[1.1.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.11...v1.1.0

[1.0.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.10...v1.0.11

[1.0.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.9...v1.0.10

[1.0.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.8...v1.0.9

[1.0.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.7...v1.0.8

[1.0.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.6...v1.0.7

[1.0.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.5...v1.0.6

[1.0.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.4...v1.0.5

[1.0.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.3...v1.0.4

[1.0.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.2...v1.0.3

[1.0.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.1...v1.0.2

[1.0.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.0...v1.0.1

[1.0.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.4.0...v1.0.0

[0.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.3.0...v0.4.0

[0.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.2.0...v0.3.0

[0.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.1.4...v0.2.0

###
[`v1.37.1`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.37.0...v1.37.1)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.2...HEAD

[1.37.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.1...v1.37.2

[1.37.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.0...v1.37.1

[1.37.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.3...v1.37.0

[1.36.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.2...v1.36.3

[1.36.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.1...v1.36.2

[1.36.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.0...v1.36.1

[1.36.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.8...v1.36.0

[1.35.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.7...v1.35.8

[1.35.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.6...v1.35.7

[1.35.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.5...v1.35.6

[1.35.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.4...v1.35.5

[1.35.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...v1.35.4

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.25...v1.13.26

[1.13.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.24...v1.13.25

[1.13.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.23...v1.13.24

[1.13.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.22...v1.13.23

[1.13.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.21...v1.13.22

[1.13.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.20...v1.13.21

[1.13.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.19...v1.13.20

[1.13.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.18...v1.13.19

[1.13.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.17...v1.13.18

[1.13.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.16...v1.13.17

[1.13.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.15...v1.13.16

[1.13.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.14...v1.13.15

[1.13.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.13...v1.13.14

[1.13.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.12...v1.13.13

[1.13.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.11...v1.13.12

[1.13.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.10...v1.13.11

[1.13.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.9...v1.13.10

[1.13.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.8...v1.13.9

[1.13.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.7...v1.13.8

[1.13.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.6...v1.13.7

[1.13.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.5...v1.13.6

[1.13.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.4...v1.13.5

[1.13.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.3...v1.13.4

[1.13.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.2...v1.13.3

[1.13.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.1...v1.13.2

[1.13.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.0...v1.13.1

[1.13.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.14...v1.13.0

[1.12.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.13...v1.12.14

[1.12.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.12...v1.12.13

[1.12.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.11...v1.12.12

[1.12.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.10...v1.12.11

[1.12.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.9...v1.12.10

[1.12.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.8...v1.12.9

[1.12.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.7...v1.12.8

[1.12.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.6...v1.12.7

[1.12.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.5...v1.12.6

[1.12.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.4...v1.12.5

[1.12.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.3...v1.12.4

[1.12.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.2...v1.12.3

[1.12.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.1...v1.12.2

[1.12.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.0...v1.12.1

[1.12.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.5...v1.12.0

[1.11.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.4...v1.11.5

[1.11.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.3...v1.11.4

[1.11.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.2...v1.11.3

[1.11.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.1...v1.11.2

[1.11.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.0...v1.11.1

[1.11.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.3...v1.11.0

[1.10.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.2...v1.10.3

[1.10.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.1...v1.10.2

[1.10.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.0...v1.10.1

[1.10.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.9.0...v1.10.0

[1.9.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.1...v1.9.0

[1.8.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.0...v1.8.1

[1.8.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.3...v1.8.0

[1.7.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.2...v1.7.3

[1.7.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.1...v1.7.2

[1.7.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.0...v1.7.1

[1.7.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.6.0...v1.7.0

[1.6.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.5.0...v1.6.0

[1.5.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.1...v1.5.0

[1.4.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.0...v1.4.1

[1.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.9...v1.4.0

[1.3.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.8...v1.3.9

[1.3.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.7...v1.3.8

[1.3.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.6...v1.3.7

[1.3.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.5...v1.3.6

[1.3.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.4...v1.3.5

[1.3.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.3...v1.3.4

[1.3.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.2...v1.3.3

[1.3.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.1...v1.3.2

[1.3.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.0...v1.3.1

[1.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.1...v1.3.0

[1.2.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.0...v1.2.1

[1.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.9...v1.2.0

[1.1.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.8...v1.1.9

[1.1.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.7...v1.1.8

[1.1.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.6...v1.1.7

[1.1.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.5...v1.1.6

[1.1.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.4...v1.1.5

[1.1.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.3...v1.1.4

[1.1.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.2...v1.1.3

[1.1.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.1...v1.1.2

[1.1.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.0...v1.1.1

[1.1.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.11...v1.1.0

[1.0.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.10...v1.0.11

[1.0.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.9...v1.0.10

[1.0.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.8...v1.0.9

[1.0.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.7...v1.0.8

[1.0.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.6...v1.0.7

[1.0.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.5...v1.0.6

[1.0.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.4...v1.0.5

[1.0.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.3...v1.0.4

[1.0.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.2...v1.0.3

[1.0.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.1...v1.0.2

[1.0.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.0...v1.0.1

[1.0.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.4.0...v1.0.0

[0.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.3.0...v0.4.0

[0.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.2.0...v0.3.0

[0.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.1.4...v0.2.0

###
[`v1.37.0`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.36.3...v1.37.0)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.2...HEAD

[1.37.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.1...v1.37.2

[1.37.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.37.0...v1.37.1

[1.37.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.3...v1.37.0

[1.36.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.2...v1.36.3

[1.36.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.1...v1.36.2

[1.36.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.0...v1.36.1

[1.36.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.8...v1.36.0

[1.35.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.7...v1.35.8

[1.35.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.6...v1.35.7

[1.35.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.5...v1.35.6

[1.35.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.4...v1.35.5

[1.35.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...v1.35.4

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]: https://redirec

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xMzEuOSIsInVwZGF0ZWRJblZlciI6IjQxLjEzMS45IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 11:21:22 +02:00
Kevin Neal 525ea78399
doc: fix example of bumping beta version without patch bump (#16132)
<!--
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?
-->
Fixes #16131

## Summary

Fix error in example documentation for bumping version.

## Test Plan

I viewed the markdown on the branch in my fork. Looked good.
2025-10-06 11:16:01 +02:00
renovate[bot] 9ec385a266
Update astral-sh/setup-uv action to v6.8.0 (#16129)
> [!NOTE]
> Mend has cancelled [the proposed
renaming](https://redirect.github.com/renovatebot/renovate/discussions/37842)
of the Renovate GitHub app being renamed to `mend[bot]`.
> 
> This notice will be removed on 2025-10-07.

<hr>

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [astral-sh/setup-uv](https://redirect.github.com/astral-sh/setup-uv) |
action | minor | `v6.7.0` -> `v6.8.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

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

###
[`v6.8.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v6.8.0):
🌈 Add **/*.py.lock to cache-dependency-glob

[Compare
Source](https://redirect.github.com/astral-sh/setup-uv/compare/v6.7.0...v6.8.0)

#### Changes

Thanks to [@&#8203;parched](https://redirect.github.com/parched) the
default `cache-dependency-glob` now also find all lock files generated
by `uv lock --script`

#### 🚀 Enhancements

- Always show prune cache output
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;597](https://redirect.github.com/astral-sh/setup-uv/issues/597))
- Add \*\*/\*.py.lock to cache-dependency-glob
[@&#8203;parched](https://redirect.github.com/parched)
([#&#8203;590](https://redirect.github.com/astral-sh/setup-uv/issues/590))

#### 🧰 Maintenance

- persist credentials for version update
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;584](https://redirect.github.com/astral-sh/setup-uv/issues/584))

#### 📚 Documentation

- README.md: Fix Python versions and update checkout action
[@&#8203;cclauss](https://redirect.github.com/cclauss)
([#&#8203;572](https://redirect.github.com/astral-sh/setup-uv/issues/572))

#### ⬆️ Dependency updates

- Bump zizmorcore/zizmor-action from 0.1.2 to 0.2.0
@&#8203;[dependabot\[bot\]](https://redirect.github.com/apps/dependabot)
([#&#8203;571](https://redirect.github.com/astral-sh/setup-uv/issues/571))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xMzEuOSIsInVwZGF0ZWRJblZlciI6IjQxLjEzMS45IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 09:15:10 +02:00
Zanie Blue 00d3aa3780
Bump version to 0.8.23 (#16119) 2025-10-04 12:46:01 -05:00
Takayuki Maeda 34ec30563e
Move `TRACING_DURATIONS_FILE` to `EnvironmentOptions` (#16109)
<!--
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? -->

Fixes a part of #14720

Add `TRACING_DURATIONS_FILE` to EnvironmentOptions.

## Test Plan

<!-- How was it tested? -->
2025-10-04 00:09:55 -05:00
Zanie Blue 241ad88051
Confirm that the directory name is a valid Python install key during managed check (#16080)
Closes https://github.com/astral-sh/uv/issues/16077

```
❯ UV_PYTHON_INSTALL_DIR=/opt uv python list --no-managed-python
cpython-3.9.6-macos-aarch64-none    /usr/bin/python3
❯ alias uv=$(pwd)/target/debug/uv
❯ UV_PYTHON_INSTALL_DIR=/opt uv python list --no-managed-python
cpython-3.13.3-macos-aarch64-none     /opt/homebrew/bin/python3.13 -> ../Cellar/python@3.13/3.13.3/bin/python3.13
cpython-3.13.3-macos-aarch64-none     /opt/homebrew/bin/python3 -> ../Cellar/python@3.13/3.13.3/bin/python3
cpython-3.12.10-macos-aarch64-none    /opt/homebrew/bin/python3.12 -> ../Cellar/python@3.12/3.12.10/bin/python3.12
cpython-3.11.12-macos-aarch64-none    /opt/homebrew/bin/python3.11 -> ../Cellar/python@3.11/3.11.12/bin/python3.11
cpython-3.9.6-macos-aarch64-none      /usr/bin/python3
pypy-3.10.14-macos-aarch64-none       /opt/homebrew/bin/pypy3 -> ../Cellar/pypy3.10/7.3.17_1/bin/pypy3
```

Prevents false positives when the `UV_PYTHON_INSTALL_DIR` is a prefix of
some other path with Python installations
2025-10-03 19:36:08 +00:00
Aria Desires b06f02f13d
Fix typo in `_CONDA_ROOT` docs (#16114)
All other references are correct, just slipped through in the actual
docs.
2025-10-03 10:38:00 -05:00
konsti 1bdb096599
Document transparent x86_64 emulation on aarch64 (#16041) 2025-10-02 18:31:33 +02:00
Charlie Marsh 8da9df3654
Avoid rejecting already-installed URL distributions with `--no-sources` (#16094)
## Summary

This PR removes a guard that was accidentally included in
https://github.com/astral-sh/uv/pull/15234/files#diff-6be6d80fe4821c47b70a372260f55e73b8da8182b8dcad7525d5cd3eb584532b.
I meant to remove that logic before merging.

Closes https://github.com/astral-sh/uv/issues/16068.
2025-10-02 09:32:14 -04:00
konsti 0bd3dfd5ea
Update slab to v0.4.11 (#16099)
A security scanner noted that v0.4.10 has a security issue.
2025-10-02 09:03:54 +00:00
konsti 553f97f767
Fix renovate typo (#16098)
Ref https://github.com/astral-sh/uv/issues/16097

In my test fork, both the version with and without the typo worked in
grouping pubgrub and version-ranges.
2025-10-02 08:44:36 +00:00
konsti ee697a2178
Fix pubgrub grouping in renovate (#16092)
`packageName` was wrong, we need `depName`
2025-10-02 07:40:25 +00:00
Charlie Marsh c2100d11f3
Make cache control lookups robust to username (#16088)
## Summary

We serialize the index to the lockfile without the username, so if we
compare based on `==` and the user _includes_ the username in their
`pyproject.toml`, the check will always fail.

Closes https://github.com/astral-sh/uv/issues/16076.
2025-10-01 16:57:50 -04:00
konsti d483b02e61
Build s390x on stable again (#16082)
Fixed by
https://github.com/rust-lang/rust/issues/141287#issuecomment-3354275747
2025-10-01 22:38:27 +02:00
liam d51a1e7456
Deduplicate marker-specific dependencies in `uv pip tree` output (#16078)
Resolves https://github.com/astral-sh/uv/issues/16067

When we build the dependency graph we add an edge per `Requires-Dist`.
If a package publishes multiple marker-guarded requirements (like
pylint’s `dill>=…` for different Python versions), more than one marker
can evaluate to true at runtime, which gives us several edges pointing
to the same installed package node.

To avoid printing the package multiple times, we gather all edges
targeting that node, pass them through a `Cursor`, and aggregate their
requirement details before we print the dependency line. That
aggregation does two things:
  1. If any edge carries a URL, we return that URL immediately.
2. Otherwise we merge all version specifiers into one canonical PEP 440
string.

I've added an integration test, namely `cargo test -p uv --test it
--features pypi -- no_duplicate_dependencies_with_markers` exercises the
new snapshot that installs pylint (with the real dill duplication
scenario present in the original issue) and asserts the tree output,
both with and without `--show-version-specifiers`, now shows a single
dill entry with the merged constraint.
2025-10-01 11:01:41 -05:00
Zanie Blue 8b86bd530e
Remove tracking of inferred dependency conflicts (#15909)
Alternative to #15884 (see commentary there)

Closes https://github.com/astral-sh/uv/issues/15869
2025-10-01 10:03:42 -05:00
Charlie Marsh ab2f394019
Use a global flags instance for wheel check (#16047)
## Summary

This stands up the idea proposed in
https://github.com/astral-sh/uv/pull/16046/files#r2384395797.
2025-09-30 00:10:11 +00:00
Charlie Marsh 7d9ea797b0
Add `UV_SKIP_WHEEL_FILENAME_CHECK` to allow installing invalid wheels (#16046)
## Summary

This PR adds a user setting to allow (in rare cases) accepting wheels
with mismatched filenames and internal metadata.

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

Closes https://github.com/astral-sh/uv/issues/15647.
2025-09-29 19:54:25 -04:00
Charlie Marsh 170ab1cd7f
Ignore origin when comparing installed tools (#16055)
## Summary

This field gets dropped when you serialize and deserialize, so we should
ignore it when comparing indexes.

Closes https://github.com/astral-sh/uv/issues/16051.
2025-09-29 17:23:18 +00:00
konsti fd908aa439
Windows arm64 and Linux RISC-V64 are Tier 2 supported (#16027)
Windows arm64 and Linux RISC-V64 are supported. Windows arm64 is special
because you can also use the x86_64 stack, which may even be a better
experience.
2025-09-29 17:54:04 +02:00
renovate[bot] ddb826cfaa
Update pre-commit dependencies (#16059)
Coming soon: The Renovate bot (GitHub App) will be renamed to Mend. PRs
from Renovate will soon appear from 'Mend'. Learn more
[here](https://redirect.github.com/renovatebot/renovate/discussions/37842).

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit)
| repository | patch | `v0.13.1` -> `v0.13.2` |
| [crate-ci/typos](https://redirect.github.com/crate-ci/typos) |
repository | patch | `v1.36.2` -> `v1.36.3` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

<details>
<summary>astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit)</summary>

###
[`v0.13.2`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.13.2)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.13.1...v0.13.2)

See: <https://github.com/astral-sh/ruff/releases/tag/0.13.2>

</details>

<details>
<summary>crate-ci/typos (crate-ci/typos)</summary>

###
[`v1.36.3`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.36.2...v1.36.3)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.3...HEAD

[1.36.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.2...v1.36.3

[1.36.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.1...v1.36.2

[1.36.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.0...v1.36.1

[1.36.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.8...v1.36.0

[1.35.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.7...v1.35.8

[1.35.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.6...v1.35.7

[1.35.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.5...v1.35.6

[1.35.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.4...v1.35.5

[1.35.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...v1.35.4

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.25...v1.13.26

[1.13.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.24...v1.13.25

[1.13.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.23...v1.13.24

[1.13.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.22...v1.13.23

[1.13.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.21...v1.13.22

[1.13.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.20...v1.13.21

[1.13.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.19...v1.13.20

[1.13.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.18...v1.13.19

[1.13.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.17...v1.13.18

[1.13.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.16...v1.13.17

[1.13.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.15...v1.13.16

[1.13.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.14...v1.13.15

[1.13.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.13...v1.13.14

[1.13.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.12...v1.13.13

[1.13.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.11...v1.13.12

[1.13.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.10...v1.13.11

[1.13.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.9...v1.13.10

[1.13.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.8...v1.13.9

[1.13.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.7...v1.13.8

[1.13.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.6...v1.13.7

[1.13.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.5...v1.13.6

[1.13.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.4...v1.13.5

[1.13.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.3...v1.13.4

[1.13.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.2...v1.13.3

[1.13.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.1...v1.13.2

[1.13.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.0...v1.13.1

[1.13.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.14...v1.13.0

[1.12.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.13...v1.12.14

[1.12.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.12...v1.12.13

[1.12.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.11...v1.12.12

[1.12.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.10...v1.12.11

[1.12.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.9...v1.12.10

[1.12.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.8...v1.12.9

[1.12.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.7...v1.12.8

[1.12.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.6...v1.12.7

[1.12.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.5...v1.12.6

[1.12.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.4...v1.12.5

[1.12.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.3...v1.12.4

[1.12.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.2...v1.12.3

[1.12.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.1...v1.12.2

[1.12.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.0...v1.12.1

[1.12.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.5...v1.12.0

[1.11.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.4...v1.11.5

[1.11.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.3...v1.11.4

[1.11.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.2...v1.11.3

[1.11.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.1...v1.11.2

[1.11.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.0...v1.11.1

[1.11.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.3...v1.11.0

[1.10.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.2...v1.10.3

[1.10.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.1...v1.10.2

[1.10.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.0...v1.10.1

[1.10.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.9.0...v1.10.0

[1.9.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.1...v1.9.0

[1.8.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.0...v1.8.1

[1.8.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.3...v1.8.0

[1.7.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.2...v1.7.3

[1.7.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.1...v1.7.2

[1.7.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.0...v1.7.1

[1.7.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.6.0...v1.7.0

[1.6.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.5.0...v1.6.0

[1.5.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.1...v1.5.0

[1.4.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.0...v1.4.1

[1.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.9...v1.4.0

[1.3.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.8...v1.3.9

[1.3.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.7...v1.3.8

[1.3.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.6...v1.3.7

[1.3.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.5...v1.3.6

[1.3.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.4...v1.3.5

[1.3.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.3...v1.3.4

[1.3.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.2...v1.3.3

[1.3.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.1...v1.3.2

[1.3.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.0...v1.3.1

[1.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.1...v1.3.0

[1.2.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.0...v1.2.1

[1.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.9...v1.2.0

[1.1.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.8...v1.1.9

[1.1.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.7...v1.1.8

[1.1.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.6...v1.1.7

[1.1.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.5...v1.1.6

[1.1.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.4...v1.1.5

[1.1.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.3...v1.1.4

[1.1.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.2...v1.1.3

[1.1.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.1...v1.1.2

[1.1.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.0...v1.1.1

[1.1.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.11...v1.1.0

[1.0.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.10...v1.0.11

[1.0.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.9...v1.0.10

[1.0.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.8...v1.0.9

[1.0.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.7...v1.0.8

[1.0.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.6...v1.0.7

[1.0.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.5...v1.0.6

[1.0.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.4...v1.0.5

[1.0.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.3...v1.0.4

[1.0.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.2...v1.0.3

[1.0.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.1...v1.0.2

[1.0.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.0...v1.0.1

[1.0.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.4.0...v1.0.0

[0.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.3.0...v0.4.0

[0.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.2.0...v0.3.0

[0.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.1.4...v0.2.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xMzEuOSIsInVwZGF0ZWRJblZlciI6IjQxLjEzMS45IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 08:43:45 -05:00
renovate[bot] 416e25c71c
Update crate-ci/typos action to v1.36.3 (#16058)
Coming soon: The Renovate bot (GitHub App) will be renamed to Mend. PRs
from Renovate will soon appear from 'Mend'. Learn more
[here](https://redirect.github.com/renovatebot/renovate/discussions/37842).

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [crate-ci/typos](https://redirect.github.com/crate-ci/typos) | action
| patch | `v1.36.2` -> `v1.36.3` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>crate-ci/typos (crate-ci/typos)</summary>

###
[`v1.36.3`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.36.2...v1.36.3)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.3...HEAD

[1.36.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.2...v1.36.3

[1.36.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.1...v1.36.2

[1.36.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.36.0...v1.36.1

[1.36.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.8...v1.36.0

[1.35.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.7...v1.35.8

[1.35.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.6...v1.35.7

[1.35.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.5...v1.35.6

[1.35.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.4...v1.35.5

[1.35.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...v1.35.4

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.25...v1.13.26

[1.13.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.24...v1.13.25

[1.13.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.23...v1.13.24

[1.13.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.22...v1.13.23

[1.13.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.21...v1.13.22

[1.13.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.20...v1.13.21

[1.13.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.19...v1.13.20

[1.13.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.18...v1.13.19

[1.13.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.17...v1.13.18

[1.13.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.16...v1.13.17

[1.13.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.15...v1.13.16

[1.13.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.14...v1.13.15

[1.13.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.13...v1.13.14

[1.13.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.12...v1.13.13

[1.13.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.11...v1.13.12

[1.13.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.10...v1.13.11

[1.13.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.9...v1.13.10

[1.13.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.8...v1.13.9

[1.13.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.7...v1.13.8

[1.13.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.6...v1.13.7

[1.13.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.5...v1.13.6

[1.13.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.4...v1.13.5

[1.13.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.3...v1.13.4

[1.13.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.2...v1.13.3

[1.13.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.1...v1.13.2

[1.13.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.0...v1.13.1

[1.13.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.14...v1.13.0

[1.12.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.13...v1.12.14

[1.12.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.12...v1.12.13

[1.12.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.11...v1.12.12

[1.12.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.10...v1.12.11

[1.12.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.9...v1.12.10

[1.12.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.8...v1.12.9

[1.12.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.7...v1.12.8

[1.12.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.6...v1.12.7

[1.12.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.5...v1.12.6

[1.12.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.4...v1.12.5

[1.12.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.3...v1.12.4

[1.12.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.2...v1.12.3

[1.12.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.1...v1.12.2

[1.12.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.0...v1.12.1

[1.12.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.5...v1.12.0

[1.11.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.4...v1.11.5

[1.11.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.3...v1.11.4

[1.11.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.2...v1.11.3

[1.11.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.1...v1.11.2

[1.11.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.0...v1.11.1

[1.11.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.3...v1.11.0

[1.10.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.2...v1.10.3

[1.10.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.1...v1.10.2

[1.10.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.0...v1.10.1

[1.10.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.9.0...v1.10.0

[1.9.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.1...v1.9.0

[1.8.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.0...v1.8.1

[1.8.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.3...v1.8.0

[1.7.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.2...v1.7.3

[1.7.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.1...v1.7.2

[1.7.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.0...v1.7.1

[1.7.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.6.0...v1.7.0

[1.6.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.5.0...v1.6.0

[1.5.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.1...v1.5.0

[1.4.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.0...v1.4.1

[1.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.9...v1.4.0

[1.3.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.8...v1.3.9

[1.3.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.7...v1.3.8

[1.3.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.6...v1.3.7

[1.3.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.5...v1.3.6

[1.3.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.4...v1.3.5

[1.3.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.3...v1.3.4

[1.3.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.2...v1.3.3

[1.3.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.1...v1.3.2

[1.3.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.0...v1.3.1

[1.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.1...v1.3.0

[1.2.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.0...v1.2.1

[1.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.9...v1.2.0

[1.1.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.8...v1.1.9

[1.1.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.7...v1.1.8

[1.1.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.6...v1.1.7

[1.1.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.5...v1.1.6

[1.1.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.4...v1.1.5

[1.1.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.3...v1.1.4

[1.1.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.2...v1.1.3

[1.1.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.1...v1.1.2

[1.1.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.0...v1.1.1

[1.1.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.11...v1.1.0

[1.0.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.10...v1.0.11

[1.0.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.9...v1.0.10

[1.0.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.8...v1.0.9

[1.0.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.7...v1.0.8

[1.0.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.6...v1.0.7

[1.0.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.5...v1.0.6

[1.0.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.4...v1.0.5

[1.0.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.3...v1.0.4

[1.0.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.2...v1.0.3

[1.0.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.1...v1.0.2

[1.0.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.0...v1.0.1

[1.0.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.4.0...v1.0.0

[0.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.3.0...v0.4.0

[0.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.2.0...v0.3.0

[0.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.1.4...v0.2.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xMzEuOSIsInVwZGF0ZWRJblZlciI6IjQxLjEzMS45IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 08:43:34 -05:00
konsti b6fbf66855
Group pubgrub/version-ranges updates (#16062)
To help with https://github.com/astral-sh/uv/pull/16056
2025-09-29 08:42:41 -05:00
Charlie Marsh e87236391f
Re-order lock validation checks by severity (#16045)
## Summary

I don't think it's common for this to matter, but in theory at least
it's important that these are ordered by severity. Otherwise, e.g,
changing the pre-release mode (and then returning early) could mean we
retain the forks when we otherwise shouldn't.
2025-09-29 09:42:21 -04:00
Charlie Marsh b2cc2c2749
Respect `--no-color` on the CLI (#16044)
## Summary

This argument has always existed for compatibility, but apparently we
don't respect it. I assume it was unintentionally dropped during a
refactor.

Closes https://github.com/astral-sh/uv/issues/15950.
2025-09-29 09:38:47 -04:00
Andrei Berenda 1d76c5a365
Add UV_LOG_CONTEXT to EnvironmentOptions (#16031)
## Summary
Add UV_LOG_CONTEXT to EnvironmentOptions
Relates https://github.com/astral-sh/uv/issues/14720

## Test Plan

Tests with existing tests
2025-09-25 21:01:29 +00:00
Andrei Berenda 372283c0cf
Add install_mirrors to EnvironmentOptions (#15937)
## Summary

Add install_mirrors to EnvironmentOptions
Relates #14720

## Test Plan

Tests with existing tests
2025-09-25 10:35:09 -05:00
Zanie Blue 15975b00ea
Upgrade rooster to v0.1.0 (#16025) 2025-09-25 09:11:04 -05:00
Charles 1c68977399
feat:add version tag annotation (pin #15980) (#16022)
## Summary

Clarifies the inline annotation for the pinned
`aws-actions/configure-aws-credentials` action. Minor changes, but it's
a pleasure to contribute to a rust project !

## Test Plan

Minor doc change only. Verified by running:

cargo nextest run    
cargo run python install
2025-09-25 08:25:27 -05:00
konsti 537d1333bd
Document why we ban URLs from index dependencies (#15929)
Document why we ban URLs from index dependencies, and also what the
lookahead resolver does.
2025-09-25 12:18:10 +02:00
Zanie Blue ade2bdbd2a
Bump version to 0.8.22 (#16005) 2025-09-23 14:43:48 -05:00
William Woodruff 92cd9cfb0c
deps: bump astral-tokio-tar to 0.5.5 (#16004) 2025-09-23 13:46:08 -04:00
github-actions[bot] 268f1325ba
Upgrade Pyodide to 0.28.3 (#15999)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-09-23 09:57:15 -05:00
Charlie Marsh f64da27450
Bump version to v0.8.21 (#16001) 2025-09-23 13:55:19 +00:00
renovate[bot] 9af64cc156
Update Rust crate anyhow to v1.0.100 (#15974)
Coming soon: The Renovate bot (GitHub App) will be renamed to Mend. PRs
from Renovate will soon appear from 'Mend'. Learn more
[here](https://redirect.github.com/renovatebot/renovate/discussions/37842).

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [anyhow](https://redirect.github.com/dtolnay/anyhow) |
workspace.dependencies | patch | `1.0.99` -> `1.0.100` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

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

###
[`v1.0.100`](https://redirect.github.com/dtolnay/anyhow/releases/tag/1.0.100)

[Compare
Source](https://redirect.github.com/dtolnay/anyhow/compare/1.0.99...1.0.100)

- Teach clippy to lint formatting arguments in `bail!`, `ensure!`,
`anyhow!`
([#&#8203;426](https://redirect.github.com/dtolnay/anyhow/issues/426))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: konstin <konstin@mailbox.org>
2025-09-23 11:49:23 +00:00
Charlie Marsh 8d6b369274
Refresh lockfile when `--refresh` is provided (#15991) (#15994)
## Summary

If you provide `--refresh` to `uv lock`, we'll now always resolve (even
though it might return the same result). This is also robust to
`--locked` such that `--refresh --locked` will only fail if the lockfile
changes.

Closes https://github.com/astral-sh/uv/issues/15997.
2025-09-23 07:25:13 -04:00
Charlie Marsh 7f7fac812c
Add S3 request signing (#15925)
## Summary

This PR enables users to mark a URL as an S3 endpoint, at which point uv
will sign requests to that URL by detecting credentials from the
standard AWS environment variables, configuration files, etc.

Signing is handled by the
[reqsign](https://docs.rs/reqsign/latest/reqsign/) crate, which we can
also use in the future to sign requests for other providers.
2025-09-22 23:59:52 +00:00
Zanie Blue 3e6fd0b775
Bump version to 0.8.20 (#15998) 2025-09-22 22:34:27 +00:00
Zanie Blue 107d4e0ac7
Add `--force` flag for `uv cache clean` (#15992)
Follows https://github.com/astral-sh/uv/pull/15990 to address concerns
there.
2025-09-22 22:15:14 +00:00
Zanie Blue c21b11edb9
Revert "Refresh lockfile when `--refresh` is provided (#15991)" (#15993)
This reverts commit aeb7ee056e from
https://github.com/astral-sh/uv/pull/15991 since it will break `uv lock
--locked --refresh` otherwise.
2025-09-22 21:13:23 +00:00
Charlie Marsh aeb7ee056e
Refresh lockfile when `--refresh` is provided (#15991)
## Summary

If you provide `--refresh` to `uv lock`, we'll now always resolve (even
though it might return the same result).
2025-09-22 15:58:20 -05:00
Zanie Blue 7697fa6740
Expand the contribution docs on issue selection (#15966) 2025-09-22 15:57:49 -05:00
Zanie Blue a502464f82
Retain the cache lock and temporary caches during `uv run` and `uvx` (#15990)
We're seeing reports of a regression from
https://github.com/astral-sh/uv/pull/15888 where `--no-cache` causes `uv
run` and `uvx` to fail to spawn a command.

The intent of this code was to allow destructive cache operations
_after_ we'd finished setting up the environment. However, it's unclear
to me that it's safe to run `uv cache clean` during a `uv run` operation
(e.g., `uv run --script` uses an environment in the cache) and, more
importantly, we cannot drop non-persistent caches (e.g., from
`--no-cache`) as they include the environment we're spawning the command
in.

Alternative to #15977 which retains release of the lock — we may want to
consider that approach still but this regression needs to be resolved
quickly.

Closes https://github.com/astral-sh/uv/issues/15989
Closes https://github.com/astral-sh/uv/issues/15987
Closes https://github.com/astral-sh/uv/issues/15967
2025-09-22 15:41:07 -05:00
Zanie Blue 1224f65b13
Hide freethreaded+debug Python downloads in `uv python list` (#15985)
ref https://github.com/astral-sh/uv/issues/15983#issuecomment-3319579833
2025-09-22 12:24:10 -05:00
samypr100 6263d1008d
Re-upstream to dist to axodotdev/cargo-dist (#15798)
## Summary

Both https://github.com/axodotdev/cargo-dist/releases/tag/v0.29.0 and
https://github.com/axodotdev/cargo-dist/releases/tag/v0.30.0 seem to
contain all relevant changes from
https://github.com/astral-sh/cargo-dist

## Test Plan

Not tested yet given I'd be running from a fork, but a dry-run run from
astral-sh/uv would be great 😅
2025-09-22 13:20:06 -04:00
Zsolt Dollenstein 46bf420eae
Allow upgrading prerelease versions of the same minor Python version (#15959)
Turns out if the minor versions matched we returned false from
`is_upgrade_of` instead of continuing to compare prerelease versions.

Closes #15955.

Note: test cases were initially generated by Claude - I tried making
them shorter.

## Test plan

```
❯ cargo run -- -v python upgrade 3.14
[...]
DEBUG Inspecting existing executable at `/Users/zsol/.local/bin/python3.14`
DEBUG Replacing existing executable for `cpython-3.14.0rc2-macos-aarch64-none` at `/Users/zsol/.local/bin/python3.14` with executable for `cpython-3.14.0rc3-macos-aarch64-none` since it is an upgrade
DEBUG Updated executable at `/Users/zsol/.local/bin/python3.14` to cpython-3.14.0rc3-macos-aarch64-none
Installed Python 3.14.0rc3 in 5.04s
 + cpython-3.14.0rc3-macos-aarch64-none (python3.14)
[...]
❯ uvx python3.14 -V
Python 3.14.0rc3
```
2025-09-22 16:59:48 +00:00
Zanie Blue 022a8f1dd1
Add test coverage for `python_upgrade` with a pre-release version (#15982)
Coverage for https://github.com/astral-sh/uv/pull/15959
2025-09-22 14:26:13 +00:00
Zanie Blue 9cabf63cd0
Add `package` level conflicts to the conflicting dependencies docs (#15963) 2025-09-22 09:07:07 -05:00
Zanie Blue 0edc5677ad
Document support for free-threaded and debug Python versions (#15961)
Closes #12707

---------

Co-authored-by: Geoffrey Thomas <geofft@ldpreload.com>
2025-09-22 13:59:40 +00:00
Zanie Blue e9987b3b57
Tweak title for viewing version in project guide (#15964) 2025-09-22 08:56:06 -05:00
renovate[bot] d8c5f3738c
Update pre-commit hook astral-sh/ruff-pre-commit to v0.13.1 (#15973)
Coming soon: The Renovate bot (GitHub App) will be renamed to Mend. PRs
from Renovate will soon appear from 'Mend'. Learn more
[here](https://redirect.github.com/renovatebot/renovate/discussions/37842).

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit)
| repository | patch | `v0.13.0` -> `v0.13.1` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

<details>
<summary>astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit)</summary>

###
[`v0.13.1`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.13.1)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.13.0...v0.13.1)

See: <https://github.com/astral-sh/ruff/releases/tag/0.13.1>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 08:29:43 -05:00
renovate[bot] 39f1c2770c
Update Rust crate clap to v4.5.48 (#15976)
Coming soon: The Renovate bot (GitHub App) will be renamed to Mend. PRs
from Renovate will soon appear from 'Mend'. Learn more
[here](https://redirect.github.com/renovatebot/renovate/discussions/37842).

This PR contains the following updates:

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

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

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

###
[`v4.5.48`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#4548---2025-09-19)

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

##### Documentation

- Add a new CLI Concepts document as another way of framing clap
- Expand the `typed_derive` cookbook entry

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 08:26:19 -05:00
renovate[bot] 5dd9a104b3
Update Rust crate cargo-util to v0.2.23 (#15975)
Coming soon: The Renovate bot (GitHub App) will be renamed to Mend. PRs
from Renovate will soon appear from 'Mend'. Learn more
[here](https://redirect.github.com/renovatebot/renovate/discussions/37842).

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [cargo-util](https://redirect.github.com/rust-lang/cargo) |
workspace.dependencies | patch | `0.2.22` -> `0.2.23` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 08:26:03 -05:00
konsti a6daab422f
Add incompatibility from proxy to base package (#15200)
Add an incompatibility that lets pubgrub skip of marker packages when
the base package already has an incompatible version to improve the
error messages (https://github.com/astral-sh/uv/issues/15199).

The change is also a small perf improvement. Overall this should be able
to improve performance in slow cases by avoiding trying proxy package
versions that are impossible anyway, for a (ideally very small cost) for
tracking the additional incompatibility and tracking the base package
for each proxy package.

```
$ hhyperfine --warmup 2 "uv pip compile --universal scripts/requirements/airflow.in" "target/release/uv pip compile --universal scripts/requirements/airflow.in"
Benchmark 1: uv pip compile --universal scripts/requirements/airflow.in
  Time (mean ± σ):     145.5 ms ±   3.9 ms    [User: 154.7 ms, System: 140.7 ms]
  Range (min … max):   139.2 ms … 153.4 ms    20 runs
 
Benchmark 2: target/release/uv pip compile --universal scripts/requirements/airflow.in
  Time (mean ± σ):     128.7 ms ±   5.5 ms    [User: 141.9 ms, System: 137.3 ms]
  Range (min … max):   121.8 ms … 142.0 ms    23 runs
 
Summary
  target/release/uv pip compile --universal scripts/requirements/airflow.in ran
    1.13 ± 0.06 times faster than uv pip compile --universal scripts/requirements/airflow.in
```

This implementation is the basic version: When we see a proxy
`foo{...}>=x,<y` we add a dependency edge `foo{...}>=x,<y` ->
`foo>=x,<y`. There are several way to extend this, which likely help
more with performance than with error messages.

One idea is that if we see `foo{...}>=x,<y` but we already made a
selection for `foo==z` outside that range, we can insert a dependency
`foo{...}!=z` -> `foo!=z`. This avoids trying any version of the proxy
package except the version that matches our previous selection.

Another is that if we see a dependency `foo>=x,<y`, we also add
`foo{...}>=x,y` -> `foo>=x,<y`. This allows backtracking beyond `foo`
immediately if all version of `foo{...}>=x,<y` are incompatible, since
`foo{...}>=x,<y` incompatible -> `foo>=x,<y` incompatible -> the package
that depended of `foo>=x,<y` is incompatible.

The cost for each of these operations is tracking an additional
incompatibility per virtual package. An alternative approach is to only
add the incompatibility lazily, only when we've tried several version of
the virtual package already. This needs to be weighed of with the better
error messages that the incompatibility gives, we unfortunately have
only few large reference examples.

Requires https://github.com/astral-sh/pubgrub/pull/45

Closes https://github.com/astral-sh/uv/issues/15199
2025-09-22 13:26:08 +02:00
Zanie Blue 1d7d7fdf00
Document pyodide support (#15962) 2025-09-20 13:11:44 -05:00
github-actions[bot] 3979c59726
Sync latest Python releases (#15958)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-09-20 02:25:21 +00:00
Zanie Blue fc7c2f8b50
Bump version to 0.8.19 (#15953) 2025-09-19 14:34:41 -05:00
github-actions[bot] 974a83b676
Sync latest Python releases (#15940)
Add Python 3.13.0rc3 (and some comments in .python-version).

Co-authored-by: Geoffrey Thomas <geofft@ldpreload.com>
2025-09-19 18:13:33 +00:00
konsti 00aa2ab672
Make `uv cache clean` parallel process safe (#15888)
Currently, `uv cache clean` and `uv cache prune` can cause crashes in
other uv processes running in parallel by removing their in-use files.

We can solve this by using a shared (read) lock on the cache directory,
while the `uv cache` operations use an exclusive (write) lock. The
drawback is that this is always one extra lock, and that we assume that
all platforms support shared locks.

Once Rust 1.89 fulfills our N-2 policy, we can add support for these
methods in fs_err and switch to
https://doc.rust-lang.org/std/fs/struct.File.html#platform-specific-behavior-2.

**Test Plan**

Open one terminal, run:

```
uv venv -c -p 3.13
UV_CACHE_DIR=cache uv cache clean
UV_CACHE_DIR=cache uv pip install numpy==2.0.0
```

Open another terminal, run:

```
UV_CACHE_DIR=cache uv cache clean
```

Fixes #15704
Part of #13883
2025-09-19 10:21:22 +02:00
Charlie Marsh 0889d53c25
Bump MSRV to 1.88 (#15935)
And bump the `rust-toolchain.toml` to `1.90`. Per our versioning policy.
2025-09-18 14:00:39 -04:00
Zanie Blue 4c2d9e19b0
Use `EnvVars` in linehaul (#15931) 2025-09-18 14:08:58 +00:00
Zanie Blue e23da5b315
Use `EnvVars` for Conda variables in tests (#15930) 2025-09-18 07:52:23 -05:00
Frazer McLean f122387f89
Fix implied `platform_machine` marker for `win_arm64` platform tag (#15921)
<!--
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

I'm back again after #14041, this time for `win_arm64`. I [asked a
Windows on ARM
user](https://github.com/getlogbook/logbook/pull/451#issuecomment-3295513650)
what the value of `platform.machine()` is, and it's `ARM64`.

## Test Plan

Updated/added tests
2025-09-17 19:33:59 -04:00
Zanie Blue c4c47814a8
Bump version to 0.8.18 (#15920) 2025-09-17 16:13:41 -05:00
Zanie Blue fa53a62f0b
Always treat conda environments named `base` and `root` as base environments (#15682)
Extends https://github.com/astral-sh/uv/pull/15679

I'm not sure if we want to do this. It is probably _generally_ true, but
I think I'd rather remove the special casing entirely? I think the main
case for this is that it could help prevent regressions from #15679 and
we can remove it in a breaking release?
2025-09-17 17:32:14 +00:00
konsti 759eab837a
Add GitHub Actions to PyPI trusted publishing example (#15753)
Add a complete example for the most common publishing workflow, GitHub
Actions to PyPI, with screenshots for settings and a standalone
companion repo.

Closes #14398
2025-09-17 19:25:17 +02:00
Zanie Blue d5012c66bd
Add handling for unnamed conda environments in base environment detection (#15681)
While investigating https://github.com/astral-sh/uv/pull/15679, I
created an unnamed conda environment and noticed this quality which
allows us to detect that it's not the base environment.
2025-09-17 11:34:23 -05:00
Aria Desires 1943aba150
Allow `[project]` to be missing from a `pyproject.toml` (#14113)
Closes #8666 
Closes https://github.com/astral-sh/uv/issues/6838

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-17 10:48:56 -05:00
Zanie Blue fa50a5cede
Add verbose output to the documentation help section (#15915)
Closes https://github.com/astral-sh/uv/issues/15914
2025-09-17 10:12:59 -05:00
Charlie Marsh 2a14edf75c
Respect `UV_INSECURE_NO_ZIP_VALIDATION=1` in duplicate header errors (#15912)
## Summary

This was just an oversight on these specific returns.

Closes https://github.com/astral-sh/uv/issues/15871.
2025-09-17 14:34:49 +00:00
Charlie Marsh dea1700945
Avoid ANSI codes in debug! messages (#15843)
## Summary

I spent time trying to figure out how to support this but came up empty.
It _seems_ like maybe the `DefaultFields` implementation in
`tracing-subscriber` uses debug formatting for fields...? So if you have
a string with ANSI codes, they end up printing as unformatted values? I
even reverted all our custom formatting in `logging.rs` and saw the same
thing.

Closes https://github.com/astral-sh/uv/issues/15840.
2025-09-17 14:30:43 +00:00
Charlie Marsh 48f507680c
Add PyG packages to torch backend (#15911)
## Summary

These are now supported on pyx.
2025-09-17 14:18:30 +00:00
Charlie Marsh d4806ee921
Re-add `triton` as a torch backend package (#15910)
## Summary

This accidentally regressed in
https://github.com/astral-sh/uv/pull/15769/files#diff-fcd4a516243929cdb086b7b79af9865a6ed432a0386765b0436392edc17a5a4eL260.
2025-09-17 14:04:50 +00:00
chisato accfb48876
Fix `uv sync --no-sources` not switching from editable to registry installations (#15234)
## Summary

Fixes issue #15190 where `uv sync --no-sources` fails to switch from
editable to registry package installations. The problem occurred because
the installer's satisfaction check didn't consider the `--no-sources`
flag when determining if an existing editable installation was
compatible with a registry requirement.

## Solution

Modified `RequirementSatisfaction::check()` to reject non-registry
installations when `SourceStrategy::Disabled` and the requirement is
from registry. Added `SourceStrategy` parameter threading through the
entire call chain from commands to the satisfaction check to ensure
consistent behavior between `uv sync --no-sources` and `uv pip install
--no-sources`.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-09-17 06:35:32 -05:00
konsti eb5ec95396
Better warning for no direct build (#15898)
**Setup**

```
$ git clone https://github.com/wheelnext/variant_aarch64
$ cd variant_aarch64
$ git checkout 1d047e667dbce4c74878a68c653a6b41bc3d3684
```

**Before**

```
$ uv build -v
[...]
DEBUG Not using uv build backend direct build of , no pyproject.toml: TOML parse error at line 5, column 1
  |
5 | [project]
  | ^^^^^^^^^
missing field `version`
[...]
```

**After**

```
$ uv build -v
[...]
DEBUG Not using uv build backend direct build of ``, pyproject.toml does not match: The value for `build_system.build-backend` should be `"uv_build"`, not `"flit_core.buildapi"`
[...]
```

The empty string gets fixed in
https://github.com/astral-sh/uv/pull/15897

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-17 13:18:41 +02:00
Zanie Blue d805d4a370
Use `_CONDA_ROOT` to detect conda base environments (#15680)
While investigating https://github.com/astral-sh/uv/pull/15679, I
noticed this variable was available in the environment and seems like a
nice additional heuristic.
2025-09-17 11:17:06 +00:00
Zanie Blue ee5f155f7e
Invert the logic for determining if a path is a base conda environment (#15679)
Closes https://github.com/astral-sh/uv/issues/15604

The previous logic does not match the discussion in the original issue
about this feature, nor does it match the comment for the function. I'm
confused because I know this logic is working for some people? I'm
consequently a little wary of making this change. I'm following up with
some additional changes that should ensure this is robust, e.g., #15680
2025-09-17 06:04:29 -05:00
github-actions[bot] 9ec7971b4a
Add GraalPy 25.0.0 with support for Python 3.12 (#15900)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-09-17 02:54:31 +00:00
Zanie Blue 664eadc59a
Fix verbose logging configuration for flake (#15902)
Alas in
https://github.com/astral-sh/uv/actions/runs/17784455913/job/50549326728?pr=15900
we caught the flake with the assert but the `-vv` flags were not in the
right position!
2025-09-17 02:54:12 +00:00
Harshith VH 705b35c552
fix misleading debug message in uv sync (#15881)
<!--
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

- Added `RemovableReason` enum to track removal context
- Updated `OnExisting::Remove` to include source information
- Modified debug message to show appropriate context
- Updated all call sites to specify correct removal source

fixes: #14734

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-16 18:39:08 +00:00
konsti 60f2ca3388
Don't user display empty string for absolute path CWD (#15897)
With the previous order an absolute path would become an empty string.
2025-09-16 17:49:08 +00:00
Charlie Marsh 422863ffde
Infer check URL from publish URL when known (#15886)
## Summary

If we know the publish URL-to-check URL mapping, we can just infer it.
2025-09-16 14:03:03 +00:00
Charlie Marsh ac52201626
Show organization name after authenticating (#15823)
## Summary

Shows the name of the logged-in organization on success, rather than
repeating the URL.
2025-09-16 09:46:43 -04:00
konsti 663053b0d1
packse: Use our own rendering exclusively, and use pylock.toml (#15796)
This PR contains two changes: The companion PR to
https://github.com/astral-sh/packse/pull/277, which moderately
simplifies the uv side, and switching to pylock.toml for packse as
dogfooding. These changes can be applied independent from each other.

Since all files, including the vendored build dependencies, are now on
GitHub Pages under the same root, we only need a packse index root URL.
2025-09-16 15:25:11 +02:00
Harshith VH 2825ee3435
Add `--no-clear` to `uv venv` to disable removal prompts (#15795)
<!--
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?
-->

Closes #15485

---------

Co-authored-by: Aditya-PS-05 <adityapratapsjnhh7654@gmail.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-16 08:24:57 -05:00
Christian Clauss fb71b079d5
guides/integration/github.md: Upgrade checkout action to v5 (#15887)
<!--
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? -->

Upgrade checkout action to v5 --
https://github.com/actions/checkout/releases

## Test Plan

<!-- How was it tested? -->
2025-09-16 07:53:00 +00:00
renovate[bot] 9fc966bdc9
Update Rust crate hashbrown to 0.16.0 (#15866)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [hashbrown](https://redirect.github.com/rust-lang/hashbrown) |
workspace.dependencies | minor | `0.15.1` -> `0.16.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>rust-lang/hashbrown (hashbrown)</summary>

###
[`v0.16.0`](https://redirect.github.com/rust-lang/hashbrown/blob/HEAD/CHANGELOG.md#0160---2025-08-28)

[Compare
Source](https://redirect.github.com/rust-lang/hashbrown/compare/v0.15.5...v0.16.0)

##### Changed

- Bump foldhash, the default hasher, to 0.2.0.
- Replaced `DefaultHashBuilder` with a newtype wrapper around `foldhash`
instead
  of re-exporting it directly.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: konstin <konstin@mailbox.org>
2025-09-15 19:33:36 +00:00
renovate[bot] 7d9633be9f
Update docker/metadata-action action to v5.8.0 (#15863)
Coming soon: The Renovate bot (GitHub App) will be renamed to Mend. PRs
from Renovate will soon appear from 'Mend'. Learn more
[here](https://redirect.github.com/renovatebot/renovate/discussions/37842).

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[docker/metadata-action](https://redirect.github.com/docker/metadata-action)
| action | minor | `v5.7.0` -> `v5.8.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>docker/metadata-action (docker/metadata-action)</summary>

###
[`v5.8.0`](https://redirect.github.com/docker/metadata-action/releases/tag/v5.8.0)

[Compare
Source](https://redirect.github.com/docker/metadata-action/compare/v5.7.0...v5.8.0)

- New `is_not_default_branch` global expression by
[@&#8203;crazy-max](https://redirect.github.com/crazy-max) in
[#&#8203;535](https://redirect.github.com/docker/metadata-action/pull/535)
- Allow to match part of the git tag or value for semver/pep440 types by
[@&#8203;crazy-max](https://redirect.github.com/crazy-max) in
[#&#8203;536](https://redirect.github.com/docker/metadata-action/pull/536)
[#&#8203;537](https://redirect.github.com/docker/metadata-action/pull/537)
- Bump
[@&#8203;actions/github](https://redirect.github.com/actions/github)
from 6.0.0 to 6.0.1 in
[#&#8203;523](https://redirect.github.com/docker/metadata-action/pull/523)
- Bump
[@&#8203;docker/actions-toolkit](https://redirect.github.com/docker/actions-toolkit)
from 0.56.0 to 0.62.1 in
[#&#8203;526](https://redirect.github.com/docker/metadata-action/pull/526)
- Bump form-data from 2.5.1 to 2.5.5 in
[#&#8203;533](https://redirect.github.com/docker/metadata-action/pull/533)
- Bump moment-timezone from 0.5.47 to 0.6.0 in
[#&#8203;525](https://redirect.github.com/docker/metadata-action/pull/525)
- Bump semver from 7.7.1 to 7.7.2 in
[#&#8203;524](https://redirect.github.com/docker/metadata-action/pull/524)

**Full Changelog**:
<https://github.com/docker/metadata-action/compare/v5.7.0...v5.8.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 13:48:03 -05:00
renovate[bot] 2bc4dc8ac2
Update docker/login-action action to v3.5.0 (#15862)
Coming soon: The Renovate bot (GitHub App) will be renamed to Mend. PRs
from Renovate will soon appear from 'Mend'. Learn more
[here](https://redirect.github.com/renovatebot/renovate/discussions/37842).

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [docker/login-action](https://redirect.github.com/docker/login-action)
| action | minor | `v3.4.0` -> `v3.5.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>docker/login-action (docker/login-action)</summary>

###
[`v3.5.0`](https://redirect.github.com/docker/login-action/releases/tag/v3.5.0)

[Compare
Source](https://redirect.github.com/docker/login-action/compare/v3.4.0...v3.5.0)

- Support dual-stack endpoints for AWS ECR by
[@&#8203;Spacefish](https://redirect.github.com/Spacefish)
[@&#8203;crazy-max](https://redirect.github.com/crazy-max) in
[#&#8203;874](https://redirect.github.com/docker/login-action/pull/874)
[#&#8203;876](https://redirect.github.com/docker/login-action/pull/876)
- Bump
[@&#8203;aws-sdk/client-ecr](https://redirect.github.com/aws-sdk/client-ecr)
to 3.859.0 in
[#&#8203;860](https://redirect.github.com/docker/login-action/pull/860)
[#&#8203;878](https://redirect.github.com/docker/login-action/pull/878)
- Bump
[@&#8203;aws-sdk/client-ecr-public](https://redirect.github.com/aws-sdk/client-ecr-public)
to 3.859.0 in
[#&#8203;860](https://redirect.github.com/docker/login-action/pull/860)
[#&#8203;878](https://redirect.github.com/docker/login-action/pull/878)
- Bump
[@&#8203;docker/actions-toolkit](https://redirect.github.com/docker/actions-toolkit)
from 0.57.0 to 0.62.1 in
[#&#8203;870](https://redirect.github.com/docker/login-action/pull/870)
- Bump form-data from 2.5.1 to 2.5.5 in
[#&#8203;875](https://redirect.github.com/docker/login-action/pull/875)

**Full Changelog**:
<https://github.com/docker/login-action/compare/v3.4.0...v3.5.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 13:47:48 -05:00
Zanie Blue 89a59749c0
Store native credentials for realms with the https scheme stripped (#15879)
Closes https://github.com/astral-sh/uv/issues/15818

Unfortunately, this is how we perform lookups. We could also change the
lookup to include the scheme. I expect all of this to change in the
future anyway, as I want to redesign the storage model for native
credentials.
2025-09-15 13:47:33 -05:00
Zanie Blue 31f46cd6a6
Use the root index URL when retrieving credentials from the native store (#15873)
Part of https://github.com/astral-sh/uv/issues/15818

We use the root when we store the credentials, so we need to use the
root when we retrieve them!
2025-09-15 13:47:24 -05:00
Zanie Blue daff98988b
Add test for realm-level authentication (#15877)
Test coverage for https://github.com/astral-sh/uv/issues/15818
2025-09-15 17:27:59 +00:00
Zanie Blue e90d13e53b
Remove `lock_git_change_log` (#15880)
This test is unstable
2025-09-15 12:04:18 -05:00
Zanie Blue 0aa3c4e094
Propagate preview flag to client for `native-auth` feature (#15872)
Somehow propagation of this got dropped during a rebase, so we never
actually used the native store during resolution.

Part of https://github.com/astral-sh/uv/issues/15818
2025-09-15 10:51:44 -05:00
Charlie Marsh d706c07ae3
Avoid re-parsing `pyproject.toml` when provided as a source (#15851)
## Summary

In the process of making a different change, I noticed that we parse
this during source discovery, throw it away, then parse it again later.
2025-09-15 10:07:38 -04:00
Charlie Marsh ef17e7d0f4
Load credentials for explicit members when lowering (#15844)
## Summary

If the target for `uv pip compile` is a `pyproject.toml` in a
subdirectory, we won't have loaded the credentials when we go to lower
(since it won't be loaded as part of "configuration discovery"). We now
add those indexes just-in-time.

Closes https://github.com/astral-sh/uv/issues/15362.
2025-09-15 13:54:38 +00:00
konsti 43c187dd93
Remove windows-sys 0.48 by updating miow (#15868) 2025-09-15 08:15:08 +00:00
renovate[bot] 1fe9e530fa
Update pre-commit hook astral-sh/ruff-pre-commit to v0.13.0 (#15864)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit)
| repository | minor | `v0.12.12` -> `v0.13.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

<details>
<summary>astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit)</summary>

###
[`v0.13.0`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.13.0)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.12.12...v0.13.0)

See: <https://github.com/astral-sh/ruff/releases/tag/0.13.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 09:50:16 +02:00
renovate[bot] 9e2a5bf5d7
Update Rust crate console to v0.16.1 (#15846)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [console](https://redirect.github.com/console-rs/console) |
workspace.dependencies | patch | `0.16.0` -> `0.16.1` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

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

###
[`v0.16.1`](https://redirect.github.com/console-rs/console/releases/tag/0.16.1)

[Compare
Source](https://redirect.github.com/console-rs/console/compare/0.16.0...0.16.1)

#### What's Changed

- Add WithoutAnsi struct that implements Display by
[@&#8203;ChocolateLoverRaj](https://redirect.github.com/ChocolateLoverRaj)
in
[#&#8203;258](https://redirect.github.com/console-rs/console/pull/258)
- Tweak style for new WithAnsi code by
[@&#8203;djc](https://redirect.github.com/djc) in
[#&#8203;266](https://redirect.github.com/console-rs/console/pull/266)
- Fix QNX 7.1 patch for libc::cfmakeraw by
[@&#8203;rafaeling](https://redirect.github.com/rafaeling) in
[#&#8203;267](https://redirect.github.com/console-rs/console/pull/267)
- Upgrade windows-sys to 0.61 by
[@&#8203;djc](https://redirect.github.com/djc) in
[#&#8203;272](https://redirect.github.com/console-rs/console/pull/272)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 09:42:53 +02:00
renovate[bot] 5fe0855169
Update Rust crate ctrlc to v3.5.0 (#15865) 2025-09-15 03:22:12 +00:00
renovate[bot] 4731c2264a
Update crate-ci/typos action to v1.36.2 (#15858) 2025-09-14 22:50:39 -04:00
renovate[bot] 1b13099cda
Update dependency koalaman/shellcheck to v0.11.0 (#15859) 2025-09-14 22:50:28 -04:00
renovate[bot] 0db76839ef
Update astral-sh/setup-uv action to v6.7.0 (#15857) 2025-09-14 22:50:05 -04:00
renovate[bot] 8938242b90
Update Rust crate wiremock to v0.6.5 (#15855) 2025-09-14 22:49:57 -04:00
renovate[bot] df01566b38
Update acj/freebsd-firecracker-action action to v0.6.0 (#15856) 2025-09-14 22:49:52 -04:00
renovate[bot] c9cc875a11
Update Rust crate whoami to v1.6.1 (#15854) 2025-09-15 01:23:03 +00:00
renovate[bot] 3005866b21
Update Rust crate url to v2.5.7 (#15853) 2025-09-15 01:19:44 +00:00
renovate[bot] 5b7c95a0b2
Update Rust crate toml_edit to v0.23.4 (#15852) 2025-09-14 21:16:33 -04:00
renovate[bot] feb4ddedc6
Update Rust crate target-lexicon to v0.13.3 (#15850) 2025-09-14 20:45:14 -04:00
renovate[bot] 19bb25720d
Update Rust crate serde-untagged to v0.1.9 (#15849) 2025-09-14 20:44:54 -04:00
renovate[bot] 669973baa9
Update aws-actions/configure-aws-credentials digest to 351d894 (#15845) 2025-09-14 20:44:39 -04:00
renovate[bot] 647fd97293
Update Rust crate serde_json to v1.0.145 (#15848) 2025-09-14 20:43:50 -04:00
Charlie Marsh 64bcd4e8a6
Improve BSD tag construction (#15829)
## Summary

I had to use ChatGPT to help with my research on the "correct"
architecture names for these platforms; there could still be some rough
edges, but this seems like an improvement.

Closes https://github.com/astral-sh/uv/issues/15799.
2025-09-14 10:48:37 -04:00
konsti 5633d3abe4
Document cache-keys for native build backends (#15811)
Update note for
https://github.com/astral-sh/uv/pull/15705#issuecomment-3285240646

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-14 14:20:17 +00:00
Charlie Marsh 3c813cde76
Error when `pyproject.toml` target does not exist for dependency groups (#15831)
## Summary

Closes https://github.com/astral-sh/uv/issues/15789.
2025-09-14 13:49:35 +00:00
Charlie Marsh b770639c91
Rename `provides_extras` to `provides_extra` (#15825)
## Summary

This is now consistent with `requires_dist` (singular).
2025-09-14 13:27:45 +00:00
Charlie Marsh 312084f2dd
Allow cached environment reuse with `@latest` (#15827)
## Summary

I think this is leftover from a prior refactor whereby we used to avoid
reusing the cached environment if `--reinstall` was passed; but then we
stopped allowing `--reinstall` in `uv tool run` anyway, and this got
changed to `--refresh`. It seems wrong to skip cache reuse with
`--refresh`, though.

Closes https://github.com/astral-sh/uv/issues/15824.
2025-09-14 09:10:46 -04:00
Charlie Marsh 0a2a7bc445
Log when the cache is disabled (#15828)
## Summary

This strikes me as fairly reasonable.

Closes https://github.com/astral-sh/uv/issues/15822.
2025-09-14 01:57:10 +00:00
Charlie Marsh f59d00b479
Allow escaping spaces in --env-file handling (#15815)
## Summary

We allow space-delimiting for `--env-file`, but Clap doesn't support any
form of escaping, so as-is, there's no way to provide a `.env` file in a
directory that contains a space. We now do the splitting ourselves and
respect escapes.

Closes https://github.com/astral-sh/uv/issues/15806.
2025-09-12 18:11:51 -04:00
William Woodruff 6876716fd2
Revert "feat(ci): build loongarch64 binaries in CI (#15387)" (#15820) 2025-09-12 17:21:15 -04:00
Charlie Marsh b1fbb524d2
Include SHA when listing lockfile changes (#15817)
## Summary

Right now, we only list changes if the _version_ differs. This PR takes
the SHA into account. We may want to list changes to _any_ sources, but
that gets more complicated (e.g., if the user swaps the index URL, we'd
have to show _all_ changes to the index URL).

Closes #15810.
2025-09-12 17:57:58 +00:00
Charlie Marsh bd8a9348bd
Document `NO_PROXY` support (#15816)
Closes https://github.com/astral-sh/uv/issues/15785.
2025-09-12 13:11:05 -04:00
Ahmed Ilyas 9153d1a5e3
Deprecate `tool.uv.dev-dependencies` (#15469)
## Summary

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

## Test Plan

`cargo test`

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-12 15:08:09 +00:00
Zanie Blue 8f3583a6e6
Allow selection of debug build interpreters (#11520)
Extends the `PythonVariant` logic to support interpreters with the debug
flag enabled.
2025-09-12 13:32:22 +00:00
Aria Desires 8917b00fd9
add docs for dependency group `requires-python` (#14282)
I specifically show more details than necessary in the example to make
it more clear that this is *NOT* the normal dependency-groups table.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-12 13:15:27 +00:00
James Bourbeau 0b8117a9f7
Add Coiled integration doc page (#14430)
This PR adds a new integrations doc page for using uv with
[Coiled](https://coiled.io/). It's a slightly adapted version of this
blog post https://docs.coiled.io/blog/uv-coiled-cloud-scripts.html

Side note: it's been really pleasant using uv and Coiled together
recently

cc @zanieb for visibility

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-12 12:24:32 +00:00
rimathia 3081557159
Better defaults for native build backend cache keys (#15705)
<!--
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

This change extends the default initialized projects with scikit and
maturin build backends to ensure that the rust or C++ build gets invoked
when a source file changes.

## Test Plan

`cargo run init cppextension-test --build-backend=scikit` followed by
manual testing of the behaviour of the resulting project under `uv run
--with jupyter jupyter lab` and changes to the source file of the
extension.
Analogous for `cargo run init maturin-test --build-backend=maturin `.

## Relevant Issues

The question of why the python extension is not rebuilt on source
changes has been discussed in
https://github.com/astral-sh/uv/issues/15701#issuecomment-3258714942.
2025-09-12 11:14:59 +02:00
William Woodruff 21a92c1632
feat(publish): include blake2b hash in upload form (#15794) 2025-09-11 16:17:07 -04:00
Harshith VH a0f8359012
Add conflict detection between --only-group and --extra flags (#15788)
<!--
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

- Added `conflicts_with = "only_group"` to `--extra` arguments in
`SyncArgs`, `RunArgs`, and `ExportArgs`
- Added tests to verify proper conflict detection and error messages

**Before:** The `--extra` flag was silently ignored when used with
`--only-group`
**After:** Clear error message: `error: the argument '--only-group
<ONLY_GROUP>' cannot be used with '--extra <EXTRA>'`

fixes: #15676 

## Test Plan

- Tests confirm proper error message format when `--only-group` and
`--extra` are used together
- Verified existing functionality remains unchanged when flags are used
independently
2025-09-11 10:34:49 -05:00
Harsh Pratap Singh 5f2871e695
Support Gitlab CI/CD as a trusted publisher (#15583)
Co-authored-by: William Woodruff <william@astral.sh>
2025-09-11 10:35:04 -04:00
Zanie Blue cbb713f705
Review for #15769 (#15775)
Addresses my review comments from
https://github.com/astral-sh/uv/pull/15769
2025-09-11 13:20:13 +00:00
konsti 2ec71bd608
Update packse to 0.3.52: Fix requires python 3.12 (#15786)
Pull in the latest packse changes to make all test work with the new
default Python requirement, 3.12.
2025-09-11 13:19:34 +00:00
Zanie Blue 97516fa89b
Include the `pyproject.toml` in the version files (#15778)
Apparently I decided to remove this special-case!
2025-09-10 23:47:07 -05:00
Zanie Blue 10960bc13a
Bump version in the `pyproject.toml` to 0.8.17 (#15777)
See failure at
https://github.com/astral-sh/uv/actions/runs/17626770657/job/50085595250

Certainly a regression from #15749
2025-09-10 16:21:32 -05:00
Zanie Blue 7789f5b217
Bump version to 0.8.17 (#15776) 2025-09-10 14:59:48 -05:00
Charlie Marsh b195d523d5
Add pyx as a supported PyTorch index URL (#15769)
## Summary

If the user explicitly authenticated to pyx, then we attempt to use the
pyx PyTorch URLs; otherwise, we stick to `download.pytorch.org` as the
default.
2025-09-10 14:38:00 -05:00
David Peter 0d174b79e2
Ignore pre-release versions in `uv init --script` (#15747)
## Summary

Ignore managed pre-release versions of Python for purposes of creating a
`requires-python` constraint when running `uv init --script`. This makes
the behavior consistent with `uv init` for normal projects.

## Test Plan

Added a regression test that makes sure that the constraint is
`requires-python = ">=3.13"`, even though a pre-release version of 3.14
is installed.
2025-09-10 14:35:29 -05:00
Zanie Blue ab2880f389
Upgrade to the latest rooster version (#15749)
For all my latest bells and whistles...

It's not published yet since it has breaking changes, so we're using the
commit. (This is what ty does)
2025-09-10 14:34:43 -05:00
Zanie Blue 27d205b0c3
Respect `PYX_API_URL` when suggesting `uv auth login` on 401 (#15774) 2025-09-10 19:24:46 +00:00
Charlie Marsh ccf01fff66
Avoid initiating login flow for invalid API keys (#15773)
## Summary

If the login flow fails, and the user provided an API key, it's
unintuitive to initiate the login flow.
2025-09-10 19:07:29 +00:00
Zanie Blue b2c59a8ace
Do not search for a password for requests with a token attached already (#15772) 2025-09-10 13:52:09 -05:00
Zanie Blue 7c716d5227
Improve error message for HTTP validation in auth services (#15768)
Follows https://github.com/astral-sh/uv/pull/15755
2025-09-10 13:15:19 -05:00
Zanie Blue 4976a6763b
Fix the glob for filtering `uv_build` loongarch wheels (#15762)
Introduced in https://github.com/astral-sh/uv/pull/15387 and broke the
latest release.

cc @SkyBird233
2025-09-09 21:41:48 -05:00
Charlie Marsh 2de677b0d3
Bump version to v0.8.16 (#15761)
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-09 21:41:56 -04:00
konsti e691ac7c9a
Build backend error message style consistency (#15735)
Consistently omit backticks after a colon in build backend messages,
following
https://github.com/astral-sh/uv/pull/15733#discussion_r2330156783.

There's still 74 matches for `: {}"` and 183 matches for `: {[^{]*}"`,
but this PR clears all matches in the build backend.
2025-09-09 17:34:56 +00:00
Zsolt Dollenstein ae2dce6d25
auth: allow insecure http only on localhost (#15755)
## Summary

This is useful for testing purposes.

## Test Plan

I tested using testing purposes.
2025-09-09 17:21:14 +01:00
Charlie Marsh 5494645fba
Use token store credentials for `uv publish` (#15759)
## Summary

Running `uv publish` to pyx should re-use the already-stored token
rather than prompting for credentials.

Closes https://github.com/astral-sh/uv/issues/15758.
2025-09-09 16:13:31 +00:00
konsti cd49e1d11f
Use the `windows` crate facade consistently (#15737)
The initial motivation for this change was that we were using both the
`windows`, the `window_sys` and the `windows_core` crate in various
places. These crates have slightly unconventional versioning scheme
where there is a large workspace with the same version in general, but
only some crates get breaking releases when a new breaking release
happens, the others stay on the previous breaking version. The `windows`
crate is a shim for all three of them, with a single version. This
simplifies handling the versions.

Using `windows` over `windows_sys` has the advantage of a higher level
error interface, we now get a `Result` for all windows API calls instead
of C-style int-returns and get-last-error calls. This makes the
uv-keyring crate more resilient.

We keep using the `windows_registry` crate, which provides a higher
level interface to windows registry access.
2025-09-09 15:07:14 +00:00
konsti 12764df8b2
Show a dedicated error for venvs in source trees (#15748)
A user in the support chat had an error message for `uv build` with the
`uv_build` backend they didn't understand, which was caused by them
having a venv in their build directory. This PR adds a dedicated error
message when adding something to a distribution that looks like a venv.
2025-09-09 12:41:58 +00:00
Zanie Blue 9d3a3843c3
Ban empty usernames and passwords in `uv auth` (#15743)
Otherwise, you can get yourself in a weird state?
2025-09-09 06:23:33 -05:00
Zanie Blue 484004871c
Remove cmake from our Docker image build (#15744)
We no longer depend on CMake
2025-09-09 06:23:14 -05:00
konsti d9b63dc547
Use workspace dependencies in uv-keyring (#15738)
It seems that this was during the vendoring of the keyring crate.
2025-09-09 09:05:43 +02:00
Yiğit Ö. Ünver 19ea0f4932
support `--no-project` in `uv format` (#15572)
When a user passes `--no-project` argument to `uv format` command,
instead of running the formatter in the context of the current project,
run it in the context of the current directory. This is useful when the
current directory is not a project.

Closes https://github.com/astral-sh/uv/issues/15462
2025-09-08 16:16:40 -05:00
renovate[bot] bd36952492
Update Rust crate toml to v0.9.5 (#15727)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [toml](https://redirect.github.com/toml-rs/toml) |
workspace.dependencies | patch | `0.9.2` -> `0.9.5` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

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

###
[`v0.9.5`](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.4...toml-v0.9.5)

[Compare
Source](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.4...toml-v0.9.5)

###
[`v0.9.4`](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.3...toml-v0.9.4)

[Compare
Source](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.3...toml-v0.9.4)

###
[`v0.9.3`](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.2...toml-v0.9.3)

[Compare
Source](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.2...toml-v0.9.3)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuOTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 15:06:38 +00:00
renovate[bot] 29e1ca160f
Update Rust crate reflink-copy to v0.1.28 (#15725)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [reflink-copy](https://redirect.github.com/cargo-bins/reflink-copy) |
workspace.dependencies | patch | `0.1.26` -> `0.1.28` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>cargo-bins/reflink-copy (reflink-copy)</summary>

###
[`v0.1.28`](https://redirect.github.com/cargo-bins/reflink-copy/blob/HEAD/CHANGELOG.md#0128---2025-09-08)

[Compare
Source](https://redirect.github.com/cargo-bins/reflink-copy/compare/v0.1.27...v0.1.28)

##### Other

- Support windows 0.61.0 as well as 0.62.0
([#&#8203;135](https://redirect.github.com/cargo-bins/reflink-copy/pull/135))

###
[`v0.1.27`](https://redirect.github.com/cargo-bins/reflink-copy/blob/HEAD/CHANGELOG.md#0127---2025-09-05)

[Compare
Source](https://redirect.github.com/cargo-bins/reflink-copy/compare/v0.1.26...v0.1.27)

##### Other

- Bump windows from 0.61.0 to 0.62.0
([#&#8203;131](https://redirect.github.com/cargo-bins/reflink-copy/pull/131))
- Bump actions/checkout from 4 to 5
([#&#8203;124](https://redirect.github.com/cargo-bins/reflink-copy/pull/124))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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.

---

- [x] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0MS45MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuOTcuMTAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImludGVybmFsIl19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 14:14:47 +00:00
konsti 39fe2d9eac
Error early for parent path in build backend (#15733)
Paths referencing above the directory of the `pyproject.toml`, such as
`module-root = ".."`, are not supported by the build backend. The check
that should catch was not working properly, so the source distribution
built successfully and only the wheel build failed. We now error early.
The same fix is applied to data includes.

Fix #15702
2025-09-08 13:53:16 +00:00
Zanie Blue 5f8c7181b9
Improve the CLI help for options that accept requirements files (#15706) 2025-09-08 08:23:52 -05:00
Zanie Blue 4de559d70d
Increase the size of the Windows dev drives from 20GB -> 25GB (#15734)
We ran out of disk space in
https://github.com/astral-sh/uv/actions/runs/17551474262/job/49844770034?pr=15727

Closes https://github.com/astral-sh/uv/issues/15729
2025-09-08 13:21:38 +00:00
renovate[bot] 9d17dfa353
Update google-github-actions/auth digest to fc21748 (#15717)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| google-github-actions/auth | action | digest | `b7593ed` -> `fc21748`
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuOTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 07:08:58 +00:00
renovate[bot] 25ca424551
Update Rust crate mimalloc to v0.1.48 (#15724)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [mimalloc](https://redirect.github.com/purpleprotocol/mimalloc_rust) |
dependencies | patch | `0.1.47` -> `0.1.48` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>purpleprotocol/mimalloc_rust (mimalloc)</summary>

###
[`v0.1.48`](https://redirect.github.com/purpleprotocol/mimalloc_rust/releases/tag/v0.1.48):
Version 0.1.48

[Compare
Source](https://redirect.github.com/purpleprotocol/mimalloc_rust/compare/v0.1.47...v0.1.48)

##### Changes

- Mimalloc `v3` feature flag. (credits
[@&#8203;gschulze](https://redirect.github.com/gschulze)).

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuOTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 07:08:37 +00:00
renovate[bot] d918086d76
Update google-github-actions/setup-gcloud digest to aa5489c (#15718)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| google-github-actions/setup-gcloud | action | digest | `26f734c` ->
`aa5489c` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuOTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 08:57:57 +02:00
renovate[bot] ec46ff739c
Update Rust crate insta to v1.43.2 (#15723)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [insta](https://insta.rs/)
([source](https://redirect.github.com/mitsuhiko/insta)) |
workspace.dependencies | patch | `1.43.1` -> `1.43.2` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>mitsuhiko/insta (insta)</summary>

###
[`v1.43.2`](https://redirect.github.com/mitsuhiko/insta/blob/HEAD/CHANGELOG.md#1432)

[Compare
Source](https://redirect.github.com/mitsuhiko/insta/compare/1.43.1...1.43.2)

- Fix panics when `cargo metadata` fails to execute or parse (e.g., when
cargo is not in PATH or returns invalid output). Now falls back to using
the manifest directory as the workspace root.
[#&#8203;798](https://redirect.github.com/mitsuhiko/insta/issues/798)
([@&#8203;adriangb](https://redirect.github.com/adriangb))
- Fix clippy `uninlined_format_args` lint warnings.
[#&#8203;801](https://redirect.github.com/mitsuhiko/insta/issues/801)
- Changed diff line numbers to 1-based indexing.
[#&#8203;799](https://redirect.github.com/mitsuhiko/insta/issues/799)
- Preserve snapshot names with `INSTA_GLOB_FILTER`.
[#&#8203;786](https://redirect.github.com/mitsuhiko/insta/issues/786)
- Bumped `libc` crate to `0.2.174`, fixing building on musl targets, and
increasing the MSRV of
`insta` to `1.64.0` (released Sept 2022).
[#&#8203;784](https://redirect.github.com/mitsuhiko/insta/issues/784)
- Fix clippy 1.88 errors.
[#&#8203;783](https://redirect.github.com/mitsuhiko/insta/issues/783)
- Fix source path in snapshots for non-child workspaces.
[#&#8203;778](https://redirect.github.com/mitsuhiko/insta/issues/778)
- Add lifetime to Selector in redaction iterator.
[#&#8203;779](https://redirect.github.com/mitsuhiko/insta/issues/779)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS45MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuOTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 08:50:29 +02:00
renovate[bot] e2ac34f95a
Update pre-commit dependencies (#15720) 2025-09-07 22:15:01 -04:00
renovate[bot] 14e78ff818
Update rui314/setup-mold digest to 725a879 (#15719) 2025-09-07 22:12:37 -04:00
renovate[bot] e2f15e28f9
Update Rust crate bitflags to v2.9.4 (#15721) 2025-09-07 22:11:52 -04:00
renovate[bot] 0991048834
Update Rust crate clap to v4.5.47 (#15722) 2025-09-07 22:11:44 -04:00
renovate[bot] 2d71a8bf12
Update aws-actions/configure-aws-credentials digest to 8e2d022 (#15716) 2025-09-07 22:11:29 -04:00
Charlie Marsh 5012b4e831
Allow `--editable` to override `editable = false` annotations (#15712)
## Summary

We support `--no-editable` on the CLI, but now that workspace members
and path dependencies can be marked as `editable = false`, I think it
makes sense for `--editable` to override that.
2025-09-07 14:37:04 -04:00
Charlie Marsh e84c9231aa
Allow `editable = false` for workspace sources (#15708)
## Summary

This ended up being a bit more complex, similar to `package = false`,
because we need to understand the editable status _globally_ across the
workspace based on the packages that depend on it.

Closes https://github.com/astral-sh/uv/issues/15686.
2025-09-07 15:41:17 +00:00
konsti 97777cda66
Don't trace logs all architectures (#15710)
On my machine, this statement print over 500 lines for `uv python list
-vv` of evident statements.
2025-09-07 17:31:50 +02:00
SkyBird 2fd9e53b25
feat(ci): build loongarch64 binaries in CI (#15387)
<!--
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? -->

This PR adds support for building loongarch64 binaries in CI. As uv
itself runs perfectly well on loongarch64 and with the latter's userbase
steadily growing, it would be a good idea to ship prebuilt binaries to
help them out.

Please note that as Ubuntu is not yet available for loongarch64, I have
elected to use a Debian Trixie container maintained by community
members. In addition, as Debian's pip does not allow installing modules
system-wide, the workflow for loongarch64 installs additional modules in
a virtual environment.

## Test Plan

<!-- How was it tested? -->

Tests are included in CI and the loongarch64 artifacts built in [this
workflow](https://github.com/SkyBird233/uv/actions/runs/17091637376/job/48466486690)
has been smoke tested.
2025-09-06 06:52:07 -05:00
Brendan cbcf51391f
fix: refresh activation scripts from upstream virtualenv (#15272)
## Summary
This refreshes the venv activation scripts from upstream `virtualenv`
project.
This was largely triggered by a problem in the activate.nu script (for
nushell):
- #14888 
- #14914 
- #14917 

I was careful to respect the git history going back to astral-sh/uv#3376
(the last time this was done).
Actually I looked at the complete history from back when this
`uv-virtualenv` crate was named after a Pokémon (⁉️), but I found
nothing (about activation scripts) from back then that hasn't been
overwritten since.

### Some post-processing was involved

- Retained license info at top of scripts
- Retained template vars (eg `{{ BIN_PATH }}`) to assure current support
toward relocatable venv
- Retained deviation from upstream in astral-sh/uv#5640. This seems to
be the only deviation that isn't in sync with upstream.

### Notable changes from upstream

- (omitted due to undesirable complexity) pypa/virtualenv#2928 and its
follow-up pypa/virtualenv#2940
- pypa/virtualenv#2910 (what prompted astral-sh/uv#14917 from
astral-sh/uv#14888)

## Test Plan

There was a request in #14917 to add unit tests to detect breakage or
errors.
I have added a CI job that runs the nushell activation script.
But I think it is better to have the CI test all/most supported shells.
See also #15294 

I have tested this locally using

- [x] nushell (v0.106.1)
- [x] cmd.exe (Microsoft Windows [Version 10.0.26100.4946])
- [x] bash in WSL (GNU bash, version 5.1.16(1)-release
(x86_64-pc-linux-gnu))
- [x] pwsh (PSVersion 5.1.26100.4768)
2025-09-05 16:12:37 -05:00
Jorge Hermo c59ead398d
Allow `uv format` in unmanaged projects (#15553)
Closes #15550

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-05 13:14:41 -05:00
Ahmed Ilyas 6eefde28e7
Support `--with-requirements script.py` and `-r script.py` to include inline dependency metadata from another script (#12763)
## Summary

Closes #6542 

## Test Plan

`cargo test`
2025-09-05 11:45:46 -05:00
Zanie Blue e136a51f3d
Skip Python interpreters that cannot be queried with permission errors (#15685)
Closes https://github.com/astral-sh/uv/issues/15651
2025-09-05 09:03:41 -05:00
konsti 7dd035c555
Update pypa/gh-action-pypi-publish to version with pinned hashes (#15350)
Use a version of the GitHub Action that uses a pinned hash, to fix the
publish test.

See https://github.com/astral-sh/uv/pull/15324 and
https://github.com/pypa/gh-action-pypi-publish/pull/378 for details.
2025-09-05 13:11:39 +00:00
konsti 549fb121ba
Support recursive requirements and constraints inclusion (#15657)
uv currently panics with a stack overflow when requirements or
constraints are recursively included. Instead, we ignore files we have
already seen. The one complexity here is that we have to track whether
we're in a requirements inclusion or in a constraints inclusion, to
allow including a file separately for requirements and for constraints,
and to handle `-r` inside or `-c` (which we treat as constraints too).

Fixes #15650
2025-09-05 11:20:12 +02:00
Antony Kellermann b68161f0ca
adds docs for docker caching for managed python (#15689)
<!--
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

Closes #15586
2025-09-04 13:50:22 -05:00
Zanie Blue 70222e9358
Add test coverage of x86-64 Python from `setup-python` on arm64 macOS (#15687) 2025-09-04 13:36:55 -05:00
konsti 4a1813f228
Extract IO errors from h2 for streaming retries of Connection Reset (#15675)
Our streaming retries were missing connection reset errors as h2 was
shadowing IO errors (https://github.com/hyperium/h2/issues/862).

**Test plan**

In one terminal:

```
cargo python uninstall 3.12 && cargo run python install 3.12 -vv
```

In another:

```
sudo tcpkill -i wlp2s0 port 443
```

Output:

```
error: Failed to install cpython-3.12.11-linux-x86_64-gnu
  Caused by: Request failed after 3 retries
  Caused by: Failed to download https://github.com/astral-sh/python-build-standalone/releases/download/20250902/cpython-3.12.11%2B20250902-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz
  Caused by: error sending request for url (https://github.com/astral-sh/python-build-standalone/releases/download/20250902/cpython-3.12.11%2B20250902-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz)
  Caused by: client error (SendRequest)
  Caused by: connection error
  Caused by: connection reset
```

I don't know how to test that from inside Rust.

Fix #14171 (again, hopefully)
2025-09-04 14:45:00 +02:00
Charlie Marsh 580bc9d079
Avoid erroring when `match-runtime` target is optional (#15671)
## Summary

If the package that has the `match-runtime` dependency itself isn't
being installed, we should avoid erroring if the package it _depends on_
isn't in the resolution.

Closes https://github.com/astral-sh/uv/issues/15661.
2025-09-04 08:34:53 -04:00
Mateus Devino 39a1c546d8
Add C compiler instructions to Fedora-based distros (#15674)
<!--
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? -->

This PR adds instructions to install a C compiler on Fedora-based Linux
distributions.

## Test Plan

```
# Start Fedora container interactively (can probably be done on Docker as well)
podman run -it registry.fedoraproject.org/fedora

# From now on, run all commands inside the container.
# Install Rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Add cargo bin folder to PATH
export PATH="${HOME}/.cargo/bin:${PATH}"

# Install git, clone uv project and get into its folder
dnf install git
git clone https://github.com/astral-sh/uv.git
cd uv

# Try to compile uv and fail (error: linker `cc` not found)
cargo build

# Install C compiler
dnf install gcc

# Try to compile uv again. This time, successfully.
cargo build
```

Signed-off-by: Mateus Devino <mdevino@ibm.com>
2025-09-04 07:23:35 -05:00
github-actions[bot] 4162005df8
Sync latest Python releases (#15670)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-09-03 20:04:55 -05:00
timrid 330e56e778
Support iOS platform tags (#15640)
## Summary
This implements the iOS part of
https://github.com/astral-sh/uv/issues/8029

FYI: @freakboy3742

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

## Test Plan
Create a venv with uv and run `cargo run pip install --python-platform
arm64-apple-ios pillow`. Then the iOS binary of pillow should be
installed inside the venv.
2025-09-03 18:24:48 -04:00
Zanie Blue 50bfa8a689
Add logging of incompatible tags on satisfies check (#15663)
I was trying to understand https://github.com/astral-sh/uv/issues/9559
and think we need more logs to see what's going on.
2025-09-03 11:45:49 -05:00
timrid d178e45368
Support Android platform tags (#15646)
## Summary
This implements the Android part of
https://github.com/astral-sh/uv/issues/8029

FYI: @freakboy3742 @mhsmith

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

## Test Plan
Create a venv with uv and run `cargo run pip install --python-platform
aarch64-linux-android pybase64`. Then the Android binary of pybase64
should be installed inside the venv.
2025-09-03 10:24:33 -04:00
konsti a94f7d0847
Clarify that `uv auth` commands take a URL (#15664)
From the previous description I tried `uv auth token pyx`, which didn't
work.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-09-03 14:16:16 +00:00
Charlie Marsh 8473ecba11
Require HTTPS for CDN requests (#15660)
## Summary

This should arguably enforce same-realm (as the API), but this is a good
guardrail for now.
2025-09-03 13:32:12 +00:00
Charlie Marsh ad35d120d6
Make `uv auth dir` service-aware (#15649)
## Summary

This got lost when https://github.com/astral-sh/uv/pull/15637 was merged
into not-`main`.
2025-09-02 22:58:59 -04:00
Charlie Marsh 70cb0df7c2
Bump version to v0.8.15 (#15648) 2025-09-03 02:10:58 +00:00
Charlie Marsh 4e48d759c4
Add zstandard support for wheels (#15645)
## Summary

This PR allows pyx to send down hashes for zstandard-compressed
tarballs. If the hash is present, then the file is assumed to be present
at `${wheel_url}.tar.zst`, similar in design to PEP 658
`${wheel_metadata}.metadata` files. The intent here is that the index
must include the wheel (to support all clients and support
random-access), but can optionally include a zstandard-compressed
version alongside it.
2025-09-03 01:38:31 +00:00
Charlie Marsh 7606f1ad3c
Add `uv publish --dry-run` (#15638)
## Summary

`uv publish --dry-run` will perform the `--check-url` validation, and
hit the `/validate` endpoint if the registry is known to support
fast-path validation (like pyx). The `/validate` endpoint lets us
validate an upload without uploading the file _contents_, which lets you
skip the expensive step for common mistakes.

In the future, my hope is that the `/validate` step will deprecated in
favor of Upload API 2.0.
2025-09-02 21:24:31 -04:00
Charlie Marsh b57ad179b6
Allow registries to pre-provide core metadata (#15644)
## Summary

This PR adds support for the `application/vnd.pyx.simple.v1` content
type, similar to `application/vnd.pypi.simple.v1` with the exception
that it can also include core metadata for package-versions directly.
2025-09-03 00:56:29 +00:00
Charlie Marsh f88aaa8740
Add pyx support to `uv auth` commands (#15636)
## Summary

This PR adds support for pyx to `uv auth login`, `uv auth logout`, and
`uv auth token`. These are generic uv commands that can be used to store
credentials for arbitrary indexes and other URLs, but we include a
fast-path for pyx that initiates the appropriate login or logout flow.
2025-09-02 18:18:09 -04:00
Zanie Blue f9e98d1fb6
Allow providing the `uv auth login` password or token via stdin (#15642) 2025-09-02 16:59:58 -05:00
Zanie Blue 63b93a1db0
Add test cases for URL matching with the native keyring (#15641) 2025-09-02 16:56:40 -05:00
github-actions[bot] 8fcd88d2d4
Sync latest Python releases (#15631)
Update to python-build-standalone 20250902 mostly for SQLite 3.50.4/CVE-2025-6965.
2025-09-02 18:43:17 +00:00
Zanie Blue aa161884aa Run the `uv-keyring` tests serially (#15629) 2025-09-02 13:16:52 -05:00
Zanie Blue 45946b80b0 Allow storage of multiple usernames per service in the plaintext store (#15628)
We weren't keying our hash map with the username, which meant that only
one user could be used per service.
2025-09-02 13:16:52 -05:00
Zanie Blue 709e0ba238 Remove the native system store from the keyring providers (#15612)
We're not sure what the best way to expose the native store to users is
yet and it's a bit weird that you can use this in the `uv auth` commands
but can't use any of the other keyring provider options. The simplest
path forward is to just not expose it to users as a keyring provider,
and instead frame it as a preview alternative to the plaintext uv
credentials store. We can revisit the best way to expose configuration
before stabilization.

Note this pull request retains the _internal_ keyring provider
implementation — we can refactor it out later but I wanted to avoid a
bunch of churn here.
2025-09-02 13:16:52 -05:00
Zanie Blue bc1bbfb066 Respect usernames when finding matching credentials in the plaintext store (#15620)
We're not respecting the username when searching for a match, which is
no good!
2025-09-02 13:16:52 -05:00
Zanie Blue 7ac957af8f Lock the credentials store when reading or writing (#15610)
Adds locking of the credentials store for concurrency safety. It's
important to hold the lock from read -> write so credentials are not
dropped during concurrent writes.

I opted not to attach the lock to the store itself. Instead, I return
the lock on read and require it on write to encourage safe use. Maybe
attaching the source path to the store struct and adding a `lock(&self)`
method would make sense? but then you can forget to take the lock at the
right time. The main problem with the interface here is to write a _new_
store you have to take the lock yourself, and you could make a mistake
by taking a lock for the wrong path or something. The fix for that would
be to introduce a new `CredentialStoreHandle` type or something, but
that seems overzealous rn. We also don't eagerly drop the lock on token
read, although we could.
2025-09-02 13:16:52 -05:00
Zanie Blue 7d627b50ef Add `uv auth dir` (#15600) 2025-09-02 13:16:52 -05:00
Charlie Marsh e3cb13868d Use a dedicated wire type for credentials serialization (#15599)
This is a little closer to what we do elsewhere when we want to
encapsulate differences in the serialization format.
2025-09-02 13:16:52 -05:00
Charlie Marsh 7d9446450b Misc. tweaks 2025-09-02 13:16:52 -05:00
Zanie Blue 32bcfdff0a Add case for `uv auth login` in registry integration tests (#15593)
Adds an alternative third-party registry test mode that uses `uv auth
login` instead of the environment variables to provide configuration.
2025-09-02 13:16:52 -05:00
Zanie Blue a13fb3ec64 Respect `UV_CREDENTIALS_DIR` (#15598) 2025-09-02 13:16:52 -05:00
Zanie Blue 4d79fd2c04 Add documentation on the uv credential store and CLI (#15597) 2025-09-02 13:16:52 -05:00
Zanie Blue 53599d9f56 Add test case for `uv auth login` in publish integration tests (#15592) 2025-09-02 13:16:52 -05:00
Zanie Blue cc4be0f2ba Add the native keyring to the documentation (#15596) 2025-09-02 13:16:52 -05:00
Zanie Blue 0b5180cb37 Strip the trailing `/simple` from index URLs provided to `uv auth login` (#15591)
I'm pretty sure we need to do this for robust lookups of URLs served by
indexes.
2025-09-02 13:16:52 -05:00
Zanie Blue ac5dc9be1f Add a plain text backend for credential storage (#15588)
Adds a default plain text storage mechanism to `uv auth`.

While we'd prefer to use the system store, the "native" keyring support
is experimental still and I don't want to ship an unusable interface.
@geofft also suggested that the story for secure credential storage is
much weaker on Linux than macOS and Windows and felt this approach would
be needed regardless.

We'll switch over to using the native keyring by default in the future.
On Linux, we can now fallback to a plaintext store the secret store is
not configured, which is a nice property.

Right now, we store credentials in a TOML file in the uv state
directory. I expect to also read from the uv config directory in the
future, but we don't need it immediately.
2025-09-02 13:16:52 -05:00
Zanie Blue ddf2f5ed8c Remove unused dependencies from `uv auth` preview refactor (#15589) 2025-09-02 13:16:52 -05:00
Zanie Blue 7162085846 Update messaging for `uv auth` (#15573)
To clarify that we are not validating the credentials
2025-09-02 13:16:52 -05:00
Zanie Blue a68007aa61 Update `uv auth` snapshots 2025-09-02 13:16:52 -05:00
Zanie Blue f9e974c1f9 Only allow HTTPS services in login for now (#15559) 2025-09-02 13:16:52 -05:00
Zanie Blue a1cc12af2b Add support for credentials in URLs to `uv auth` (#15554)
Allows cases like `uv auth login https://username:password@example.com`
for coherence with the rest of our interfaces.
2025-09-02 13:16:52 -05:00
Zanie Blue 4ad5ae5e6f Add preview warnings to `native-keyring` usage (#15555)
The refactor here was all done by Claude Code.
2025-09-02 13:16:52 -05:00
Zanie Blue 460ea6e9eb Add `uv auth` commands (`login`, `logout`, and `token`) (#15539)
Picks up the work from

- #14559
- https://github.com/astral-sh/uv/pull/14896

There are some high-level changes from those pull requests

1. We do not stash seen credentials in the keyring automatically
2. We use `auth login` and `auth logout` (for future consistency)
3. We add a `token` command for showing the credential that will be used

As well as many smaller changes to API, messaging, testing, etc.

---------

Co-authored-by: John Mumm <jtfmumm@gmail.com>
2025-09-02 13:16:52 -05:00
Jo Shields f76e0fe5e6
Add `--python-platform riscv64-unknown-linux` to various commands (#15630)
## Summary

We (and I'm sure many others) are currently doing a lot of RISC-V work
in QEMU. It is possible to significantly improve the speed of
Python-related builds by taking care of the environment setup using an
AMD64 `uv` binary (bypassing binfmt/qemu-system emulation).

Some approx numbers from local testing in riscv64 Ubuntu in QEMU:

| Resolver arch | Command | Time |
| --- | --- | --- |
| riscv64 | `pip install --upgrade --break-system-packages
--index-url=https://gitlab.com/api/v4/projects/riseproject%2Fpython%2Fwheel_builder/packages/pypi/simple
openai-harmony` | 15s |
| riscv64 | `uv pip install --upgrade --system --break-system-packages
--index-url=https://gitlab.com/api/v4/projects/riseproject%2Fpython%2Fwheel_builder/packages/pypi/simple
openai-harmony` | 5s |
| amd64 | `uv pip install --python-platform=riscv64-unknown-linux
--upgrade --system --break-system-packages
--index-url=https://gitlab.com/api/v4/projects/riseproject%2Fpython%2Fwheel_builder/packages/pypi/simple
openai-harmony` | 4s |

The numbers from some larger internal packages with deeper dependency
trees are much more pronounced - 3m6 vs 43s vs 8s, in one example.

Manylinux 2.39 is specified, as it's the first (only?) RISC-V manylinux

## Test Plan

Locally, in QEMU.

`$ docker run --platform linux/riscv64 -it ubuntu:latest`, get amd64
libc into LD_LIBRARY_PATH, tests as above
2025-09-02 13:17:30 -04:00
github-actions[bot] 35ce8e1dae
Sync latest Python releases (#15578)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-09-02 14:24:39 +00:00
Michał Górny 4aaf71a38a
Fix `python_module` test failures w/ system Python and installed uv (#15611)
## Summary

Override `sys.base_prefix` when performing `python_module` tests, in
order to prevent `find_uv_bin()` from finding `uv` installed alongside
system Python, and therefore fix test failures on Gentoo.

Fixes #15368

## Test Plan

```
cargo test --profile=fast-build --features git --features pypi --features python --no-default-features --test it python_module
```

Signed-off-by: Michał Górny <mgorny@gentoo.org>
2025-09-02 08:45:14 -05:00
konsti d5bcc0535a
Add test case for status code retries (#15617)
When migrating from the `reqwest_retry` crate, we want to ensure that
the status codes we retry stay the same. This also helps us to
intentionally migrate to a different list later, by enumerating the list
of status codes that are retried.
2025-09-02 15:41:54 +02:00
konsti 19e19d5795
Add error trace to invalid package format (#15626)
In https://github.com/astral-sh/uv/issues/11636, we're getting reports
for installation flakes that report an invalid package format for what
appears to be a network problem. Since we're cutting the error reporting
to the first error message in the chain, we're not reporting the actual
network error underneath it.

This PR displays the whole error chain for invalid package format
errors, so we can debug and eventually catch-and-retry
https://github.com/astral-sh/uv/issues/11636.
2025-09-02 15:22:42 +02:00
Charlie Marsh d70ea34d45
Fix settings rendering for `extra-build-dependencies` (#15622)
## Summary

This was fixed in https://github.com/astral-sh/uv/pull/15161, then
reverted as it regressed the error handling. I've re-applied the change
here, but moved the error handling to the runtime, rather than
parse-time. I think this is slightly worse in that we no longer include
the originating source code snippet, but it at least gives us the
expected behavior :(

Closes https://github.com/astral-sh/uv/issues/15124.
2025-09-02 09:06:21 -04:00
Anthony Wu 5f0ec6805a
Fix simple typo for python that ends in -m (#15624)
## Summary

Fix simple doc typo `s/pythom/python`

## Test Plan

Doc string update, proposing accept as-is.
2025-09-01 21:43:48 -04:00
adamnemecek 9be016f3f8
refactored IndexUrl (#15613) 2025-09-01 09:28:42 +02:00
renovate[bot] 7adc065612
Update zizmorcore/zizmor-action action to v0.1.2 (#15585)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[zizmorcore/zizmor-action](https://redirect.github.com/zizmorcore/zizmor-action)
| action | patch | `v0.1.1` -> `v0.1.2` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>zizmorcore/zizmor-action (zizmorcore/zizmor-action)</summary>

###
[`v0.1.2`](https://redirect.github.com/zizmorcore/zizmor-action/releases/tag/v0.1.2)

[Compare
Source](https://redirect.github.com/zizmorcore/zizmor-action/compare/v0.1.1...v0.1.2)

#### What's Changed

- fix: allow version prefix of `v` by
[@&#8203;martincostello](https://redirect.github.com/martincostello) in
[#&#8203;31](https://redirect.github.com/zizmorcore/zizmor-action/pull/31)

#### New Contributors

- [@&#8203;martincostello](https://redirect.github.com/martincostello)
made their first contribution in
[#&#8203;31](https://redirect.github.com/zizmorcore/zizmor-action/pull/31)

**Full Changelog**:
<https://github.com/zizmorcore/zizmor-action/compare/v0.1.1...v0.1.2>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 06:51:51 +00:00
renovate[bot] 937fadc5c6
Update aws-actions/configure-aws-credentials digest to c5a43c3 (#14923)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| aws-actions/configure-aws-credentials | action | digest | `a159d7b` ->
`c5a43c3` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS40MC4wIiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 08:40:51 +02:00
adamnemecek 36216363eb
Refactored Refresh::combine (#15609) 2025-08-31 12:18:58 -05:00
konsti 22f80ca00d
Retry streaming Python and bin download errors (#15567)
When there is an error during the streaming download and unpack for
Python interpreter and bin installs, we would previously fail, causing a
lot of CI flakes on GitHub Actions.

The problem was that the error is not one of the extended IO errors we
were previously handling, but a regular reqwest error, nested below
layers of errors of other crates processing the stream, including some
IO errors. We now handle nested reqwest errors, too.

This surfaced another problem: Our manual retry loop couldn't inform the
retry middleware that it already performed the limit of retries, and
that the middleware should not retry anymore. While too many retries are
more a problem for debugging than for the user, this causes confusing
error output. To work around this, we disable the retries in the client
and handle all retry errors in our loop.

Fixes https://github.com/astral-sh/uv/issues/14171

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-08-31 15:07:22 +00:00
Charlie Marsh 01e5195ef3
Skip non-existent directories in bytecode compilation (#15608)
## Summary

Closes https://github.com/astral-sh/uv/issues/15577.
2025-08-31 14:53:00 +00:00
Zsolt Dollenstein c2c713e5d2
fix ecosystem::transformers test (#15605)
## Summary
This test is broken because faiss-1.8.0.post1's sdist was deleted(?)
from [pypi](https://pypi.org/project/faiss-cpu/1.8.0.post1/#files).
2025-08-31 11:55:59 +01:00
Zanie Blue a60312a9b8
Improve publish test error when no versions are found (#15594) 2025-08-30 10:35:54 -05:00
renovate[bot] d3c5b3fba7
Update Rust crate tracing-subscriber to v0.3.20 (#15584) 2025-08-29 20:20:58 -04:00
konsti 289ed86e63
Use a global `BaseClientBuilder` (#15548)
Alternative to #15105

Instead of building a `BaseClientBuilder` from `NetworkSettings` each
time we need a client, we instead build a single `BaseClientBuilder` and
pass it around. The `RegistryClientBuilder` then uses
`BaseClientBuilder` exclusively for configuration. This removes a chunk
of copy-and-paste code, and also moves the fallible `retries_from_env`
into a single place

Borrow vs. clone is mostly ad-hoc, we can change it in either direction
if it matters.

Closes #15105
2025-08-29 13:30:51 -05:00
konsti 882c9d9482
Make `cache_index_credentials()` misuse resistant (#15546)
https://github.com/astral-sh/uv/issues/11836#issuecomment-3022735011 was
caused by a missing `cache_index_credentials()` call. This call was
always preceding a registry client builder. We can improve this
situation by caching index credentials in the registry client builder.
2025-08-29 15:11:54 +00:00
Harsh Pratap Singh d877899920
Add `uv tree --show-sizes` to show package sizes (#15531)
<!--
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

Adds the enhancement proposed in #15470. Each package in the dependency
tree now shows its compressed wheel file size, reading the wheel sizes
directly from the lockfile (uv.lock). Doesn't break existing tree
formatting or options. If no wheel size is available, nothing is added.

Now, developers can identify large packages in their dependency tree. 

The tree still shows extras exactly as before, and then appends a size
for the package.

## Test Plan

Manually tested :
```
harsh@fcr-node:~/uv/test-uv-tree-sizes$ ../target/debug/uv tree
Using CPython 3.13.7
warning: No `requires-python` value found in the workspace. Defaulting to `>=3.13`.
Resolved 4 packages in 6ms
pure-python v0.1.0
├── click v8.2.1
└── six v1.17.0
harsh@fcr-node:~/uv/test-uv-tree-sizes$ ../target/debug/uv tree --show-sizes
Using CPython 3.13.7
warning: No `requires-python` value found in the workspace. Defaulting to `>=3.13`.
Resolved 4 packages in 6ms
pure-python v0.1.0
├── click v8.2.1 (99.8KiB)
└── six v1.17.0 (10.8KiB)
```
2025-08-29 08:31:46 -05:00
Charlie Marsh 4168d9b320
Add `--python-platform` to `uv run` and `uv tool` (#15515)
## Summary

Closes https://github.com/astral-sh/uv/issues/11120.
2025-08-29 00:51:39 +00:00
Zanie Blue af856fb883
Bump version to 0.8.14 (#15576) 2025-08-28 21:25:02 +00:00
Zanie Blue bf189c5414
Split the "Authentication" page into sections (#15575)
In preparation for more content for #15570
2025-08-28 15:45:31 -05:00
github-actions[bot] f461438611
Add managed Python distributions for aarch64-musl (#15574)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-08-28 15:44:51 -05:00
Zanie Blue 2fd2e19ce3
Use `thiserror` for keyring error type (#15561) 2025-08-28 08:09:11 -05:00
Zanie Blue 17a86d83ca
Ensure we get the last error from Windows on the same thread (#15564)
Reverts #15552
Closes https://github.com/astral-sh/uv/pull/15562
Closes https://github.com/astral-sh/uv/issues/15558

The `GetLastError` calls must be on the same thread, or we can pull the
wrong last error!
2025-08-27 21:42:37 +00:00
konsti 7d49571336
Rename `Dev` to `Group` internally (#15557)
The "dev" naming is a pre-PEP 735 artifact.
2025-08-27 18:35:43 +00:00
Zanie Blue 960714d4d6
Tweak some documentation headings (#15556)
Just some nits I want to make incremental progress on
2025-08-27 18:16:52 +00:00
Zanie Blue 9b1328af3d
Lock during installs in `uv format` to prevent races (#15551)
Closes https://github.com/astral-sh/uv/issues/15513
2025-08-27 11:42:39 -05:00
konsti 0bde9e4b8f
Read index credentials from env for `uv publish` (#15545)
We were previously missing the
`index_locations.cache_index_credentials()` call in `uv publish` to load
index credentials from the env.

See https://github.com/astral-sh/uv/issues/11836#issuecomment-3022735011
Fixes #11836
2025-08-27 11:19:10 -05:00
Zanie Blue bce30be3a5
Treat a 203 error on credential removal as a missing entry on Windows (#15552)
Attempting to address the failure at
https://github.com/astral-sh/uv/actions/runs/17269216842/job/49009070733?pr=15539
2025-08-27 10:48:12 -05:00
Zsolt Dollenstein 83e42fdd13
Stop setting `CLICOLOR_FORCE=1` when calling build backends (#15472)
## Summary

`CLICOLOR_FORCE` changes the output of underlying build commands, which
messes with wrapper tools trying to parse their output.

Closes #12564, closes #15415.
2025-08-27 16:28:02 +01:00
Charlie Marsh 9108b04642
Support file or directory removal for Windows symlinks (#15543)
## Summary

Similar to https://github.com/rust-lang/cargo/pull/13910.

I think this should close https://github.com/astral-sh/uv/issues/15541
since we're indiscriminately calling `remove_dir` on that dangling
symlink.
2025-08-27 07:43:03 -04:00
konsti db4dfb4cf9
Add logging to the uv build backend (#15533)
Add support for `RUST_LOG` to the uv build backend. While we were
previously using logging statements in the uv build backend, they could
only be shown when when using the direct build fast path through uv, as
there was no tracing subscriber to write log messages out. This means no
debug logging when using the build backend through pip, `python -m
build`, an incompatible version of uv, or any other build frontend; No
option to figure why includes and excludes behave the way they do.

This PR closes this gap by adding a tracing subscriber. The only option
to enable it is `RUST_LOG`, as we don't have a CLI. The formatting style
is the same as for uv, and color is also support in the same way, albeit
only through anstream's support for TTYs and environment variables. We
recommend only `RUST_LOG=uv=debug` and `RUST_LOG=uv=verbose` in the
docs, but this can be used to debug into crates such as `glob`, too.

<img width="1008" height="325" alt="image"
src="https://github.com/user-attachments/assets/d33df219-750b-46a2-b3b4-8895aa137ab9"
/>

**Before**

```
$ pip wheel . -v [...]
Looking in links: /home/konsti/projects/uv/target/wheels/
Processing /home/konsti/projects/uv/scripts/packages/built-by-uv
  Running command pip subprocess to install build dependencies
  Looking in links: /home/konsti/projects/uv/target/wheels/
  Processing /home/konsti/projects/uv/target/wheels/uv_build-0.8.13-py3-none-manylinux_2_39_x86_64.whl
  Installing collected packages: uv_build
  Successfully installed uv_build-0.8.13
  Installing build dependencies ... done
  Running command Getting requirements to build wheel
  Getting requirements to build wheel ... done
  Running command Preparing metadata (pyproject.toml)
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: built-by-uv
  Running command Building wheel for built-by-uv (pyproject.toml)
  Error: Unsupported glob expression in: `tool.uv.build-backend.*-exclude`

  Caused by:
      Invalid character `!` at position 10 in glob: `**/build-*!$§%!½¼²¼³¬!§%$§%.h`. hint: Characters can be escaped with a backslash
  error: subprocess-exited-with-error

  × Building wheel for built-by-uv (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> See above for output.

  note: This error originates from a subprocess, and is likely not a problem with pip.
  full command: /usr/bin/python3 /usr/lib/python3/dist-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py build_wheel /tmp/tmpow1illc9
  cwd: /home/konsti/projects/uv/scripts/packages/built-by-uv
  Building wheel for built-by-uv (pyproject.toml) ... error
  ERROR: Failed building wheel for built-by-uv
Failed to build built-by-uv
ERROR: Failed to build one or more wheels
```

**After**

```
$ RUST_LOG=uv=debug pip wheel . -v [...]
Looking in links: /home/konsti/projects/uv/target/wheels/
Processing /home/konsti/projects/uv/scripts/packages/built-by-uv
  Running command pip subprocess to install build dependencies
  Looking in links: /home/konsti/projects/uv/target/wheels/
  Processing /home/konsti/projects/uv/target/wheels/uv_build-0.8.13-py3-none-manylinux_2_39_x86_64.whl
  Installing collected packages: uv_build
  Successfully installed uv_build-0.8.13
  Installing build dependencies ... done
  Running command Getting requirements to build wheel
  Getting requirements to build wheel ... done
  Running command Preparing metadata (pyproject.toml)
  DEBUG Writing metadata files to /tmp/pip-modern-metadata-l_kh78cj
  DEBUG Found PEP 639 license declarations, using METADATA 2.4
  DEBUG License files match: `LICENSE-APACHE`
  DEBUG License files match: `LICENSE-MIT`
  DEBUG License files match: `third-party-licenses/PEP-401.txt`
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: built-by-uv
  Running command Building wheel for built-by-uv (pyproject.toml)
  DEBUG Checking metadata directory /tmp/pip-modern-metadata-l_kh78cj/built_by_uv-0.1.0.dist-info
  DEBUG Found PEP 639 license declarations, using METADATA 2.4
  DEBUG License files match: `LICENSE-APACHE`
  DEBUG License files match: `LICENSE-MIT`
  DEBUG License files match: `third-party-licenses/PEP-401.txt`
  DEBUG Writing wheel at /tmp/pip-wheel-bu6to9i7/built_by_uv-0.1.0-py3-none-any.whl
  DEBUG Wheel excludes: ["__pycache__", "*.pyc", "*.pyo", "build-*!$§%!½¼²¼³¬!§%$§%.h", "/src/built_by_uv/not-packaged.txt"]
  Error: Unsupported glob expression in: `tool.uv.build-backend.*-exclude`

  Caused by:
      Invalid character `!` at position 10 in glob: `**/build-*!$§%!½¼²¼³¬!§%$§%.h`. hint: Characters can be escaped with a backslash
  error: subprocess-exited-with-error

  × Building wheel for built-by-uv (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> See above for output.

  note: This error originates from a subprocess, and is likely not a problem with pip.
  full command: /usr/bin/python3 /usr/lib/python3/dist-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py build_wheel /tmp/tmpjrxou13a
  cwd: /home/konsti/projects/uv/scripts/packages/built-by-uv
  Building wheel for built-by-uv (pyproject.toml) ... error
  ERROR: Failed building wheel for built-by-uv
Failed to build built-by-uv
ERROR: Failed to build one or more wheels
```

(There is no color in the above uv log statements, as pip doesn't
register as a TTY)

Fixes #12723
2025-08-27 09:14:00 +02:00
Charlie Marsh 2eb1c725aa
Fix failing virtualenv test on Windows (#15542)
Closes https://github.com/astral-sh/uv/issues/15540.
2025-08-26 23:14:13 -04:00
Charlie Marsh 9eb5fc240c
Refuse to remove non-virtual environments in `uv venv` (#15538)
## Summary

Closes https://github.com/astral-sh/uv/issues/15474.
2025-08-26 13:26:20 -04:00
Charlie Marsh 0c674619b2
Avoid erroring when creating `venv` in current working directory (#15537)
## Summary

A strange use-case, but the current behavior is definitely a bug.

Part of https://github.com/astral-sh/uv/issues/15474.
2025-08-26 12:43:29 -04:00
pythonweb2 2ca8f6d250
update uninstall docs to mention uvw[.exe] needs to be removed (#15536)
<!--
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 to the docs

## Test Plan

CI
2025-08-26 11:22:38 -05:00
Charlie Marsh b2c8f5ef68
Add `rkyv` implementation for Core Metadata (#15532)
## Summary

Enables us to store Core Metadata in zero-copy format.
2025-08-26 10:22:50 -04:00
Charlie Marsh 615e076beb
Rename some Simple API structs (#15530)
## Summary

For clarity.
2025-08-26 13:55:58 +00:00
konsti 439395dadf
Fix unused code warning around `entry_from_constructor` (#15525) 2025-08-26 13:30:46 +02:00
Harsh Pratap Singh 39537f4372
format {version} on failure (#15527)
<!--
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

Fixes #15512 

## Test Plan

Manually tested :
```
~/uv/target/release/uv format --version 999.999.999 --preview-features format
error: Failed to install ruff 999.999.999
  Caused by: Failed to download from: https://github.com/astral-sh/ruff/releases/download/999.999.999/ruff-x86_64-unknown-linux-gnu.tar.gz
  Caused by: HTTP status client error (404 Not Found) for url (https://github.com/astral-sh/ruff/releases/download/999.999.999/ruff-x86_64-unknown-linux-gnu.tar.gz)
  ```
2025-08-26 11:26:10 +00:00
renovate[bot] 8c21baf9f1
Migrate renovate config (#15524)
The Renovate config in this repository needs migrating. Typically this
is because one or more configuration options you are using have been
renamed.

You don't need to merge this PR right away, because Renovate will
continue to migrate these fields internally each time it runs. But later
some of these fields may be fully deprecated and the migrations removed.
So it's a good idea to merge this migration PR soon.



#### [PLEASE
NOTE](https://docs.renovatebot.com/configuration-options#configmigration):
JSON5 config file migrated! All comments & trailing commas were removed.

🔕 **Ignore**: Close this PR and you won't be reminded about config
migration again, but one day your current config may no longer be valid.

 Got questions? Does something look wrong to you? Please don't hesitate
to [request help
here](https://redirect.github.com/renovatebot/renovate/discussions).


---

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

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: konstin <konstin@mailbox.org>
2025-08-26 09:04:11 +00:00
renovate[bot] e2b3ec2843
Migrate renovate config (#15275)
The Renovate config in this repository needs migrating. Typically this
is because one or more configuration options you are using have been
renamed.

You don't need to merge this PR right away, because Renovate will
continue to migrate these fields internally each time it runs. But later
some of these fields may be fully deprecated and the migrations removed.
So it's a good idea to merge this migration PR soon.



#### [PLEASE
NOTE](https://docs.renovatebot.com/configuration-options#configmigration):
JSON5 config file migrated! All comments & trailing commas were removed.

🔕 **Ignore**: Close this PR and you won't be reminded about config
migration again, but one day your current config may no longer be valid.

 Got questions? Does something look wrong to you? Please don't hesitate
to [request help
here](https://redirect.github.com/renovatebot/renovate/discussions).


---

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

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: konstin <konstin@mailbox.org>
2025-08-26 08:30:23 +00:00
github-actions[bot] 75741082f7
Update Pyodide build number to 0.28.2 (#15523)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-08-26 00:58:20 +00:00
Charlie Marsh b723129c7c
Clear discovered interpreters when creating virtual environment (#15522)
## Summary

Closes https://github.com/astral-sh/uv/issues/15518.
2025-08-25 20:24:24 -04:00
Zanie Blue 9b8d6989d4
Allow pinning managed Python versions to specific build versions (#15314)
Allows pinning the Python build version via environment variables, e.g.,
`UV_PYTHON_CPYTHON_BUILD=...`. Each variable is implementation specific,
because they use different versioning schemes.

Updates the Python download metadata to include a `build` string, so we
can filter downloads by the pin. Writes the build version to a file in
the managed install, e.g., `cpython-3.10.18-macos-aarch64-none/BUILD`,
so we can filter installed versions by the pin.

Some important follow-up here:

- Include the build version in not found errors (when pinned)
- Automatically use a remote list of Python downloads to satisfy build
versions not present in the latest embedded download metadata

Some less important follow-ups to consider:

- Allow using ranges for build version pins
2025-08-25 16:25:05 -05:00
Charlie Marsh b6f1fb7d3f
Respect `[pip]` settings for `uv pip check` (#15514)
## Summary

Oversight from #15486.
2025-08-25 13:47:30 +00:00
Charlie Marsh ef9a332364
Cache `WHEEL` and `METADATA` reads in installed distributions (#15489)
## Summary

Uses interior mutability to cache the reads. This follows the pattern we
use for reading the platform tags in `Interpreter::tags`.
2025-08-25 13:40:20 +00:00
Charlie Marsh be4d5b72aa
Reject already-installed wheels that don't match the target platform (#15484)
## Summary

We've received several requests to validate that installed wheels match
the current Python platform. This isn't _super_ common, since it
requires that your platform changes in some meaningful way (e.g., you
switch from x86 to ARM), though in practice, it sounds like it _can_
happen in HPC environments. This seems like a good thing to do
regardless, so we now validate that the tags (as recoded in `WHEEL`) are
consistent with the current platform during installs.

Closes https://github.com/astral-sh/uv/issues/15035.
2025-08-25 09:20:54 -04:00
renovate[bot] 563adb8904
Update google-github-actions/setup-gcloud digest to 26f734c (#15490)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| google-github-actions/setup-gcloud | action | digest | `cb1e50a` ->
`26f734c` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 15:09:14 +02:00
Pepe Osca 6e802873cc
fix(tests): Refactor uv format tests (#15468)
Closes #15458

## Summary

Refactor uv format tests to reduce noise.
2025-08-25 07:40:20 -05:00
Charlie Marsh 7f1a464216
Remove redundant STOPSHIPs (#15511)
## Summary

I accidentally merged these from a partial refactor.
2025-08-25 08:34:47 -04:00
renovate[bot] b7d304d2bf
Update Rust crate syn to v2.0.106 (#15505)
This PR contains the following updates:

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

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

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

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

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

- Replace `~const` syntax with `[const]` conditionally const syntax in
trait bounds
([#&#8203;1896](https://redirect.github.com/dtolnay/syn/issues/1896),
[rust-lang/rust#139858](https://redirect.github.com/rust-lang/rust/pull/139858))
- Support conditionally const impl Trait types
([#&#8203;1897](https://redirect.github.com/dtolnay/syn/issues/1897))
- Reject polarity modifier and lifetime binder used in the same trait
bound
([#&#8203;1899](https://redirect.github.com/dtolnay/syn/issues/1899),
[rust-lang/rust#127054](https://redirect.github.com/rust-lang/rust/pull/127054))
- Parse const trait bounds with bound lifetimes
([#&#8203;1902](https://redirect.github.com/dtolnay/syn/issues/1902))
- Parse bound lifetimes with lifetime bounds
([#&#8203;1903](https://redirect.github.com/dtolnay/syn/issues/1903))
- Allow type parameters and const parameters in trait bounds and generic
closures
([#&#8203;1904](https://redirect.github.com/dtolnay/syn/issues/1904),
[#&#8203;1907](https://redirect.github.com/dtolnay/syn/issues/1907),
[#&#8203;1908](https://redirect.github.com/dtolnay/syn/issues/1908),
[#&#8203;1909](https://redirect.github.com/dtolnay/syn/issues/1909))

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

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

- Disallow "negative" inherent impls like `impl !T {}`
([#&#8203;1881](https://redirect.github.com/dtolnay/syn/issues/1881),
[rust-lang/rust#144386](https://redirect.github.com/rust-lang/rust/pull/144386))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 10:46:07 +00:00
renovate[bot] 66b3fa6ec2
Update Rust crate serde-untagged to v0.1.8 (#15504)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [serde-untagged](https://redirect.github.com/dtolnay/serde-untagged) |
workspace.dependencies | patch | `0.1.7` -> `0.1.8` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>dtolnay/serde-untagged (serde-untagged)</summary>

###
[`v0.1.8`](https://redirect.github.com/dtolnay/serde-untagged/releases/tag/0.1.8)

[Compare
Source](https://redirect.github.com/dtolnay/serde-untagged/compare/0.1.7...0.1.8)

- Add support for `visit_none`
([#&#8203;10](https://redirect.github.com/dtolnay/serde-untagged/issues/10),
thanks [@&#8203;epage](https://redirect.github.com/epage))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 12:36:16 +02:00
renovate[bot] b053ad1f9d
Update Rust crate thiserror to v2.0.16 (#15506)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [thiserror](https://redirect.github.com/dtolnay/thiserror) |
workspace.dependencies | patch | `2.0.12` -> `2.0.16` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

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

###
[`v2.0.16`](https://redirect.github.com/dtolnay/thiserror/releases/tag/2.0.16)

[Compare
Source](https://redirect.github.com/dtolnay/thiserror/compare/2.0.15...2.0.16)

- Add to "no-std" crates.io category
([#&#8203;429](https://redirect.github.com/dtolnay/thiserror/issues/429))

###
[`v2.0.15`](https://redirect.github.com/dtolnay/thiserror/releases/tag/2.0.15)

[Compare
Source](https://redirect.github.com/dtolnay/thiserror/compare/2.0.14...2.0.15)

- Prevent `Error::provide` API becoming unavailable from a future new
compiler lint
([#&#8203;427](https://redirect.github.com/dtolnay/thiserror/issues/427))

###
[`v2.0.14`](https://redirect.github.com/dtolnay/thiserror/releases/tag/2.0.14)

[Compare
Source](https://redirect.github.com/dtolnay/thiserror/compare/2.0.13...2.0.14)

- Allow build-script cleanup failure with NFSv3 output directory to be
non-fatal
([#&#8203;426](https://redirect.github.com/dtolnay/thiserror/issues/426))

###
[`v2.0.13`](https://redirect.github.com/dtolnay/thiserror/releases/tag/2.0.13)

[Compare
Source](https://redirect.github.com/dtolnay/thiserror/compare/2.0.12...2.0.13)

- Documentation improvements

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 12:34:29 +02:00
renovate[bot] 6ce0f9fa51
Update Rust crate tokio to v1.47.1 (#15507)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [tokio](https://tokio.rs)
([source](https://redirect.github.com/tokio-rs/tokio)) |
workspace.dependencies | patch | `1.47.0` -> `1.47.1` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

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

###
[`v1.47.1`](https://redirect.github.com/tokio-rs/tokio/releases/tag/tokio-1.47.1):
Tokio v1.47.1

[Compare
Source](https://redirect.github.com/tokio-rs/tokio/compare/tokio-1.47.0...tokio-1.47.1)

### 1.47.1 (August 1st, 2025)

##### Fixed

- process: fix panic from spurious pidfd wakeup ([#&#8203;7494])
- sync: fix broken link of Python `asyncio.Event` in `SetOnce` docs
([#&#8203;7485])

[#&#8203;7485]: https://redirect.github.com/tokio-rs/tokio/pull/7485

[#&#8203;7494]: https://redirect.github.com/tokio-rs/tokio/pull/7494

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 12:33:51 +02:00
renovate[bot] 100ab1e325
Update Rust crate serde_json to v1.0.143 (#15503)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [serde_json](https://redirect.github.com/serde-rs/json) |
workspace.dependencies | patch | `1.0.142` -> `1.0.143` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>serde-rs/json (serde_json)</summary>

###
[`v1.0.143`](https://redirect.github.com/serde-rs/json/releases/tag/v1.0.143)

[Compare
Source](https://redirect.github.com/serde-rs/json/compare/v1.0.142...v1.0.143)

- Implement Clone and Debug for serde\_json::Map iterators
([#&#8203;1264](https://redirect.github.com/serde-rs/json/issues/1264),
thanks [@&#8203;xlambein](https://redirect.github.com/xlambein))
- Implement Default for CompactFormatter
([#&#8203;1268](https://redirect.github.com/serde-rs/json/issues/1268),
thanks [@&#8203;SOF3](https://redirect.github.com/SOF3))
- Implement FromStr for serde\_json::Map
([#&#8203;1271](https://redirect.github.com/serde-rs/json/issues/1271),
thanks
[@&#8203;mickvangelderen](https://redirect.github.com/mickvangelderen))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 10:38:57 +02:00
renovate[bot] d649f9ca0f
Update Rust crate proc-macro2 to v1.0.101 (#15499) 2025-08-25 02:57:48 +00:00
renovate[bot] 5e07a244ba
Update Rust crate regex-automata to v0.4.10 (#15501) 2025-08-25 02:57:19 +00:00
renovate[bot] d08951c15b
Update Rust crate regex to v1.11.2 (#15500) 2025-08-25 02:57:11 +00:00
renovate[bot] 79341574c5
Update Rust crate percent-encoding to v2.3.2 (#15498) 2025-08-25 02:52:46 +00:00
renovate[bot] 6d8184b3c0
Update Rust crate goblin to v0.10.1 (#15497) 2025-08-25 02:40:05 +00:00
renovate[bot] 124bacc20e
Update Rust crate glob to v0.3.3 (#15496) 2025-08-25 02:18:20 +00:00
renovate[bot] e85fd45816
Update Rust crate boxcar to v0.2.14 (#15493) 2025-08-25 02:12:14 +00:00
renovate[bot] af4fb55448
Update Rust crate clap to v4.5.45 (#15494) 2025-08-25 02:10:02 +00:00
renovate[bot] 8a1e979f94
Update Rust crate filetime to v0.2.26 (#15495) 2025-08-25 02:09:50 +00:00
renovate[bot] 12d06f756e
Update Rust crate bitflags to v2.9.3 (#15492) 2025-08-25 02:08:23 +00:00
renovate[bot] 6b6459110c
Update pre-commit dependencies (#15491) 2025-08-25 02:07:24 +00:00
Charlie Marsh d19d0e26aa
Add `--python-platform` to `uv pip check` (#15486)
## Summary

I want this to facilitate some testing for
https://github.com/astral-sh/uv/issues/15035.
2025-08-24 14:14:42 -04:00
Charlie Marsh 99f1f4fee4
Move wheel tag methods to `WheelTag` (#15487)
## Summary

Lowering these out of `WheelFilename`.
2025-08-24 14:00:08 -04:00
Charlie Marsh 6d874b1a25
Move WHEEL file parsing into a struct (#15483)
## Summary

No functional changes, but I need to add more behavior here for
https://github.com/astral-sh/uv/issues/15035, so seems nice to do this
separately.
2025-08-24 15:53:12 +00:00
Charlie Marsh f16760e10a
Cache PyTorch wheels by default (#15481)
## Summary

After chatting with the PyTorch team, it looks like some number of
wheels were accidentally uploaded with
`no-cache,no-store,must-revalidate` due to
https://github.com/pytorch/pytorch/pull/149218. They're going to correct
this for the respective wheels. I've encouraged them to set an immutable
caching header for these files, and it might happen. But even if this
isn't set, by default we only allow these wheels to be cached for 600s,
since the other wheels don't include a `Cache-Control` header at all
(but do include a `Last-Modified`, so we cache based on our heuristic:
`Freshness lifetime heuristically assumed because of presence of
last-modified header: 600s`). This probably leads to tons of unnecessary
downloads for users over time. Andrey from the PyTorch team agreed that
we should do this.

Closes https://github.com/astral-sh/uv/issues/15480.
2025-08-24 11:41:17 -04:00
Charlie Marsh ac84f5aedc
Move preview features into a dedicated crate (#15482)
## Summary

This is causing some cyclic dependencies issues for me, because these
can be used in virtually _any_ crate (like `uv-install-wheel`), which
then means that all of `uv-configuration` becomes a dependency, etc. I
think this should be a leaf crate so that we can safely depend on it
anywhere.
2025-08-24 09:55:30 -04:00
Giuseppe Di Giorno f1647838ae
Move Resolver to new Internals section in the Reference (#15465)
## Summary

Move the Resolver reference into a new Internals section in the
reference. Add the new nav item, fix internal linking to the new path,
fix server side redirect to the new path for external traffic via
redirect_maps, fix non existent anchor in
"docs/concepts/projects/dependencies.md"

Closes #15412
2025-08-22 19:26:16 -05:00
Jorge Hermo e6def43110
Run the formatter in the project root instead of the working directory (#15440)
Closes https://github.com/astral-sh/uv/issues/15430

This PR is a branch from https://github.com/astral-sh/uv/pull/15438,
opening it as a draft until that is merged to main and I'll merge main
with this branch to have a cleaner diff

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-08-22 15:09:42 -05:00
Xavi Simpson 6e2fbbc30f
Add `--no-install-local` option to `uv sync`, `uv add` and `uv export` (#15328)
<!--
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? -->

Closes #14866. Adds a `no-install-local` flag to the sync and export
commands that excludes locally defined packages from being installed.

This helps with if you're caching your virtual environment. You can
exclude local packages since they're more likely to change between
builds.

## Test Plan

snapshot test: `sync::no_install_local`
CI

## Notes
I made an `InstallOptions` struct to avoid a crate isolation issue I was
running into while implementing.

Thanks for maintaining this project!
2025-08-22 11:31:52 -05:00
KotlinIsland f231d0f2a5
Fix github doc highlight lines (#15443)
<!--
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

i noticed some of the line highlights are wrong in the docs

## Test Plan

visual verification
2025-08-22 11:28:41 -05:00
Jorge Hermo a2d97ae9b1
Respect `--project` in `uv format` (#15438)
Closes https://github.com/astral-sh/uv/issues/15431

This is my first contribution, so I'm sorry if I miss something! Didn't
update any documentation nor added tests. Tell me if this is needed for
this feature 😄

I will try to address https://github.com/astral-sh/uv/issues/15430 in
another PR
2025-08-22 11:27:34 -05:00
Charlie Marsh 93630a8f79
Include cycle error message in `uv pip` CLI (#15453)
## Summary

The use of `format!` was dropping the error chain.

Closes https://github.com/astral-sh/uv/issues/15397.
2025-08-22 15:30:33 +01:00
Charlie Marsh 088c908cda
Allow more trailing null bytes in zip files (#15452)
## Summary

There isn't any risk here, and we have reports of at least one zip file
with more than one (but fewer than, e.g., 10) null bytes.

Closes https://github.com/astral-sh/uv/issues/15451.
2025-08-22 14:50:07 +01:00
Charlie Marsh 3e34aee63e
Avoid introducing unnecessary system dependency on CUDA (#15449)
## Summary

Packages like `triton` should come from the PyTorch index, but they
don't actually vary across (e.g.) the `cu128` or `cu129` indexes.

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

## Test Plan

Validate that the following pins to `cu128`, rather than `cpu`:

```
echo "vllm\ntorch==2.7.1+cu128" | cargo run pip compile --torch-backend=auto --extra-index-url https://wheels.vllm.ai/b2f6c247a9b84556a8ea0e75bb4a2db765ff3315 - --python-platform linux --python-version 3.13 -v
```
2025-08-22 12:06:23 +01:00
renovate[bot] 965d5b338f
Update Rust crate cargo-util to v0.2.22 (#15211) 2025-08-22 10:17:36 +00:00
Charlie Marsh b826c8db04
Bump MSRV to 1.87 (#15441)
## Summary

Per our N-2 policy (1.89 is latest).
2025-08-22 10:21:24 +01:00
Charlie Marsh 35a8dd514e
Import `PackageName` from `uv-normalize` (#15439)
## Summary

This might be unintentional? `PackageName` is re-exported from
`uv-pep508`, so some crates import it from there.
2025-08-21 23:15:40 +00:00
Charlie Marsh 91d66e0bd8
Add file-to-CLI overrides for build isolation configuration (#15437)
## Summary

Like #15395, but `--no-build-isolation`.
2025-08-21 23:54:39 +01:00
Charlie Marsh d1e0c26678
Add an environment variable for `UV_ISOLATED` (#15428)
Closes https://github.com/astral-sh/uv/issues/15427.
2025-08-21 23:39:04 +01:00
Pepe Osca c4f2da729c
fix(tests): Ensure show_settings tests don't load user config (#15434)
## Summary

Ensure show_settings tests set XDG_CONFIG_HOME to avoid loading user
configuration

Closes #15403
2025-08-21 17:10:11 -05:00
Zanie Blue f210db0907
Fix format doc mistake (#15433) 2025-08-21 21:12:06 +00:00
Charlie Marsh 330b54b173
Add file-to-CLI overrides for reinstall configuration (#15426)
## Summary

This is like #15395, but for `--reinstall` and `--reinstall-package`.
2025-08-21 19:03:03 +00:00
Zanie Blue ede75fe628
Bump version to 0.8.13 (#15423) 2025-08-21 13:52:39 -05:00
Charlie Marsh e4de538dae
Remove `UpgradeSelection` struct (#15422)
## Summary

After #15395, I realized that we didn't actually need a separate struct
for this since we now pass it around as an `Option`. (The key change
from #15395 is that when combining, we treat the options as a single
unit.)
2025-08-21 18:35:07 +00:00
Zsolt Dollenstein 11633549fd
extra-build-dependencies: Allow version specifiers if match-runtime is explicitly false (#15420)
## Summary

`match-runtime` can be explicitly specified, and if it's `false` it
should behave the same way as if it's omitted.

## Test Plan

Added snapshot test
2025-08-21 17:50:15 +01:00
konsti 25bedeadea
Stop leaking strings in Python downloads (#15418)
We should not unnecessarily leak memory. Instead, we follow the general
patterns and use `Cow` for strings that can be from either a static or a
dynamic source.
2025-08-21 17:54:39 +02:00
Charlie Marsh 0397595e53
Treat `--upgrade-package` on the command-line as overriding `upgrade = false` in configuration (#15395)
## Summary

Right now, if you put `upgrade = false` in a `uv.toml`, then pass
`--upgrade-package numpy` on the CLI, we won't upgrade NumPy. This PR
fixes that interaction by ensuring that when we "combine", we look at
those arguments holistically (i.e., we bundle `upgrade` and
`upgrade-package` into a single struct, which then goes through the
`.combine` logic), rather than combining `upgrade` and `upgrade-package`
independently.

If approved, I then need to add the same thing for `no-build-isolation`,
`reinstall`, `no-build`, and `no-binary`.
2025-08-21 16:20:55 +01:00
youkaichao b950453891
Add CUDA 12.9 backend (#15416)
## Summary

Add torch cuda 12.9 backend

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

## Test Plan

<!-- How was it tested? -->

---------

Signed-off-by: youkaichao <youkaichao@gmail.com>
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-08-21 16:01:02 +01:00
Charlie Marsh 61e2308806
Add `triton` to `torch-backend` manifest (#15405)
## Summary

The PyTorch team publishes ARM Linux wheels for `triton` to the PyTorch
index, which aren't available on PyPI.

## Test Plan

```
echo "torch" | cargo run pip compile - --torch-backend=cu128 --python-platform aarch64-unknown-linux-gnu --python-version 3.13
```

Previously failed because it couldn't find a compatible `triton` wheel.
2025-08-21 13:23:12 +00:00
Zanie Blue e31f000da7
Add an experimental `uv format` command (#15017)
As a frontend to Ruff's formatter.

There are some interesting choices here, some of which may just be
temporary:

1. We pin a default version of Ruff, so `uv format` is stable for a
given uv version
2. We install Ruff from GitHub instead of PyPI, which means we don't
need a Python interpreter or environment
3. We do not read the Ruff version from the dependency tree

See https://github.com/astral-sh/ruff/pull/19665 for a prototype of the
LSP integration.
2025-08-21 06:33:18 -05:00
Anuraag (Rag) Agrawal 8d6ea3f2ea
Fix uv_build wheel hashes (#15400)
<!--
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? -->

Currently record hashes are the hex encoded sha-256 sum. However,
they're supposed to be urlsafe-base64-nopad.


https://packaging.python.org/en/latest/specifications/recording-installed-packages/#the-record-file

Fixes #15398 

## Test Plan

<!-- How was it tested? -->

Build any wheel

```
uv build --wheel
```

Unpack the wheel

```
uvx wheel unpack dist/*.whl
```

Before this change, it will fail with a hash mismatch. I could confirm
with a local build that now the wheel can be unpacked with the `wheel`
command. While I don't enable hash checking when syncing, presumably it
would also currently fail.
2025-08-21 10:54:21 +02:00
github-actions[bot] f1a023d384
Update Pyodide to 0.28.2 (#15385)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-08-21 01:04:15 +00:00
Charlie Marsh fcc8cfc0bc
Add `uv sync` tests for upgrade CLI interaction (#15396)
Like #15393 but for `uv sync`, since the settings logic is a bit
different than `uv pip`.
2025-08-20 18:31:18 +01:00
Charlie Marsh 1176e5e00c
Retain IO error source in extraction (#15388)
## Summary

This was accidentally lost in 4f4492dd53.
2025-08-20 16:27:26 +01:00
Charlie Marsh 02e7a8216b
Add tests to track `upgrade` and `upgrade-package` interactions (#15393)
## Summary

There are some non-ideal behaviors here, but want to start by tacking
them.
2025-08-20 16:27:18 +01:00
Charlie Marsh 08233c2ac7
Avoid panicking when resolver returns stale distributions (#15389)
## Summary

I've written a reasonably-long comment to explain what's going on here.
We should fix this, but it's better to continue using a
potentially-stale distribution than to panic.

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

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-08-20 14:04:39 +00:00
Zsolt Dollenstein 5271ce5e24
Fix typo in extra-build-dependencies docs (#15390)
## Summary

Smol but important typo.
2025-08-20 12:39:59 +01:00
Zanie Blue 249154ee08
Restore DockerHub publishing (#15381)
- **Revert "Skip DockerHub annotations too (#15366)"**
- **Revert "Disable DockerHub publish for extended images in 0.8.12
(#15365)"**

Closes https://github.com/astral-sh/uv/issues/15367
2025-08-19 11:45:41 -05:00
konsti 4fbf3097d8
Update astral-tokio-tar to v0.5.3 (#15382)
This is not actually an update, we're just updating the commit tag to a
crates.io release for
https://github.com/astral-sh/uv/pull/15202#issuecomment-3200209608
2025-08-19 15:49:01 +00:00
Charlie Marsh 68f33e8fef
Add `--no-install-*` arguments to `uv add` (#15375)
## Summary

Closes https://github.com/astral-sh/uv/issues/15369.
2025-08-19 15:32:56 +01:00
Charlie Marsh c58192eebe
Initialize Git prior to reading author (#15377)
## Summary

Closes https://github.com/astral-sh/uv/issues/15372.
2025-08-19 08:37:20 -05:00
Hood Chatham 075291f23e
Update pyodide-build to 0.30.7 (#15373)
And remove `--prerelease=allow`. We can remove this once I get the
jsdelivr index set up.
2025-08-19 15:33:38 +02:00
Michał Górny ec27ab6033
Mark `find_uv_bin_py38` test as requiring `python-eol` (#15379)
## Summary

Mark `find_uv_bin_py38` test as requiring `python-eol`. Resolves one of
the issues reported in #15368.

## Test Plan

```
cargo test --profile=dev --features git --features pypi --features python --no-default-features
```

(without Python 3.8 installed)

Signed-off-by: Michał Górny <mgorny@gentoo.org>
2025-08-19 13:06:03 +00:00
konsti 7a9e07a98e
Block `tokio::fs` symbols (#15374)
Inspired by #15017, mirror the blocking of `std::fs` symbols in favor of
`fs_err` and block `tokio::fs` symbols in favor of `fs_err::tokio`.
2025-08-19 13:13:46 +02:00
Michael Richter f98cb7dece
Update libc to fix build on AIX #15128 (#15376)
<!--
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 libc dependencies to fix #15128

Note: The whole build on AIX still doesn't work but libc works now.

## Test Plan

<!-- How was it tested? -->
Build on AIX 7300-03-00-2446:

```
bash-5.2$ cargo build --offline
   Compiling proc-macro2 v1.0.95
   Compiling unicode-ident v1.0.18
   Compiling libc v0.2.175       # worked
   Compiling cfg-if v1.0.1
   Compiling memchr v2.7.5
   Compiling serde v1.0.219
   Compiling pin-project-lite v0.2.16
   Compiling shlex v1.3.0
   Compiling itoa v1.0.15
   Compiling once_cell v1.21.3
   Compiling smallvec v1.15.1
   Compiling bytes v1.10.1
   Compiling stable_deref_trait v1.2.0
   Compiling ryu v1.0.20
   Compiling tracing-core v0.1.34
   Compiling bitflags v2.9.1
   Compiling foldhash v0.1.5
   Compiling allocator-api2 v0.2.21
   Compiling equivalent v1.0.2
   Compiling litemap v0.8.0
   Compiling quote v1.0.40
   Compiling jobserver v0.1.33     # worked too (depends on libc)
   Compiling hashbrown v0.15.5
   Compiling syn v2.0.104
   Compiling serde_json v1.0.142
   Compiling cc v1.2.30
   Compiling autocfg v1.5.0
   Compiling writeable v0.6.1
   Compiling signal-hook-registry v1.4.5
   Compiling mio v1.0.4
   Compiling socket2 v0.6.0
   Compiling icu_normalizer_data v2.0.0
   Compiling icu_properties_data v2.0.1
   Compiling percent-encoding v2.3.1
   Compiling form_urlencoded v1.2.1
[...]
```

Co-authored-by: Richter, Michael (G-GP-A/4) <michael.richter@vwgis.de>
2025-08-19 10:27:43 +00:00
Zanie Blue 36151df0e4
Skip DockerHub annotations too (#15366) 2025-08-18 18:36:31 -05:00
Zanie Blue f77d78a8ad
Disable DockerHub publish for extended images in 0.8.12 (#15365) 2025-08-18 18:21:39 -05:00
samypr100 5b088e9430
chore: add trixie base images and alpine 3.22 support (#15351)
## Summary

Follow up to
https://github.com/astral-sh/uv/pull/15269#issuecomment-3194000772

Enables the following additional images to be published
* buildpack-deps:trixie
* debian:trixie-slim
* alpine:3.22

## Test Plan

Existing Tests. The newly added images were checked manually.
2025-08-18 17:55:42 -05:00
Zanie Blue f784c334cf
Bump version to 0.8.12 (#15364) 2025-08-18 22:14:51 +00:00
konsti 4f4492dd53
Add hint for venv in source distribution error (#15202)
Venvs should not be in source distributions, and on Unix, we now reject
them for having a link outside the source directory. This PR adds a hint
for that since users were confused (#15096).

In the process, we're differentiating IO errors for format error for
uncompression generally.

Fixes #15096
2025-08-18 22:07:57 +00:00
github-actions[bot] 724e4c7e5e
Sync latest Python releases (#15363)
Automated update for Python releases.

---------

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-08-18 21:49:00 +00:00
Charlie Marsh fa3c20a177
Document improvements to build-isolation setups (#15326)
## Summary

There's a lot here (maybe it should go somewhere else?), but this is a
complex topic and I wanted to include a lot of copy-pasteable examples
for common cases.

Closes https://github.com/astral-sh/uv/issues/10694.
Closes https://github.com/astral-sh/uv/issues/13959.
Closes https://github.com/astral-sh/uv/issues/15248.
Closes https://github.com/astral-sh/uv/issues/15316.
2025-08-18 16:15:35 -05:00
Zanie Blue 0c825c5609
Use Depot macOS runner in CI (#15358) 2025-08-18 14:24:58 -05:00
renovate[bot] bbef948b6a
Update pre-commit dependencies (#15338)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit)
| repository | patch | `v0.12.8` -> `v0.12.9` |
| [crate-ci/typos](https://redirect.github.com/crate-ci/typos) |
repository | patch | `v1.35.3` -> `v1.35.4` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

<details>
<summary>astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit)</summary>

###
[`v0.12.9`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.12.9)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.12.8...v0.12.9)

See: https://github.com/astral-sh/ruff/releases/tag/0.12.9

</details>

<details>
<summary>crate-ci/typos (crate-ci/typos)</summary>

###
[`v1.35.4`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.35.3...v1.35.4)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.4...HEAD

[1.35.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...v1.35.4

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.25...v1.13.26

[1.13.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.24...v1.13.25

[1.13.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.23...v1.13.24

[1.13.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.22...v1.13.23

[1.13.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.21...v1.13.22

[1.13.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.20...v1.13.21

[1.13.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.19...v1.13.20

[1.13.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.18...v1.13.19

[1.13.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.17...v1.13.18

[1.13.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.16...v1.13.17

[1.13.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.15...v1.13.16

[1.13.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.14...v1.13.15

[1.13.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.13...v1.13.14

[1.13.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.12...v1.13.13

[1.13.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.11...v1.13.12

[1.13.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.10...v1.13.11

[1.13.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.9...v1.13.10

[1.13.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.8...v1.13.9

[1.13.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.7...v1.13.8

[1.13.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.6...v1.13.7

[1.13.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.5...v1.13.6

[1.13.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.4...v1.13.5

[1.13.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.3...v1.13.4

[1.13.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.2...v1.13.3

[1.13.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.1...v1.13.2

[1.13.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.0...v1.13.1

[1.13.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.14...v1.13.0

[1.12.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.13...v1.12.14

[1.12.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.12...v1.12.13

[1.12.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.11...v1.12.12

[1.12.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.10...v1.12.11

[1.12.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.9...v1.12.10

[1.12.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.8...v1.12.9

[1.12.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.7...v1.12.8

[1.12.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.6...v1.12.7

[1.12.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.5...v1.12.6

[1.12.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.4...v1.12.5

[1.12.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.3...v1.12.4

[1.12.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.2...v1.12.3

[1.12.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.1...v1.12.2

[1.12.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.0...v1.12.1

[1.12.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.5...v1.12.0

[1.11.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.4...v1.11.5

[1.11.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.3...v1.11.4

[1.11.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.2...v1.11.3

[1.11.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.1...v1.11.2

[1.11.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.0...v1.11.1

[1.11.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.3...v1.11.0

[1.10.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.2...v1.10.3

[1.10.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.1...v1.10.2

[1.10.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.0...v1.10.1

[1.10.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.9.0...v1.10.0

[1.9.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.1...v1.9.0

[1.8.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.0...v1.8.1

[1.8.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.3...v1.8.0

[1.7.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.2...v1.7.3

[1.7.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.1...v1.7.2

[1.7.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.0...v1.7.1

[1.7.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.6.0...v1.7.0

[1.6.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.5.0...v1.6.0

[1.5.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.1...v1.5.0

[1.4.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.0...v1.4.1

[1.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.9...v1.4.0

[1.3.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.8...v1.3.9

[1.3.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.7...v1.3.8

[1.3.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.6...v1.3.7

[1.3.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.5...v1.3.6

[1.3.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.4...v1.3.5

[1.3.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.3...v1.3.4

[1.3.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.2...v1.3.3

[1.3.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.1...v1.3.2

[1.3.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.0...v1.3.1

[1.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.1...v1.3.0

[1.2.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.0...v1.2.1

[1.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.9...v1.2.0

[1.1.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.8...v1.1.9

[1.1.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.7...v1.1.8

[1.1.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.6...v1.1.7

[1.1.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.5...v1.1.6

[1.1.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.4...v1.1.5

[1.1.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.3...v1.1.4

[1.1.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.2...v1.1.3

[1.1.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.1...v1.1.2

[1.1.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.0...v1.1.1

[1.1.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.11...v1.1.0

[1.0.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.10...v1.0.11

[1.0.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.9...v1.0.10

[1.0.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.8...v1.0.9

[1.0.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.7...v1.0.8

[1.0.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.6...v1.0.7

[1.0.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.5...v1.0.6

[1.0.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.4...v1.0.5

[1.0.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.3...v1.0.4

[1.0.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.2...v1.0.3

[1.0.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.1...v1.0.2

[1.0.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.0...v1.0.1

[1.0.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.4.0...v1.0.0

[0.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.3.0...v0.4.0

[0.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.2.0...v0.3.0

[0.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.1.4...v0.2.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0MS43MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 14:07:12 -05:00
Ed Rogers 4b88b1379a
Add fallback parent process detection to `uv tool update-shell` (#15356)
## Summary

Closes #15355

This PR adds a fallback mechanism to `Shell::from_env()` that inspects
the parent process when shell environment variables are not available on
Unix-like systems.

Currently, `uv tool update-shell` fails with "the current shell could
not be determined" when environment variables like `ZSH_VERSION`,
`BASH_VERSION`, or `SHELL` are not exported. This commonly occurs in
automated environments such as GitHub Actions runners.

The fallback approach:
1. Uses `nix::unistd::getppid()` to get the parent process ID
2. Reads `/proc/<ppid>/exe` to determine the parent executable path
3. Falls back to `/proc/<ppid>/comm` if the exe symlink fails  
4. Uses existing `parse_shell_from_path()` to identify the shell type

This maintains full backward compatibility - the fallback only activates
when environment variables are unavailable and an error would otherwise
occur.

## Test Plan

Tested locally with:

```bash
env -u ZSH_VERSION -u SHELL PATH="/usr/bin:/bin" $(which cargo) run -- tool update-shell --verbose
```
```text
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/uv tool update-shell --verbose`
DEBUG uv 0.8.11
DEBUG Ensuring that the executable directory is in PATH: /home/user/.local/bin
DEBUG Detected parent process ID: 4147396
DEBUG Parent process executable: /usr/bin/zsh
Updated configuration file: /home/user/.zshenv
Restart your shell to apply changes
```
2025-08-18 13:48:34 -05:00
Zanie Blue 242214c546
Add an `aarch64-pc-windows-msvc` target (#15347)
I needed this and was surprised it didn't exist!

---------

Co-authored-by: konsti <konstin@mailbox.org>
2025-08-18 17:57:27 +00:00
Zanie Blue ceacd27edb
Add WSL testing for pyenv-win (#15317)
Adds test coverage for https://github.com/astral-sh/uv/pull/15315

I reproduced the error there, then rebased on #15315 which resolves the
issue.
2025-08-18 11:21:44 -05:00
Zanie Blue a4d14710d4
Skip all generated content checks when in CI (#15354) 2025-08-18 16:09:14 +00:00
Zanie Blue 00e888098f
Skip interpreters that are not found on query (#15315)
Closes https://github.com/astral-sh/uv/issues/12155

We already throw this error earlier if we cannot find the interpreter
c318e8860e/crates/uv-python/src/interpreter.rs (L1039)

Why the pyenv-win shim _exists_ but fails to run with a not found error
is beyond me. I think I'll take the incremental improvement here by just
ignoring it. We can try to support their shims later?

#15317 confirms the fix.
2025-08-18 10:42:48 -05:00
renovate[bot] 3d8c21284a
Update Rust crate async-trait to v0.1.89 (#15341)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [async-trait](https://redirect.github.com/dtolnay/async-trait) |
workspace.dependencies | patch | `0.1.88` -> `0.1.89` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>dtolnay/async-trait (async-trait)</summary>

###
[`v0.1.89`](https://redirect.github.com/dtolnay/async-trait/releases/tag/0.1.89)

[Compare
Source](https://redirect.github.com/dtolnay/async-trait/compare/0.1.88...0.1.89)

- Improve IDE functionality
([#&#8203;293](https://redirect.github.com/dtolnay/async-trait/issues/293),
thanks [@&#8203;Veykril](https://redirect.github.com/Veykril))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS43MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 14:38:16 +00:00
renovate[bot] b1295c3ee6
Update Rust crate anyhow to v1.0.99 (#15340)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [anyhow](https://redirect.github.com/dtolnay/anyhow) |
workspace.dependencies | patch | `1.0.98` -> `1.0.99` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

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

###
[`v1.0.99`](https://redirect.github.com/dtolnay/anyhow/releases/tag/1.0.99)

[Compare
Source](https://redirect.github.com/dtolnay/anyhow/compare/1.0.98...1.0.99)

- Allow build-script cleanup failure with NFSv3 output directory to be
non-fatal
([#&#8203;420](https://redirect.github.com/dtolnay/anyhow/issues/420))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS43MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 09:26:41 -05:00
renovate[bot] 9c9efc8957
Update PyO3/maturin-action action to v1.49.4 (#15339)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [PyO3/maturin-action](https://redirect.github.com/PyO3/maturin-action)
| action | patch | `v1.49.3` -> `v1.49.4` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>PyO3/maturin-action (PyO3/maturin-action)</summary>

###
[`v1.49.4`](https://redirect.github.com/PyO3/maturin-action/releases/tag/v1.49.4)

[Compare
Source](https://redirect.github.com/PyO3/maturin-action/compare/v1.49.3...v1.49.4)

##### What's Changed

- add `LD_` to allowed env prefixes by
[@&#8203;jakobhellermann](https://redirect.github.com/jakobhellermann)
in
[https://github.com/PyO3/maturin-action/pull/363](https://redirect.github.com/PyO3/maturin-action/pull/363)
- Add recent manylinux images for i686 by
[@&#8203;ZedThree](https://redirect.github.com/ZedThree) in
[https://github.com/PyO3/maturin-action/pull/371](https://redirect.github.com/PyO3/maturin-action/pull/371)

##### New Contributors

- [@&#8203;jakobhellermann](https://redirect.github.com/jakobhellermann)
made their first contribution in
[https://github.com/PyO3/maturin-action/pull/363](https://redirect.github.com/PyO3/maturin-action/pull/363)
- [@&#8203;ZedThree](https://redirect.github.com/ZedThree) made their
first contribution in
[https://github.com/PyO3/maturin-action/pull/371](https://redirect.github.com/PyO3/maturin-action/pull/371)

**Full Changelog**:
https://github.com/PyO3/maturin-action/compare/v1.49.3...v1.49.4

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS43MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 09:26:23 -05:00
renovate[bot] 9f6f389a5d
Update EmbarkStudios/cargo-deny-action action to v2.0.13 (#15337)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[EmbarkStudios/cargo-deny-action](https://redirect.github.com/EmbarkStudios/cargo-deny-action)
| action | patch | `v2.0.12` -> `v2.0.13` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>EmbarkStudios/cargo-deny-action
(EmbarkStudios/cargo-deny-action)</summary>

###
[`v2.0.13`](https://redirect.github.com/EmbarkStudios/cargo-deny-action/releases/tag/v2.0.13):
Release 2.0.13 - cargo-deny 0.18.4

[Compare
Source](https://redirect.github.com/EmbarkStudios/cargo-deny-action/compare/v2.0.12...v2.0.13)

##### Added

-
[PR#779](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/779)
added the `--metadata-path` argument to use a cargo metadata JSON file
instead of calling cargo metadata, resolving
[#&#8203;777](https://redirect.github.com/EmbarkStudios/cargo-deny/issues/777).
-
[PR#782](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/782)
added `sources.unused-allow-source` to allow configuration of the lint
level when a source is allowed but not used by any crate in the graph,
closing
[#&#8203;781](https://redirect.github.com/EmbarkStudios/cargo-deny/issues/781).

##### Changed

-
[PR#786](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/786)
changed the license check output. `/` is no longer corrected to `OR`,
and if the license expression is found in the package's manifest, that
span is used in diagnostic messages instead of the synthesized manifest.

##### Fixed

-
[PR#786](https://redirect.github.com/EmbarkStudios/cargo-deny/pull/786)
resolved
[#&#8203;784](https://redirect.github.com/EmbarkStudios/cargo-deny/issues/784)
by updating `spdx` to a new version that forces all GNU licenses to be
exactly equal when comparing license expressions to licensee
expressions, which is incredibly pedantic, but means the license
comparison is entirely in the hands of the user so that I no longer have
to deal with GNU licenses.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS43MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 09:25:27 -05:00
renovate[bot] b0ae6e082a
Update depot/build-push-action action to v1.16.2 (#15336)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[depot/build-push-action](https://redirect.github.com/depot/build-push-action)
| action | patch | `v1.16.1` -> `v1.16.2` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>depot/build-push-action (depot/build-push-action)</summary>

###
[`v1.16.2`](https://redirect.github.com/depot/build-push-action/releases/tag/v1.16.2)

[Compare
Source](https://redirect.github.com/depot/build-push-action/compare/v1.16.1...v1.16.2)

##### What's Changed

- Use ubuntu-latest for release workflow
([#&#8203;42](https://redirect.github.com/depot/build-push-action/issues/42))
[@&#8203;jacobwgillespie](https://redirect.github.com/jacobwgillespie)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS43MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 09:25:03 -05:00
Zanie Blue 70515a3c57
Allow pre-releases in pyodide test job (#15349) 2025-08-18 14:22:03 +00:00
Zanie Blue 4b88d8d74b
Update freebsd-firecracker-action to v0.5.2 (#15348)
Takes us back off the fork
2025-08-18 09:10:46 -05:00
Zanie Blue 31ad3525ab
Use freebsd action fork with pinned checkout action (#15324)
Picks up https://github.com/acj/freebsd-firecracker-action/pull/5 to
unblock CI
2025-08-16 12:59:22 +00:00
Charlie Marsh 0243f91c9b
Add retries to tests that download from R2 (#15322)
Closes https://github.com/astral-sh/uv/issues/15318.
2025-08-16 00:31:22 +01:00
Charlie Marsh d8f3f03198
Move `--no-build-isolation-package` into `pyproject.toml` in tests (#15319) 2025-08-15 23:16:07 +01:00
Charlie Marsh 7ba6d50767
Install non-build-isolation packages in a second phase (#15306)
## Summary

This PR productionizes an idea I saw in
https://github.com/astral-sh/uv/issues/15248, which was added in Pixi:
https://github.com/prefix-dev/pixi/pull/4247. The core of the idea is
that if we install all build isolation-enabled packages first, and the
build isolation-disabled packages in a second phase, the sync is more
likely to "just work", because if all the build dependencies of the
build isolation-disabled packages are included as dependencies (as is
the case for `flash-attn`, at least), they'll be present.

This isn't really a silver bullet, because it requires that all the
build dependencies are included as first-party dependencies, and if you
have packages that want build isolation to be disabled but rely on other
packages that also require build isolation disabled, that won't work
either. I think `extra-build-dependencies` will be more robust and have
much better caching behavior, but this will get more cases right than
our current behavior, and I don't see any downsides.

Closes https://github.com/astral-sh/uv/issues/15301.
2025-08-15 22:00:55 +00:00
Chris Hughes 9346b4d0f6
fix: Handle dotted package names in script path resolution (#15300)
<!--
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 WindowsRunnable::from_script_path to correctly append extensions
instead of replacing them when resolving executable paths. This resolves
https://github.com/astral-sh/uv/issues/15165#issue-3304086689.

- Add add_extension_to_path helper that appends extensions properly
- Update extension resolution to use the new helper
- Add tests

## Test Plan

Added unit tests for the new and existing functionality that the change
touches. Tested manually locally on Windows.
<!-- How was it tested? -->

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-08-15 16:44:59 -05:00
NewDestinyDan 191c9175fe
Update cli.md to use proper uv cache subcommand "clean" (#15313)
Correct typo. "uv cache clear" is not a command.

<!--
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? -->

## Test Plan

<!-- How was it tested? -->
2025-08-15 21:06:37 +00:00
Charlie Marsh 58c7cc0e0f
Reject already-installed wheels built with outdated settings (#15289)
## Summary

With this PR, we track the settings that were used to build a wheel
(`--config-settings`, plus any `extra-build-dependencies` or
`extra-build-variables`) and write those to the `.dist-info` directory
upon install. This then allows us to "reject" already-installed wheels,
if the user changes the build dependencies or `--config-settings` (or,
crucially, if they use `match-runtime = true` and the resolution
changes).

Closes https://github.com/astral-sh/uv/issues/15218.
2025-08-15 15:15:55 +00:00
John Mumm 880eb286e8
Add new `uv-keyring` crate that vendors the `keyring-rs` crate (#14725)
This PR is a first step toward support for storing credentials in the
system keyring. The `keyring-rs` crate is the best option for system
keyring integration, but the latest version (v4) requires either that
Linux users have `libdbus` installed or that it is built with `libdbus`
vendored in. This is because v4 depends on
[dbus-secret-service](https://github.com/open-source-cooperative/dbus-secret-service),
which was created as an alternative to
[secret-service](https://github.com/open-source-cooperative/secret-service-rs)
so that users are not required to use an async runtime. Since uv does
use an async runtime, this is not a good tradeoff for uv.

This PR:
* Vendors `keyring-rs` crate into a new `uv-keyring` workspace crate
* Moves to the async `secret-service` crate that does not require
clients on Linux to have `libdbus` on their machines. This includes
updating `CredentialsAPI` trait (and implementations) to use async
methods.
* Adds `uv-keyring` tests to `cargo test` jobs. For `cargo test |
ubuntu`, this meant setting up secret service and priming gnome-keyring
as an earlier step.
* Removes iOS code paths
* Patches in @oconnor663 's changes from his [`keyring-rs`
PR](https://github.com/open-source-cooperative/keyring-rs/pull/261)
* Applies many clippy-driven updates
2025-08-15 15:57:56 +02:00
Zanie Blue 77fe8d2e60
Move python bin filtering into test that needs it (#15305)
Closes #15188
2025-08-15 08:06:52 -05:00
Charlie Marsh d75ab0c316
Avoid `&String` in installer (#15299)
## Summary

Not sure why this is here.
2025-08-15 11:05:11 +01:00
Charlie Marsh 627c062cab
Reject `match-runtime = true` for dynamic packages (#15292)
## Summary

If `match-runtime = true`, but we can't resolve a package's metadata
statically, then we can't _know_ what the runtime version of the package
will be -- because we can't resolve without building it. This PR makes
that footgun clearer by raising an error.

Closes https://github.com/astral-sh/uv/issues/15264.
2025-08-15 10:18:11 +01:00
Charlie Marsh 7eb076aaef
Force cache indexes to set hash digests and cache info (#15291)
## Summary

Making it harder to accidentally omit these.
2025-08-14 22:28:56 +00:00
Charlie Marsh bcfa8443da
Rename `InstalledDist` methods to reflect read operation (#15290)
## Summary

I found it surprising that these don't "just" return fields from the
struct.
2025-08-14 22:39:40 +01:00
Zanie Blue f892276ac8
Bump version to 0.8.11 (#15287) 2025-08-14 14:21:10 -05:00
github-actions[bot] 42beb20b90
Add Python 3.14.0rc2 (#15285)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-08-14 13:18:36 -05:00
Ahmed Ilyas e7b55fefbf
Add `extra-build-dependencies` hint for any missing module on build failure (#15252)
Alternative to https://github.com/astral-sh/uv/pull/15251.

As suggested in
https://github.com/astral-sh/uv/issues/15118#issuecomment-3175735416

## Test Plan

`cargo test`

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2025-08-14 12:00:57 -05:00
Nils Koch 4bc6c77f02
Allow passing custom reqwest client to RegistryClient (#15281)
<!--
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? -->
We are using UV as a library and we would like to provide an custom
reqwest client to the `RegistryClient`/`BaseClient`. We have a central
place in our repo where we configure the reqwest client to our needs
(certs, proxy, ...) and it is safer for us to just pass the same client
to UV rather than trying to reproduce the same client config with the
APIs that UV exposes.

Are you ok with that change?


## Test Plan

<!-- How was it tested? -->
2025-08-14 12:00:09 -05:00
Charlie Marsh 82d5b6780a
Move `--config-settings` structs into `uv-distribution-types` (#15278)
## Summary

This breaks up a cycle I'm running into in incorporating the build
configuration into our cache keys. This is actually a type that ends up
in the frontend build system, etc., so I think it makes more sense here
anyway (as opposed to `uv-configuration` which tend to be our own
user-facing types).
2025-08-14 15:07:47 +01:00
smeng9 d8d4f02e5e
Add Debian 13 trixie to published Docker images (#15269)
Closes https://github.com/astral-sh/uv/issues/15230

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-08-14 08:54:24 -05:00
renovate[bot] 6010a21d6b
Update depot/build-push-action action to v1.16.1 (#15277)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[depot/build-push-action](https://redirect.github.com/depot/build-push-action)
| action | patch | `v1.16.0` -> `v1.16.1` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>depot/build-push-action (depot/build-push-action)</summary>

###
[`v1.16.1`](https://redirect.github.com/depot/build-push-action/releases/tag/v1.16.1)

[Compare
Source](https://redirect.github.com/depot/build-push-action/compare/v1.16.0...v1.16.1)

##### What's Changed

- Update
[@&#8203;depot/actions-public-oidc-client](https://redirect.github.com/depot/actions-public-oidc-client)
to v1.1.0
([#&#8203;41](https://redirect.github.com/depot/build-push-action/issues/41))
[@&#8203;jacobwgillespie](https://redirect.github.com/jacobwgillespie)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS43MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNzEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-14 08:27:08 -05:00
Charlie Marsh 7cdb2d08d9
Persist cache info when re-installing cached wheels (#15274)
## Summary

I noticed that these paths aren't returning the cache information, so if
you install through these paths, we actually don't write `uv_cache.json`
at all. I'm not sure how a user would actually end up here, because
assuming there are no bugs, we don't really ever use this path? The
install plan indexes the cached wheels and marks the wheel as installed,
which means it's typically a mistake if we're asking the
`DistributionDatabase` for a wheel that's already available in the
cache... But I did verify that if I _skip_ the install plan's cache
lookup, we write a wheel without `uv_cache.json`, so this is definitely
more correct.
2025-08-14 13:05:41 +01:00
github-actions[bot] c4e5984258
Sync latest Python releases (#15266)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-08-14 04:28:47 +00:00
Charlie Marsh c514e0eda9
Make 'v' prefix cyan in overlap warnings (#15259) 2025-08-13 22:41:28 +01:00
Zanie Blue 3cc895a99a
Fix missing uv version in Docker image tags (#15263)
The change in https://github.com/astral-sh/uv/pull/15245 added a
variable to the step scope that's shadowed by the inner loop.

Closes https://github.com/astral-sh/uv/issues/15262
2025-08-13 21:36:02 +00:00
renovate[bot] 9a53806419
Update depot/build-push-action action to v1.16.0 (#15261)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[depot/build-push-action](https://redirect.github.com/depot/build-push-action)
| action | minor | `v1.15.0` -> `v1.16.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>depot/build-push-action (depot/build-push-action)</summary>

###
[`v1.16.0`](https://redirect.github.com/depot/build-push-action/releases/tag/v1.16.0)

[Compare
Source](https://redirect.github.com/depot/build-push-action/compare/v1.15.0...v1.16.0)

##### What's Changed

- Add support for annotations
([#&#8203;38](https://redirect.github.com/depot/build-push-action/issues/38))
[@&#8203;zanieb](https://redirect.github.com/zanieb)
- feat: add `save-tags` for multiple depot registry tags
([#&#8203;40](https://redirect.github.com/depot/build-push-action/issues/40))
[@&#8203;goller](https://redirect.github.com/goller)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS42Ni4yIiwidXBkYXRlZEluVmVyIjoiNDEuNjYuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-13 16:25:59 -05:00
Zanie Blue 7e817bafde
Bump version to 0.8.10 (#15257) 2025-08-13 20:06:08 +00:00
Zanie Blue b8049eaa20
Move warnings for conflicting modules into preview (#15253) 2025-08-13 19:39:09 +00:00
Zanie Blue 2c54d3929c
Allow selection of pyodide interpreters with "pyodide" (#15256) 2025-08-13 19:08:55 +00:00
Ahmed Ilyas 88a7b2d864
Fix clippy warnings in downloads.rs (#15255)
## Summary

Fixes clippy warnings on main.

## Test Plan

`cargo clippy`
2025-08-13 12:21:03 -05:00
Hood Chatham c8d0bfba5c
Add support for installing pyodide Pythons (#14518)
- [x] Add tests

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2025-08-13 11:03:25 -05:00
Zanie Blue b38edb9b7d
Allow Python requests with missing segments (#14399)
This allows `PythonDownloadRequest` which is used for parsing general
install key requests to have missing segments, which unblocks requests
like `windows-aarch64` or `cpython-linux` (whereas before those would
require `any-any-windows-aarch64` and `cpython-any-linux` respectively).
We still require strict ordering of segments.

Previously, we only allowed missing segments at the end of the key.

This uses a state machine for parsing, which is quite a bit more
complicated.

I'm a little hesitant about the possibility that this regresses error
messages and the complexity of the implementation, but `uv run -p
aarch64` seems valuable following #13724. The alternative to this would
probably be to make these explicit in various places? e.g., expose
`--python-arch`, `--python-libc`, and `--python-os`? Or make
`--python-platform` (which already exists) accept a subset of the keys?

There is a possibility of regressions here, e.g., if something matches
this parser it will not fallback to the `PythonRequest::ExecutableName`
case and we've made this parser more permissive, but I think that should
be quite rare?
2025-08-13 11:03:09 -05:00
Zanie Blue 78c8c711fa
Refactor os, arch, and libc information into a shared `Platform` type (#15027)
Addresses this outstanding item from a previous review
https://github.com/astral-sh/uv/pull/13724#discussion_r2114867288

I'm interested in this now for consolidating some logic in #12731
2025-08-13 09:02:55 -05:00
samypr100 323aa8f332
chore(🧹): cleanup env var usage (#15247)
## Summary

Split the cleanup fixes from https://github.com/astral-sh/uv/pull/15196
into a separate PR for easier review.

This cleans up some minor env var usage / references throughout tests
and runtime code.

## Test Plan

Existing Tests. No functional changes.
2025-08-12 21:11:28 -05:00
William Woodruff 65b4dbc47e
chore(ci): address lint findings in build-docker.yml (#15245)
## Summary

This re-creates #15145, with fixes following the revert in #15174.

The overall approach is the same, except that I've added an explicit
permissions block to `docker-annotate-base` that should cover the needed
permissions in that job.

(One confusion is around how that wasn't failing before -- FWICT it was
receiving the default `GITHUB_TOKEN`, which doesn't include `id-token:
write` or `packages: write`. So it _should_ have been failing even
before I explicitly did `permissions: {}`...)

Edit: Oh, I see why -- the actual release process does a
`workflow_call`, so this inherits its `GITHUB_TOKEN` from
`release.yml:custom-build-docker`, which in turn has the right
permissions granted to it.

## Test Plan

See what happens in CI. Plus maybe we could do a release dry-run?

Signed-off-by: William Woodruff <william@astral.sh>
2025-08-12 13:59:27 -05:00
Zanie Blue 959c9521c8
Add the Rust toolchain to Renovate (#15223)
Inspired by

-
https://github.com/Turbo87/renovate-config/blob/master/rust/updateToolchain.json
-
https://github.com/rust-lang/cargo/blob/master/.github/renovate.json5#L66-L81
2025-08-11 21:27:07 -05:00
Zanie Blue 68c0bf8a2c
Bump version to 0.8.9 (#15229) 2025-08-11 21:07:59 -05:00
Charlie Marsh dacc86ff03
Revert "Fix settings rendering for `extra-build-dependencies`" (#15228)
Reverts astral-sh/uv#15161
2025-08-11 19:37:12 -05:00
Charlie Marsh 9ba1ef1155
Fix settings rendering for `extra-build-dependencies` (#15161)
## Summary

It would be nice if this rendered as
`[tool.uv.extra-build-dependencies]` and `[extra-build-dependencies]`
(in `uv.toml`), but this is at least correct.

Closes https://github.com/astral-sh/uv/issues/15124.
2025-08-11 22:24:31 +01:00
Ubaid Shaikh 43341ad0a6
Filter date in GitHub release URLs for `python_install_no_cache` test (#15204)
## Summary

fixes https://github.com/astral-sh/uv/issues/15172

This change adds a regex filter to normalize dates in GitHub release
URLs within the `python_install_no_cache` test snapshot.

**Problem:**
The test was hardcoding the date `20250808` in the expected error
message URL:

```console
https://github.com/astral-sh/python-build-standalone/releases/download/20250808/cpython-3.12.[PATCH]-[DATE]-[PLATFORM].tar.gz
```

This creates a maintenance burden as the snapshot would need to be
updated whenever the underlying Python release date changes.

**Solution:**
Added a regex filter `r"releases/download/\d{8}/"` →
`"releases/download/[DATE]/"` to replace any 8-digit date in the GitHub
release URL path with a generic `[DATE]` placeholder.

**Result:**
The test is now resilient to new Python releases and won't require
snapshot updates when the underlying release date changes. The error
message now consistently shows:

```console
https://github.com/astral-sh/python-build-standalone/releases/download/[DATE]/cpython-3.12.[PATCH]-[DATE]-[PLATFORM].tar.gz
```

## Test Plan

`python_install` tests seem to pass  

```console
$ cargo test --package uv --test it -- python_install
   Compiling uv-cli v0.0.1 (/home/ubaid/projects/uv/crates/uv-cli)
   Compiling uv v0.8.8 (/home/ubaid/projects/uv/crates/uv)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 19.04s
     Running tests/it/main.rs (target/debug/deps/it-14d47eb0324a8a0a)

running 30 tests
test python_install::python_install_unknown ... ok
test network::python_install_io_error ... ok
test network::python_install_http_500 ... ok
test python_install::python_install_invalid_request ... ok
test python_install::python_install_broken_link ... ok
test python_install::python_install_preview_no_bin ... ok
test python_install::regression_cpython ... ok
test python_install::uninstall_last_patch ... ok
test python_install::install_no_transparent_upgrade_with_venv_patch_specification ... ok
test python_install::install_lower_patch_automatically ... ok
test python_install::uninstall_highest_patch ... ok
test python_install::install_transparent_patch_upgrade_venv_module ... ok
test python_install::python_install_default_from_env ... ok
test python_install::python_install ... ok
test python_install::python_reinstall_patch ... ok
test python_install::python_install_force ... ok
test python_install::install_transparent_patch_upgrade_uv_venv ... ok
test python_install::install_multiple_patches ... ok
test python_install::python_install_314 ... ok
test python_install::python_install_default ... ok
test python_install::python_install_automatic ... ok
test python_install::python_install_freethreaded ... ok
test python_install::python_install_preview_upgrade ... ok
test python_install::python_install_no_cache ... ok
test python_install::python_install_default_preview ... ok
test python_install::python_install_preview ... ok
test python_install::python_install_minor ... ok
test python_install::python_reinstall ... ok
test python_install::python_install_cached ... ok
test python_install::python_install_multiple_patch ... ok

test result: ok. 30 passed; 0 failed; 0 ignored; 0 measured; 2207 filtered out; finished in 23.34s
```
2025-08-11 16:15:52 -05:00
Charlie Marsh 40b894bb1d
Include build settings in cache key for registry source distribution lookups (#15225)
## Summary

Like #15030, but for source distributions built from a registry.
2025-08-11 22:14:27 +01:00
John Mumm 76b4ae40c6
Never create bin links on `uv python upgrade` if they don't already exist (#15192)
Fixes #15178
2025-08-11 15:36:03 -05:00
Henry Schreiner 193ab23e17
chore: add 3.14 classifier (#15187)
<!--
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? -->

Adding a 3.14 classifier.

## Test Plan

<!-- How was it tested? -->

3.14 is already supported.

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
2025-08-11 15:22:25 -05:00
renovate[bot] 0fef3f6d61
Update Rust crate hashbrown to v0.15.5 (#15213)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [hashbrown](https://redirect.github.com/rust-lang/hashbrown) |
workspace.dependencies | patch | `0.15.4` -> `0.15.5` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>rust-lang/hashbrown (hashbrown)</summary>

###
[`v0.15.5`](https://redirect.github.com/rust-lang/hashbrown/blob/HEAD/CHANGELOG.md#0155---2025-08-07)

[Compare
Source](https://redirect.github.com/rust-lang/hashbrown/compare/v0.15.4...v0.15.5)

##### Added

- Added `Entry::or_default_entry` and `Entry::or_insert_entry`.

##### Changed

- Re-implemented likely/unlikely with `#[cold]`

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS42MC40IiwidXBkYXRlZEluVmVyIjoiNDEuNjAuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 15:20:58 -05:00
John Mumm 23245c63e9
Add `--reinstall` flag to `uv python upgrade` (#15194)
As described in #15179, there are cases where it can be useful to
reinstall the latest patch on upgrade if it is already installed. Using
this flag, you don't need to know ahead of time if you have the latest
patch already.

Closes #15179.
2025-08-11 15:20:04 -05:00
renovate[bot] 96ea35fa2d
Update Rust crate clap to v4.5.43 (#15212)
This PR contains the following updates:

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

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

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

###
[`v4.5.43`](https://redirect.github.com/clap-rs/clap/blob/HEAD/CHANGELOG.md#4543---2025-08-06)

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

##### Fixes

- *(help)* In long help, list Possible Values before defaults, rather
than after, for a more consistent look

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS42MC40IiwidXBkYXRlZEluVmVyIjoiNDEuNjAuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 11:39:57 -05:00
renovate[bot] ba489a8063
Update pre-commit dependencies (#15209)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit)
| repository | patch | `v0.12.7` -> `v0.12.8` |
| [crate-ci/typos](https://redirect.github.com/crate-ci/typos) |
repository | minor | `v1.34.0` -> `v1.35.3` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

<details>
<summary>astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit)</summary>

###
[`v0.12.8`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.12.8)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.12.7...v0.12.8)

See: https://github.com/astral-sh/ruff/releases/tag/0.12.8

</details>

<details>
<summary>crate-ci/typos (crate-ci/typos)</summary>

###
[`v1.35.3`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...HEAD

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.25...v1.13.26

[1.13.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.24...v1.13.25

[1.13.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.23...v1.13.24

[1.13.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.22...v1.13.23

[1.13.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.21...v1.13.22

[1.13.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.20...v1.13.21

[1.13.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.19...v1.13.20

[1.13.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.18...v1.13.19

[1.13.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.17...v1.13.18

[1.13.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.16...v1.13.17

[1.13.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.15...v1.13.16

[1.13.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.14...v1.13.15

[1.13.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.13...v1.13.14

[1.13.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.12...v1.13.13

[1.13.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.11...v1.13.12

[1.13.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.10...v1.13.11

[1.13.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.9...v1.13.10

[1.13.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.8...v1.13.9

[1.13.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.7...v1.13.8

[1.13.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.6...v1.13.7

[1.13.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.5...v1.13.6

[1.13.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.4...v1.13.5

[1.13.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.3...v1.13.4

[1.13.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.2...v1.13.3

[1.13.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.1...v1.13.2

[1.13.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.0...v1.13.1

[1.13.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.14...v1.13.0

[1.12.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.13...v1.12.14

[1.12.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.12...v1.12.13

[1.12.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.11...v1.12.12

[1.12.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.10...v1.12.11

[1.12.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.9...v1.12.10

[1.12.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.8...v1.12.9

[1.12.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.7...v1.12.8

[1.12.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.6...v1.12.7

[1.12.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.5...v1.12.6

[1.12.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.4...v1.12.5

[1.12.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.3...v1.12.4

[1.12.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.2...v1.12.3

[1.12.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.1...v1.12.2

[1.12.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.0...v1.12.1

[1.12.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.5...v1.12.0

[1.11.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.4...v1.11.5

[1.11.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.3...v1.11.4

[1.11.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.2...v1.11.3

[1.11.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.1...v1.11.2

[1.11.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.0...v1.11.1

[1.11.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.3...v1.11.0

[1.10.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.2...v1.10.3

[1.10.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.1...v1.10.2

[1.10.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.0...v1.10.1

[1.10.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.9.0...v1.10.0

[1.9.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.1...v1.9.0

[1.8.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.0...v1.8.1

[1.8.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.3...v1.8.0

[1.7.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.2...v1.7.3

[1.7.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.1...v1.7.2

[1.7.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.0...v1.7.1

[1.7.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.6.0...v1.7.0

[1.6.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.5.0...v1.6.0

[1.5.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.1...v1.5.0

[1.4.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.0...v1.4.1

[1.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.9...v1.4.0

[1.3.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.8...v1.3.9

[1.3.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.7...v1.3.8

[1.3.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.6...v1.3.7

[1.3.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.5...v1.3.6

[1.3.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.4...v1.3.5

[1.3.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.3...v1.3.4

[1.3.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.2...v1.3.3

[1.3.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.1...v1.3.2

[1.3.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.0...v1.3.1

[1.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.1...v1.3.0

[1.2.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.0...v1.2.1

[1.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.9...v1.2.0

[1.1.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.8...v1.1.9

[1.1.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.7...v1.1.8

[1.1.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.6...v1.1.7

[1.1.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.5...v1.1.6

[1.1.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.4...v1.1.5

[1.1.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.3...v1.1.4

[1.1.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.2...v1.1.3

[1.1.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.1...v1.1.2

[1.1.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.0...v1.1.1

[1.1.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.11...v1.1.0

[1.0.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.10...v1.0.11

[1.0.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.9...v1.0.10

[1.0.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.8...v1.0.9

[1.0.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.7...v1.0.8

[1.0.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.6...v1.0.7

[1.0.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.5...v1.0.6

[1.0.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.4...v1.0.5

[1.0.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.3...v1.0.4

[1.0.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.2...v1.0.3

[1.0.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.1...v1.0.2

[1.0.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.0...v1.0.1

[1.0.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.4.0...v1.0.0

[0.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.3.0...v0.4.0

[0.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.2.0...v0.3.0

[0.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.1.4...v0.2.0

###
[`v1.35.2`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...HEAD

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.25...v1.13.26

[1.13.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.24...v1.13.25

[1.13.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.23...v1.13.24

[1.13.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.22...v1.13.23

[1.13.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.21...v1.13.22

[1.13.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.20...v1.13.21

[1.13.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.19...v1.13.20

[1.13.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.18...v1.13.19

[1.13.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.17...v1.13.18

[1.13.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.16...v1.13.17

[1.13.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.15...v1.13.16

[1.13.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.14...v1.13.15

[1.13.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.13...v1.13.14

[1.13.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.12...v1.13.13

[1.13.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.11...v1.13.12

[1.13.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.10...v1.13.11

[1.13.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.9...v1.13.10

[1.13.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.8...v1.13.9

[1.13.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.7...v1.13.8

[1.13.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.6...v1.13.7

[1.13.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.5...v1.13.6

[1.13.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.4...v1.13.5

[1.13.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.3...v1.13.4

[1.13.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.2...v1.13.3

[1.13.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.1...v1.13.2

[1.13.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.0...v1.13.1

[1.13.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.14...v1.13.0

[1.12.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.13...v1.12.14

[1.12.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.12...v1.12.13

[1.12.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.11...v1.12.12

[1.12.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.10...v1.12.11

[1.12.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.9...v1.12.10

[1.12.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.8...v1.12.9

[1.12.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.7...v1.12.8

[1.12.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.6...v1.12.7

[1.12.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.5...v1.12.6

[1.12.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.4...v1.12.5

[1.12.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.3...v1.12.4

[1.12.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.2...v1.12.3

[1.12.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.1...v1.12.2

[1.12.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.0...v1.12.1

[1.12.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.5...v1.12.0

[1.11.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.4...v1.11.5

[1.11.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.3...v1.11.4

[1.11.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.2...v1.11.3

[1.11.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.1...v1.11.2

[1.11.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.11.0...v1.11.1

[1.11.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.3...v1.11.0

[1.10.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.2...v1.10.3

[1.10.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.1...v1.10.2

[1.10.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.10.0...v1.10.1

[1.10.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.9.0...v1.10.0

[1.9.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.1...v1.9.0

[1.8.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.8.0...v1.8.1

[1.8.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.3...v1.8.0

[1.7.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.2...v1.7.3

[1.7.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.1...v1.7.2

[1.7.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.7.0...v1.7.1

[1.7.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.6.0...v1.7.0

[1.6.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.5.0...v1.6.0

[1.5.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.1...v1.5.0

[1.4.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.4.0...v1.4.1

[1.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.9...v1.4.0

[1.3.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.8...v1.3.9

[1.3.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.7...v1.3.8

[1.3.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.6...v1.3.7

[1.3.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.5...v1.3.6

[1.3.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.4...v1.3.5

[1.3.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.3...v1.3.4

[1.3.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.2...v1.3.3

[1.3.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.1...v1.3.2

[1.3.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.3.0...v1.3.1

[1.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.1...v1.3.0

[1.2.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.2.0...v1.2.1

[1.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.9...v1.2.0

[1.1.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.8...v1.1.9

[1.1.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.7...v1.1.8

[1.1.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.6...v1.1.7

[1.1.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.5...v1.1.6

[1.1.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.4...v1.1.5

[1.1.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.3...v1.1.4

[1.1.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.2...v1.1.3

[1.1.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.1...v1.1.2

[1.1.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.1.0...v1.1.1

[1.1.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.11...v1.1.0

[1.0.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.10...v1.0.11

[1.0.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.9...v1.0.10

[1.0.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.8...v1.0.9

[1.0.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.7...v1.0.8

[1.0.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.6...v1.0.7

[1.0.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.5...v1.0.6

[1.0.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.4...v1.0.5

[1.0.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.3...v1.0.4

[1.0.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.2...v1.0.3

[1.0.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.1...v1.0.2

[1.0.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.0.0...v1.0.1

[1.0.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.4.0...v1.0.0

[0.4.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.3.0...v0.4.0

[0.3.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.2.0...v0.3.0

[0.2.0]:
https://redirect.github.com/crate-ci/typos/compare/v0.1.4...v0.2.0

###
[`v1.35.1`](https://redirect.github.com/crate-ci/typos/blob/HEAD/CHANGELOG.md#014---2019-11-03)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1)

##### Bug Fixes

- Ignore numbers as identifiers
([a00831c8](a00831c847))
- Improve the organization of --help
([a48a457c](a48a457cc3))

##### Features

- Dump files, identifiers, and words
([ce365ae1](ce365ae12e),
closes
[#&#8203;41](https://redirect.github.com/crate-ci/typos/issues/41))
- Give control over allowed identifier characters for leading vs rest
([107308a6](107308a655))

##### Performance

- Use standard identifier rules to avoid doing umber checks
([107308a6](107308a655))
- Only do hex check if digits are in identifiers
([68cd36d0](68cd36d0de))

<!-- next-url -->

[Unreleased]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.3...HEAD

[1.35.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.2...v1.35.3

[1.35.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.1...v1.35.2

[1.35.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.35.0...v1.35.1

[1.35.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.34.0...v1.35.0

[1.34.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0

[1.33.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.33.0...v1.33.1

[1.33.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.32.0...v1.33.0

[1.32.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.2...v1.32.0

[1.31.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.1...v1.31.2

[1.31.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.31.0...v1.31.1

[1.31.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.3...v1.31.0

[1.30.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.2...v1.30.3

[1.30.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.1...v1.30.2

[1.30.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.30.0...v1.30.1

[1.30.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.10...v1.30.0

[1.29.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.9...v1.29.10

[1.29.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.8...v1.29.9

[1.29.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.7...v1.29.8

[1.29.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.6...v1.29.7

[1.29.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.5...v1.29.6

[1.29.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.4...v1.29.5

[1.29.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.3...v1.29.4

[1.29.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.2...v1.29.3

[1.29.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.1...v1.29.2

[1.29.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.29.0...v1.29.1

[1.29.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.4...v1.29.0

[1.28.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.3...v1.28.4

[1.28.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.2...v1.28.3

[1.28.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.1...v1.28.2

[1.28.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.28.0...v1.28.1

[1.28.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.3...v1.28.0

[1.27.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.2...v1.27.3

[1.27.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.1...v1.27.2

[1.27.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.27.0...v1.27.1

[1.27.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.8...v1.27.0

[1.26.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.7...v1.26.8

[1.26.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.6...v1.26.7

[1.26.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.5...v1.26.6

[1.26.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.4...v1.26.5

[1.26.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.3...v1.26.4

[1.26.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.2...v1.26.3

[1.26.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.1...v1.26.2

[1.26.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.26.0...v1.26.1

[1.26.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.25.0...v1.26.0

[1.25.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.6...v1.25.0

[1.24.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.5...v1.24.6

[1.24.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.4...v1.24.5

[1.24.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.3...v1.24.4

[1.24.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.2...v1.24.3

[1.24.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.1...v1.24.2

[1.24.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.24.0...v1.24.1

[1.24.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.7...v1.24.0

[1.23.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.6...v1.23.7

[1.23.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.5...v1.23.6

[1.23.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.4...v1.23.5

[1.23.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.3...v1.23.4

[1.23.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.2...v1.23.3

[1.23.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.1...v1.23.2

[1.23.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.23.0...v1.23.1

[1.23.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.9...v1.23.0

[1.22.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.8...v1.22.9

[1.22.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.7...v1.22.8

[1.22.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.6...v1.22.7

[1.22.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.5...v1.22.6

[1.22.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.4...v1.22.5

[1.22.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.3...v1.22.4

[1.22.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.2...v1.22.3

[1.22.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.1...v1.22.2

[1.22.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.22.0...v1.22.1

[1.22.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.21.0...v1.22.0

[1.21.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.10...v1.21.0

[1.20.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.9...v1.20.10

[1.20.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.8...v1.20.9

[1.20.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.7...v1.20.8

[1.20.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.6...v1.20.7

[1.20.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.5...v1.20.6

[1.20.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.4...v1.20.5

[1.20.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.3...v1.20.4

[1.20.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.2...v1.20.3

[1.20.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.1...v1.20.2

[1.20.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.20.0...v1.20.1

[1.20.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.19.0...v1.20.0

[1.19.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.2...v1.19.0

[1.18.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.1...v1.18.2

[1.18.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.18.0...v1.18.1

[1.18.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.2...v1.18.0

[1.17.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.1...v1.17.2

[1.17.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.17.0...v1.17.1

[1.17.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.26...v1.17.0

[1.16.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.25...v1.16.26

[1.16.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.24...v1.16.25

[1.16.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.23...v1.16.24

[1.16.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.22...v1.16.23

[1.16.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.21...v1.16.22

[1.16.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.20...v1.16.21

[1.16.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.19...v1.16.20

[1.16.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.18...v1.16.19

[1.16.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.17...v1.16.18

[1.16.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.16...v1.16.17

[1.16.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.15...v1.16.16

[1.16.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.14...v1.16.15

[1.16.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.13...v1.16.14

[1.16.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.12...v1.16.13

[1.16.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.11...v1.16.12

[1.16.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.10...v1.16.11

[1.16.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.9...v1.16.10

[1.16.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.8...v1.16.9

[1.16.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.7...v1.16.8

[1.16.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.6...v1.16.7

[1.16.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.5...v1.16.6

[1.16.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.4...v1.16.5

[1.16.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.3...v1.16.4

[1.16.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.2...v1.16.3

[1.16.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.1...v1.16.2

[1.16.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.16.0...v1.16.1

[1.16.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.10...v1.16.0

[1.15.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.9...v1.15.10

[1.15.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.8...v1.15.9

[1.15.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.7...v1.15.8

[1.15.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.6...v1.15.7

[1.15.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.5...v1.15.6

[1.15.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.4...v1.15.5

[1.15.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.3...v1.15.4

[1.15.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.2...v1.15.3

[1.15.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.1...v1.15.2

[1.15.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.15.0...v1.15.1

[1.15.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.12...v1.15.0

[1.14.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.11...v1.14.12

[1.14.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.10...v1.14.11

[1.14.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.9...v1.14.10

[1.14.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.8...v1.14.9

[1.14.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.7...v1.14.8

[1.14.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.6...v1.14.7

[1.14.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.5...v1.14.6

[1.14.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.4...v1.14.5

[1.14.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.3...v1.14.4

[1.14.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.2...v1.14.3

[1.14.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.1...v1.14.2

[1.14.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.14.0...v1.14.1

[1.14.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.26...v1.14.0

[1.13.26]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.25...v1.13.26

[1.13.25]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.24...v1.13.25

[1.13.24]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.23...v1.13.24

[1.13.23]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.22...v1.13.23

[1.13.22]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.21...v1.13.22

[1.13.21]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.20...v1.13.21

[1.13.20]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.19...v1.13.20

[1.13.19]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.18...v1.13.19

[1.13.18]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.17...v1.13.18

[1.13.17]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.16...v1.13.17

[1.13.16]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.15...v1.13.16

[1.13.15]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.14...v1.13.15

[1.13.14]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.13...v1.13.14

[1.13.13]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.12...v1.13.13

[1.13.12]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.11...v1.13.12

[1.13.11]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.10...v1.13.11

[1.13.10]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.9...v1.13.10

[1.13.9]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.8...v1.13.9

[1.13.8]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.7...v1.13.8

[1.13.7]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.6...v1.13.7

[1.13.6]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.5...v1.13.6

[1.13.5]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.4...v1.13.5

[1.13.4]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.3...v1.13.4

[1.13.3]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.2...v1.13.3

[1.13.2]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.1...v1.13.2

[1.13.1]:
https://redirect.github.com/crate-ci/typos/compare/v1.13.0...v1.13.1

[1.13.0]:
https://redirect.github.com/crate-ci/typos/compare/v1.12.14...v1.13.0

[1.12.14]: https://redirect.github.com/crate-ci/typos

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0MS42MC40IiwidXBkYXRlZEluVmVyIjoiNDEuNjAuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 11:39:53 -05:00
renovate[bot] 0b155eca30
Update rui314/setup-mold digest to 7344740 (#15208)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [rui314/setup-mold](https://redirect.github.com/rui314/setup-mold) |
action | digest | `702b190` -> `7344740` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS42MC40IiwidXBkYXRlZEluVmVyIjoiNDEuNjAuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 11:39:39 -05:00
renovate[bot] 8d3df51bf4
Update google-github-actions/setup-gcloud digest to cb1e50a (#15207)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| google-github-actions/setup-gcloud | action | digest | `6a7c903` ->
`cb1e50a` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS42MC40IiwidXBkYXRlZEluVmVyIjoiNDEuNjAuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 11:39:15 -05:00
renovate[bot] bef73604cd
Update Rust crate anstream to v0.6.20 (#15210)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [anstream](https://redirect.github.com/rust-cli/anstyle) |
workspace.dependencies | patch | `0.6.19` -> `0.6.20` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>rust-cli/anstyle (anstream)</summary>

###
[`v0.6.20`](https://redirect.github.com/rust-cli/anstyle/compare/anstream-v0.6.19...anstream-v0.6.20)

[Compare
Source](https://redirect.github.com/rust-cli/anstyle/compare/anstream-v0.6.19...anstream-v0.6.20)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS42MC40IiwidXBkYXRlZEluVmVyIjoiNDEuNjAuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 11:39:11 -05:00
Zanie Blue 50fbedb2bc
Respect system proxies on macOS and Windows (#15221)
Per https://github.com/astral-sh/uv/issues/15203#issuecomment-3175273357
this was an accidental breaking change for Windows proxy support.
2025-08-11 10:39:40 -05:00
github-actions[bot] ed499d7453
Sync latest Python releases (#15186)
Automated update for Python releases.

Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
2025-08-09 00:43:10 +00:00
Zanie Blue 6c9544ed5f
Add test coverage for the `find_uv_bin` error message (#15185)
All of these went away as we fixed the bugs! it seems nice to retain a
snapshot of the error
2025-08-09 00:28:40 +00:00
Zanie Blue 66c1c0d24c
Update the target Python version for Ruff to 3.7 in the `uv` module (#15183)
This fixes our Ruff target Python version for our Python module, which
avoids it enforcing things that break compatibility like
https://github.com/astral-sh/uv/issues/15176

---------

Co-authored-by: arielle <me@arielle.codes>
2025-08-08 19:23:08 -05:00
856 changed files with 102480 additions and 27500 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

@ -2,3 +2,14 @@
# Mark tests that take longer than 10s as slow.
# Terminate after 120s as a stop-gap measure to terminate on deadlock.
slow-timeout = { period = "10s", terminate-after = 12 }
[test-groups]
serial = { max-threads = 1 }
[[profile.default.overrides]]
filter = 'test(native_auth)'
test-group = 'serial'
[[profile.default.overrides]]
filter = 'package(uv-keyring)'
test-group = 'serial'

View File

@ -3,20 +3,19 @@
dependencyDashboard: true,
suppressNotifications: ["prEditedNotification"],
extends: [
"config:recommended",
"github>astral-sh/renovate-config",
// For tool versions defined in GitHub Actions:
"customManagers:githubActionsVersions",
],
labels: ["internal"],
schedule: ["before 4am on Monday"],
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
rangeStrategy: "update-lockfile",
fileMatch: ["^crates/.*Cargo\\.toml$"],
managerFilePatterns: ["/^Cargo\\.toml$/", "/^crates/.*Cargo\\.toml$/"],
},
"pre-commit": {
enabled: true,
@ -86,18 +85,61 @@
description: "Weekly update of pyo3 dependencies",
enabled: false,
},
{
groupName: "pubgrub",
matchManagers: ["cargo"],
matchDepNames: ["pubgrub", "version-ranges"],
description: "version-ranges and pubgrub are in the same Git repository",
},
{
commitMessageTopic: "MSRV",
matchManagers: ["custom.regex"],
matchDepNames: ["msrv"],
// We have a rolling support policy for the MSRV
// 2 releases back * 6 weeks per release * 7 days per week + 1
minimumReleaseAge: "85 days",
internalChecksFilter: "strict",
groupName: "MSRV",
},
{
matchManagers: ["custom.regex"],
matchDepNames: ["rust"],
commitMessageTopic: "Rust",
},
],
customManagers: [
// Update major GitHub actions references in documentation.
{
customType: "regex",
fileMatch: ["^docs/.*\\.md$"],
managerFilePatterns: ["/^docs/.*\\.md$/"],
matchStrings: [
"\\suses: (?<depName>[\\w-]+/[\\w-]+)(?<path>/.*)?@(?<currentValue>.+?)\\s",
],
datasourceTemplate: "github-tags",
versioningTemplate: "regex:^v(?<major>\\d+)$",
},
// Minimum supported Rust toolchain version
{
customType: "regex",
managerFilePatterns: ["/(^|/)Cargo\\.toml?$/"],
matchStrings: [
'rust-version\\s*=\\s*"(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)"',
],
depNameTemplate: "msrv",
packageNameTemplate: "rust-lang/rust",
datasourceTemplate: "github-releases",
},
// Rust toolchain version
{
customType: "regex",
managerFilePatterns: ["/(^|/)rust-toolchain\\.toml?$/"],
matchStrings: [
'channel\\s*=\\s*"(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)"',
],
depNameTemplate: "rust",
packageNameTemplate: "rust-lang/rust",
datasourceTemplate: "github-releases",
},
],
vulnerabilityAlerts: {
commitMessageSuffix: "",

View File

@ -59,8 +59,9 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build sdist"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
command: sdist
args: --out dist
- name: "Test sdist"
@ -79,8 +80,9 @@ jobs:
# uv-build
- name: "Build sdist uv-build"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
command: sdist
args: --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
- name: "Test sdist uv-build"
@ -96,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:
@ -111,8 +113,9 @@ jobs:
# uv
- name: "Build wheels - x86_64"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: x86_64
args: --release --locked --out dist --features self-update
- name: "Upload wheels"
@ -141,8 +144,9 @@ jobs:
# uv-build
- name: "Build wheels uv-build - x86_64"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: x86_64
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
- name: "Upload wheels uv-build"
@ -153,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:
@ -168,8 +172,9 @@ jobs:
# uv
- name: "Build wheels - aarch64"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: aarch64
args: --release --locked --out dist --features self-update
- name: "Test wheel - aarch64"
@ -204,8 +209,9 @@ jobs:
# uv-build
- name: "Build wheels uv-build - aarch64"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: aarch64
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
- name: "Test wheel - aarch64"
@ -245,8 +251,9 @@ jobs:
# uv
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
args: --release --locked --out dist --features self-update,windows-gui-bin
- name: "Test wheel"
@ -283,8 +290,9 @@ jobs:
# uv-build
- name: "Build wheels uv-build"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
- name: "Test wheel uv-build"
@ -322,8 +330,9 @@ jobs:
# uv
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.target }}
# Generally, we try to build in a target docker container. In this case however, a
# 32-bit compiler runs out of memory (4GB memory limit for 32-bit), so we cross compile
@ -388,8 +397,9 @@ jobs:
# uv-build
- name: "Build wheels uv-build"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.target }}
manylinux: auto
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
@ -407,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:
@ -435,8 +445,9 @@ jobs:
# uv
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
# On `aarch64`, use `manylinux: 2_28`; otherwise, use `manylinux: auto`.
manylinux: ${{ matrix.platform.arch == 'aarch64' && '2_28' || 'auto' }}
@ -488,8 +499,9 @@ jobs:
# uv-build
- name: "Build wheels uv-build"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
# On `aarch64`, use `manylinux: 2_28`; otherwise, use `manylinux: auto`.
manylinux: ${{ matrix.platform.arch == 'aarch64' && '2_28' || 'auto' }}
@ -542,15 +554,14 @@ jobs:
# uv
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
manylinux: auto
docker-options: ${{ matrix.platform.maturin_docker_options }}
args: --release --locked --out dist --features self-update
# Until the llvm updates hit stable
# https://github.com/rust-lang/rust/issues/141287
rust-toolchain: nightly-2025-05-25
rust-toolchain: ${{ matrix.platform.toolchain || null }}
- uses: uraimo/run-on-arch-action@d94c13912ea685de38fccc1109385b83fd79427d # v3.0.1
if: matrix.platform.arch != 'ppc64'
name: "Test wheel"
@ -598,8 +609,9 @@ jobs:
# uv-build
- name: "Build wheels uv-build"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
manylinux: auto
docker-options: ${{ matrix.platform.maturin_docker_options }}
@ -657,8 +669,9 @@ jobs:
# uv
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
manylinux: auto
docker-options: ${{ matrix.platform.maturin_docker_options }}
@ -715,8 +728,9 @@ jobs:
# uv-build
- name: "Build wheels uv-build"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
manylinux: auto
docker-options: ${{ matrix.platform.maturin_docker_options }}
@ -759,8 +773,9 @@ jobs:
# uv
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
manylinux: auto
docker-options: ${{ matrix.platform.maturin_docker_options }}
@ -812,8 +827,9 @@ jobs:
# uv-build
- name: "Build wheels uv-build"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
manylinux: auto
docker-options: ${{ matrix.platform.maturin_docker_options }}
@ -864,8 +880,9 @@ jobs:
# uv
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.target }}
manylinux: musllinux_1_1
args: --release --locked --out dist --features self-update
@ -912,8 +929,9 @@ jobs:
# uv-build
- name: "Build wheels uv-build"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.target }}
manylinux: musllinux_1_1
args: --profile minimal-size --locked --out crates/uv-build/dist -m crates/uv-build/Cargo.toml
@ -938,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:
@ -962,8 +980,9 @@ jobs:
# uv
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_1
args: --release --locked --out dist --features self-update ${{ matrix.platform.arch == 'aarch64' && '--compatibility 2_17' || ''}}
@ -1034,8 +1053,9 @@ jobs:
# uv-build
- name: "Build wheels"
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
with:
maturin-version: v1.9.6
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_1
args: --profile minimal-size --locked ${{ matrix.platform.arch == 'aarch64' && '--compatibility 2_17' || ''}} --out crates/uv-build/dist -m crates/uv-build/Cargo.toml

View File

@ -40,6 +40,8 @@ env:
UV_GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/uv
UV_DOCKERHUB_IMAGE: docker.io/astral/uv
permissions: {}
jobs:
docker-plan:
name: plan
@ -57,13 +59,13 @@ jobs:
IS_LOCAL_PR: ${{ github.event.pull_request.head.repo.full_name == 'astral-sh/uv' }}
id: plan
run: |
if [ "${{ env.DRY_RUN }}" == "false" ]; then
if [ "${DRY_RUN}" == "false" ]; then
echo "login=true" >> "$GITHUB_OUTPUT"
echo "push=true" >> "$GITHUB_OUTPUT"
echo "tag=${{ env.TAG }}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "action=build and publish" >> "$GITHUB_OUTPUT"
else
echo "login=${{ env.IS_LOCAL_PR }}" >> "$GITHUB_OUTPUT"
echo "login=${IS_LOCAL_PR}" >> "$GITHUB_OUTPUT"
echo "push=false" >> "$GITHUB_OUTPUT"
echo "tag=dry-run" >> "$GITHUB_OUTPUT"
echo "action=build" >> "$GITHUB_OUTPUT"
@ -91,15 +93,16 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
persist-credentials: false
# Login to DockerHub (when not pushing, it's to avoid rate-limiting)
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
if: ${{ needs.docker-plan.outputs.login == 'true' }}
with:
username: ${{ needs.docker-plan.outputs.push == 'true' && 'astral' || 'astralshbot' }}
password: ${{ needs.docker-plan.outputs.push == 'true' && secrets.DOCKERHUB_TOKEN_RW || secrets.DOCKERHUB_TOKEN_RO }}
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@ -111,18 +114,20 @@ jobs:
if: ${{ needs.docker-plan.outputs.push == 'true' }}
run: |
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
if [ "${{ needs.docker-plan.outputs.tag }}" != "${version}" ]; then
if [ "${TAG}" != "${version}" ]; then
echo "The input tag does not match the version from pyproject.toml:" >&2
echo "${{ needs.docker-plan.outputs.tag }}" >&2
echo "${TAG}" >&2
echo "${version}" >&2
exit 1
else
echo "Releasing ${version}"
fi
env:
TAG: ${{ needs.docker-plan.outputs.tag }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
with:
@ -137,7 +142,7 @@ jobs:
- name: Build and push by digest
id: build
uses: depot/build-push-action@2583627a84956d07561420dcc1d0eb1f2af3fac0 # v1.15.0
uses: depot/build-push-action@9785b135c3c76c33db102e45be96a25ab55cd507 # v1.16.2
with:
project: 7hd4vdzmw5 # astral-sh/uv
context: .
@ -173,24 +178,39 @@ jobs:
# Mapping of base image followed by a comma followed by one or more base tags (comma separated)
# Note, org.opencontainers.image.version label will use the first base tag (use the most specific tag first)
image-mapping:
- alpine:3.21,alpine3.21,alpine
- debian:bookworm-slim,bookworm-slim,debian-slim
- buildpack-deps:bookworm,bookworm,debian
- python:3.14-rc-alpine,python3.14-rc-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-rc-bookworm,python3.14-rc-bookworm
- alpine:3.22,alpine3.22,alpine
- alpine:3.21,alpine3.21
- debian:trixie-slim,trixie-slim,debian-slim
- buildpack-deps:trixie,trixie,debian
- debian:bookworm-slim,bookworm-slim
- buildpack-deps:bookworm,bookworm
- python:3.14-alpine3.23,python3.14-alpine3.23,python3.14-alpine
- python:3.13-alpine3.23,python3.13-alpine3.23,python3.13-alpine
- python:3.12-alpine3.23,python3.12-alpine3.23,python3.12-alpine
- python:3.11-alpine3.23,python3.11-alpine3.23,python3.11-alpine
- python:3.10-alpine3.23,python3.10-alpine3.23,python3.10-alpine
- python:3.9-alpine3.22,python3.9-alpine3.22,python3.9-alpine
- python:3.8-alpine3.20,python3.8-alpine3.20,python3.8-alpine
- python:3.14-trixie,python3.14-trixie
- python:3.13-trixie,python3.13-trixie
- python:3.12-trixie,python3.12-trixie
- python:3.11-trixie,python3.11-trixie
- python:3.10-trixie,python3.10-trixie
- python:3.9-trixie,python3.9-trixie
- python:3.14-slim-trixie,python3.14-trixie-slim
- python:3.13-slim-trixie,python3.13-trixie-slim
- python:3.12-slim-trixie,python3.12-trixie-slim
- python:3.11-slim-trixie,python3.11-trixie-slim
- python:3.10-slim-trixie,python3.10-trixie-slim
- python:3.9-slim-trixie,python3.9-trixie-slim
- python:3.14-bookworm,python3.14-bookworm
- python:3.13-bookworm,python3.13-bookworm
- python:3.12-bookworm,python3.12-bookworm
- python:3.11-bookworm,python3.11-bookworm
- python:3.10-bookworm,python3.10-bookworm
- python:3.9-bookworm,python3.9-bookworm
- python:3.8-bookworm,python3.8-bookworm
- python:3.14-rc-slim-bookworm,python3.14-rc-bookworm-slim
- python:3.14-slim-bookworm,python3.14-bookworm-slim
- python:3.13-slim-bookworm,python3.13-bookworm-slim
- python:3.12-slim-bookworm,python3.12-bookworm-slim
- python:3.11-slim-bookworm,python3.11-bookworm-slim
@ -199,13 +219,13 @@ jobs:
- python:3.8-slim-bookworm,python3.8-bookworm-slim
steps:
# Login to DockerHub (when not pushing, it's to avoid rate-limiting)
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
if: ${{ needs.docker-plan.outputs.login == 'true' }}
with:
username: ${{ needs.docker-plan.outputs.push == 'true' && 'astral' || 'astralshbot' }}
password: ${{ needs.docker-plan.outputs.push == 'true' && secrets.DOCKERHUB_TOKEN_RW || secrets.DOCKERHUB_TOKEN_RO }}
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@ -224,7 +244,7 @@ jobs:
# Generate Dockerfile content
cat <<EOF > Dockerfile
FROM ${BASE_IMAGE}
COPY --from=${{ env.UV_GHCR_IMAGE }}:latest /uv /uvx /usr/local/bin/
COPY --from=${UV_GHCR_IMAGE}:latest /uv /uvx /usr/local/bin/
ENV UV_TOOL_BIN_DIR="/usr/local/bin"
ENTRYPOINT []
CMD ["/usr/local/bin/uv"]
@ -236,8 +256,8 @@ jobs:
# Loop through all base tags and append its docker metadata pattern to the list
# Order is on purpose such that the label org.opencontainers.image.version has the first pattern with the full version
IFS=','; for TAG in ${BASE_TAGS}; do
TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ version }},suffix=-${TAG},value=${{ needs.docker-plan.outputs.tag }}\n"
TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ major }}.{{ minor }},suffix=-${TAG},value=${{ needs.docker-plan.outputs.tag }}\n"
TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ version }},suffix=-${TAG},value=${VERSION}\n"
TAG_PATTERNS="${TAG_PATTERNS}type=pep440,pattern={{ major }}.{{ minor }},suffix=-${TAG},value=${VERSION}\n"
TAG_PATTERNS="${TAG_PATTERNS}type=raw,value=${TAG}\n"
done
@ -250,10 +270,12 @@ jobs:
echo -e "${TAG_PATTERNS}"
echo EOF
} >> $GITHUB_ENV
env:
VERSION: ${{ needs.docker-plan.outputs.tag }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
# ghcr.io prefers index level annotations
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
@ -268,7 +290,7 @@ jobs:
- name: Build and push
id: build-and-push
uses: depot/build-push-action@2583627a84956d07561420dcc1d0eb1f2af3fac0 # v1.15.0
uses: depot/build-push-action@9785b135c3c76c33db102e45be96a25ab55cd507 # v1.16.2
with:
context: .
project: 7hd4vdzmw5 # astral-sh/uv
@ -334,6 +356,11 @@ jobs:
docker-annotate-base:
name: annotate uv
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # for GHCR signing
packages: write # for GHCR image pushes
attestations: write # for GHCR attestations
environment:
name: ${{ needs.docker-plan.outputs.push == 'true' && 'release' || '' }}
needs:
@ -342,12 +369,12 @@ jobs:
- docker-publish-extra
if: ${{ needs.docker-plan.outputs.push == 'true' }}
steps:
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
username: astral
password: ${{ secrets.DOCKERHUB_TOKEN_RW }}
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
- uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}

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"
@ -87,7 +96,9 @@ jobs:
run: rustup component add rustfmt
- name: "Install uv"
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
version: "0.9.13"
- name: "rustfmt"
run: cargo fmt --all --check
@ -116,7 +127,7 @@ jobs:
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
env:
# renovate: datasource=github-tags depName=koalaman/shellcheck
SHELLCHECK_VERSION: "v0.10.0"
SHELLCHECK_VERSION: "v0.11.0"
SHELLCHECK_OPTS: --shell bash
with:
version: ${{ env.SHELLCHECK_VERSION }}
@ -133,11 +144,11 @@ 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"
uses: EmbarkStudios/cargo-deny-action@30f817c6f72275c6d54dc744fbca09ebc958599f # v2.0.12
uses: EmbarkStudios/cargo-deny-action@f2ba7abc2abebaf185c833c3961145a3c275caad # v2.0.13
with:
command: check bans
manifest-path: crates/uv-build/Cargo.toml
@ -165,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 }}
@ -176,6 +187,22 @@ jobs:
working-directory: ${{ env.UV_WORKSPACE }}
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
cargo-publish-dry-run:
timeout-minutes: 20
needs: determine_changes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
runs-on: depot-ubuntu-22.04-8
name: "cargo publish dry-run"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "cargo publish dry-run"
run: cargo publish --workspace --dry-run
cargo-dev-generate-all:
timeout-minutes: 10
needs: determine_changes
@ -186,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
@ -201,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
@ -221,17 +253,31 @@ jobs:
with:
persist-credentials: false
- uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
version: "0.9.13"
- name: "Install required Python versions"
run: uv python install
- name: "Install secret service"
run: |
sudo apt update -y
sudo apt install -y gnome-keyring
- name: "Start gnome-keyring"
# run gnome-keyring with 'foobar' as password for the login keyring
# this will create a new login keyring and unlock it
# the login password doesn't matter, but the keyring must be unlocked for the tests to work
run: gnome-keyring-daemon --components=secrets --daemonize --unlock <<< 'foobar'
- name: "Install cargo nextest"
uses: taiki-e/install-action@a416ddeedbd372e614cc1386e8b642692f66865e # v2.57.1
with:
@ -243,30 +289,34 @@ jobs:
UV_HTTP_RETRIES: 5
run: |
cargo nextest run \
--features python-patch \
--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' }}
runs-on: macos-latest-xlarge # github-macos-14-aarch64-6
runs-on: depot-macos-14
name: "cargo test | macos"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
version: "0.9.13"
- name: "Install required Python versions"
run: uv python install
@ -281,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 \
--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
@ -305,11 +356,14 @@ jobs:
run: |
Copy-Item -Path "${{ github.workspace }}" -Destination "$Env:UV_WORKSPACE" -Recurse
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
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 }}
@ -333,8 +387,9 @@ jobs:
shell: bash
run: |
cargo nextest run \
--cargo-profile fast-build \
--no-default-features \
--features python,pypi,python-managed \
--features python,pypi,python-managed,native-auth,windows-native \
--workspace \
--status-level skip --failure-output immediate-final --no-fail-fast -j 20 --final-status-level slow
@ -362,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
@ -422,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"
@ -440,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: |
@ -454,7 +509,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: crate-ci/typos@392b78fe18a52790c53f42456e46124f77346842 # v1.34.0
- uses: crate-ci/typos@64e4db431eb262bb5c6baa19dce280d78532830c # v1.37.3
docs:
timeout-minutes: 10
@ -467,8 +522,19 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
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
@ -493,20 +559,20 @@ jobs:
with:
persist-credentials: false
- uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- 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:
@ -520,20 +586,20 @@ jobs:
with:
persist-credentials: false
- uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- 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:
@ -547,25 +613,25 @@ jobs:
with:
persist-credentials: false
- uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Setup musl"
run: |
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:
@ -579,19 +645,19 @@ jobs:
with:
persist-credentials: false
- uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- 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:
@ -605,19 +671,19 @@ jobs:
with:
persist-credentials: false
- uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- 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:
@ -639,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:
@ -676,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 }}
@ -685,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:
@ -716,12 +782,12 @@ jobs:
env:
MSRV: ${{ steps.msrv.outputs.value }}
- name: "Install mold"
uses: rui314/setup-mold@702b1908b5edf30d71a8d1666b724e0f0c6fa035 # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- run: cargo +${MSRV} build
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- 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
@ -734,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`
@ -742,10 +808,10 @@ 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@136ca0bce2adade21e526ceb07db643ad23dd2dd # v0.5.1
uses: acj/freebsd-firecracker-action@a5a3fc1709c5b5368141a5699f10259aca3cd965 # v0.6.0
with:
verbose: false
checkout: false
@ -756,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" \
@ -767,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
@ -1000,6 +1066,54 @@ jobs:
run: |
(& ./uvx --generate-shell-completion powershell) | Out-String | Invoke-Expression
integration-test-nushell:
timeout-minutes: 10
needs: build-binary-linux-libc
name: "integration test | activate nushell venv"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Install nushell
env:
# This token only needs read access to the GitHub repository nushell/nushell.
# This token is used (via gh-cli) to avoid hitting GitHub REST API rate limits.
GITHUB_TOKEN: ${{ github.token }}
run: |-
# get latest nushell tag name
nu_latest=$(gh release list --repo nushell/nushell --limit 1 --exclude-pre-releases --exclude-drafts --json "tagName" --jq '.[0].tagName')
# trim any trailing whitespace from output
nu_tag=${nu_latest%%[[:space:]]*}
# download binary for x86_64-unknown-linux-gnu target
gh release download ${nu_tag} --repo nushell/nushell --pattern "nu-${nu_tag}-x86_64-unknown-linux-gnu.tar.gz"
# extract nu binary from tar.gz
tar -xf "nu-${nu_tag}-x86_64-unknown-linux-gnu.tar.gz"
# make the binary executable
chmod +x "./nu-${nu_tag}-x86_64-unknown-linux-gnu/nu"
# add it to PATH
echo "${{ github.workspace }}/nu-${nu_tag}-x86_64-unknown-linux-gnu" >> "${GITHUB_PATH}"
- name: Download binary
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: uv-linux-libc-${{ github.sha }}
- name: Prepare binary
run: chmod +x ./uv
- name: Create venv
# The python version is arbitrary for this test.
# We only want to ensure the activation script behaves properly
run: ./uv venv
- name: Activate venv
shell: nu {0}
run: overlay use ${{ github.workspace }}/.venv/bin/activate.nu
integration-test-conda:
timeout-minutes: 10
needs: build-binary-linux-libc
@ -1216,6 +1330,30 @@ jobs:
./uv run python -c ""
./uv run -p 3.13 python -c ""
integration-test-windows-python-install-manager:
timeout-minutes: 10
needs: build-binary-windows-x86_64
name: "integration test | windows python install manager"
runs-on: windows-latest
steps:
- name: "Download binary"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: uv-windows-x86_64-${{ github.sha }}
- name: "Install Python via Python Install manager"
run: |
# https://www.python.org/downloads/release/pymanager-250/
winget install --accept-package-agreements --accept-source-agreements 9NQ7512CXL7T
# Call Python Install Manager's py.exe by full path to avoid legacy py.exe
& "$env:LOCALAPPDATA\Microsoft\WindowsApps\py.exe" install 3.14
# https://github.com/astral-sh/uv/issues/16204
- name: "Check temporary environment creation"
run: |
./uv run -p $env:LOCALAPPDATA\Python\pythoncore-3.14-64\python.exe --with numpy python -c "import sys; print(sys.executable)"
integration-test-pypy-linux:
timeout-minutes: 10
needs: build-binary-linux-libc
@ -1491,9 +1629,9 @@ jobs:
./uv venv venv-native -p 3.12
# We use features added in 0.30.3 but there is no known breakage in
# newer versions.
./uv pip install -p venv-native/bin/python pyodide-build==0.30.3 pip
./uv pip install -p venv-native/bin/python pyodide-build==0.30.7 pip
- name: "Install pyodide interpreter"
- name: "Install Pyodide interpreter"
run: |
source ./venv-native/bin/activate
pyodide xbuildenv install 0.27.5
@ -1502,13 +1640,25 @@ jobs:
echo "PYODIDE_PYTHON=$PYODIDE_PYTHON" >> $GITHUB_ENV
echo "PYODIDE_INDEX=$PYODIDE_INDEX" >> $GITHUB_ENV
- name: "Create pyodide virtual environment"
- name: "Create Pyodide virtual environment"
run: |
./uv venv -p $PYODIDE_PYTHON venv-pyodide
source ./venv-pyodide/bin/activate
./uv pip install --extra-index-url=$PYODIDE_INDEX --no-build numpy
python -c 'import numpy'
- name: "Install Pyodide with uv python"
run: |
./uv python install cpython-3.13.2-emscripten-wasm32-musl
- name: "Create a Pyodide virtual environment using uv installed Python"
run: |
./uv venv -p cpython-3.13.2-emscripten-wasm32-musl venv-pyodide2
# TODO: be able to install Emscripten wheels here...
source ./venv-pyodide2/bin/activate
./uv pip install packaging
python -c 'import packaging'
integration-test-github-actions:
timeout-minutes: 10
needs: build-binary-linux-libc
@ -1606,6 +1756,58 @@ jobs:
run: |
.venv/bin/python -c "import sys; exit(1) if sys._is_gil_enabled() else exit(0)"
integration-test-wsl:
timeout-minutes: 15
needs: build-binary-linux-musl
name: "integration test | pyenv on wsl x86-64"
runs-on: windows-latest
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event.pull_request.head.repo.fork != true }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: "Download binary"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: uv-linux-musl-${{ github.sha }}
- name: "Setup WSL"
uses: Vampire/setup-wsl@6a8db447be7ed35f2f499c02c6e60ff77ef11278 # v6.0.0
with:
distribution: Ubuntu-22.04
- name: "Install pyenv-win"
shell: pwsh
run: |
Write-Host "Installing pyenv-win..."
Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"
.\install-pyenv-win.ps1
# Add pyenv-win to PATH for this session
$env:PYENV = "$env:USERPROFILE\.pyenv\pyenv-win"
$env:PATH = "$env:PYENV\bin;$env:PYENV\shims;$env:PATH"
# Add to GITHUB_PATH so WSL can find the shims
echo "$env:PYENV\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
echo "$env:PYENV\shims" | Out-File -FilePath $env:GITHUB_PATH -Append
Write-Host "Installing Python 3.11.9 via pyenv-win..."
& "$env:PYENV\bin\pyenv.bat" install 3.11.9
& "$env:PYENV\bin\pyenv.bat" global 3.11.9
Write-Host "Verifying pyenv-win installation..."
& "$env:PYENV\bin\pyenv.bat" versions
- name: "Test uv"
shell: wsl-bash {0}
run: |
set -x
chmod +x ./uv
# Check that we don't fail on `pyenv-win` shims
./uv python list -v
integration-test-publish-changed:
timeout-minutes: 10
needs: build-binary-linux-libc
@ -1671,7 +1873,7 @@ jobs:
run: chmod +x ./uv
- name: "Configure AWS credentials"
uses: aws-actions/configure-aws-credentials@a159d7bb5354cf786f855f2f5d1d8d768d9a08d1
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 }}
@ -1690,12 +1892,12 @@ jobs:
- name: "Authenticate with GCP"
id: "auth"
uses: "google-github-actions/auth@b7593ed2efd1c1617e1b0254da33b86225adb2a5"
uses: "google-github-actions/auth@fc2174804b84f912b1f6d334e9463f484f1c552d"
with:
credentials_json: "${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}"
- name: "Set up GCP SDK"
uses: "google-github-actions/setup-gcloud@6a7c903a70c8625ed6700fa299f5ddb4ca6022e9"
uses: "google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db"
- name: "Get GCP Artifact Registry token"
id: get_token
@ -1704,8 +1906,32 @@ jobs:
echo "::add-mask::$UV_TEST_GCP_TOKEN"
echo "UV_TEST_GCP_TOKEN=$UV_TEST_GCP_TOKEN" >> $GITHUB_ENV
- name: "Run registry tests"
run: ./uv run -p "${PYTHON_VERSION}" scripts/registries-test.py --uv ./uv --color always --all
- name: "Run registry tests with environment variable backend"
run: ./uv run -p "${PYTHON_VERSION}" scripts/registries-test.py --uv ./uv --color always --all --auth-method env
env:
RUST_LOG: uv=debug
UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }}
UV_TEST_ARTIFACTORY_URL: ${{ secrets.UV_TEST_ARTIFACTORY_URL }}
UV_TEST_ARTIFACTORY_USERNAME: ${{ secrets.UV_TEST_ARTIFACTORY_USERNAME }}
UV_TEST_AWS_URL: ${{ secrets.UV_TEST_AWS_URL }}
UV_TEST_AWS_USERNAME: aws
UV_TEST_AZURE_TOKEN: ${{ secrets.UV_TEST_AZURE_TOKEN }}
UV_TEST_AZURE_URL: ${{ secrets.UV_TEST_AZURE_URL }}
UV_TEST_AZURE_USERNAME: dummy
UV_TEST_CLOUDSMITH_TOKEN: ${{ secrets.UV_TEST_CLOUDSMITH_TOKEN }}
UV_TEST_CLOUDSMITH_URL: ${{ secrets.UV_TEST_CLOUDSMITH_URL }}
UV_TEST_CLOUDSMITH_USERNAME: ${{ secrets.UV_TEST_CLOUDSMITH_USERNAME }}
UV_TEST_GCP_URL: ${{ secrets.UV_TEST_GCP_URL }}
UV_TEST_GCP_USERNAME: oauth2accesstoken
UV_TEST_GEMFURY_TOKEN: ${{ secrets.UV_TEST_GEMFURY_TOKEN }}
UV_TEST_GEMFURY_URL: ${{ secrets.UV_TEST_GEMFURY_URL }}
UV_TEST_GEMFURY_USERNAME: ${{ secrets.UV_TEST_GEMFURY_USERNAME }}
UV_TEST_GITLAB_TOKEN: ${{ secrets.UV_TEST_GITLAB_TOKEN }}
UV_TEST_GITLAB_URL: ${{ secrets.UV_TEST_GITLAB_URL }}
UV_TEST_GITLAB_USERNAME: token
- name: "Run registry tests with text store backend"
run: ./uv run -p "${PYTHON_VERSION}" scripts/registries-test.py --uv ./uv --color always --all --auth-method text-store
env:
RUST_LOG: uv=debug
UV_TEST_ARTIFACTORY_TOKEN: ${{ secrets.UV_TEST_ARTIFACTORY_TOKEN }}
@ -1773,7 +1999,7 @@ jobs:
../uv build
- name: "Publish astral-test-pypa-gh-action"
uses: pypa/gh-action-pypi-publish@db8f07d3871a0a180efa06b95d467625c19d5d5f # release/v1
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
# With this GitHub action, we can't do as rigid checks as with our custom Python script, so we publish more
# leniently
@ -1790,6 +2016,12 @@ jobs:
env:
UV_TEST_PUBLISH_KEYRING: ${{ secrets.UV_TEST_PUBLISH_KEYRING }}
- name: "Add password to uv text store"
run: |
./uv auth login https://test.pypi.org/legacy/?astral-test-text-store --token ${UV_TEST_PUBLISH_TEXT_STORE}
env:
UV_TEST_PUBLISH_TEXT_STORE: ${{ secrets.UV_TEST_PUBLISH_TEXT_STORE }}
- name: "Publish test packages"
# `-p 3.12` prefers the python we just installed over the one locked in `.python_version`.
run: ./uv run -p "${PYTHON_VERSION}" scripts/publish/test_publish.py --uv ./uv all
@ -1800,6 +2032,7 @@ jobs:
UV_TEST_PUBLISH_GITLAB_PAT: ${{ secrets.UV_TEST_PUBLISH_GITLAB_PAT }}
UV_TEST_PUBLISH_CODEBERG_TOKEN: ${{ secrets.UV_TEST_PUBLISH_CODEBERG_TOKEN }}
UV_TEST_PUBLISH_CLOUDSMITH_TOKEN: ${{ secrets.UV_TEST_PUBLISH_CLOUDSMITH_TOKEN }}
UV_TEST_PUBLISH_PYX_TOKEN: ${{ secrets.UV_TEST_PUBLISH_PYX_TOKEN }}
UV_TEST_PUBLISH_PYTHON_VERSION: ${{ env.PYTHON_VERSION }}
integration-uv-build-backend:
@ -1834,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:
@ -2035,11 +2268,11 @@ jobs:
needs: build-binary-linux-musl
name: "check system | python on rocky linux ${{ matrix.rocky-version }}"
runs-on: ubuntu-latest
container: rockylinux:${{ matrix.rocky-version }}
container: rockylinux/rockylinux:${{ matrix.rocky-version }}
strategy:
fail-fast: false
matrix:
rocky-version: ["8", "9"]
rocky-version: ["8", "9", "10"]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
@ -2048,12 +2281,29 @@ jobs:
- name: "Install Python"
if: matrix.rocky-version == '8'
run: |
dnf install python39 python39-pip which -y
for i in {1..5}; do
dnf install python39 python39-pip which -y && break || { echo "Attempt $i failed, retrying in 10 seconds..."; sleep 10; }
if [ $i -eq 5 ]; then
echo "Failed to install Python after 5 attempts"
exit 1
fi
done
- name: "Install Python"
if: matrix.rocky-version == '9'
run: |
dnf install python3.9 python3.9-pip which -y
for i in {1..5}; do
dnf install python3.9 python3.9-pip which -y && break || { echo "Attempt $i failed, retrying in 10 seconds..."; sleep 10; }
if [ $i -eq 5 ]; then
echo "Failed to install Python after 5 attempts"
exit 1
fi
done
- name: "Install Python"
if: matrix.rocky-version == '10'
run: |
dnf install python3 python3-pip which -y
- name: "Download binary"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
@ -2200,8 +2450,6 @@ jobs:
- name: "Prepare binary"
run: chmod +x ./uv
# This should be the macOS system Python
# We'd like to test with Homebrew but this Python takes precedence in system Python discovery
- name: "Print Python path"
run: echo $(which python3)
@ -2235,11 +2483,40 @@ jobs:
- name: "Validate global Python install"
run: python3 scripts/check_system_python.py --uv ./uv --externally-managed
system-test-macos-aarch64-emulated:
timeout-minutes: 10
needs: build-binary-macos-aarch64
name: "check system | x86-64 python on macos aarch64"
runs-on: macos-14 # github-macos-14-aarch64-3
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: 3.13
architecture: x64
- name: "Download binary"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: uv-macos-aarch64-${{ github.sha }}
- name: "Prepare binary"
run: chmod +x ./uv
- name: "Print Python path"
run: echo $(which python3)
- name: "Validate global Python install"
run: python3 scripts/check_system_python.py --uv ./uv --externally-managed
system-test-macos-x86_64:
timeout-minutes: 10
needs: build-binary-macos-x86_64
name: "check system | python on macos x86-64"
runs-on: macos-13 # github-macos-13-x86_64-4
runs-on: macos-15-intel # github-macos-15-x86_64-4
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
@ -2261,7 +2538,7 @@ jobs:
run: echo $(which python3)
- name: "Validate global Python install"
run: python3 scripts/check_system_python.py --uv ./uv
run: python3 scripts/check_system_python.py --uv ./uv --externally-managed
system-test-windows-python-310:
timeout-minutes: 10
@ -2647,14 +2924,14 @@ jobs:
runs-on: codspeed-macro
needs: determine_changes
if: ${{ github.repository == 'astral-sh/uv' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
timeout-minutes: 25
steps:
- name: "Checkout Branch"
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: "Install Rust toolchain"
run: rustup show
@ -2669,16 +2946,17 @@ 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 --features codspeed -p uv-bench
run: cargo codspeed build --profile profiling -p uv-bench
- name: "Run benchmarks"
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
with:
run: cargo codspeed run
mode: walltime
token: ${{ secrets.CODSPEED_TOKEN }}
benchmarks-instrumented:
@ -2693,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
@ -2708,14 +2986,15 @@ 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 --features codspeed -p uv-bench
run: cargo codspeed build --profile profiling -p uv-bench
- name: "Run benchmarks"
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
with:
run: cargo codspeed run
mode: instrumentation
token: ${{ secrets.CODSPEED_TOKEN }}

33
.github/workflows/publish-crates.yml vendored Normal file
View File

@ -0,0 +1,33 @@
# Publish a release to crates.io.
#
# Assumed to run as a subworkflow of .github/workflows/release.yml; specifically, as a publish job
# within `cargo-dist`.
name: "Publish to crates.io"
on:
workflow_call:
inputs:
plan:
required: true
type: string
jobs:
crates-publish-uv:
name: Upload uv to crates.io
runs-on: ubuntu-latest
environment:
name: release
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
# TODO(zanieb): Switch to trusted publishing once published
# - uses: rust-lang/crates-io-auth-action@v1
# id: auth
- name: Publish workspace crates
# 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,11 +18,10 @@ 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: wheels_uv-*
@ -37,11 +36,10 @@ 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: wheels_uv_build-*

View File

@ -1,7 +1,6 @@
# This file was autogenerated by dist: https://github.com/astral-sh/cargo-dist
# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist
#
# Copyright 2022-2024, axodotdev
# Copyright 2025 Astral Software Inc.
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
@ -69,7 +68,7 @@ jobs:
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.7/cargo-dist-installer.sh | sh"
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.2/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
@ -169,8 +168,8 @@ jobs:
- custom-build-binaries
- custom-build-docker
- build-global-artifacts
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') }}
# Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "depot-ubuntu-latest-4"
@ -223,17 +222,36 @@ jobs:
"id-token": "write"
"packages": "write"
custom-publish-crates:
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:
plan: ${{ needs.plan.outputs.val }}
secrets: inherit
# publish jobs get escalated permissions
permissions:
"contents": "read"
# Create a GitHub Release while uploading all files to it
announce:
needs:
- plan
- host
- custom-publish-pypi
- custom-publish-crates
# use "always() && ..." to allow us to wait for all publish jobs while
# still allowing individual publish jobs to skip themselves (for prereleases).
# "host" however must run to completion, no skipping allowed!
if: ${{ always() && needs.host.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') }}
if: ${{ always() && needs.host.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') && (needs.custom-publish-crates.result == 'skipped' || needs.custom-publish-crates.result == 'success') }}
runs-on: "depot-ubuntu-latest-4"
permissions:
"attestations": "write"
"contents": "write"
"id-token": "write"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
@ -252,6 +270,15 @@ jobs:
run: |
# Remove the granular manifests
rm -f artifacts/*-dist-manifest.json
- name: Attest
uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2
with:
subject-path: |
artifacts/*.json
artifacts/*.sh
artifacts/*.ps1
artifacts/*.zip
artifacts/*.tar.gz
- name: Create GitHub Release
env:
PRERELEASE_FLAG: "${{ fromJson(needs.host.outputs.val).announcement_is_prerelease && '--prerelease' || '' }}"

View File

@ -24,7 +24,7 @@ if ($env:DEPOT_RUNNER -eq "1") {
# Create VHD and configure drive using diskpart
$vhdPath = "C:\uv_dev_drive.vhdx"
@"
create vdisk file="$vhdPath" maximum=20480 type=expandable
create vdisk file="$vhdPath" maximum=25600 type=expandable
attach vdisk
create partition primary
active
@ -41,9 +41,9 @@ assign letter=V
Write-Output "Using existing drive at D:"
$Drive = "D:"
} else {
# The size (20 GB) is chosen empirically to be large enough for our
# The size (25 GB) is chosen empirically to be large enough for our
# workflows; larger drives can take longer to set up.
$Volume = New-VHD -Path C:/uv_dev_drive.vhdx -SizeBytes 20GB |
$Volume = New-VHD -Path C:/uv_dev_drive.vhdx -SizeBytes 25GB |
Mount-VHD -Passthru |
Initialize-Disk -Passthru |
New-Partition -AssignDriveLetter -UseMaximumSize |

View File

@ -20,7 +20,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
version: "latest"
enable-cache: true
@ -49,3 +49,4 @@ jobs:
title: "Sync latest Python releases"
body: "Automated update for Python releases."
base: "main"
draft: true

View File

@ -21,4 +21,4 @@ jobs:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@f52a838cfabf134edcbaa7c8b3677dde20045018 # v0.1.1
uses: zizmorcore/zizmor-action@5ca5fc7a4779c5263a3ffa0e1f693009994446d1 # v0.1.2

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

@ -12,7 +12,7 @@ repos:
- id: validate-pyproject
- repo: https://github.com/crate-ci/typos
rev: v1.34.0
rev: v1.37.2
hooks:
- id: typos
@ -42,7 +42,7 @@ repos:
types_or: [yaml, json5]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.7
rev: v0.13.3
hooks:
- id: ruff-format
- id: ruff

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

@ -1,3 +1,13 @@
# These are versions of Python required for running uv's own test suite. You can add or remove
# versions here as needed for tests; this doesn't impact uv's own functionality. They can be
# installed through any means you like, e.g. `uv python install` if you already have a build of uv,
# `cargo run python install`, or through some other installer.
#
# In uv's CI in GitHub Actions, they are bootstrapped by an existing released version of uv,
# installed by the astral-sh/setup-uv action If you need a newer or different version, you will
# first need to complete a uv release capable of installing that version, get it picked up by
# astral-sh/setup-uv, and update its hash in .github/workflows.
3.14.0
3.13.2
3.12.9
@ -10,3 +20,5 @@
3.9.12
# The following is needed for `==3.13` request tests
3.13.0
# A pre-release version required for testing
3.14.0rc2

View File

@ -3,375 +3,550 @@
<!-- prettier-ignore-start -->
## 0.8.8
## 0.9.18
### Bug fixes
- Fix `find_uv_bin` compatibility with Python <3.10 ([#15177](https://github.com/astral-sh/uv/pull/15177))
## 0.8.7
### Python
- On Mac/Linux, libtcl, libtk, and _tkinter are built as separate shared objects, which fixes matplotlib's `tkagg` backend (the default on Linux), Pillow's `PIL.ImageTk` library, and other extension modules that need to use libtcl/libtk directly.
- Tix is no longer provided on Linux. This is a deprecated Tk extension that appears to have been previously broken.
See the [`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250808) for details.
Released on 2025-12-16.
### Enhancements
- Do not update `uv.lock` when using `--isolated` ([#15154](https://github.com/astral-sh/uv/pull/15154))
- Add support for `--prefix` and `--with` installations in `find_uv_bin` ([#14184](https://github.com/astral-sh/uv/pull/14184))
- Add support for discovering base prefix installations in `find_uv_bin` ([#14181](https://github.com/astral-sh/uv/pull/14181))
- Improve error messages in `find_uv_bin` ([#14182](https://github.com/astral-sh/uv/pull/14182))
- Warn when two packages write to the same module ([#13437](https://github.com/astral-sh/uv/pull/13437))
### Preview features
- Add support for `package`-level conflicts in workspaces ([#14906](https://github.com/astral-sh/uv/pull/14906))
- 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
- Add `UV_DEV` and `UV_NO_DEV` environment variables (for `--dev` and `--no-dev`) ([#15010](https://github.com/astral-sh/uv/pull/15010))
- 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
- Fix regression where `--require-hashes` applied to build dependencies in `uv pip install` ([#15153](https://github.com/astral-sh/uv/pull/15153))
- Ignore GraalPy devtags ([#15013](https://github.com/astral-sh/uv/pull/15013))
- Include all site packages directories in ephemeral environment overlays ([#15121](https://github.com/astral-sh/uv/pull/15121))
- Search in the user scheme scripts directory last in `find_uv_bin` ([#14191](https://github.com/astral-sh/uv/pull/14191))
- 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
- Add missing periods (`.`) to list elements in `Features` docs page ([#15138](https://github.com/astral-sh/uv/pull/15138))
- 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.8.6
## 0.9.17
This release contains hardening measures to address differentials in behavior between uv and Python's built-in ZIP parser ([CVE-2025-54368](https://github.com/astral-sh/uv/security/advisories/GHSA-8qf3-x8v5-2pj8)).
Released on 2025-12-09.
Prior to this release, attackers could construct ZIP files that would be extracted differently by pip, uv, and other tools. As a result, ZIPs could be constructed that would be considered harmless by (e.g.) scanners, but contain a malicious payload when extracted by uv. As of v0.8.6, uv now applies additional checks to reject such ZIPs.
### Enhancements
Thanks to a triage effort with the [Python Security Response Team](https://devguide.python.org/developer-workflow/psrt/) and PyPI maintainers, we were able to determine that these differentials **were not exploited** via PyPI during the time they were present. The PyPI team has also implemented similar checks and now guards against these parsing differentials on upload.
- 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))
Although the practical risk of exploitation is low, we take the *hypothetical* risk of parser differentials very seriously. Out of an abundance of caution, we have assigned this advisory a CVE identifier and have given it a "moderate" severity suggestion.
### Bug fixes
These changes have been validated against the top 15,000 PyPI packages; however, it's plausible that a non-malicious ZIP could be falsely rejected with this additional hardening. As an escape hatch, users who do encounter breaking changes can enable `UV_INSECURE_NO_ZIP_VALIDATION` to restore the previous behavior. If you encounter such a rejection, please file an issue in uv and to the upstream package.
- Respect dropped (but explicit) indexes in dependency groups ([#17012](https://github.com/astral-sh/uv/pull/17012))
For additional information, please refer to the following blog posts:
### Documentation
* [Astral: uv security advisory: ZIP payload obfuscation](https://astral.sh/blog/uv-security-advisory-cve-2025-54368)
* [PyPI: Preventing ZIP parser confusion attacks on Python package installers](https://blog.pypi.org/posts/2025-08-07-wheel-archive-confusion-attacks/)
- 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.
### Python
- Add CPython 3.15.0a2
See the [`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20251120) for details.
### Enhancements
- Add SBOM support to `uv export` ([#16523](https://github.com/astral-sh/uv/pull/16523))
- Publish to `crates.io` ([#16770](https://github.com/astral-sh/uv/pull/16770))
### Preview features
- Add `uv workspace list --paths` ([#16776](https://github.com/astral-sh/uv/pull/16776))
- Fix the preview warning on `uv workspace dir` ([#16775](https://github.com/astral-sh/uv/pull/16775))
### Bug fixes
- Fix `uv init` author serialization via `toml_edit` inline tables ([#16778](https://github.com/astral-sh/uv/pull/16778))
- Fix status messages without TTY ([#16785](https://github.com/astral-sh/uv/pull/16785))
- Preserve end-of-line comment whitespace when editing `pyproject.toml` ([#16734](https://github.com/astral-sh/uv/pull/16734))
- Disable `always-authenticate` when running under Dependabot ([#16773](https://github.com/astral-sh/uv/pull/16773))
### Documentation
- Document the new behavior for free-threaded python versions ([#16781](https://github.com/astral-sh/uv/pull/16781))
- Improve note about build system in publish guide ([#16788](https://github.com/astral-sh/uv/pull/16788))
- Move do not upload publish note out of the guide into concepts ([#16789](https://github.com/astral-sh/uv/pull/16789))
## 0.9.10
Released on 2025-11-17.
### Enhancements
- Add support for `SSL_CERT_DIR` ([#16473](https://github.com/astral-sh/uv/pull/16473))
- Enforce UTF8-encoded license files during `uv build` ([#16699](https://github.com/astral-sh/uv/pull/16699))
- Error when a `project.license-files` glob matches nothing ([#16697](https://github.com/astral-sh/uv/pull/16697))
- `pip install --target` (and `sync`) install Python if necessary ([#16694](https://github.com/astral-sh/uv/pull/16694))
- Account for `python_downloads_json_url` in pre-release Python version warnings ([#16737](https://github.com/astral-sh/uv/pull/16737))
- Support HTTP/HTTPS URLs in `uv python --python-downloads-json-url` ([#16542](https://github.com/astral-sh/uv/pull/16542))
### Preview features
- Add support for `--upgrade` in `uv python install` ([#16676](https://github.com/astral-sh/uv/pull/16676))
- Fix handling of `python install --default` for pre-release Python versions ([#16706](https://github.com/astral-sh/uv/pull/16706))
- Add `uv workspace list` to list workspace members ([#16691](https://github.com/astral-sh/uv/pull/16691))
### Bug fixes
- Don't check file URLs for ambiguously parsed credentials ([#16759](https://github.com/astral-sh/uv/pull/16759))
### Documentation
- Add a "storage" reference document ([#15954](https://github.com/astral-sh/uv/pull/15954))
## 0.9.9
Released on 2025-11-12.
### Deprecations
- Deprecate use of `--project` in `uv init` ([#16674](https://github.com/astral-sh/uv/pull/16674))
### Enhancements
- Add iOS support to Python interpreter discovery ([#16686](https://github.com/astral-sh/uv/pull/16686))
- Reject ambiguously parsed URLs ([#16622](https://github.com/astral-sh/uv/pull/16622))
- Allow explicit values in `uv version --bump` ([#16555](https://github.com/astral-sh/uv/pull/16555))
- Warn on use of managed pre-release Python versions when a stable version is available ([#16619](https://github.com/astral-sh/uv/pull/16619))
- Allow signing trampolines on Windows by using `.rcdata` to store metadata ([#15068](https://github.com/astral-sh/uv/pull/15068))
- Add `--only-emit-workspace` and similar variants to `uv export` ([#16681](https://github.com/astral-sh/uv/pull/16681))
### Preview features
- Add `uv workspace dir` command ([#16678](https://github.com/astral-sh/uv/pull/16678))
- Add `uv workspace metadata` command ([#16516](https://github.com/astral-sh/uv/pull/16516))
### Configuration
- Add `UV_NO_DEFAULT_GROUPS` environment variable ([#16645](https://github.com/astral-sh/uv/pull/16645))
### Bug fixes
- Remove `torch-model-archiver` and `torch-tb-profiler` from PyTorch backend ([#16655](https://github.com/astral-sh/uv/pull/16655))
- Fix Pixi environment detection ([#16585](https://github.com/astral-sh/uv/pull/16585))
### Documentation
- Fix `CMD` path in FastAPI Dockerfile ([#16701](https://github.com/astral-sh/uv/pull/16701))
## 0.9.8
Released on 2025-11-07.
### Enhancements
- Accept multiple packages in `uv export` ([#16603](https://github.com/astral-sh/uv/pull/16603))
- Accept multiple packages in `uv sync` ([#16543](https://github.com/astral-sh/uv/pull/16543))
- Add a `uv cache size` command ([#16032](https://github.com/astral-sh/uv/pull/16032))
- Add prerelease guidance for build-system resolution failures ([#16550](https://github.com/astral-sh/uv/pull/16550))
- Allow Python requests to include `+gil` to require a GIL-enabled interpreter ([#16537](https://github.com/astral-sh/uv/pull/16537))
- Avoid pluralizing 'retry' for single value ([#16535](https://github.com/astral-sh/uv/pull/16535))
- Enable first-class dependency exclusions ([#16528](https://github.com/astral-sh/uv/pull/16528))
- Fix inclusive constraints on available package versions in resolver errors ([#16629](https://github.com/astral-sh/uv/pull/16629))
- Improve `uv init` error for invalid directory names ([#16554](https://github.com/astral-sh/uv/pull/16554))
- Show help on `uv build -h` ([#16632](https://github.com/astral-sh/uv/pull/16632))
- Include the Python variant suffix in "Using Python ..." messages ([#16536](https://github.com/astral-sh/uv/pull/16536))
- Log most recently modified file for cache-keys ([#16338](https://github.com/astral-sh/uv/pull/16338))
- Update Docker builds to use nightly Rust toolchain with musl v1.2.5 ([#16584](https://github.com/astral-sh/uv/pull/16584))
- Add GitHub attestations for uv release artifacts ([#11357](https://github.com/astral-sh/uv/pull/11357))
### Configuration
- Expose `UV_NO_GROUP` as an environment variable ([#16529](https://github.com/astral-sh/uv/pull/16529))
- Add `UV_NO_SOURCES` as an environment variable ([#15883](https://github.com/astral-sh/uv/pull/15883))
### Bug fixes
- Allow `--check` and `--locked` to be used together in `uv lock` ([#16538](https://github.com/astral-sh/uv/pull/16538))
- Allow for unnormalized names in the METADATA file (#16547) ([#16548](https://github.com/astral-sh/uv/pull/16548))
- Fix missing value_type for `default-groups` in schema ([#16575](https://github.com/astral-sh/uv/pull/16575))
- Respect multi-GPU outputs in `nvidia-smi` ([#15460](https://github.com/astral-sh/uv/pull/15460))
- Fix DNS lookup errors in Docker containers ([#8450](https://github.com/astral-sh/uv/issues/8450))
### Documentation
- Fix typo in uv tool list doc ([#16625](https://github.com/astral-sh/uv/pull/16625))
- Note `uv pip list` name normalization in docs ([#13210](https://github.com/astral-sh/uv/pull/13210))
### Other changes
- Update Rust toolchain to 1.91 and MSRV to 1.89 ([#16531](https://github.com/astral-sh/uv/pull/16531))
## 0.9.7
Released on 2025-10-30.
### Enhancements
- Add Windows x86-32 emulation support to interpreter architecture checks ([#13475](https://github.com/astral-sh/uv/pull/13475))
- Improve readability of progress bars ([#16509](https://github.com/astral-sh/uv/pull/16509))
### Bug fixes
- Drop terminal coloring from `uv auth token` output ([#16504](https://github.com/astral-sh/uv/pull/16504))
- Don't use UV_LOCKED to enable `--check` flag ([#16521](https://github.com/astral-sh/uv/pull/16521))
## 0.9.6
Released on 2025-10-29.
This release contains an upgrade to Astral's fork of `async_zip`, which addresses potential sources of ZIP parsing differentials between uv and other Python packaging tooling. See [GHSA-pqhf-p39g-3x64](https://github.com/astral-sh/uv/security/advisories/GHSA-pqhf-p39g-3x64) for additional details.
### Security
- Harden ZIP streaming to reject repeated entries and other malformed ZIP files ([#15136](https://github.com/astral-sh/uv/pull/15136))
* Address ZIP parsing differentials ([GHSA-pqhf-p39g-3x64](https://github.com/astral-sh/uv/security/advisories/GHSA-pqhf-p39g-3x64))
### Python
- Add CPython 3.13.6
### Configuration
- Add support for per-project build-time environment variables ([#15095](https://github.com/astral-sh/uv/pull/15095))
### Bug fixes
- Avoid invalid simplification with conflict markers ([#15041](https://github.com/astral-sh/uv/pull/15041))
- Respect `UV_HTTP_RETRIES` in `uv publish` ([#15106](https://github.com/astral-sh/uv/pull/15106))
- Support `UV_NO_EDITABLE` where `--no-editable` is supported ([#15107](https://github.com/astral-sh/uv/pull/15107))
- Upgrade `cargo-dist` to add `UV_INSTALLER_URL` to PowerShell installer ([#15114](https://github.com/astral-sh/uv/pull/15114))
- Upgrade `h2` again to avoid `too_many_internal_resets` errors ([#15111](https://github.com/astral-sh/uv/pull/15111))
- Consider `pythonw` when copying entry points in uv run ([#15134](https://github.com/astral-sh/uv/pull/15134))
### Documentation
- Ensure symlink warning is shown ([#15126](https://github.com/astral-sh/uv/pull/15126))
## 0.8.5
- Upgrade GraalPy to 25.0.1 ([#16401](https://github.com/astral-sh/uv/pull/16401))
### Enhancements
- Enable `uv run` with a GitHub Gist ([#15058](https://github.com/astral-sh/uv/pull/15058))
- Improve HTTP response caching log messages ([#15067](https://github.com/astral-sh/uv/pull/15067))
- Show wheel tag hints in install plan ([#15066](https://github.com/astral-sh/uv/pull/15066))
- Support installing additional executables in `uv tool install` ([#14014](https://github.com/astral-sh/uv/pull/14014))
### Preview features
- Enable extra build dependencies to 'match runtime' versions ([#15036](https://github.com/astral-sh/uv/pull/15036))
- Remove duplicate `extra-build-dependencies` warnings for `uv pip` ([#15088](https://github.com/astral-sh/uv/pull/15088))
- Use "option" instead of "setting" in `pylock` warning ([#15089](https://github.com/astral-sh/uv/pull/15089))
- Respect extra build requires when reading from wheel cache ([#15030](https://github.com/astral-sh/uv/pull/15030))
- Preserve lowered extra build dependencies ([#15038](https://github.com/astral-sh/uv/pull/15038))
- Add `--clear` to `uv build` to remove old build artifacts ([#16371](https://github.com/astral-sh/uv/pull/16371))
- Add `--no-create-gitignore` to `uv build` ([#16369](https://github.com/astral-sh/uv/pull/16369))
- Do not error when a virtual environment directory cannot be removed due to a busy error ([#16394](https://github.com/astral-sh/uv/pull/16394))
- Improve hint on `pip install --system` when externally managed ([#16392](https://github.com/astral-sh/uv/pull/16392))
- Running `uv lock --check` with outdated lockfile will print that `--check` was passed, instead of `--locked` ([#16322](https://github.com/astral-sh/uv/pull/16322))
- Update `uv init` template for Maturin ([#16449](https://github.com/astral-sh/uv/pull/16449))
- Improve ordering of Python sources in logs ([#16463](https://github.com/astral-sh/uv/pull/16463))
- Restore DockerHub release images and annotations ([#16441](https://github.com/astral-sh/uv/pull/16441))
### Bug fixes
- Add Python versions to markers implied from wheels ([#14913](https://github.com/astral-sh/uv/pull/14913))
- Ensure consistent indentation when adding dependencies ([#14991](https://github.com/astral-sh/uv/pull/14991))
- Fix handling of `python-preference = system` when managed interpreters are on the PATH ([#15059](https://github.com/astral-sh/uv/pull/15059))
- Fix symlink preservation in virtual environment creation ([#14933](https://github.com/astral-sh/uv/pull/14933))
- Gracefully handle entrypoint permission errors ([#15026](https://github.com/astral-sh/uv/pull/15026))
- Include wheel hashes from local Simple indexes ([#14993](https://github.com/astral-sh/uv/pull/14993))
- Prefer system Python installations over managed ones when `--system` is used ([#15061](https://github.com/astral-sh/uv/pull/15061))
- Remove retry wrapper when matching on error kind ([#14996](https://github.com/astral-sh/uv/pull/14996))
- Revert `h2` upgrade ([#15079](https://github.com/astral-sh/uv/pull/15079))
- Check for matching Python implementation during `uv python upgrade` ([#16420](https://github.com/astral-sh/uv/pull/16420))
- Deterministically order `--find-links` distributions ([#16446](https://github.com/astral-sh/uv/pull/16446))
- Don't panic in `uv export --frozen` when the lockfile is outdated ([#16407](https://github.com/astral-sh/uv/pull/16407))
- Fix root of `uv tree` when `--package` is used with circular dependencies ([#15908](https://github.com/astral-sh/uv/pull/15908))
- Show package list with `pip freeze --quiet` ([#16491](https://github.com/astral-sh/uv/pull/16491))
- Limit `uv auth login pyx.dev` retries to 60s ([#16498](https://github.com/astral-sh/uv/pull/16498))
- Add an empty group with `uv add --group ... -r ...` ([#16490](https://github.com/astral-sh/uv/pull/16490))
### Documentation
- Improve visibility of copy and line separator in dark mode ([#14987](https://github.com/astral-sh/uv/pull/14987))
- Update docs for maturin build backend init template ([#16469](https://github.com/astral-sh/uv/pull/16469))
- Update docs to reflect previous changes to signal forwarding semantics ([#16430](https://github.com/astral-sh/uv/pull/16430))
- Add instructions for installing via MacPorts ([#16039](https://github.com/astral-sh/uv/pull/16039))
## 0.8.4
## 0.9.5
Released on 2025-10-21.
This release contains an upgrade to `astral-tokio-tar`, which addresses a vulnerability in tar extraction on malformed archives with mismatching size information between the ustar header and PAX extensions. While the `astral-tokio-tar` advisory has been graded as "high" due its potential broader impact, the *specific* impact to uv is **low** due to a lack of novel attacker capability. Specifically, uv only processes tar archives from source distributions, which already possess the capability for full arbitrary code execution by design, meaning that an attacker gains no additional capabilities through `astral-tokio-tar`.
Regardless, we take the hypothetical risk of parser differentials very seriously. Out of an abundance of caution, we have assigned this upgrade an advisory: https://github.com/astral-sh/uv/security/advisories/GHSA-w476-p2h3-79g9
### Security
* Upgrade `astral-tokio-tar` to 0.5.6 to address a parsing differential ([#16387](https://github.com/astral-sh/uv/pull/16387))
### Enhancements
- Improve styling of warning cause chains ([#14934](https://github.com/astral-sh/uv/pull/14934))
- Extend wheel filtering to Android tags ([#14977](https://github.com/astral-sh/uv/pull/14977))
- Perform wheel lockfile filtering based on platform and OS intersection ([#14976](https://github.com/astral-sh/uv/pull/14976))
- Clarify messaging when a new resolution needs to be performed ([#14938](https://github.com/astral-sh/uv/pull/14938))
### Preview features
- Add support for extending package's build dependencies with `extra-build-dependencies` ([#14735](https://github.com/astral-sh/uv/pull/14735))
- Split preview mode into separate feature flags ([#14823](https://github.com/astral-sh/uv/pull/14823))
### Configuration
- Add support for package specific `exclude-newer` dates via `exclude-newer-package` ([#14489](https://github.com/astral-sh/uv/pull/14489))
- Add required environment marker example to hint ([#16244](https://github.com/astral-sh/uv/pull/16244))
- Fix typo in MissingTopLevel warning ([#16351](https://github.com/astral-sh/uv/pull/16351))
- Improve 403 Forbidden error message to indicate package may not exist ([#16353](https://github.com/astral-sh/uv/pull/16353))
- Add a hint on `uv pip install` failure if the `--system` flag is used to select an externally managed interpreter ([#16318](https://github.com/astral-sh/uv/pull/16318))
### Bug fixes
- Avoid invalidating lockfile when path or workspace dependencies define explicit indexes ([#14876](https://github.com/astral-sh/uv/pull/14876))
- Copy entrypoints that have a shebang that differs in `python` vs `python3` ([#14970](https://github.com/astral-sh/uv/pull/14970))
- Fix incorrect file permissions in wheel packages ([#14930](https://github.com/astral-sh/uv/pull/14930))
- Update validation for `environments` and `required-environments` in `uv.toml` ([#14905](https://github.com/astral-sh/uv/pull/14905))
- Fix backtick escaping for PowerShell ([#16307](https://github.com/astral-sh/uv/pull/16307))
### Documentation
- Show `uv_build` in projects documentation ([#14968](https://github.com/astral-sh/uv/pull/14968))
- Add `UV_` prefix to installer environment variables ([#14964](https://github.com/astral-sh/uv/pull/14964))
- Un-hide `uv` from `--build-backend` options ([#14939](https://github.com/astral-sh/uv/pull/14939))
- Update documentation for preview flags ([#14902](https://github.com/astral-sh/uv/pull/14902))
- Document metadata consistency expectation ([#15683](https://github.com/astral-sh/uv/pull/15683))
- Remove outdated aarch64 musl note ([#16385](https://github.com/astral-sh/uv/pull/16385))
## 0.8.3
## 0.9.4
Released on 2025-10-17.
### Enhancements
- Add CUDA 13.0 support ([#16321](https://github.com/astral-sh/uv/pull/16321))
- Add auto-detection for Intel GPU on Windows ([#16280](https://github.com/astral-sh/uv/pull/16280))
- Implement display of RFC 9457 HTTP error contexts ([#16199](https://github.com/astral-sh/uv/pull/16199))
### Bug fixes
- Avoid obfuscating pyx tokens in `uv auth token` output ([#16345](https://github.com/astral-sh/uv/pull/16345))
## 0.9.3
Released on 2025-10-14.
### Python
- Add CPython 3.14.0rc1
See the [`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250723) for more details.
- Add CPython 3.15.0a1
- Add CPython 3.13.9
### Enhancements
- Allow non-standard entrypoint names in `uv_build` ([#14867](https://github.com/astral-sh/uv/pull/14867))
- Publish riscv64 wheels to PyPI ([#14852](https://github.com/astral-sh/uv/pull/14852))
- Obfuscate secret token values in logs ([#16164](https://github.com/astral-sh/uv/pull/16164))
### Bug fixes
- Avoid writing redacted credentials to tool receipt ([#14855](https://github.com/astral-sh/uv/pull/14855))
- Respect `--with` versions over base environment versions ([#14863](https://github.com/astral-sh/uv/pull/14863))
- Respect credentials from all defined indexes ([#14858](https://github.com/astral-sh/uv/pull/14858))
- Fix missed stabilization of removal of registry entry during Python uninstall ([#14859](https://github.com/astral-sh/uv/pull/14859))
- Improve concurrency safety of Python downloads into cache ([#14846](https://github.com/astral-sh/uv/pull/14846))
- Fix workspace with relative pathing ([#16296](https://github.com/astral-sh/uv/pull/16296))
### Documentation
## 0.9.2
- Fix typos in `uv_build` reference documentation ([#14853](https://github.com/astral-sh/uv/pull/14853))
- Move the "Cargo" install method further down in docs ([#14842](https://github.com/astral-sh/uv/pull/14842))
Released on 2025-10-10.
## 0.8.2
### Python
- Add CPython 3.9.24.
- Add CPython 3.10.19.
- Add CPython 3.11.14.
- Add CPython 3.12.12.
### Enhancements
- Add derivation chains for dependency errors ([#14824](https://github.com/astral-sh/uv/pull/14824))
- Avoid inferring check URLs for pyx in `uv publish` ([#16234](https://github.com/astral-sh/uv/pull/16234))
- Add `uv tool list --show-python` ([#15814](https://github.com/astral-sh/uv/pull/15814))
### Documentation
- Add missing "added in" to new environment variables in reference ([#16217](https://github.com/astral-sh/uv/pull/16217))
## 0.9.1
Released on 2025-10-09.
### Enhancements
- Log Python choice in `uv init` ([#16182](https://github.com/astral-sh/uv/pull/16182))
- Fix `pylock.toml` config conflict error messages ([#16211](https://github.com/astral-sh/uv/pull/16211))
### Configuration
- Add `UV_INIT_BUILD_BACKEND` ([#14821](https://github.com/astral-sh/uv/pull/14821))
- Add `UV_UPLOAD_HTTP_TIMEOUT` and respect `UV_HTTP_TIMEOUT` in uploads ([#16040](https://github.com/astral-sh/uv/pull/16040))
- Support `UV_WORKING_DIRECTORY` for setting `--directory` ([#16125](https://github.com/astral-sh/uv/pull/16125))
### Bug fixes
- Avoid reading files in the environment bin that are not entrypoints ([#14830](https://github.com/astral-sh/uv/pull/14830))
- Avoid removing empty directories when constructing virtual environments ([#14822](https://github.com/astral-sh/uv/pull/14822))
- Preserve index URL priority order when writing to pyproject.toml ([#14831](https://github.com/astral-sh/uv/pull/14831))
### Rust API
- Expose `tls_built_in_root_certs` for client ([#14816](https://github.com/astral-sh/uv/pull/14816))
- Allow missing `Scripts` directory ([#16206](https://github.com/astral-sh/uv/pull/16206))
- Fix handling of Python requests with pre-releases in ranges ([#16208](https://github.com/astral-sh/uv/pull/16208))
- Preserve comments on version bump ([#16141](https://github.com/astral-sh/uv/pull/16141))
- Retry all HTTP/2 errors ([#16038](https://github.com/astral-sh/uv/pull/16038))
- Treat deleted Windows registry keys as equivalent to missing ones ([#16194](https://github.com/astral-sh/uv/pull/16194))
- Ignore pre-release Python versions when a patch version is requested ([#16210](https://github.com/astral-sh/uv/pull/16210))
### Documentation
- Archive the 0.7.x changelog ([#14819](https://github.com/astral-sh/uv/pull/14819))
- Document why uv discards upper bounds on `requires-python` ([#15927](https://github.com/astral-sh/uv/pull/15927))
- Document uv version environment variables were added in ([#15196](https://github.com/astral-sh/uv/pull/15196))
## 0.8.1
## 0.9.0
### Enhancements
Released on 2025-10-07.
- Add support for `HF_TOKEN` ([#14797](https://github.com/astral-sh/uv/pull/14797))
- Allow `--config-settings-package` to apply configuration settings at the package level ([#14573](https://github.com/astral-sh/uv/pull/14573))
- Create (e.g.) `python3.13t` executables in `uv venv` ([#14764](https://github.com/astral-sh/uv/pull/14764))
- Disallow writing symlinks outside the source distribution target directory ([#12259](https://github.com/astral-sh/uv/pull/12259))
- Elide traceback when `python -m uv` in interrupted with Ctrl-C on Windows ([#14715](https://github.com/astral-sh/uv/pull/14715))
- Match `--bounds` formatting for `uv_build` bounds in `uv init` ([#14731](https://github.com/astral-sh/uv/pull/14731))
- Support `extras` and `dependency_groups` markers in PEP 508 grammar ([#14753](https://github.com/astral-sh/uv/pull/14753))
- Support `extras` and `dependency_groups` markers on `uv pip install` and `uv pip sync` ([#14755](https://github.com/astral-sh/uv/pull/14755))
- Add hint to use `uv self version` when `uv version` cannot find a project ([#14738](https://github.com/astral-sh/uv/pull/14738))
- Improve error reporting when removing Python versions from the Windows registry ([#14722](https://github.com/astral-sh/uv/pull/14722))
- Make warnings about masked `[tool.uv]` fields more precise ([#14325](https://github.com/astral-sh/uv/pull/14325))
This breaking release is primarily motivated by the release of Python 3.14, which contains some breaking changes (we recommend reading the ["What's new in Python 3.14"](https://docs.python.org/3/whatsnew/3.14.html) page). uv may use Python 3.14 in cases where it previously used 3.13, e.g., if you have not pinned your Python version and do not have any Python versions installed on your machine. While we think this is uncommon, we prefer to be cautious. We've included some additional small changes that could break workflows.
### Preview features
See our [Python 3.14](https://astral.sh/blog/python-3.14) blog post for some discussion of features we're excited about!
- Emit JSON output in `uv sync` with `--quiet` ([#14810](https://github.com/astral-sh/uv/pull/14810))
### Bug fixes
- Allow removal of virtual environments with missing interpreters ([#14812](https://github.com/astral-sh/uv/pull/14812))
- Apply `Cache-Control` overrides to response, not request headers ([#14736](https://github.com/astral-sh/uv/pull/14736))
- Copy entry points into ephemeral environments to ensure layers are respected ([#14790](https://github.com/astral-sh/uv/pull/14790))
- Workaround Jupyter Lab application directory discovery in ephemeral environments ([#14790](https://github.com/astral-sh/uv/pull/14790))
- Enforce `requires-python` in `pylock.toml` ([#14787](https://github.com/astral-sh/uv/pull/14787))
- Fix kebab casing of `README` variants in build backend ([#14762](https://github.com/astral-sh/uv/pull/14762))
- Improve concurrency resilience of removing Python versions from the Windows registry ([#14717](https://github.com/astral-sh/uv/pull/14717))
- Retry HTTP requests on invalid data errors ([#14703](https://github.com/astral-sh/uv/pull/14703))
- Update virtual environment removal to delete `pyvenv.cfg` last ([#14808](https://github.com/astral-sh/uv/pull/14808))
- Error on unknown fields in `dependency-metadata` ([#14801](https://github.com/astral-sh/uv/pull/14801))
### Documentation
- Recommend installing `setup-uv` after `setup-python` in Github Actions integration guide ([#14741](https://github.com/astral-sh/uv/pull/14741))
- Clarify which portions of `requires-python` behavior are consistent with pip ([#14752](https://github.com/astral-sh/uv/pull/14752))
## 0.8.0
Since we released uv [0.7.0](https://github.com/astral-sh/uv/releases/tag/0.7.0) in April, we've accumulated various changes that improve correctness and user experience, but could break some workflows. This release contains those changes; many have been marked as breaking out of an abundance of caution. We expect most users to be able to upgrade without making changes.
This release also includes the stabilization of a couple `uv python install` features, which have been available under preview since late last year.
There are no breaking changes to [`uv_build`](https://docs.astral.sh/uv/concepts/build-backend/). If you have an upper bound in your `[build-system]` table, you should update it.
### Breaking changes
- **Install Python executables into a directory on the `PATH` ([#14626](https://github.com/astral-sh/uv/pull/14626))**
`uv python install` now installs a versioned Python executable (e.g., `python3.13`) into a directory on the `PATH` (e.g., `~/.local/bin`) by default. This behavior has been available under the `--preview` flag since [Oct 2024](https://github.com/astral-sh/uv/pull/8458). This change should not be breaking unless it shadows a Python executable elsewhere on the `PATH`.
To install unversioned executables, i.e., `python3` and `python`, use the `--default` flag. The `--default` flag has also been in preview, but is not stabilized in this release.
Note that these executables point to the base Python installation and only include the standard library. That means they will not include dependencies from your current project (use `uv run python` instead) and you cannot install packages into their environment (use `uvx --with <package> python` instead).
As with tool installation, the target directory respects common variables like `XDG_BIN_HOME` and can be overridden with a `UV_PYTHON_BIN_DIR` variable.
You can opt out of this behavior with `uv python install --no-bin` or `UV_PYTHON_INSTALL_BIN=0`.
See the [documentation on installing Python executables](https://docs.astral.sh/uv/concepts/python-versions/#installing-python-executables) for more details.
- **Register Python versions with the Windows Registry ([#14625](https://github.com/astral-sh/uv/pull/14625))**
`uv python install` now registers the installed Python version with the Windows Registry as specified by [PEP 514](https://peps.python.org/pep-0514/). This allows using uv installed Python versions via the `py` launcher. This behavior has been available under the `--preview` flag since [Jan 2025](https://github.com/astral-sh/uv/pull/10634). This change should not be breaking, as using the uv Python versions with `py` requires explicit opt in.
You can opt out of this behavior with `uv python install --no-registry` or `UV_PYTHON_INSTALL_REGISTRY=0`.
- **Prompt before removing an existing directory in `uv venv` ([#14309](https://github.com/astral-sh/uv/pull/14309))**
Previously, `uv venv` would remove an existing virtual environment without confirmation. While this is consistent with the behavior of project commands (e.g., `uv sync`), it's surprising to users that are using imperative workflows (i.e., `uv pip`). Now, `uv venv` will prompt for confirmation before removing an existing virtual environment. **If not in an interactive context, uv will still remove the virtual environment for backwards compatibility. However, this behavior is likely to change in a future release.**
The behavior for other commands (e.g., `uv sync`) is unchanged.
You can opt out of this behavior by setting `UV_VENV_CLEAR=1` or passing the `--clear` flag.
- **Validate that discovered interpreters meet the Python preference ([#7934](https://github.com/astral-sh/uv/pull/7934))**
uv allows opting out of its managed Python versions with the `--no-managed-python` and `python-preference` options.
Previously, uv would not enforce this option for Python interpreters discovered on the `PATH`. For example, if a symlink to a managed Python interpreter was created, uv would allow it to be used even if `--no-managed-python` was provided. Now, uv ignores Python interpreters that do not match the Python preference *unless* they are in an active virtual environment or are explicitly requested, e.g., with `--python /path/to/python3.13`.
Similarly, uv would previously not invalidate existing project environments if they did not match the Python preference. Now, uv will invalidate and recreate project environments when the Python preference changes.
You can opt out of this behavior by providing the explicit path to the Python interpreter providing `--managed-python` / `--no-managed-python` matching the interpreter you want.
- **Install dependencies without build systems when they are `path` sources ([#14413](https://github.com/astral-sh/uv/pull/14413))**
When working on a project, uv uses the [presence of a build system](https://docs.astral.sh/uv/concepts/projects/config/#build-systems) to determine if it should be built and installed into the environment. However, when a project is a dependency of another project, it can be surprising for the dependency to be missing from the environment.
Previously, uv would not build and install dependencies with [`path` sources](https://docs.astral.sh/uv/concepts/projects/dependencies/#path) unless they declared a build system or set `tool.uv.package = true`. Now, dependencies with `path` sources are built and installed regardless of the presence of a build system. If a build system is not present, the `setuptools.build_meta:__legacy__ ` backend will be used (per [PEP 517](https://peps.python.org/pep-0517/#source-trees)).
You can opt out of this behavior by setting `package = false` in the source declaration, e.g.:
```toml
[tool.uv.sources]
foo = { path = "./foo", package = false }
```
Or, by setting `tool.uv.package = false` in the dependent `pyproject.toml`.
See the documentation on [virtual dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/#virtual-dependencies) for details.
- **Install dependencies without build systems when they are workspace members ([#14663](https://github.com/astral-sh/uv/pull/14663))**
As described above for dependencies with `path` sources, uv previously would not build and install workspace members that did not declare a build system. Now, uv will build and install workspace members that are a dependency of *another* workspace member regardless of the presence of a build system. The behavior is unchanged for workspace members that are not included in the `project.dependencies`, `project.optional-dependencies`, or `dependency-groups` tables of another workspace member.
You can opt out of this behavior by setting `tool.uv.package = false` in the workspace member's `pyproject.toml`.
See the documentation on [virtual dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/#virtual-dependencies) for details.
- **Bump `--python-platform linux` to `manylinux_2_28` ([#14300](https://github.com/astral-sh/uv/pull/14300))**
uv allows performing [platform-specific resolution](https://docs.astral.sh/uv/concepts/resolution/#platform-specific-resolution) for explicit targets and provides short aliases, e.g., `linux`, for common targets.
Previously, the default target for `--python-platform linux` was `manylinux_2_17`, which is compatible with most Linux distributions from 2014 or newer. We now default to `manylinux_2_28`, which is compatible with most Linux distributions from 2019 or newer. This change follows the lead of other tools, such as `cibuildwheel`, which changed their default to `manylinux_2_28` in [Mar 2025](https://github.com/pypa/cibuildwheel/pull/2330).
This change only affects users requesting a specific target platform. Otherwise, uv detects the `manylinux` target from your local glibc version.
You can opt out of this behavior by using `--python-platform x86_64-manylinux_2_17` instead.
- **Remove `uv version` fallback ([#14161](https://github.com/astral-sh/uv/pull/14161))**
In [Apr 2025](https://github.com/astral-sh/uv/pull/12349), uv changed the `uv version` command to an interface for viewing and updating the version of the current project. However, when outside a project, `uv version` would continue to display uv's version for backwards compatibility. Now, when used outside of a project, `uv version` will fail.
You cannot opt out of this behavior. Use `uv self version` instead.
- **Require `--global` for removal of the global Python pin ([#14169](https://github.com/astral-sh/uv/pull/14169))**
Previously, `uv python pin --rm` would allow you to remove the global Python pin without opt in. Now, uv requires the `--global` flag to remove the global Python pin.
You cannot opt out of this behavior. Use the `--global` flag instead.
- **Support conflicting editable settings across groups ([#14197](https://github.com/astral-sh/uv/pull/14197))**
Previously, uv would always treat a package as editable if any requirement requested it as editable. However, this prevented users from declaring `path` sources that toggled the `editable` setting across dependency groups. Now, uv allows declaring different `editable` values for conflicting groups. However, if a project includes a path dependency twice, once with `editable = true` and once without any editable annotation, those are now considered conflicting, and uv will exit with an error.
You cannot opt out of this behavior. Use consistent `editable` settings or [mark groups as conflicting](https://docs.astral.sh/uv/concepts/projects/config/#conflicting-dependencies).
- **Make `uv_build` the default build backend in `uv init` ([#14661](https://github.com/astral-sh/uv/pull/14661))**
The uv build backend (`uv_build`) was [stabilized in uv 0.7.19](https://github.com/astral-sh/uv/releases/tag/0.7.19). Now, it is the default build backend for `uv init --package` and `uv init --lib`. Previously, `hatchling` was the default build backend. A build backend is still not used without opt-in in `uv init`, but we expect to change this in a future release.
You can opt out of this behavior with `uv init --build-backend hatchling`.
- **Set default `UV_TOOL_BIN_DIR` on Docker images ([#13391](https://github.com/astral-sh/uv/pull/13391))**
Previously, `UV_TOOL_BIN_DIR` was not set in Docker images which meant that `uv tool install` did not install tools into a directory on the `PATH` without additional configuration. Now, `UV_TOOL_BIN_DIR` is set to `/usr/local/bin` in all Docker derived images.
When the default image user is overridden (e.g. `USER <UID>`) with a less privileged user, this may cause `uv tool install` to fail.
You can opt out of this behavior by setting an alternative `UV_TOOL_BIN_DIR`.
- **Update `--check` to return an exit code of 1 ([#14167](https://github.com/astral-sh/uv/pull/14167))**
uv uses an exit code of 1 to indicate a "successful failure" and an exit code of 2 to indicate an "error".
Previously, `uv lock --check` and `uv sync --check` would exit with a code of 2 when the lockfile or environment were outdated. Now, uv will exit with a code of 1.
You cannot opt out of this behavior.
- **Use an ephemeral environment for `uv run --with` invocations ([#14447](https://github.com/astral-sh/uv/pull/14447))**
When using `uv run --with`, uv layers the requirements requested using `--with` into another virtual environment and caches it. Previously, uv would invoke the Python interpreter in this layered environment. However, this allows poisoning the cached environment and introduces race conditions for concurrent invocations. Now, uv will layer *another* empty virtual environment on top of the cached environment and invoke the Python interpreter there. This should only cause breakage in cases where the environment is being inspected at runtime.
You cannot opt out of this behavior.
- **Restructure the `uv venv` command output and exit codes ([#14546](https://github.com/astral-sh/uv/pull/14546))**
Previously, uv used `miette` to format the `uv venv` output. However, this was inconsistent with most of the uv CLI. Now, the output is a little different and the exit code has switched from 1 to 2 for some error cases.
You cannot opt out of this behavior.
- **Default to `--workspace` when adding subdirectories ([#14529](https://github.com/astral-sh/uv/pull/14529))**
When using `uv add` to add a subdirectory in a workspace, uv now defaults to adding the target as a workspace member.
You can opt out of this behavior by providing `--no-workspace`.
- **Add missing validations for disallowed `uv.toml` fields ([#14322](https://github.com/astral-sh/uv/pull/14322))**
uv does not allow some settings in the `uv.toml`. Previously, some settings were silently ignored when present in the `uv.toml`. Now, uv will error.
You cannot opt out of this behavior. Use `--no-config` or remove the invalid settings.
- **Python 3.14 is now the default stable version**
### Configuration
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))
- Add support for toggling Python bin and registry install options via env vars ([#14662](https://github.com/astral-sh/uv/pull/14662))
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
- Add CPython 3.14.0
- Add CPython 3.13.8
### Enhancements
- Don't warn when a dependency is constrained by another dependency ([#16149](https://github.com/astral-sh/uv/pull/16149))
### Bug fixes
- Fix `uv python upgrade / install` output when there is a no-op for one request ([#16158](https://github.com/astral-sh/uv/pull/16158))
- Surface pinned-version hint when `uv tool upgrade` cant move the tool ([#16081](https://github.com/astral-sh/uv/pull/16081))
- Ban pre-release versions in `uv python upgrade` requests ([#16160](https://github.com/astral-sh/uv/pull/16160))
- Fix `uv python upgrade` replacement of installed binaries on pre-release to stable ([#16159](https://github.com/astral-sh/uv/pull/16159))
### Documentation
- Update `uv pip compile` args in `layout.md` ([#16155](https://github.com/astral-sh/uv/pull/16155))
## 0.8.x
See [changelogs/0.8.x](./changelogs/0.8.x.md)
## 0.7.x

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

@ -1,10 +1,34 @@
# Contributing
We have issues labeled as
[Good First Issue](https://github.com/astral-sh/uv/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
and
[Help Wanted](https://github.com/astral-sh/uv/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22)
which are good opportunities for new contributors.
## Finding ways to help
We label issues that would be good for a first time contributor as
[`good first issue`](https://github.com/astral-sh/uv/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
These usually do not require significant experience with Rust or the uv code base.
We label issues that we think are a good opportunity for subsequent contributions as
[`help wanted`](https://github.com/astral-sh/uv/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22).
These require varying levels of experience with Rust and uv. Often, we want to accomplish these
tasks but do not have the resources to do so ourselves.
You don't need our permission to start on an issue we have labeled as appropriate for community
contribution as described above. However, it's a good idea to indicate that you are going to work on
an issue to avoid concurrent attempts to solve the same problem.
Please check in with us before starting work on an issue that has not been labeled as appropriate
for community contribution. We're happy to receive contributions for other issues, but it's
important to make sure we have consensus on the solution to the problem first.
Outside of issues with the labels above, issues labeled as
[`bug`](https://github.com/astral-sh/uv/issues?q=is%3Aopen+is%3Aissue+label%3A%22bug%22) are the
best candidates for contribution. In contrast, issues labeled with `needs-decision` or
`needs-design` are _not_ good candidates for contribution. Please do not open pull requests for
issues with these labels.
Please do not open pull requests for new features without prior discussion. While we appreciate
exploration of new features, we will almost always close these pull requests immediately. Adding a
new feature to uv creates a long-term maintenance burden and requires strong consensus from the uv
team before it is appropriate to begin work on an implementation.
## Setup
@ -16,6 +40,12 @@ On Ubuntu and other Debian-based distributions, you can install a C compiler wit
sudo apt install build-essential
```
On Fedora-based distributions, you can install a C compiler with:
```shell
sudo dnf install gcc
```
## Testing
For running tests, we recommend [nextest](https://nexte.st/).
@ -56,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:
@ -65,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
@ -90,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:
@ -101,7 +147,7 @@ uv run resolver \
--poetry \
--benchmark \
resolve-cold \
../scripts/requirements/trio.in
../test/requirements/trio.in
```
### Analyzing concurrency
@ -111,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

2277
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,85 +4,88 @@ exclude = [
"scripts",
# Needs nightly
"crates/uv-trampoline",
# Only used to pull in features, allocators, etc. — we specifically don't want them
# to be part of a workspace-wide cargo check, cargo clippy, etc.
"crates/uv-performance-memory-allocator",
]
resolver = "2"
[workspace.package]
edition = "2024"
rust-version = "1.86"
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 = { path = "crates/uv-auth" }
uv-build-backend = { path = "crates/uv-build-backend" }
uv-build-frontend = { path = "crates/uv-build-frontend" }
uv-cache = { path = "crates/uv-cache" }
uv-cache-info = { path = "crates/uv-cache-info" }
uv-cache-key = { path = "crates/uv-cache-key" }
uv-cli = { path = "crates/uv-cli" }
uv-client = { path = "crates/uv-client" }
uv-configuration = { path = "crates/uv-configuration" }
uv-console = { path = "crates/uv-console" }
uv-dirs = { path = "crates/uv-dirs" }
uv-dispatch = { path = "crates/uv-dispatch" }
uv-distribution = { path = "crates/uv-distribution" }
uv-distribution-filename = { path = "crates/uv-distribution-filename" }
uv-distribution-types = { path = "crates/uv-distribution-types" }
uv-extract = { path = "crates/uv-extract" }
uv-fs = { path = "crates/uv-fs", features = ["serde", "tokio"] }
uv-git = { path = "crates/uv-git" }
uv-git-types = { path = "crates/uv-git-types" }
uv-globfilter = { path = "crates/uv-globfilter" }
uv-install-wheel = { path = "crates/uv-install-wheel", default-features = false }
uv-installer = { path = "crates/uv-installer" }
uv-macros = { path = "crates/uv-macros" }
uv-metadata = { path = "crates/uv-metadata" }
uv-normalize = { path = "crates/uv-normalize" }
uv-once-map = { path = "crates/uv-once-map" }
uv-options-metadata = { path = "crates/uv-options-metadata" }
uv-pep440 = { path = "crates/uv-pep440", features = ["tracing", "rkyv", "version-ranges"] }
uv-pep508 = { path = "crates/uv-pep508", features = ["non-pep508-extensions"] }
uv-platform = { path = "crates/uv-platform" }
uv-platform-tags = { path = "crates/uv-platform-tags" }
uv-publish = { path = "crates/uv-publish" }
uv-pypi-types = { path = "crates/uv-pypi-types" }
uv-python = { path = "crates/uv-python" }
uv-redacted = { path = "crates/uv-redacted" }
uv-requirements = { path = "crates/uv-requirements" }
uv-requirements-txt = { path = "crates/uv-requirements-txt" }
uv-resolver = { path = "crates/uv-resolver" }
uv-scripts = { path = "crates/uv-scripts" }
uv-settings = { path = "crates/uv-settings" }
uv-shell = { path = "crates/uv-shell" }
uv-small-str = { path = "crates/uv-small-str" }
uv-state = { path = "crates/uv-state" }
uv-static = { path = "crates/uv-static" }
uv-tool = { path = "crates/uv-tool" }
uv-torch = { path = "crates/uv-torch" }
uv-trampoline-builder = { path = "crates/uv-trampoline-builder" }
uv-types = { path = "crates/uv-types" }
uv-version = { path = "crates/uv-version" }
uv-virtualenv = { path = "crates/uv-virtualenv" }
uv-warnings = { path = "crates/uv-warnings" }
uv-workspace = { 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.7", default-features = false, features = ["astral-reqwest-middleware"] }
anstream = { version = "0.6.15" }
anyhow = { version = "1.0.89" }
arcstr = { version = "1.2.0" }
arrayvec = { version = "0.7.6" }
astral-tokio-tar = { version = "0.5.2" }
astral-tokio-tar = { version = "0.5.6" }
async-channel = { version = "2.3.1" }
async-compression = { version = "0.4.12", features = ["bzip2", "gzip", "xz", "zstd"] }
async-trait = { version = "0.1.82" }
async_http_range_reader = { version = "0.9.1" }
async_zip = { git = "https://github.com/astral-sh/rs-async-zip", rev = "285e48742b74ab109887d62e1ae79e7c15fd4878", features = ["bzip2", "deflate", "lzma", "tokio", "xz", "zstd"] }
async_http_range_reader = { version = "0.9.1", package = "astral_async_http_range_reader" }
async_zip = { version = "0.0.17", package = "astral_async_zip", features = ["bzip2", "deflate", "lzma", "tokio", "xz", "zstd"] }
axoupdater = { version = "0.9.0", default-features = false }
backon = { version = "1.3.0" }
base64 = { version = "0.22.1" }
@ -97,25 +100,27 @@ configparser = { version = "3.1.0" }
console = { version = "0.16.0", default-features = false, features = ["std"] }
csv = { version = "1.3.0" }
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" }
etcetera = { version = "0.10.0" }
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"] }
fs2 = { version = "0.4.3" }
futures = { version = "0.3.30" }
glob = { version = "0.3.1" }
globset = { version = "0.4.15" }
globwalk = { version = "0.9.1" }
goblin = { version = "0.10.0", default-features = false, features = ["std", "elf32", "elf64", "endian_fd"] }
h2 = { version = "0.4.7" }
hashbrown = { version = "0.15.1" }
hashbrown = { version = "0.16.0" }
hex = { version = "0.4.3" }
home = { version = "0.5.9" }
html-escape = { version = "0.2.13" }
http = { version = "1.1.0" }
indexmap = { version = "2.5.0" }
@ -130,7 +135,7 @@ memchr = { version = "2.7.4" }
miette = { version = "7.2.0", features = ["fancy-no-backtrace"] }
nanoid = { version = "0.4.0" }
nix = { version = "0.30.0", features = ["signal"] }
once_cell = { version = "1.20.2" }
open = { version = "5.3.2" }
owo-colors = { version = "4.1.0" }
path-slash = { version = "0.2.1" }
pathdiff = { version = "0.2.1" }
@ -138,16 +143,17 @@ 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 = { git = "https://github.com/astral-sh/pubgrub", rev = "06ec5a5f59ffaeb6cf5079c6cb184467da06c9db" }
pubgrub = { version = "0.3.3" , package = "astral-pubgrub" }
quote = { version = "1.0.37" }
rayon = { version = "1.10.0" }
ref-cast = { version = "1.0.24" }
reflink-copy = { version = "0.1.19" }
regex = { version = "1.10.6" }
regex-automata = { version = "0.4.8", default-features = false, features = ["dfa-build", "dfa-search", "perf", "std", "syntax"] }
reqwest = { version = "0.12.22", default-features = false, features = ["json", "gzip", "deflate", "zstd", "stream", "rustls-tls", "rustls-tls-native-roots", "socks", "multipart", "http2", "blocking"] }
reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8", features = ["multipart"] }
reqwest-retry = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8" }
reqsign = { version = "0.18.0", features = ["aws", "default-context"], default-features = false }
reqwest = { version = "0.12.22", default-features = false, features = ["json", "gzip", "deflate", "zstd", "stream", "system-proxy", "rustls-tls", "rustls-tls-native-roots", "socks", "multipart", "http2", "blocking"] }
reqwest-middleware = { version = "0.4.2", package = "astral-reqwest-middleware", features = ["multipart"] }
reqwest-retry = { version = "0.7.0", package = "astral-reqwest-retry" }
rkyv = { version = "0.8.8", features = ["bytecheck"] }
rmp-serde = { version = "1.3.0" }
rust-netrc = { version = "0.1.2" }
@ -156,13 +162,15 @@ rustix = { version = "1.0.0", default-features = false, features = ["fs", "std"]
same-file = { version = "1.0.6" }
schemars = { version = "1.0.0", features = ["url2"] }
seahash = { version = "4.1.0" }
secret-service = { version = "5.0.0", features = ["rt-tokio-crypto-rust"] }
security-framework = { version = "3" }
self-replace = { version = "1.5.0" }
serde = { version = "1.0.210", features = ["derive", "rc"] }
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.10.6" }
spdx = { version = "0.13.0" }
syn = { version = "2.0.77" }
sys-info = { version = "0.9.1" }
tar = { version = "0.4.43" }
@ -170,31 +178,32 @@ target-lexicon = { version = "0.13.0" }
tempfile = { version = "3.14.0" }
textwrap = { version = "0.16.1" }
thiserror = { version = "2.0.0" }
tl = { git = "https://github.com/astral-sh/tl.git", rev = "6e25b2ee2513d75385101a8ff9f591ef51f314ec" }
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"] }
toml_edit = { version = "0.23.2", features = ["serde"] }
tracing = { version = "0.1.40" }
tracing-durations-export = { version = "0.3.0", features = ["plot"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry"] }
tracing-subscriber = { version = "0.3.18" } # Default feature set for uv_build, uv activates extra features
tracing-test = { version = "0.2.5" }
tracing-tree = { version = "0.4.0" }
unicode-width = { version = "0.2.0" }
unscanny = { version = "0.1.0" }
url = { version = "2.5.2", features = ["serde"] }
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "06ec5a5f59ffaeb6cf5079c6cb184467da06c9db" }
uuid = { version = "1.16.0" }
version-ranges = { version = "0.1.3", package = "astral-version-ranges" }
walkdir = { version = "2.5.0" }
which = { version = "8.0.0", features = ["regex"] }
windows = { version = "0.59.0", features = ["Win32_Globalization", "Win32_System_Console", "Win32_System_Kernel", "Win32_System_Diagnostics_Debug", "Win32_Storage_FileSystem"] }
windows-core = { version = "0.59.0" }
windows = { version = "0.59.0", features = ["std", "Win32_Globalization", "Win32_System_LibraryLoader", "Win32_System_Console", "Win32_System_Kernel", "Win32_System_Diagnostics_Debug", "Win32_Storage_FileSystem", "Win32_Security", "Win32_System_Registry", "Win32_System_IO", "Win32_System_Ioctl"] }
windows-registry = { version = "0.5.0" }
windows-result = { version = "0.3.0" }
windows-sys = { version = "0.59.0", features = ["Win32_Foundation", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Ioctl", "Win32_System_IO", "Win32_System_Registry"] }
wiremock = { version = "0.6.4" }
wmi = { version = "0.16.0", default-features = false }
xz2 = { version = "0.1.7" }
zeroize = { version = "1.8.1" }
zip = { version = "2.2.3", default-features = false, features = ["deflate", "zstd", "bzip2", "lzma", "xz"] }
zstd = { version = "0.13.3" }
# dev-dependencies
assert_cmd = { version = "2.0.16" }
@ -203,19 +212,19 @@ byteorder = { version = "1.5.0" }
filetime = { version = "0.2.25" }
http-body-util = { version = "0.1.2" }
hyper = { version = "1.4.1", features = ["server", "http1"] }
hyper-util = { version = "0.1.8", features = ["tokio"] }
hyper-util = { version = "0.1.8", features = ["tokio", "server", "http1"] }
ignore = { version = "0.4.23" }
insta = { version = "1.40.0", features = ["json", "filters", "redactions"] }
predicates = { version = "3.1.2" }
rcgen = { version = "0.14.5", features = ["crypto", "pem", "ring"], default-features = false }
rustls = { version = "0.23.29", default-features = false }
similar = { version = "2.6.0" }
temp-env = { version = "0.3.6" }
test-case = { version = "3.3.1" }
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"]
[workspace.lints.rust]
unsafe_code = "warn"
unreachable_pub = "warn"
@ -301,8 +310,18 @@ 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"
@ -317,7 +336,3 @@ codegen-units = 1
# The profile that 'cargo dist' will build with.
[profile.dist]
inherits = "release"
[patch.crates-io]
reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8" }
reqwest-retry = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "ad8b9d332d1773fde8b4cd008486de5973e0a3f8" }

View File

@ -7,7 +7,6 @@ RUN apt update \
build-essential \
curl \
python3-venv \
cmake \
&& apt clean \
&& rm -rf /var/lib/apt/lists/*
@ -24,8 +23,15 @@ RUN case "$TARGETPLATFORM" in \
*) exit 1 ;; \
esac
# Temporarily using nightly-2025-11-02 for bundled musl v1.2.5
# Ref: https://github.com/rust-lang/rust/pull/142682
# TODO(samypr100): Remove when toolchain updates to 1.93
COPY <<EOF rust-toolchain.toml
[toolchain]
channel = "nightly-2025-11-02"
EOF
# Update rustup whenever we bump the rust version
COPY rust-toolchain.toml rust-toolchain.toml
# COPY rust-toolchain.toml rust-toolchain.toml
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --target $(cat rust_target.txt) --profile minimal --default-toolchain none
ENV PATH="$HOME/.cargo/bin:$PATH"
# Install the toolchain then the musl target

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

@ -16,7 +16,7 @@ documentation_.
1. If a message ends with a single relevant value, precede it with a colon, e.g.,
`This is the value: value`. If the value is a literal, wrap it in backticks.
1. Markdown files should be wrapped at 100 characters.
1. Use a space, not an equals sign, for command line arguments with a value, e.g.
1. Use a space, not an equals sign, for command-line arguments with a value, e.g.
`--resolution lowest`, not `--resolution=lowest`.
## Styling uv

View File

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

View File

@ -982,7 +982,7 @@ for more details.
([#9135](https://github.com/astral-sh/uv/pull/9135))
- Tweak script `--no-project` comment ([#10331](https://github.com/astral-sh/uv/pull/10331))
- Update copyright year ([#10297](https://github.com/astral-sh/uv/pull/10297))
- Add instructinos for installing with Scoop ([#10332](https://github.com/astral-sh/uv/pull/10332))
- Add instructions for installing with Scoop ([#10332](https://github.com/astral-sh/uv/pull/10332))
## 0.5.16

1108
changelogs/0.8.x.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,11 @@ disallowed-types = [
"std::fs::File",
"std::fs::OpenOptions",
"std::fs::ReadDir",
"tokio::fs::DirBuilder",
"tokio::fs::DirEntry",
"tokio::fs::File",
"tokio::fs::OpenOptions",
"tokio::fs::ReadDir",
]
disallowed-methods = [
@ -38,7 +43,28 @@ disallowed-methods = [
"std::fs::soft_link",
"std::fs::symlink_metadata",
"std::fs::write",
"tokio::fs::canonicalize",
"tokio::fs::copy",
"tokio::fs::create_dir",
"tokio::fs::create_dir_all",
"tokio::fs::hard_link",
"tokio::fs::metadata",
"tokio::fs::read",
"tokio::fs::read_dir",
"tokio::fs::read_link",
"tokio::fs::read_to_string",
"tokio::fs::remove_dir",
"tokio::fs::remove_dir_all",
"tokio::fs::remove_file",
"tokio::fs::rename",
"tokio::fs::set_permissions",
"tokio::fs::symlink_metadata",
"tokio::fs::try_exists",
"tokio::fs::write",
{ path = "std::os::unix::fs::symlink", allow-invalid = true },
{ path = "std::os::windows::fs::symlink_dir", allow-invalid = true },
{ path = "std::os::windows::fs::symlink_file", allow-invalid = true },
{ path = "tokio::fs::symlink", allow-invalid = true },
{ path = "tokio::fs::symlink_dir", allow-invalid = true },
{ path = "tokio::fs::symlink_file", allow-invalid = true },
]

View File

@ -1,7 +1,13 @@
[package]
name = "uv-auth"
version = "0.0.1"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
[lib]
doctest = false
@ -10,25 +16,38 @@ doctest = false
workspace = true
[dependencies]
uv-cache-key = { workspace = true }
uv-fs = { workspace = true }
uv-keyring = { workspace = true, features = ["apple-native", "secret-service", "windows-native"] }
uv-once-map = { workspace = true }
uv-preview = { workspace = true }
uv-redacted = { workspace = true }
uv-small-str = { workspace = true }
uv-state = { workspace = true }
uv-static = { workspace = true }
uv-warnings = { workspace = true }
anyhow = { workspace = true }
arcstr = { workspace = true }
async-trait = { workspace = true }
base64 = { workspace = true }
etcetera = { workspace = true }
fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true }
http = { workspace = true }
jiff = { workspace = true }
percent-encoding = { workspace = true }
reqsign = { workspace = true }
reqwest = { workspace = true }
reqwest-middleware = { workspace = true }
rust-netrc = { workspace = true }
rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true }
url = { 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

@ -0,0 +1,34 @@
/// An encoded JWT access token.
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(transparent)]
pub struct AccessToken(String);
impl AccessToken {
/// Return the [`AccessToken`] as a vector of bytes.
pub fn into_bytes(self) -> Vec<u8> {
self.0.into_bytes()
}
/// Return the [`AccessToken`] as a string slice.
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for AccessToken {
fn from(value: String) -> Self {
Self(value)
}
}
impl AsRef<[u8]> for AccessToken {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl std::fmt::Display for AccessToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "****")
}
}

View File

@ -11,8 +11,8 @@ use url::Url;
use uv_once_map::OnceMap;
use uv_redacted::DisplaySafeUrl;
use crate::Realm;
use crate::credentials::{Credentials, Username};
use crate::credentials::{Authentication, Username};
use crate::{Credentials, Realm};
type FxOnceMap<K, V> = OnceMap<K, V, BuildHasherDefault<FxHasher>>;
@ -33,13 +33,14 @@ 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<Credentials>>>,
realms: RwLock<FxHashMap<(Realm, Username), Arc<Authentication>>>,
/// A cache tracking the result of realm or index URL fetches from external services
pub(crate) fetches: FxOnceMap<(FetchUrl, Username), Option<Arc<Credentials>>>,
pub(crate) fetches: FxOnceMap<(FetchUrl, Username), Option<Arc<Authentication>>>,
/// A cache per URL, uses a trie for efficient prefix queries.
urls: RwLock<UrlTrie>,
urls: RwLock<UrlTrie<Arc<Authentication>>>,
}
impl Default for CredentialsCache {
@ -58,8 +59,33 @@ 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, realm: Realm, username: Username) -> Option<Arc<Credentials>> {
pub(crate) fn get_realm(
&self,
realm: Realm,
username: Username,
) -> Option<Arc<Authentication>> {
let realms = self.realms.read().unwrap();
let given_username = username.is_some();
let key = (realm, username);
@ -93,7 +119,7 @@ impl CredentialsCache {
/// Note we do not cache per username, but if a username is passed we will confirm that the
/// cached credentials have a username equal to the provided one — otherwise `None` is returned.
/// If multiple usernames are used per URL, the realm cache should be queried instead.
pub(crate) fn get_url(&self, url: &Url, username: &Username) -> Option<Arc<Credentials>> {
pub(crate) fn get_url(&self, url: &Url, username: &Username) -> Option<Arc<Authentication>> {
let urls = self.urls.read().unwrap();
let credentials = urls.get(url);
if let Some(credentials) = credentials {
@ -112,7 +138,7 @@ impl CredentialsCache {
}
/// Update the cache with the given credentials.
pub(crate) fn insert(&self, url: &Url, credentials: Arc<Credentials>) {
pub(crate) fn insert(&self, url: &Url, credentials: Arc<Authentication>) {
// Do not cache empty credentials
if credentials.is_empty() {
return;
@ -139,8 +165,8 @@ impl CredentialsCache {
fn insert_realm(
&self,
key: (Realm, Username),
credentials: &Arc<Credentials>,
) -> Option<Arc<Credentials>> {
credentials: &Arc<Authentication>,
) -> Option<Arc<Authentication>> {
// Do not cache empty credentials
if credentials.is_empty() {
return None;
@ -148,8 +174,8 @@ impl CredentialsCache {
let mut realms = self.realms.write().unwrap();
// Always replace existing entries if we have a password
if credentials.password().is_some() {
// Always replace existing entries if we have a password or token
if credentials.is_authenticated() {
return realms.insert(key, credentials.clone());
}
@ -166,24 +192,33 @@ impl CredentialsCache {
}
#[derive(Debug)]
struct UrlTrie {
states: Vec<TrieState>,
struct UrlTrie<T> {
states: Vec<TrieState<T>>,
}
#[derive(Debug, Default)]
struct TrieState {
#[derive(Debug)]
struct TrieState<T> {
children: Vec<(String, usize)>,
value: Option<Arc<Credentials>>,
value: Option<T>,
}
impl UrlTrie {
impl<T> Default for TrieState<T> {
fn default() -> Self {
Self {
children: vec![],
value: None,
}
}
}
impl<T> UrlTrie<T> {
fn new() -> Self {
let mut trie = Self { states: vec![] };
trie.alloc();
trie
}
fn get(&self, url: &Url) -> Option<&Arc<Credentials>> {
fn get(&self, url: &Url) -> Option<&T> {
let mut state = 0;
let realm = Realm::from(url).to_string();
for component in [realm.as_str()]
@ -198,7 +233,7 @@ impl UrlTrie {
self.states[state].value.as_ref()
}
fn insert(&mut self, url: &Url, value: Arc<Credentials>) {
fn insert(&mut self, url: &Url, value: T) {
let mut state = 0;
let realm = Realm::from(url).to_string();
for component in [realm.as_str()]
@ -226,7 +261,7 @@ impl UrlTrie {
}
}
impl TrieState {
impl<T> TrieState<T> {
fn get(&self, component: &str) -> Option<usize> {
let i = self.index(component).ok()?;
Some(self.children[i].1)
@ -260,28 +295,21 @@ impl From<(Realm, Username)> for RealmUsername {
#[cfg(test)]
mod tests {
use crate::Credentials;
use crate::credentials::Password;
use super::*;
#[test]
fn test_trie() {
let credentials1 = Arc::new(Credentials::basic(
Some("username1".to_string()),
Some("password1".to_string()),
));
let credentials2 = Arc::new(Credentials::basic(
Some("username2".to_string()),
Some("password2".to_string()),
));
let credentials3 = Arc::new(Credentials::basic(
Some("username3".to_string()),
Some("password3".to_string()),
));
let credentials4 = Arc::new(Credentials::basic(
Some("username4".to_string()),
Some("password4".to_string()),
));
let credentials1 =
Credentials::basic(Some("username1".to_string()), Some("password1".to_string()));
let credentials2 =
Credentials::basic(Some("username2".to_string()), Some("password2".to_string()));
let credentials3 =
Credentials::basic(Some("username3".to_string()), Some("password3".to_string()));
let credentials4 =
Credentials::basic(Some("username4".to_string()), Some("password4".to_string()));
let mut trie = UrlTrie::new();
trie.insert(
@ -339,10 +367,10 @@ mod tests {
fn test_url_with_credentials() {
let username = Username::new(Some(String::from("username")));
let password = Password::new(String::from("password"));
let credentials = Arc::new(Credentials::Basic {
let credentials = Arc::new(Authentication::from(Credentials::Basic {
username: username.clone(),
password: Some(password),
});
}));
let cache = CredentialsCache::default();
// Insert with URL with credentials and get with redacted URL.
let url = Url::parse("https://username:password@example.com/foobar").unwrap();

View File

@ -1,34 +1,41 @@
use std::borrow::Cow;
use std::fmt;
use std::io::Read;
use std::io::Write;
use std::str::FromStr;
use base64::prelude::BASE64_STANDARD;
use base64::read::DecoderReader;
use base64::write::EncoderWriter;
use std::borrow::Cow;
use std::fmt;
use uv_redacted::DisplaySafeUrl;
use http::Uri;
use netrc::Netrc;
use reqsign::aws::DefaultSigner;
use reqwest::Request;
use reqwest::header::HeaderValue;
use std::io::Read;
use std::io::Write;
use serde::{Deserialize, Serialize};
use url::Url;
use uv_redacted::DisplaySafeUrl;
use uv_static::EnvVars;
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Credentials {
/// RFC 7617 HTTP Basic Authentication
Basic {
/// The username to use for authentication.
username: Username,
/// The password to use for authentication.
password: Option<Password>,
},
/// RFC 6750 Bearer Token Authentication
Bearer {
/// The token to use for authentication.
token: Vec<u8>,
token: Token,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Default)]
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Username(Option<String>);
impl Username {
@ -69,7 +76,8 @@ impl From<Option<String>> for Username {
}
}
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Default)]
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Password(String);
impl Password {
@ -77,9 +85,15 @@ impl Password {
Self(password)
}
/// Return the [`Password`] as a string slice.
pub fn as_str(&self) -> &str {
self.0.as_str()
}
/// Convert the [`Password`] into its underlying [`String`].
pub fn into_string(self) -> String {
self.0
}
}
impl fmt::Debug for Password {
@ -88,6 +102,36 @@ impl fmt::Debug for Password {
}
}
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Default, Deserialize)]
#[serde(transparent)]
pub struct Token(Vec<u8>);
impl Token {
pub fn new(token: Vec<u8>) -> Self {
Self(token)
}
/// Return the [`Token`] as a byte slice.
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
/// Convert the [`Token`] into its underlying [`Vec<u8>`].
pub fn into_bytes(self) -> Vec<u8> {
self.0
}
/// Return whether the [`Token`] is empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl fmt::Debug for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "****")
}
}
impl Credentials {
/// Create a set of HTTP Basic Authentication credentials.
#[allow(dead_code)]
@ -101,7 +145,9 @@ impl Credentials {
/// Create a set of Bearer Authentication credentials.
#[allow(dead_code)]
pub fn bearer(token: Vec<u8>) -> Self {
Self::Bearer { token }
Self::Bearer {
token: Token::new(token),
}
}
pub fn username(&self) -> Option<&str> {
@ -132,6 +178,16 @@ impl Credentials {
}
}
pub fn is_authenticated(&self) -> bool {
match self {
Self::Basic {
username: _,
password,
} => password.is_some(),
Self::Bearer { token } => !token.is_empty(),
}
}
pub(crate) fn is_empty(&self) -> bool {
match self {
Self::Basic { username, password } => username.is_none() && password.is_none(),
@ -262,7 +318,7 @@ impl Credentials {
// Parse a `Bearer` authentication header.
if let Some(token) = header.as_bytes().strip_prefix(b"Bearer ") {
return Some(Self::Bearer {
token: token.to_vec(),
token: Token::new(token.to_vec()),
});
}
@ -326,6 +382,127 @@ impl Credentials {
}
}
#[derive(Clone, Debug)]
pub(crate) enum Authentication {
/// HTTP Basic or Bearer Authentication credentials.
Credentials(Credentials),
/// AWS Signature Version 4 signing.
Signer(DefaultSigner),
}
impl PartialEq for Authentication {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Credentials(a), Self::Credentials(b)) => a == b,
(Self::Signer(..), Self::Signer(..)) => true,
_ => false,
}
}
}
impl Eq for Authentication {}
impl From<Credentials> for Authentication {
fn from(credentials: Credentials) -> Self {
Self::Credentials(credentials)
}
}
impl From<DefaultSigner> for Authentication {
fn from(signer: DefaultSigner) -> Self {
Self::Signer(signer)
}
}
impl Authentication {
/// Return the password used for authentication, if any.
pub(crate) fn password(&self) -> Option<&str> {
match self {
Self::Credentials(credentials) => credentials.password(),
Self::Signer(..) => None,
}
}
/// Return the username used for authentication, if any.
pub(crate) fn username(&self) -> Option<&str> {
match self {
Self::Credentials(credentials) => credentials.username(),
Self::Signer(..) => None,
}
}
/// Return the username used for authentication, if any.
pub(crate) fn as_username(&self) -> Cow<'_, Username> {
match self {
Self::Credentials(credentials) => credentials.as_username(),
Self::Signer(..) => Cow::Owned(Username::none()),
}
}
/// Return the username used for authentication, if any.
pub(crate) fn to_username(&self) -> Username {
match self {
Self::Credentials(credentials) => credentials.to_username(),
Self::Signer(..) => Username::none(),
}
}
/// Return `true` if the object contains a means of authenticating.
pub(crate) fn is_authenticated(&self) -> bool {
match self {
Self::Credentials(credentials) => credentials.is_authenticated(),
Self::Signer(..) => true,
}
}
/// Return `true` if the object contains no credentials.
pub(crate) fn is_empty(&self) -> bool {
match self {
Self::Credentials(credentials) => credentials.is_empty(),
Self::Signer(..) => false,
}
}
/// Apply the authentication to the given request.
///
/// Any existing credentials will be overridden.
#[must_use]
pub(crate) async fn authenticate(&self, mut request: Request) -> Request {
match self {
Self::Credentials(credentials) => credentials.authenticate(request),
Self::Signer(signer) => {
// Build an `http::Request` from the `reqwest::Request`.
// SAFETY: If we have a valid `reqwest::Request`, we expect (e.g.) the URL to be valid.
let uri = Uri::from_str(request.url().as_str()).unwrap();
let mut http_req = http::Request::builder()
.method(request.method().clone())
.uri(uri)
.body(())
.unwrap();
*http_req.headers_mut() = request.headers().clone();
// Sign the parts.
let (mut parts, ()) = http_req.into_parts();
signer
.sign(&mut parts, None)
.await
.expect("AWS signing should succeed");
// Copy over the signed headers.
request.headers_mut().extend(parts.headers);
// Copy over the signed path and query, if any.
if let Some(path_and_query) = parts.uri.path_and_query() {
request.url_mut().set_path(path_and_query.path());
request.url_mut().set_query(path_and_query.query());
}
request
}
}
}
}
#[cfg(test)]
mod tests {
use insta::assert_debug_snapshot;
@ -446,4 +623,15 @@ mod tests {
"Basic { username: Username(Some(\"user\")), password: Some(****) }"
);
}
#[test]
fn test_bearer_token_obfuscation() {
let token = "super_secret_token";
let credentials = Credentials::bearer(token.into());
let debugged = format!("{credentials:?}");
assert!(
!debugged.contains(token),
"Token should be obfuscated in Debug impl: {debugged}"
);
}
}

View File

@ -95,9 +95,9 @@ impl Indexes {
index_urls
}
/// Get the index URL prefix for a URL if one exists.
pub fn index_url_for(&self, url: &Url) -> Option<&DisplaySafeUrl> {
self.find_prefix_index(url).map(|index| &index.url)
/// Get the index for a URL if one exists.
pub fn index_for(&self, url: &Url) -> Option<&Index> {
self.find_prefix_index(url)
}
/// Get the [`AuthPolicy`] for a URL.

View File

@ -1,11 +1,14 @@
use std::{io::Write, process::Stdio};
use tokio::process::Command;
use tracing::{instrument, trace, warn};
use tracing::{debug, instrument, trace, warn};
use uv_redacted::DisplaySafeUrl;
use uv_warnings::warn_user_once;
use crate::credentials::Credentials;
/// Service name prefix for storing credentials in a keyring.
static UV_SERVICE_PREFIX: &str = "uv:";
/// A backend for retrieving credentials from a keyring.
///
/// See pip's implementation for reference
@ -15,15 +18,47 @@ pub struct KeyringProvider {
backend: KeyringProviderBackend,
}
#[derive(Debug)]
pub(crate) enum KeyringProviderBackend {
/// Use the `keyring` command to fetch credentials.
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Keyring(#[from] uv_keyring::Error),
#[error("The '{0}' keyring provider does not support storing credentials")]
StoreUnsupported(KeyringProviderBackend),
#[error("The '{0}' keyring provider does not support removing credentials")]
RemoveUnsupported(KeyringProviderBackend),
}
#[derive(Debug, Clone)]
pub enum KeyringProviderBackend {
/// Use a native system keyring integration for credentials.
Native,
/// Use the external `keyring` command for credentials.
Subprocess,
#[cfg(test)]
Dummy(Vec<(String, &'static str, &'static str)>),
}
impl std::fmt::Display for KeyringProviderBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Native => write!(f, "native"),
Self::Subprocess => write!(f, "subprocess"),
#[cfg(test)]
Self::Dummy(_) => write!(f, "dummy"),
}
}
}
impl KeyringProvider {
/// Create a new [`KeyringProvider::Native`].
pub fn native() -> Self {
Self {
backend: KeyringProviderBackend::Native,
}
}
/// Create a new [`KeyringProvider::Subprocess`].
pub fn subprocess() -> Self {
Self {
@ -31,6 +66,124 @@ impl KeyringProvider {
}
}
/// Store credentials for the given [`DisplaySafeUrl`] to the keyring.
///
/// Only [`KeyringProviderBackend::Native`] is supported at this time.
#[instrument(skip_all, fields(url = % url.to_string(), username))]
pub async fn store(
&self,
url: &DisplaySafeUrl,
credentials: &Credentials,
) -> Result<bool, Error> {
let Some(username) = credentials.username() else {
trace!("Unable to store credentials in keyring for {url} due to missing username");
return Ok(false);
};
let Some(password) = credentials.password() else {
trace!("Unable to store credentials in keyring for {url} due to missing password");
return Ok(false);
};
// Ensure we strip credentials from the URL before storing
let url = url.without_credentials();
// If there's no path, we'll perform a host-level login
let target = if let Some(host) = url.host_str().filter(|_| !url.path().is_empty()) {
let mut target = String::new();
if url.scheme() != "https" {
target.push_str(url.scheme());
target.push_str("://");
}
target.push_str(host);
if let Some(port) = url.port() {
target.push(':');
target.push_str(&port.to_string());
}
target
} else {
url.to_string()
};
match &self.backend {
KeyringProviderBackend::Native => {
self.store_native(&target, username, password).await?;
Ok(true)
}
KeyringProviderBackend::Subprocess => {
Err(Error::StoreUnsupported(self.backend.clone()))
}
#[cfg(test)]
KeyringProviderBackend::Dummy(_) => Err(Error::StoreUnsupported(self.backend.clone())),
}
}
/// Store credentials to the system keyring.
#[instrument(skip(self))]
async fn store_native(
&self,
service: &str,
username: &str,
password: &str,
) -> Result<(), Error> {
let prefixed_service = format!("{UV_SERVICE_PREFIX}{service}");
let entry = uv_keyring::Entry::new(&prefixed_service, username)?;
entry.set_password(password).await?;
Ok(())
}
/// Remove credentials for the given [`DisplaySafeUrl`] and username from the keyring.
///
/// Only [`KeyringProviderBackend::Native`] is supported at this time.
#[instrument(skip_all, fields(url = % url.to_string(), username))]
pub async fn remove(&self, url: &DisplaySafeUrl, username: &str) -> Result<(), Error> {
// Ensure we strip credentials from the URL before storing
let url = url.without_credentials();
// If there's no path, we'll perform a host-level login
let target = if let Some(host) = url.host_str().filter(|_| !url.path().is_empty()) {
let mut target = String::new();
if url.scheme() != "https" {
target.push_str(url.scheme());
target.push_str("://");
}
target.push_str(host);
if let Some(port) = url.port() {
target.push(':');
target.push_str(&port.to_string());
}
target
} else {
url.to_string()
};
match &self.backend {
KeyringProviderBackend::Native => {
self.remove_native(&target, username).await?;
Ok(())
}
KeyringProviderBackend::Subprocess => {
Err(Error::RemoveUnsupported(self.backend.clone()))
}
#[cfg(test)]
KeyringProviderBackend::Dummy(_) => Err(Error::RemoveUnsupported(self.backend.clone())),
}
}
/// Remove credentials from the system keyring for the given `service_name`/`username`
/// pair.
#[instrument(skip(self))]
async fn remove_native(
&self,
service_name: &str,
username: &str,
) -> Result<(), uv_keyring::Error> {
let prefixed_service = format!("{UV_SERVICE_PREFIX}{service_name}");
let entry = uv_keyring::Entry::new(&prefixed_service, username)?;
entry.delete_credential().await?;
trace!("Removed credentials for {username}@{service_name} from system keyring");
Ok(())
}
/// Fetch credentials for the given [`Url`] from the keyring.
///
/// Returns [`None`] if no password was found for the username or if any errors
@ -40,11 +193,11 @@ impl KeyringProvider {
// Validate the request
debug_assert!(
url.host_str().is_some(),
"Should only use keyring for urls with host"
"Should only use keyring for URLs with host"
);
debug_assert!(
url.password().is_none(),
"Should only use keyring for urls without a password"
"Should only use keyring for URLs without a password"
);
debug_assert!(
!username.map(str::is_empty).unwrap_or(false),
@ -55,6 +208,7 @@ impl KeyringProvider {
// <https://github.com/pypa/pip/blob/ae5fff36b0aad6e5e0037884927eaa29163c0611/src/pip/_internal/network/auth.py#L376C1-L379C14>
trace!("Checking keyring for URL {url}");
let mut credentials = match self.backend {
KeyringProviderBackend::Native => self.fetch_native(url.as_str(), username).await,
KeyringProviderBackend::Subprocess => {
self.fetch_subprocess(url.as_str(), username).await
}
@ -72,6 +226,7 @@ impl KeyringProvider {
};
trace!("Checking keyring for host {host}");
credentials = match self.backend {
KeyringProviderBackend::Native => self.fetch_native(&host, username).await,
KeyringProviderBackend::Subprocess => self.fetch_subprocess(&host, username).await,
#[cfg(test)]
KeyringProviderBackend::Dummy(ref store) => {
@ -165,7 +320,7 @@ impl KeyringProvider {
// N.B. We do not show the `service_name` here because we'll show the warning twice
// otherwise, once for the URL and once for the realm.
warn_user_once!(
"Attempted to fetch credentials using the `keyring` command, but it does not support `--mode creds`; upgrade to `keyring>=v25.2.1` for support or provide a username"
"Attempted to fetch credentials using the `keyring` command, but it does not support `--mode creds`; upgrade to `keyring>=v25.2.1` or provide a username"
);
} else if username.is_none() {
// If we captured stderr, display it in case it's helpful to the user
@ -175,6 +330,31 @@ impl KeyringProvider {
}
}
#[instrument(skip(self))]
async fn fetch_native(
&self,
service: &str,
username: Option<&str>,
) -> Option<(String, String)> {
let prefixed_service = format!("{UV_SERVICE_PREFIX}{service}");
let username = username?;
let Ok(entry) = uv_keyring::Entry::new(&prefixed_service, username) else {
return None;
};
match entry.get_password().await {
Ok(password) => return Some((username.to_string(), password)),
Err(uv_keyring::Error::NoEntry) => {
debug!("No entry found in system keyring for {service}");
}
Err(err) => {
warn_user_once!(
"Unable to fetch credentials for {service} from system keyring: {err}"
);
}
}
None
}
#[cfg(test)]
fn fetch_dummy(
store: &Vec<(String, &'static str, &'static str)>,
@ -224,12 +404,13 @@ mod tests {
let url = Url::parse("file:/etc/bin/").unwrap();
let keyring = KeyringProvider::empty();
// Panics due to debug assertion; returns `None` in production
let result = std::panic::AssertUnwindSafe(
keyring.fetch(DisplaySafeUrl::ref_cast(&url), Some("user")),
)
.catch_unwind()
.await;
assert!(result.is_err());
let fetch = keyring.fetch(DisplaySafeUrl::ref_cast(&url), Some("user"));
if cfg!(debug_assertions) {
let result = std::panic::AssertUnwindSafe(fetch).catch_unwind().await;
assert!(result.is_err());
} else {
assert_eq!(fetch.await, None);
}
}
#[tokio::test]
@ -237,12 +418,13 @@ mod tests {
let url = Url::parse("https://user:password@example.com").unwrap();
let keyring = KeyringProvider::empty();
// Panics due to debug assertion; returns `None` in production
let result = std::panic::AssertUnwindSafe(
keyring.fetch(DisplaySafeUrl::ref_cast(&url), Some(url.username())),
)
.catch_unwind()
.await;
assert!(result.is_err());
let fetch = keyring.fetch(DisplaySafeUrl::ref_cast(&url), Some(url.username()));
if cfg!(debug_assertions) {
let result = std::panic::AssertUnwindSafe(fetch).catch_unwind().await;
assert!(result.is_err());
} else {
assert_eq!(fetch.await, None);
}
}
#[tokio::test]
@ -250,12 +432,13 @@ mod tests {
let url = Url::parse("https://example.com").unwrap();
let keyring = KeyringProvider::empty();
// Panics due to debug assertion; returns `None` in production
let result = std::panic::AssertUnwindSafe(
keyring.fetch(DisplaySafeUrl::ref_cast(&url), Some(url.username())),
)
.catch_unwind()
.await;
assert!(result.is_err());
let fetch = keyring.fetch(DisplaySafeUrl::ref_cast(&url), Some(url.username()));
if cfg!(debug_assertions) {
let result = std::panic::AssertUnwindSafe(fetch).catch_unwind().await;
assert!(result.is_err());
} else {
assert_eq!(fetch.await, None);
}
}
#[tokio::test]

View File

@ -1,48 +1,24 @@
use std::sync::{Arc, LazyLock};
use tracing::trace;
use cache::CredentialsCache;
pub use credentials::Credentials;
pub use access_token::AccessToken;
pub use cache::CredentialsCache;
pub use credentials::{Credentials, Username};
pub use index::{AuthPolicy, Index, Indexes};
pub use keyring::KeyringProvider;
pub use middleware::AuthMiddleware;
use realm::Realm;
use uv_redacted::DisplaySafeUrl;
pub use pyx::{
DEFAULT_TOLERANCE_SECS, PyxJwt, PyxOAuthTokens, PyxTokenStore, PyxTokens, TokenStoreError,
};
pub use realm::{Realm, RealmRef};
pub use service::{Service, ServiceParseError};
pub use store::{AuthBackend, AuthScheme, TextCredentialStore, TomlCredentialError};
mod access_token;
mod cache;
mod credentials;
mod index;
mod keyring;
mod middleware;
mod providers;
mod pyx;
mod realm;
// 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(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: Arc<Credentials>) {
trace!("Caching credentials for {url}");
CREDENTIALS_CACHE.insert(url, credentials);
}
mod service;
mod store;

View File

@ -4,18 +4,30 @@ use anyhow::{anyhow, format_err};
use http::{Extensions, StatusCode};
use netrc::Netrc;
use reqwest::{Request, Response};
use reqwest_middleware::{Error, Middleware, Next};
use reqwest_middleware::{ClientWithMiddleware, Error, Middleware, Next};
use tokio::sync::Mutex;
use tracing::{debug, trace, warn};
use crate::providers::HuggingFaceProvider;
use uv_preview::{Preview, PreviewFeatures};
use uv_redacted::DisplaySafeUrl;
use uv_static::EnvVars;
use uv_warnings::owo_colors::OwoColorize;
use crate::credentials::Authentication;
use crate::providers::{HuggingFaceProvider, S3EndpointProvider};
use crate::pyx::{DEFAULT_TOLERANCE_SECS, PyxTokenStore};
use crate::{
CREDENTIALS_CACHE, CredentialsCache, KeyringProvider,
AccessToken, CredentialsCache, KeyringProvider,
cache::FetchUrl,
credentials::{Credentials, Username},
index::{AuthPolicy, Indexes},
realm::Realm,
};
use uv_redacted::DisplaySafeUrl;
use crate::{Index, TextCredentialStore};
/// Cached check for whether we're running in Dependabot.
static IS_DEPENDABOT: LazyLock<bool> =
LazyLock::new(|| std::env::var(EnvVars::DEPENDABOT).is_ok_and(|value| value == "true"));
/// Strategy for loading netrc files.
enum NetrcMode {
@ -51,29 +63,128 @@ impl NetrcMode {
}
}
/// Strategy for loading text-based credential files.
enum TextStoreMode {
Automatic(tokio::sync::OnceCell<Option<TextCredentialStore>>),
Enabled(TextCredentialStore),
Disabled,
}
impl Default for TextStoreMode {
fn default() -> Self {
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.
async fn get(&self) -> Option<&TextCredentialStore> {
match self {
// 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,
}
}
}
#[derive(Debug, Clone)]
enum TokenState {
/// The token state has not yet been initialized from the store.
Uninitialized,
/// The token state has been initialized, and the store either returned tokens or `None` if
/// the user has not yet authenticated.
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
/// fetches credentials from a netrc file and the keyring.
/// fetches credentials from a netrc file, TOML file, and the keyring.
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
/// unauthenticated request, avoiding cloning an uncloneable request.
only_authenticated: bool,
/// The base client to use for requests within the middleware.
base_client: Option<ClientWithMiddleware>,
/// The pyx token store to use for persistent credentials.
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(),
}
}
@ -90,6 +201,19 @@ impl AuthMiddleware {
self
}
/// Configure the text credential store to use.
///
/// `None` disables authentication via text store.
#[must_use]
pub fn with_text_store(mut self, store: Option<TextCredentialStore>) -> Self {
self.text_store = if let Some(store) = store {
TextStoreMode::Enabled(store)
} else {
TextStoreMode::Disabled
};
self
}
/// Configure the [`KeyringProvider`] to use.
#[must_use]
pub fn with_keyring(mut self, keyring: Option<KeyringProvider>) -> Self {
@ -97,10 +221,24 @@ impl AuthMiddleware {
self
}
/// Configure the [`Preview`] features to use.
#[must_use]
pub fn with_preview(mut self, preview: Preview) -> Self {
self.preview = preview;
self
}
/// 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
}
@ -119,17 +257,23 @@ impl AuthMiddleware {
self
}
/// Get the configured authentication store.
///
/// If not set, the global store is used.
fn cache(&self) -> &CredentialsCache {
self.cache.as_ref().unwrap_or(&CREDENTIALS_CACHE)
/// Configure the [`ClientWithMiddleware`] to use for requests within the middleware.
#[must_use]
pub fn with_base_client(mut self, client: ClientWithMiddleware) -> Self {
self.base_client = Some(client);
self
}
}
impl Default for AuthMiddleware {
fn default() -> Self {
Self::new()
/// Configure the [`PyxTokenStore`] to use for persistent credentials.
#[must_use]
pub fn with_pyx_token_store(mut self, token_store: PyxTokenStore) -> Self {
self.pyx_token_store = Some(token_store);
self
}
/// Global authentication cache for a uv invocation to share credentials across uv clients.
fn cache(&self) -> &CredentialsCache {
&self.cache
}
}
@ -178,16 +322,16 @@ impl Middleware for AuthMiddleware {
next: Next<'_>,
) -> reqwest_middleware::Result<Response> {
// Check for credentials attached to the request already
let request_credentials = Credentials::from_request(&request);
let request_credentials = Credentials::from_request(&request).map(Authentication::from);
// In the middleware, existing credentials are already moved from the URL
// to the headers so for display purposes we restore some information
let url = tracing_url(&request, request_credentials.as_ref());
let maybe_index_url = self.indexes.index_url_for(request.url());
let index = self.indexes.index_for(request.url());
let auth_policy = self.indexes.auth_policy_for(request.url());
trace!("Handling request for {url} with authentication policy {auth_policy}");
let credentials: Option<Arc<Credentials>> = if matches!(auth_policy, AuthPolicy::Never) {
let credentials: Option<Arc<Authentication>> = if matches!(auth_policy, AuthPolicy::Never) {
None
} else {
if let Some(request_credentials) = request_credentials {
@ -198,7 +342,7 @@ impl Middleware for AuthMiddleware {
extensions,
next,
&url,
maybe_index_url,
index,
auth_policy,
)
.await;
@ -211,10 +355,10 @@ impl Middleware for AuthMiddleware {
// making a failing request
let credentials = self.cache().get_url(request.url(), &Username::none());
if let Some(credentials) = credentials.as_ref() {
request = credentials.authenticate(request);
request = credentials.authenticate(request).await;
// If it's fully authenticated, finish the request
if credentials.password().is_some() {
if credentials.is_authenticated() {
trace!("Request for {url} is fully authenticated");
return self
.complete_request(None, request, extensions, next, auth_policy)
@ -231,9 +375,24 @@ impl Middleware for AuthMiddleware {
.as_ref()
.is_some_and(|credentials| credentials.username().is_some());
let retry_unauthenticated =
!self.only_authenticated && !matches!(auth_policy, AuthPolicy::Always);
let (mut retry_request, response) = if retry_unauthenticated {
// Determine whether this is a "known" URL.
let is_known_url = self
.pyx_token_store
.as_ref()
.is_some_and(|token_store| token_store.is_known_url(request.url()));
let must_authenticate = self.only_authenticated
|| (match auth_policy {
AuthPolicy::Auto => is_known_url,
AuthPolicy::Always => true,
AuthPolicy::Never => false,
}
// Dependabot intercepts HTTP requests and injects credentials, which means that we
// cannot eagerly enforce an `AuthPolicy` as we don't know whether credentials will be
// added outside of uv.
&& !*IS_DEPENDABOT);
let (mut retry_request, response) = if !must_authenticate {
let url = tracing_url(&request, credentials.as_deref());
if credentials.is_none() {
trace!("Attempting unauthenticated request for {url}");
@ -281,8 +440,8 @@ impl Middleware for AuthMiddleware {
.as_ref()
.map(|credentials| credentials.to_username())
.unwrap_or(Username::none());
let credentials = if let Some(index_url) = maybe_index_url {
self.cache().get_url(index_url, &username).or_else(|| {
let credentials = if let Some(index) = index {
self.cache().get_url(&index.url, &username).or_else(|| {
self.cache()
.get_realm(Realm::from(&**retry_request_url), username)
})
@ -295,9 +454,9 @@ impl Middleware for AuthMiddleware {
.or(credentials);
if let Some(credentials) = credentials.as_ref() {
if credentials.password().is_some() {
if credentials.is_authenticated() {
trace!("Retrying request for {url} with credentials from cache {credentials:?}");
retry_request = credentials.authenticate(retry_request);
retry_request = credentials.authenticate(retry_request).await;
return self
.complete_request(None, retry_request, extensions, next, auth_policy)
.await;
@ -310,12 +469,12 @@ impl Middleware for AuthMiddleware {
.fetch_credentials(
credentials.as_deref(),
retry_request_url,
maybe_index_url,
index,
auth_policy,
)
.await
{
retry_request = credentials.authenticate(retry_request);
retry_request = credentials.authenticate(retry_request).await;
trace!("Retrying request for {url} with {credentials:?}");
return self
.complete_request(
@ -331,7 +490,7 @@ impl Middleware for AuthMiddleware {
if let Some(credentials) = credentials.as_ref() {
if !attempt_has_username {
trace!("Retrying request for {url} with username from cache {credentials:?}");
retry_request = credentials.authenticate(retry_request);
retry_request = credentials.authenticate(retry_request).await;
return self
.complete_request(None, retry_request, extensions, next, auth_policy)
.await;
@ -340,6 +499,19 @@ impl Middleware for AuthMiddleware {
if let Some(response) = response {
Ok(response)
} else if let Some(store) = is_known_url
.then_some(self.pyx_token_store.as_ref())
.flatten()
{
let domain = store
.api()
.domain()
.unwrap_or("pyx.dev")
.trim_start_matches("api.");
Err(Error::Middleware(format_err!(
"Run `{}` to authenticate uv with pyx",
format!("uv auth login {domain}").green()
)))
} else {
Err(Error::Middleware(format_err!(
"Missing credentials for {url}"
@ -354,7 +526,7 @@ impl AuthMiddleware {
/// If credentials are present, insert them into the cache on success.
async fn complete_request(
&self,
credentials: Option<Arc<Credentials>>,
credentials: Option<Arc<Authentication>>,
request: Request,
extensions: &mut Extensions,
next: Next<'_>,
@ -364,7 +536,7 @@ impl AuthMiddleware {
// Nothing to insert into the cache if we don't have credentials
return next.run(request, extensions).await;
};
let url = DisplaySafeUrl::from(request.url().clone());
let url = DisplaySafeUrl::from_url(request.url().clone());
if matches!(auth_policy, AuthPolicy::Always) && credentials.password().is_none() {
return Err(Error::Middleware(format_err!("Missing password for {url}")));
}
@ -375,6 +547,7 @@ impl AuthMiddleware {
.as_ref()
.is_ok_and(|response| response.error_for_status_ref().is_ok())
{
// TODO(zanieb): Consider also updating the system keyring after successful use
trace!("Updating cached credentials for {url} to {credentials:?}");
self.cache().insert(&url, credentials);
}
@ -385,18 +558,18 @@ impl AuthMiddleware {
/// Use known request credentials to complete the request.
async fn complete_request_with_request_credentials(
&self,
credentials: Credentials,
credentials: Authentication,
mut request: Request,
extensions: &mut Extensions,
next: Next<'_>,
url: &DisplaySafeUrl,
index_url: Option<&DisplaySafeUrl>,
index: Option<&Index>,
auth_policy: AuthPolicy,
) -> reqwest_middleware::Result<Response> {
let credentials = Arc::new(credentials);
// If there's a password, send the request and cache
if credentials.password().is_some() {
if credentials.is_authenticated() {
trace!("Request for {url} already contains username and password");
return self
.complete_request(Some(credentials), request, extensions, next, auth_policy)
@ -406,17 +579,21 @@ impl AuthMiddleware {
trace!("Request for {url} is missing a password, looking for credentials");
// There's just a username, try to find a password.
// If we have an index URL, check the cache for that URL. Otherwise,
// If we have an index, check the cache for that URL. Otherwise,
// check for the realm.
let maybe_cached_credentials = if let Some(index_url) = index_url {
let maybe_cached_credentials = if let Some(index) = index {
self.cache()
.get_url(index_url, credentials.as_username().as_ref())
.get_url(&index.url, credentials.as_username().as_ref())
.or_else(|| {
self.cache()
.get_url(&index.root_url, credentials.as_username().as_ref())
})
} else {
self.cache()
.get_realm(Realm::from(request.url()), credentials.to_username())
};
if let Some(credentials) = maybe_cached_credentials {
request = credentials.authenticate(request);
request = credentials.authenticate(request).await;
// Do not insert already-cached credentials
let credentials = None;
return self
@ -428,27 +605,27 @@ impl AuthMiddleware {
.cache()
.get_url(request.url(), credentials.as_username().as_ref())
{
request = credentials.authenticate(request);
request = credentials.authenticate(request).await;
// Do not insert already-cached credentials
None
} else if let Some(credentials) = self
.fetch_credentials(
Some(&credentials),
DisplaySafeUrl::ref_cast(request.url()),
index_url,
index,
auth_policy,
)
.await
{
request = credentials.authenticate(request);
request = credentials.authenticate(request).await;
Some(credentials)
} else if index_url.is_some() {
} else if index.is_some() {
// If this is a known index, we fall back to checking for the realm.
if let Some(credentials) = self
.cache()
.get_realm(Realm::from(request.url()), credentials.to_username())
{
request = credentials.authenticate(request);
request = credentials.authenticate(request).await;
Some(credentials)
} else {
Some(credentials)
@ -467,19 +644,19 @@ impl AuthMiddleware {
/// Supports netrc file and keyring lookups.
async fn fetch_credentials(
&self,
credentials: Option<&Credentials>,
credentials: Option<&Authentication>,
url: &DisplaySafeUrl,
maybe_index_url: Option<&DisplaySafeUrl>,
index: Option<&Index>,
auth_policy: AuthPolicy,
) -> Option<Arc<Credentials>> {
) -> Option<Arc<Authentication>> {
let username = Username::from(
credentials.map(|credentials| credentials.username().unwrap_or_default().to_string()),
);
// Fetches can be expensive, so we will only run them _once_ per realm or index URL and username combination
// All other requests for the same realm or index URL will wait until the first one completes
let key = if let Some(index_url) = maybe_index_url {
(FetchUrl::Index(index_url.clone()), username)
let key = if let Some(index) = index {
(FetchUrl::Index(index.url.clone()), username)
} else {
(FetchUrl::Realm(Realm::from(&**url)), username)
};
@ -503,13 +680,78 @@ impl AuthMiddleware {
return credentials;
}
// Support for known providers, like Hugging Face.
if let Some(credentials) = HuggingFaceProvider::credentials_for(url).map(Arc::new) {
// Support for known providers, like Hugging Face and S3.
if let Some(credentials) = HuggingFaceProvider::credentials_for(url)
.map(Authentication::from)
.map(Arc::new)
{
debug!("Found Hugging Face 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.
if let Some(base_client) = self.base_client.as_ref() {
if let Some(token_store) = self.pyx_token_store.as_ref() {
if token_store.is_known_url(url) {
let mut token_state = self.pyx_token_state.lock().await;
// If the token store is uninitialized, initialize it.
let token = match *token_state {
TokenState::Uninitialized => {
trace!("Initializing token store for {url}");
let generated = match token_store
.access_token(base_client, DEFAULT_TOLERANCE_SECS)
.await
{
Ok(Some(token)) => Some(token),
Ok(None) => None,
Err(err) => {
warn!("Failed to generate access tokens: {err}");
None
}
};
*token_state = TokenState::Initialized(generated.clone());
generated
}
TokenState::Initialized(ref tokens) => tokens.clone(),
};
let credentials = token.map(|token| {
trace!("Using credentials from token store for {url}");
Arc::new(Authentication::from(Credentials::from(token)))
});
// Register the fetch for this key
self.cache().fetches.done(key.clone(), credentials.clone());
return credentials;
}
}
}
// Netrc support based on: <https://github.com/gribouille/netrc>.
let credentials = if let Some(credentials) = self.netrc.get().and_then(|netrc| {
debug!("Checking netrc for credentials for {url}");
@ -524,6 +766,51 @@ impl AuthMiddleware {
debug!("Found credentials in netrc file for {url}");
Some(credentials)
// Text credential store support.
} 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()
}) {
debug!("Found credentials in plaintext store for {url}");
Some(credentials)
} else if let Some(credentials) = {
if self.preview.is_enabled(PreviewFeatures::NATIVE_AUTH) {
let native_store = KeyringProvider::native();
let username = credentials.and_then(|credentials| credentials.username());
let display_username = if let Some(username) = username {
format!("{username}@")
} else {
String::new()
};
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
);
native_store.fetch(&index.root_url, username).await
} else {
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
} else {
None
}
} {
debug!("Found credentials in native store for {url}");
Some(credentials)
// N.B. The keyring provider performs lookups for the exact URL then falls back to the host.
// But, in the absence of an index URL, we cache the result per realm. So in that case,
// if a keyring implementation returns different credentials for different URLs in the
@ -534,24 +821,37 @@ impl AuthMiddleware {
// URLs; instead, we fetch if there's a username or if the user has requested to
// always authenticate.
if let Some(username) = credentials.and_then(|credentials| credentials.username()) {
if let Some(index_url) = maybe_index_url {
debug!("Checking keyring for credentials for index URL {}@{}", username, index_url);
keyring.fetch(DisplaySafeUrl::ref_cast(index_url), Some(username)).await
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
} 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) {
if let Some(index_url) = maybe_index_url {
if let Some(index) = index {
debug!(
"Checking keyring for credentials for index URL {index_url} without username due to `authenticate = always`"
"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
}
}
@ -561,8 +861,9 @@ impl AuthMiddleware {
Some(credentials)
} else {
None
}
.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());
@ -571,9 +872,9 @@ impl AuthMiddleware {
}
}
fn tracing_url(request: &Request, credentials: Option<&Credentials>) -> DisplaySafeUrl {
let mut url = DisplaySafeUrl::from(request.url().clone());
if let Some(creds) = credentials {
fn tracing_url(request: &Request, credentials: Option<&Authentication>) -> DisplaySafeUrl {
let mut url = DisplaySafeUrl::from_url(request.url().clone());
if let Some(Authentication::Credentials(creds)) = credentials {
if let Some(username) = creds.username() {
let _ = url.set_username(username);
}
@ -714,10 +1015,10 @@ mod tests {
let cache = CredentialsCache::new();
cache.insert(
&base_url,
Arc::new(Credentials::basic(
Arc::new(Authentication::from(Credentials::basic(
Some(username.to_string()),
Some(password.to_string()),
)),
))),
);
let client = test_client_builder()
@ -768,7 +1069,10 @@ mod tests {
let cache = CredentialsCache::new();
cache.insert(
&base_url,
Arc::new(Credentials::basic(Some(username.to_string()), None)),
Arc::new(Authentication::from(Credentials::basic(
Some(username.to_string()),
None,
))),
);
let client = test_client_builder()
@ -1161,7 +1465,10 @@ mod tests {
// URL.
cache.insert(
&base_url,
Arc::new(Credentials::basic(Some(username.to_string()), None)),
Arc::new(Authentication::from(Credentials::basic(
Some(username.to_string()),
None,
))),
);
let client = test_client_builder()
.with(AuthMiddleware::new().with_cache(cache).with_keyring(Some(
@ -1210,17 +1517,17 @@ mod tests {
// Seed the cache with our credentials
cache.insert(
&base_url_1,
Arc::new(Credentials::basic(
Arc::new(Authentication::from(Credentials::basic(
Some(username_1.to_string()),
Some(password_1.to_string()),
)),
))),
);
cache.insert(
&base_url_2,
Arc::new(Credentials::basic(
Arc::new(Authentication::from(Credentials::basic(
Some(username_2.to_string()),
Some(password_2.to_string()),
)),
))),
);
let client = test_client_builder()
@ -1405,17 +1712,17 @@ mod tests {
// Seed the cache with our credentials
cache.insert(
&base_url_1,
Arc::new(Credentials::basic(
Arc::new(Authentication::from(Credentials::basic(
Some(username_1.to_string()),
Some(password_1.to_string()),
)),
))),
);
cache.insert(
&base_url_2,
Arc::new(Credentials::basic(
Arc::new(Authentication::from(Credentials::basic(
Some(username_2.to_string()),
Some(password_2.to_string()),
)),
))),
);
let client = test_client_builder()
@ -1755,13 +2062,13 @@ mod tests {
let base_url_2 = base_url.join("prefix_2")?;
let indexes = Indexes::from_indexes(vec![
Index {
url: DisplaySafeUrl::from(base_url_1.clone()),
root_url: DisplaySafeUrl::from(base_url_1.clone()),
url: DisplaySafeUrl::from_url(base_url_1.clone()),
root_url: DisplaySafeUrl::from_url(base_url_1.clone()),
auth_policy: AuthPolicy::Auto,
},
Index {
url: DisplaySafeUrl::from(base_url_2.clone()),
root_url: DisplaySafeUrl::from(base_url_2.clone()),
url: DisplaySafeUrl::from_url(base_url_2.clone()),
root_url: DisplaySafeUrl::from_url(base_url_2.clone()),
auth_policy: AuthPolicy::Auto,
},
]);
@ -1863,8 +2170,8 @@ mod tests {
let base_url = Url::parse(&server.uri())?;
let index_url = base_url.join("prefix_1")?;
let indexes = Indexes::from_indexes(vec![Index {
url: DisplaySafeUrl::from(index_url.clone()),
root_url: DisplaySafeUrl::from(index_url.clone()),
url: DisplaySafeUrl::from_url(index_url.clone()),
root_url: DisplaySafeUrl::from_url(index_url.clone()),
auth_policy: AuthPolicy::Auto,
}]);
@ -1918,7 +2225,7 @@ mod tests {
}
fn indexes_for(url: &Url, policy: AuthPolicy) -> Indexes {
let mut url = DisplaySafeUrl::from(url.clone());
let mut url = DisplaySafeUrl::from_url(url.clone());
url.set_password(None).ok();
url.set_username("").ok();
Indexes::from_indexes(vec![Index {
@ -2019,7 +2326,7 @@ mod tests {
assert!(matches!(
client.get(server.uri()).send().await,
Err(reqwest_middleware::Error::Middleware(_))
),);
));
Ok(())
}
@ -2118,20 +2425,20 @@ mod tests {
DisplaySafeUrl::parse("https://pypi-proxy.fly.dev/basic-auth/simple").unwrap()
);
let creds = Credentials::Basic {
let creds = Authentication::from(Credentials::Basic {
username: Username::new(Some(String::from("user"))),
password: None,
};
});
let req = create_request("https://pypi-proxy.fly.dev/basic-auth/simple");
assert_eq!(
tracing_url(&req, Some(&creds)),
DisplaySafeUrl::parse("https://user@pypi-proxy.fly.dev/basic-auth/simple").unwrap()
);
let creds = Credentials::Basic {
let creds = Authentication::from(Credentials::Basic {
username: Username::new(Some(String::from("user"))),
password: Some(Password::new(String::from("password"))),
};
});
let req = create_request("https://pypi-proxy.fly.dev/basic-auth/simple");
assert_eq!(
tracing_url(&req, Some(&creds)),
@ -2140,6 +2447,132 @@ mod tests {
);
}
#[test(tokio::test)]
async fn test_text_store_basic_auth() -> Result<(), Error> {
let username = "user";
let password = "password";
let server = start_test_server(username, password).await;
let base_url = Url::parse(&server.uri())?;
// Create a text credential store with matching credentials
let mut store = TextCredentialStore::default();
let service = crate::Service::try_from(base_url.to_string()).unwrap();
let credentials =
Credentials::basic(Some(username.to_string()), Some(password.to_string()));
store.insert(service.clone(), credentials);
let client = test_client_builder()
.with(
AuthMiddleware::new()
.with_cache(CredentialsCache::new())
.with_text_store(Some(store)),
)
.build();
assert_eq!(
client.get(server.uri()).send().await?.status(),
200,
"Credentials should be pulled from the text store"
);
Ok(())
}
#[test(tokio::test)]
async fn test_text_store_disabled() -> Result<(), Error> {
let username = "user";
let password = "password";
let server = start_test_server(username, password).await;
let client = test_client_builder()
.with(
AuthMiddleware::new()
.with_cache(CredentialsCache::new())
.with_text_store(None), // Explicitly disable text store
)
.build();
assert_eq!(
client.get(server.uri()).send().await?.status(),
401,
"Credentials should not be found when text store is disabled"
);
Ok(())
}
#[test(tokio::test)]
async fn test_text_store_by_username() -> Result<(), Error> {
let username = "testuser";
let password = "testpass";
let wrong_username = "wronguser";
let server = start_test_server(username, password).await;
let base_url = Url::parse(&server.uri())?;
let mut store = TextCredentialStore::default();
let service = crate::Service::try_from(base_url.to_string()).unwrap();
let credentials =
crate::Credentials::basic(Some(username.to_string()), Some(password.to_string()));
store.insert(service.clone(), credentials);
let client = test_client_builder()
.with(
AuthMiddleware::new()
.with_cache(CredentialsCache::new())
.with_text_store(Some(store)),
)
.build();
// Request with matching username should succeed
let url_with_username = format!(
"{}://{}@{}",
base_url.scheme(),
username,
base_url.host_str().unwrap()
);
let url_with_port = if let Some(port) = base_url.port() {
format!("{}:{}{}", url_with_username, port, base_url.path())
} else {
format!("{}{}", url_with_username, base_url.path())
};
assert_eq!(
client.get(&url_with_port).send().await?.status(),
200,
"Request with matching username should succeed"
);
// Request with non-matching username should fail
let url_with_wrong_username = format!(
"{}://{}@{}",
base_url.scheme(),
wrong_username,
base_url.host_str().unwrap()
);
let url_with_port = if let Some(port) = base_url.port() {
format!("{}:{}{}", url_with_wrong_username, port, base_url.path())
} else {
format!("{}{}", url_with_wrong_username, base_url.path())
};
assert_eq!(
client.get(&url_with_port).send().await?.status(),
401,
"Request with non-matching username should fail"
);
// Request without username should succeed
assert_eq!(
client.get(server.uri()).send().await?.status(),
200,
"Request with no username should succeed"
);
Ok(())
}
fn create_request(url: &str) -> Request {
Request::new(Method::GET, Url::parse(url).unwrap())
}

View File

@ -1,10 +1,16 @@
use std::borrow::Cow;
use std::sync::LazyLock;
use reqsign::aws::DefaultSigner;
use tracing::debug;
use url::Url;
use uv_preview::{Preview, PreviewFeatures};
use uv_static::EnvVars;
use uv_warnings::warn_user_once;
use crate::Credentials;
use crate::credentials::Token;
use crate::realm::{Realm, RealmRef};
/// The [`Realm`] for the Hugging Face platform.
@ -40,10 +46,59 @@ impl HuggingFaceProvider {
if RealmRef::from(url) == *HUGGING_FACE_REALM {
if let Some(token) = HUGGING_FACE_TOKEN.as_ref() {
return Some(Credentials::Bearer {
token: token.clone(),
token: Token::new(token.clone()),
});
}
}
None
}
}
/// The [`Url`] for the S3 endpoint, if set.
static S3_ENDPOINT_REALM: LazyLock<Option<Realm>> = LazyLock::new(|| {
let s3_endpoint_url = std::env::var(EnvVars::UV_S3_ENDPOINT_URL).ok()?;
let url = Url::parse(&s3_endpoint_url).expect("Failed to parse S3 endpoint URL");
Some(Realm::from(&url))
});
/// A provider for authentication credentials for S3 endpoints.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct S3EndpointProvider;
impl S3EndpointProvider {
/// 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!(
"The `s3-endpoint` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::S3_ENDPOINT
);
}
// 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) {
return true;
}
}
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)
}
}

737
crates/uv-auth/src/pyx.rs Normal file
View File

@ -0,0 +1,737 @@
use std::io;
use std::path::{Path, PathBuf};
use std::time::Duration;
use base64::Engine;
use base64::prelude::BASE64_URL_SAFE_NO_PAD;
use etcetera::BaseStrategy;
use reqwest_middleware::ClientWithMiddleware;
use tracing::debug;
use url::Url;
use uv_cache_key::CanonicalUrl;
use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlError};
use uv_small_str::SmallString;
use uv_state::{StateBucket, StateStore};
use uv_static::EnvVars;
use crate::credentials::Token;
use crate::{AccessToken, Credentials, Realm};
/// Retrieve the pyx API key from the environment variable, or return `None`.
fn read_pyx_api_key() -> Option<String> {
std::env::var(EnvVars::PYX_API_KEY)
.ok()
.or_else(|| std::env::var(EnvVars::UV_API_KEY).ok())
}
/// Retrieve the pyx authentication token (JWT) from the environment variable, or return `None`.
fn read_pyx_auth_token() -> Option<AccessToken> {
std::env::var(EnvVars::PYX_AUTH_TOKEN)
.ok()
.or_else(|| std::env::var(EnvVars::UV_AUTH_TOKEN).ok())
.map(AccessToken::from)
}
/// An access token with an accompanying refresh token.
///
/// Refresh tokens are single-use tokens that can be exchanged for a renewed access token
/// and a new refresh token.
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct PyxOAuthTokens {
pub access_token: AccessToken,
pub refresh_token: String,
}
/// An access token with an accompanying API key.
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct PyxApiKeyTokens {
pub access_token: AccessToken,
pub api_key: String,
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub enum PyxTokens {
/// An access token with an accompanying refresh token.
///
/// Refresh tokens are single-use tokens that can be exchanged for a renewed access token
/// and a new refresh token.
OAuth(PyxOAuthTokens),
/// An access token with an accompanying API key.
///
/// API keys are long-lived tokens that can be exchanged for an access token.
ApiKey(PyxApiKeyTokens),
}
impl From<PyxTokens> for AccessToken {
fn from(tokens: PyxTokens) -> Self {
match tokens {
PyxTokens::OAuth(PyxOAuthTokens { access_token, .. }) => access_token,
PyxTokens::ApiKey(PyxApiKeyTokens { access_token, .. }) => access_token,
}
}
}
impl From<PyxTokens> for Credentials {
fn from(tokens: PyxTokens) -> Self {
let access_token = match tokens {
PyxTokens::OAuth(PyxOAuthTokens { access_token, .. }) => access_token,
PyxTokens::ApiKey(PyxApiKeyTokens { access_token, .. }) => access_token,
};
Self::from(access_token)
}
}
impl From<AccessToken> for Credentials {
fn from(access_token: AccessToken) -> Self {
Self::Bearer {
token: Token::new(access_token.into_bytes()),
}
}
}
/// The default tolerance for the access token expiration.
pub const DEFAULT_TOLERANCE_SECS: u64 = 60 * 5;
#[derive(Debug, Clone)]
struct PyxDirectories {
/// The root directory for the token store (e.g., `/Users/ferris/.local/share/pyx/credentials`).
root: PathBuf,
/// The subdirectory for the token store (e.g., `/Users/ferris/.local/share/uv/credentials/3859a629b26fda96`).
subdirectory: PathBuf,
}
impl PyxDirectories {
/// Detect the [`PyxDirectories`] for a given API URL.
fn from_api(api: &DisplaySafeUrl) -> Result<Self, io::Error> {
// Store credentials in a subdirectory based on the API URL.
let digest = uv_cache_key::cache_digest(&CanonicalUrl::new(api));
// If the user explicitly set `PYX_CREDENTIALS_DIR`, use that.
if let Some(root) = std::env::var_os(EnvVars::PYX_CREDENTIALS_DIR) {
let root = std::path::absolute(root)?;
let subdirectory = root.join(&digest);
return Ok(Self { root, subdirectory });
}
// If the user has pyx credentials in their uv credentials directory, read them for
// backwards compatibility.
let root = if let Some(tool_dir) = std::env::var_os(EnvVars::UV_CREDENTIALS_DIR) {
std::path::absolute(tool_dir)?
} else {
StateStore::from_settings(None)?.bucket(StateBucket::Credentials)
};
let subdirectory = root.join(&digest);
if subdirectory.exists() {
return Ok(Self { root, subdirectory });
}
// Otherwise, use (e.g.) `~/.local/share/pyx`.
let Ok(xdg) = etcetera::base_strategy::choose_base_strategy() else {
return Err(io::Error::new(
io::ErrorKind::NotFound,
"Could not determine user data directory",
));
};
let root = xdg.data_dir().join("pyx").join("credentials");
let subdirectory = root.join(&digest);
Ok(Self { root, subdirectory })
}
}
#[derive(Debug, Clone)]
pub struct PyxTokenStore {
/// The root directory for the token store (e.g., `/Users/ferris/.local/share/pyx/credentials`).
root: PathBuf,
/// The subdirectory for the token store (e.g., `/Users/ferris/.local/share/uv/credentials/3859a629b26fda96`).
subdirectory: PathBuf,
/// The API URL for the token store (e.g., `https://api.pyx.dev`).
api: DisplaySafeUrl,
/// The CDN domain for the token store (e.g., `astralhosted.com`).
cdn: SmallString,
}
impl PyxTokenStore {
/// Create a new [`PyxTokenStore`] from settings.
pub fn from_settings() -> Result<Self, TokenStoreError> {
// Read the API URL and CDN domain from the environment variables, or fallback to the
// defaults.
let api = if let Ok(api_url) = std::env::var(EnvVars::PYX_API_URL) {
DisplaySafeUrl::parse(&api_url)
} else {
DisplaySafeUrl::parse("https://api.pyx.dev")
}?;
let cdn = std::env::var(EnvVars::PYX_CDN_DOMAIN)
.ok()
.map(SmallString::from)
.unwrap_or_else(|| SmallString::from(arcstr::literal!("astralhosted.com")));
// Determine the root directory for the token store.
let PyxDirectories { root, subdirectory } = PyxDirectories::from_api(&api)?;
Ok(Self {
root,
subdirectory,
api,
cdn,
})
}
/// Return the root directory for the token store.
pub fn root(&self) -> &Path {
&self.root
}
/// Return the API URL for the token store.
pub fn api(&self) -> &DisplaySafeUrl {
&self.api
}
/// Get or initialize an [`AccessToken`] from the store.
///
/// If an access token is set in the environment, it will be returned as-is.
///
/// If an access token is present on-disk, it will be returned (and refreshed, if necessary).
///
/// If no access token is found, but an API key is present, the API key will be used to
/// bootstrap an access token.
pub async fn access_token(
&self,
client: &ClientWithMiddleware,
tolerance_secs: u64,
) -> Result<Option<AccessToken>, TokenStoreError> {
// If the access token is already set in the environment, return it.
if let Some(access_token) = read_pyx_auth_token() {
return Ok(Some(access_token));
}
// Initialize the tokens from the store.
let tokens = self.init(client, tolerance_secs).await?;
// Extract the access token from the OAuth tokens or API key.
Ok(tokens.map(AccessToken::from))
}
/// Initialize the [`PyxTokens`] from the store.
///
/// If an access token is already present, it will be returned (and refreshed, if necessary).
///
/// If no access token is found, but an API key is present, the API key will be used to
/// bootstrap an access token.
pub async fn init(
&self,
client: &ClientWithMiddleware,
tolerance_secs: u64,
) -> Result<Option<PyxTokens>, TokenStoreError> {
match self.read().await? {
Some(tokens) => {
// Refresh the tokens if they are expired.
let tokens = self.refresh(tokens, client, tolerance_secs).await?;
Ok(Some(tokens))
}
None => {
// If no tokens are present, bootstrap them from an API key.
self.bootstrap(client).await
}
}
}
/// Write the tokens to the store.
pub async fn write(&self, tokens: &PyxTokens) -> Result<(), TokenStoreError> {
fs_err::tokio::create_dir_all(&self.subdirectory).await?;
match tokens {
PyxTokens::OAuth(tokens) => {
// Write OAuth tokens to a generic `tokens.json` file.
fs_err::tokio::write(
self.subdirectory.join("tokens.json"),
serde_json::to_vec(tokens)?,
)
.await?;
}
PyxTokens::ApiKey(tokens) => {
// Write API key tokens to a file based on the API key.
let digest = uv_cache_key::cache_digest(&tokens.api_key);
fs_err::tokio::write(
self.subdirectory.join(format!("{digest}.json")),
&tokens.access_token,
)
.await?;
}
}
Ok(())
}
/// Returns `true` if the user appears to have an authentication token set.
pub fn has_auth_token(&self) -> bool {
read_pyx_auth_token().is_some()
}
/// Returns `true` if the user appears to have an API key set.
pub fn has_api_key(&self) -> bool {
read_pyx_api_key().is_some()
}
/// Returns `true` if the user appears to have OAuth tokens stored on disk.
pub fn has_oauth_tokens(&self) -> bool {
self.subdirectory.join("tokens.json").is_file()
}
/// Returns `true` if the user appears to have credentials (which may be invalid).
pub fn has_credentials(&self) -> bool {
self.has_auth_token() || self.has_api_key() || self.has_oauth_tokens()
}
/// Read the tokens from the store.
pub async fn read(&self) -> Result<Option<PyxTokens>, TokenStoreError> {
if let Some(api_key) = read_pyx_api_key() {
// Read the API key tokens from a file based on the API key.
let digest = uv_cache_key::cache_digest(&api_key);
match fs_err::tokio::read(self.subdirectory.join(format!("{digest}.json"))).await {
Ok(data) => {
let access_token =
AccessToken::from(String::from_utf8(data).expect("Invalid UTF-8"));
Ok(Some(PyxTokens::ApiKey(PyxApiKeyTokens {
access_token,
api_key,
})))
}
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err.into()),
}
} else {
match fs_err::tokio::read(self.subdirectory.join("tokens.json")).await {
Ok(data) => {
let tokens: PyxOAuthTokens = serde_json::from_slice(&data)?;
Ok(Some(PyxTokens::OAuth(tokens)))
}
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err.into()),
}
}
}
/// Remove the tokens from the store.
pub async fn delete(&self) -> Result<(), io::Error> {
fs_err::tokio::remove_dir_all(&self.subdirectory).await?;
Ok(())
}
/// Bootstrap the tokens from the store.
async fn bootstrap(
&self,
client: &ClientWithMiddleware,
) -> Result<Option<PyxTokens>, TokenStoreError> {
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
struct Payload {
access_token: AccessToken,
}
// Retrieve the API key from the environment variable, if set.
let Some(api_key) = read_pyx_api_key() else {
return Ok(None);
};
debug!("Bootstrapping access token from an API key");
// Parse the API URL.
let mut url = self.api.clone();
url.set_path("auth/cli/access-token");
let mut request = reqwest::Request::new(reqwest::Method::POST, Url::from(url));
request.headers_mut().insert(
"Authorization",
reqwest::header::HeaderValue::from_str(&format!("Bearer {api_key}"))?,
);
let response = client.execute(request).await?;
let Payload { access_token } = response.error_for_status()?.json::<Payload>().await?;
let tokens = PyxTokens::ApiKey(PyxApiKeyTokens {
access_token,
api_key,
});
// Write the tokens to disk.
self.write(&tokens).await?;
Ok(Some(tokens))
}
/// Refresh the tokens in the store, if they are expired.
///
/// In theory, we should _also_ refresh if we hit a 401; but for now, we only refresh ahead of
/// time.
async fn refresh(
&self,
tokens: PyxTokens,
client: &ClientWithMiddleware,
tolerance_secs: u64,
) -> Result<PyxTokens, TokenStoreError> {
// Decode the access token.
let jwt = PyxJwt::decode(match &tokens {
PyxTokens::OAuth(PyxOAuthTokens { access_token, .. }) => access_token,
PyxTokens::ApiKey(PyxApiKeyTokens { access_token, .. }) => access_token,
})?;
// If the access token is expired, refresh it.
let is_up_to_date = match jwt.exp {
None => {
debug!("Access token has no expiration; refreshing...");
false
}
Some(..) if tolerance_secs == 0 => {
debug!("Refreshing access token due to zero tolerance...");
false
}
Some(jwt) => {
let exp = jiff::Timestamp::from_second(jwt)?;
let now = jiff::Timestamp::now();
if exp < now {
debug!("Access token is expired (`{exp}`); refreshing...");
false
} else if exp < now + Duration::from_secs(tolerance_secs) {
debug!(
"Access token will expire within the tolerance (`{exp}`); refreshing..."
);
false
} else {
debug!("Access token is up-to-date (`{exp}`)");
true
}
}
};
if is_up_to_date {
return Ok(tokens);
}
let tokens = match tokens {
PyxTokens::OAuth(PyxOAuthTokens { refresh_token, .. }) => {
// Parse the API URL.
let mut url = self.api.clone();
url.set_path("auth/cli/refresh");
let mut request = reqwest::Request::new(reqwest::Method::POST, Url::from(url));
let body = serde_json::json!({
"refresh_token": refresh_token
});
*request.body_mut() = Some(body.to_string().into());
let response = client.execute(request).await?;
let tokens = response
.error_for_status()?
.json::<PyxOAuthTokens>()
.await?;
PyxTokens::OAuth(tokens)
}
PyxTokens::ApiKey(PyxApiKeyTokens { api_key, .. }) => {
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
struct Payload {
access_token: AccessToken,
}
// Parse the API URL.
let mut url = self.api.clone();
url.set_path("auth/cli/access-token");
let mut request = reqwest::Request::new(reqwest::Method::POST, Url::from(url));
request.headers_mut().insert(
"Authorization",
reqwest::header::HeaderValue::from_str(&format!("Bearer {api_key}"))?,
);
let response = client.execute(request).await?;
let Payload { access_token } =
response.error_for_status()?.json::<Payload>().await?;
PyxTokens::ApiKey(PyxApiKeyTokens {
access_token,
api_key,
})
}
};
// Write the new tokens to disk.
self.write(&tokens).await?;
Ok(tokens)
}
/// Returns `true` if the given URL is "known" to this token store (i.e., should be
/// authenticated using the store's tokens).
pub fn is_known_url(&self, url: &Url) -> bool {
is_known_url(url, &self.api, &self.cdn)
}
/// Returns `true` if the URL is on a "known" domain (i.e., the same domain as the API or CDN).
///
/// Like [`is_known_url`](Self::is_known_url), but also returns `true` if the API is on the
/// subdomain of the URL (e.g., if the API is `api.pyx.dev` and the URL is `pyx.dev`).
pub fn is_known_domain(&self, url: &Url) -> bool {
is_known_domain(url, &self.api, &self.cdn)
}
}
#[derive(thiserror::Error, Debug)]
pub enum TokenStoreError {
#[error(transparent)]
Url(#[from] DisplaySafeUrlError),
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Serialization(#[from] serde_json::Error),
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
#[error(transparent)]
ReqwestMiddleware(#[from] reqwest_middleware::Error),
#[error(transparent)]
InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue),
#[error(transparent)]
Jiff(#[from] jiff::Error),
#[error(transparent)]
Jwt(#[from] JwtError),
}
impl TokenStoreError {
/// Returns `true` if the error is a 401 (Unauthorized) error.
pub fn is_unauthorized(&self) -> bool {
match self {
Self::Reqwest(err) => err.status() == Some(reqwest::StatusCode::UNAUTHORIZED),
Self::ReqwestMiddleware(err) => err.status() == Some(reqwest::StatusCode::UNAUTHORIZED),
_ => false,
}
}
}
/// The payload of the JWT.
#[derive(Debug, serde::Deserialize)]
pub struct PyxJwt {
/// The expiration time of the JWT, as a Unix timestamp.
pub exp: Option<i64>,
/// The issuer of the JWT.
pub iss: Option<String>,
/// The name of the organization, if any.
#[serde(rename = "urn:pyx:org_name")]
pub name: Option<String>,
}
impl PyxJwt {
/// Decode the JWT from the access token.
pub fn decode(access_token: &AccessToken) -> Result<Self, JwtError> {
let mut token_segments = access_token.as_str().splitn(3, '.');
let _header = token_segments.next().ok_or(JwtError::MissingHeader)?;
let payload = token_segments.next().ok_or(JwtError::MissingPayload)?;
let _signature = token_segments.next().ok_or(JwtError::MissingSignature)?;
if token_segments.next().is_some() {
return Err(JwtError::TooManySegments);
}
let decoded = BASE64_URL_SAFE_NO_PAD.decode(payload)?;
let jwt = serde_json::from_slice::<Self>(&decoded)?;
Ok(jwt)
}
}
#[derive(thiserror::Error, Debug)]
pub enum JwtError {
#[error("JWT is missing a header")]
MissingHeader,
#[error("JWT is missing a payload")]
MissingPayload,
#[error("JWT is missing a signature")]
MissingSignature,
#[error("JWT has too many segments")]
TooManySegments,
#[error(transparent)]
Base64(#[from] base64::DecodeError),
#[error(transparent)]
Serde(#[from] serde_json::Error),
}
fn is_known_url(url: &Url, api: &DisplaySafeUrl, cdn: &str) -> bool {
// Determine whether the URL matches the API realm.
if Realm::from(url) == Realm::from(&**api) {
return true;
}
// Determine whether the URL matches the CDN domain (or a subdomain of it).
//
// For example, if URL is on `files.astralhosted.com` and the CDN domain is
// `astralhosted.com`, consider it known.
if matches!(url.scheme(), "https") && matches_domain(url, cdn) {
return true;
}
false
}
fn is_known_domain(url: &Url, api: &DisplaySafeUrl, cdn: &str) -> bool {
// Determine whether the URL matches the API domain.
if let Some(domain) = url.domain() {
if matches_domain(api, domain) {
return true;
}
}
is_known_url(url, api, cdn)
}
/// Returns `true` if the target URL is on the given domain.
fn matches_domain(url: &Url, domain: &str) -> bool {
url.domain().is_some_and(|subdomain| {
subdomain == domain
|| subdomain
.strip_suffix(domain)
.is_some_and(|prefix| prefix.ends_with('.'))
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_known_url() {
let api_url = DisplaySafeUrl::parse("https://api.pyx.dev").unwrap();
let cdn_domain = "astralhosted.com";
// Same realm as API.
assert!(is_known_url(
&Url::parse("https://api.pyx.dev/simple/").unwrap(),
&api_url,
cdn_domain
));
// Different path on same API domain
assert!(is_known_url(
&Url::parse("https://api.pyx.dev/v1/").unwrap(),
&api_url,
cdn_domain
));
// CDN domain.
assert!(is_known_url(
&Url::parse("https://astralhosted.com/packages/").unwrap(),
&api_url,
cdn_domain
));
// CDN subdomain.
assert!(is_known_url(
&Url::parse("https://files.astralhosted.com/packages/").unwrap(),
&api_url,
cdn_domain
));
// CDN on HTTP.
assert!(!is_known_url(
&Url::parse("http://astralhosted.com/packages/").unwrap(),
&api_url,
cdn_domain
));
// Unknown domain.
assert!(!is_known_url(
&Url::parse("https://pypi.org/simple/").unwrap(),
&api_url,
cdn_domain
));
// Similar but not matching domain.
assert!(!is_known_url(
&Url::parse("https://badastralhosted.com/packages/").unwrap(),
&api_url,
cdn_domain
));
}
#[test]
fn test_is_known_domain() {
let api_url = DisplaySafeUrl::parse("https://api.pyx.dev").unwrap();
let cdn_domain = "astralhosted.com";
// Same realm as API.
assert!(is_known_domain(
&Url::parse("https://api.pyx.dev/simple/").unwrap(),
&api_url,
cdn_domain
));
// API super-domain.
assert!(is_known_domain(
&Url::parse("https://pyx.dev").unwrap(),
&api_url,
cdn_domain
));
// API subdomain.
assert!(!is_known_domain(
&Url::parse("https://foo.api.pyx.dev").unwrap(),
&api_url,
cdn_domain
));
// Different subdomain.
assert!(!is_known_domain(
&Url::parse("https://beta.pyx.dev/").unwrap(),
&api_url,
cdn_domain
));
// CDN domain.
assert!(is_known_domain(
&Url::parse("https://astralhosted.com/packages/").unwrap(),
&api_url,
cdn_domain
));
// CDN subdomain.
assert!(is_known_domain(
&Url::parse("https://files.astralhosted.com/packages/").unwrap(),
&api_url,
cdn_domain
));
// Unknown domain.
assert!(!is_known_domain(
&Url::parse("https://pypi.org/simple/").unwrap(),
&api_url,
cdn_domain
));
// Different TLD.
assert!(!is_known_domain(
&Url::parse("https://pyx.com/").unwrap(),
&api_url,
cdn_domain
));
}
#[test]
fn test_matches_domain() {
assert!(matches_domain(
&Url::parse("https://example.com").unwrap(),
"example.com"
));
assert!(matches_domain(
&Url::parse("https://foo.example.com").unwrap(),
"example.com"
));
assert!(matches_domain(
&Url::parse("https://bar.foo.example.com").unwrap(),
"example.com"
));
assert!(!matches_domain(
&Url::parse("https://example.com").unwrap(),
"other.com"
));
assert!(!matches_domain(
&Url::parse("https://example.org").unwrap(),
"example.com"
));
assert!(!matches_domain(
&Url::parse("https://badexample.com").unwrap(),
"example.com"
));
}
}

View File

@ -1,6 +1,7 @@
use std::hash::{Hash, Hasher};
use std::{fmt::Display, fmt::Formatter};
use url::Url;
use uv_redacted::DisplaySafeUrl;
use uv_small_str::SmallString;
/// Used to determine if authentication information should be retained on a new URL.
@ -23,12 +24,18 @@ use uv_small_str::SmallString;
// However, `url` (and therefore `reqwest`) sets the `port` to `None` if it matches the default port
// so we do not need any special handling here.
#[derive(Debug, Clone)]
pub(crate) struct Realm {
pub struct Realm {
scheme: SmallString,
host: Option<SmallString>,
port: Option<u16>,
}
impl From<&DisplaySafeUrl> for Realm {
fn from(url: &DisplaySafeUrl) -> Self {
Self::from(&**url)
}
}
impl From<&Url> for Realm {
fn from(url: &Url) -> Self {
Self {
@ -75,12 +82,27 @@ impl Hash for Realm {
/// A reference to a [`Realm`] that can be used for zero-allocation comparisons.
#[derive(Debug, Copy, Clone)]
pub(crate) struct RealmRef<'a> {
pub struct RealmRef<'a> {
scheme: &'a str,
host: Option<&'a str>,
port: Option<u16>,
}
impl RealmRef<'_> {
/// Returns true if this realm is a subdomain of the other realm.
pub(crate) fn is_subdomain_of(&self, other: Self) -> bool {
other.scheme == self.scheme
&& other.port == self.port
&& other.host.is_some_and(|other_host| {
self.host.is_some_and(|self_host| {
self_host
.strip_suffix(other_host)
.is_some_and(|prefix| prefix.ends_with('.'))
})
})
}
}
impl<'a> From<&'a Url> for RealmRef<'a> {
fn from(url: &'a Url) -> Self {
Self {
@ -215,4 +237,87 @@ mod tests {
Ok(())
}
#[test]
fn test_is_subdomain_of() -> Result<(), ParseError> {
use crate::realm::RealmRef;
// Subdomain relationship: sub.example.com is a subdomain of example.com
let subdomain_url = Url::parse("https://sub.example.com")?;
let domain_url = Url::parse("https://example.com")?;
let subdomain = RealmRef::from(&subdomain_url);
let domain = RealmRef::from(&domain_url);
assert!(subdomain.is_subdomain_of(domain));
// Deeper subdomain: foo.bar.example.com is a subdomain of example.com
let deep_subdomain_url = Url::parse("https://foo.bar.example.com")?;
let deep_subdomain = RealmRef::from(&deep_subdomain_url);
assert!(deep_subdomain.is_subdomain_of(domain));
// Deeper subdomain: foo.bar.example.com is also a subdomain of bar.example.com
let parent_subdomain_url = Url::parse("https://bar.example.com")?;
let parent_subdomain = RealmRef::from(&parent_subdomain_url);
assert!(deep_subdomain.is_subdomain_of(parent_subdomain));
// Not a subdomain: example.com is not a subdomain of sub.example.com
assert!(!domain.is_subdomain_of(subdomain));
// Same domain is not a subdomain of itself
assert!(!domain.is_subdomain_of(domain));
// Different TLD: example.org is not a subdomain of example.com
let different_tld_url = Url::parse("https://example.org")?;
let different_tld = RealmRef::from(&different_tld_url);
assert!(!different_tld.is_subdomain_of(domain));
// Partial match but not a subdomain: notexample.com is not a subdomain of example.com
let partial_match_url = Url::parse("https://notexample.com")?;
let partial_match = RealmRef::from(&partial_match_url);
assert!(!partial_match.is_subdomain_of(domain));
// Different scheme: http subdomain is not a subdomain of https domain
let http_subdomain_url = Url::parse("http://sub.example.com")?;
let https_domain_url = Url::parse("https://example.com")?;
let http_subdomain = RealmRef::from(&http_subdomain_url);
let https_domain = RealmRef::from(&https_domain_url);
assert!(!http_subdomain.is_subdomain_of(https_domain));
// Different port: same subdomain with different port is not a subdomain
let subdomain_port_8080_url = Url::parse("https://sub.example.com:8080")?;
let domain_port_9090_url = Url::parse("https://example.com:9090")?;
let subdomain_port_8080 = RealmRef::from(&subdomain_port_8080_url);
let domain_port_9090 = RealmRef::from(&domain_port_9090_url);
assert!(!subdomain_port_8080.is_subdomain_of(domain_port_9090));
// Same port: subdomain with same explicit port is a subdomain
let subdomain_with_port_url = Url::parse("https://sub.example.com:8080")?;
let domain_with_port_url = Url::parse("https://example.com:8080")?;
let subdomain_with_port = RealmRef::from(&subdomain_with_port_url);
let domain_with_port = RealmRef::from(&domain_with_port_url);
assert!(subdomain_with_port.is_subdomain_of(domain_with_port));
// Default port handling: subdomain with implicit port is a subdomain
let subdomain_default_url = Url::parse("https://sub.example.com")?;
let domain_explicit_443_url = Url::parse("https://example.com:443")?;
let subdomain_default = RealmRef::from(&subdomain_default_url);
let domain_explicit_443 = RealmRef::from(&domain_explicit_443_url);
assert!(subdomain_default.is_subdomain_of(domain_explicit_443));
// Edge case: empty host (shouldn't happen with valid URLs but testing defensive code)
let file_url = Url::parse("file:///path/to/file")?;
let https_url = Url::parse("https://example.com")?;
let file_realm = RealmRef::from(&file_url);
let https_realm = RealmRef::from(&https_url);
assert!(!file_realm.is_subdomain_of(https_realm));
assert!(!https_realm.is_subdomain_of(file_realm));
// Subdomain with path (path should be ignored)
let subdomain_with_path_url = Url::parse("https://sub.example.com/path")?;
let domain_with_path_url = Url::parse("https://example.com/other")?;
let subdomain_with_path = RealmRef::from(&subdomain_with_path_url);
let domain_with_path = RealmRef::from(&domain_with_path_url);
assert!(subdomain_with_path.is_subdomain_of(domain_with_path));
Ok(())
}
}

View File

@ -0,0 +1,95 @@
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use thiserror::Error;
use url::Url;
use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlError};
#[derive(Error, Debug)]
pub enum ServiceParseError {
#[error(transparent)]
InvalidUrl(#[from] DisplaySafeUrlError),
#[error("Unsupported scheme: {0}")]
UnsupportedScheme(String),
#[error("HTTPS is required for non-local hosts")]
HttpsRequired,
}
/// A service URL that wraps [`DisplaySafeUrl`] for CLI usage.
///
/// This type provides automatic URL parsing and validation when used as a CLI argument,
/// eliminating the need for manual parsing in command functions.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct Service(DisplaySafeUrl);
impl Service {
/// Get the underlying [`DisplaySafeUrl`].
pub fn url(&self) -> &DisplaySafeUrl {
&self.0
}
/// Convert into the underlying [`DisplaySafeUrl`].
pub fn into_url(self) -> DisplaySafeUrl {
self.0
}
/// Validate that the URL scheme is supported.
fn check_scheme(url: &Url) -> Result<(), ServiceParseError> {
match url.scheme() {
"https" => Ok(()),
"http" if matches!(url.host_str(), Some("localhost" | "127.0.0.1")) => Ok(()),
"http" => Err(ServiceParseError::HttpsRequired),
value => Err(ServiceParseError::UnsupportedScheme(value.to_string())),
}
}
}
impl FromStr for Service {
type Err = ServiceParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// First try parsing as-is
let url = match DisplaySafeUrl::parse(s) {
Ok(url) => url,
Err(DisplaySafeUrlError::Url(url::ParseError::RelativeUrlWithoutBase)) => {
// If it's a relative URL, try prepending https://
let with_https = format!("https://{s}");
DisplaySafeUrl::parse(&with_https)?
}
Err(err) => return Err(err.into()),
};
Self::check_scheme(&url)?;
Ok(Self(url))
}
}
impl std::fmt::Display for Service {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl TryFrom<String> for Service {
type Error = ServiceParseError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::from_str(&value)
}
}
impl From<Service> for String {
fn from(service: Service) -> Self {
service.to_string()
}
}
impl TryFrom<DisplaySafeUrl> for Service {
type Error = ServiceParseError;
fn try_from(value: DisplaySafeUrl) -> Result<Self, Self::Error> {
Self::check_scheme(&value)?;
Ok(Self(value))
}
}

688
crates/uv-auth/src/store.rs Normal file
View File

@ -0,0 +1,688 @@
use std::ops::Deref;
use std::path::{Path, PathBuf};
use fs_err as fs;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use uv_fs::{LockedFile, LockedFileError, LockedFileMode, with_added_extension};
use uv_preview::{Preview, PreviewFeatures};
use uv_redacted::DisplaySafeUrl;
use uv_state::{StateBucket, StateStore};
use uv_static::EnvVars;
use crate::credentials::{Password, Token, Username};
use crate::realm::Realm;
use crate::service::Service;
use crate::{Credentials, KeyringProvider};
/// The storage backend to use in `uv auth` commands.
#[derive(Debug)]
pub enum AuthBackend {
// TODO(zanieb): Right now, we're using a keyring provider for the system store but that's just
// where the native implementation is living at the moment. We should consider refactoring these
// into a shared API in the future.
System(KeyringProvider),
TextStore(TextCredentialStore, LockedFile),
}
impl AuthBackend {
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()));
}
// Otherwise, we'll use the plaintext credential store
let path = TextCredentialStore::default_file()?;
match TextCredentialStore::read(&path).await {
Ok((store, lock)) => Ok(Self::TextStore(store, lock)),
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).await?,
))
}
Err(err) => Err(err),
}
}
}
/// Authentication scheme to use.
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuthScheme {
/// HTTP Basic Authentication
///
/// Uses a username and password.
#[default]
Basic,
/// Bearer token authentication.
///
/// Uses a token provided as `Bearer <token>` in the `Authorization` header.
Bearer,
}
/// Errors that can occur when working with TOML credential storage.
#[derive(Debug, Error)]
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")]
SerializeError(#[from] toml::ser::Error),
#[error(transparent)]
BasicAuthError(#[from] BasicAuthError),
#[error(transparent)]
BearerAuthError(#[from] BearerAuthError),
#[error("Failed to determine credentials directory")]
CredentialsDirError,
#[error("Token is not valid unicode")]
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`")]
MissingUsername,
#[error("`token` cannot be provided with `scheme = basic`")]
UnexpectedToken,
}
#[derive(Debug, Error)]
pub enum BearerAuthError {
#[error("`token` is required with `scheme = bearer`")]
MissingToken,
#[error("`username` cannot be provided with `scheme = bearer`")]
UnexpectedUsername,
#[error("`password` cannot be provided with `scheme = bearer`")]
UnexpectedPassword,
}
/// A single credential entry in a TOML credentials file.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "TomlCredentialWire", into = "TomlCredentialWire")]
struct TomlCredential {
/// The service URL for this credential.
service: Service,
/// The credentials for this entry.
credentials: Credentials,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TomlCredentialWire {
/// The service URL for this credential.
service: Service,
/// The username to use. Only allowed with [`AuthScheme::Basic`].
username: Username,
/// The authentication scheme.
#[serde(default)]
scheme: AuthScheme,
/// The password to use. Only allowed with [`AuthScheme::Basic`].
password: Option<Password>,
/// The token to use. Only allowed with [`AuthScheme::Bearer`].
token: Option<String>,
}
impl From<TomlCredential> for TomlCredentialWire {
fn from(value: TomlCredential) -> Self {
match value.credentials {
Credentials::Basic { username, password } => Self {
service: value.service,
username,
scheme: AuthScheme::Basic,
password,
token: None,
},
Credentials::Bearer { token } => Self {
service: value.service,
username: Username::new(None),
scheme: AuthScheme::Bearer,
password: None,
token: Some(String::from_utf8(token.into_bytes()).expect("Token is valid UTF-8")),
},
}
}
}
impl TryFrom<TomlCredentialWire> for TomlCredential {
type Error = TomlCredentialError;
fn try_from(value: TomlCredentialWire) -> Result<Self, Self::Error> {
match value.scheme {
AuthScheme::Basic => {
if value.username.as_deref().is_none() {
return Err(TomlCredentialError::BasicAuthError(
BasicAuthError::MissingUsername,
));
}
if value.token.is_some() {
return Err(TomlCredentialError::BasicAuthError(
BasicAuthError::UnexpectedToken,
));
}
let credentials = Credentials::Basic {
username: value.username,
password: value.password,
};
Ok(Self {
service: value.service,
credentials,
})
}
AuthScheme::Bearer => {
if value.username.is_some() {
return Err(TomlCredentialError::BearerAuthError(
BearerAuthError::UnexpectedUsername,
));
}
if value.password.is_some() {
return Err(TomlCredentialError::BearerAuthError(
BearerAuthError::UnexpectedPassword,
));
}
if value.token.is_none() {
return Err(TomlCredentialError::BearerAuthError(
BearerAuthError::MissingToken,
));
}
let credentials = Credentials::Bearer {
token: Token::new(value.token.unwrap().into_bytes()),
};
Ok(Self {
service: value.service,
credentials,
})
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
struct TomlCredentials {
/// Array of credential entries.
#[serde(rename = "credential")]
credentials: Vec<TomlCredential>,
}
/// A credential store with a plain text storage backend.
#[derive(Debug, Default)]
pub struct TextCredentialStore {
credentials: FxHashMap<(Service, Username), Credentials>,
}
impl TextCredentialStore {
/// Return the directory for storing credentials.
pub fn directory_path() -> Result<PathBuf, TomlCredentialError> {
if let Some(dir) = std::env::var_os(EnvVars::UV_CREDENTIALS_DIR)
.filter(|s| !s.is_empty())
.map(PathBuf::from)
{
return Ok(dir);
}
Ok(StateStore::from_settings(None)?.bucket(StateBucket::Credentials))
}
/// Return the standard file path for storing credentials.
pub fn default_file() -> Result<PathBuf, TomlCredentialError> {
let dir = Self::directory_path()?;
Ok(dir.join("credentials.toml"))
}
/// Acquire a lock on the credentials file at the given path.
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(lock, LockedFileMode::Exclusive, "credentials store").await?)
}
/// Read credentials from a file.
fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, TomlCredentialError> {
let content = fs::read_to_string(path)?;
let credentials: TomlCredentials = toml::from_str(&content)?;
let credentials: FxHashMap<(Service, Username), Credentials> = credentials
.credentials
.into_iter()
.map(|credential| {
let username = match &credential.credentials {
Credentials::Basic { username, .. } => username.clone(),
Credentials::Bearer { .. } => Username::none(),
};
(
(credential.service.clone(), username),
credential.credentials,
)
})
.collect();
Ok(Self { credentials })
}
/// Read credentials from a file.
///
/// 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 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))
}
/// Persist credentials to a file.
///
/// Requires a [`LockedFile`] from [`TextCredentialStore::lock`] or
/// [`TextCredentialStore::read`] to ensure exclusive access.
pub fn write<P: AsRef<Path>>(
self,
path: P,
_lock: LockedFile,
) -> Result<(), TomlCredentialError> {
let credentials = self
.credentials
.into_iter()
.map(|((service, _username), credentials)| TomlCredential {
service,
credentials,
})
.collect::<Vec<_>>();
let toml_creds = TomlCredentials { credentials };
let content = toml::to_string_pretty(&toml_creds)?;
fs::create_dir_all(
path.as_ref()
.parent()
.ok_or(TomlCredentialError::CredentialsDirError)?,
)?;
// TODO(zanieb): We should use an atomic write here
fs::write(path, content)?;
Ok(())
}
/// Get credentials for a given URL and username.
///
/// The most specific URL prefix match in the same [`Realm`] is returned, if any.
pub fn get_credentials(
&self,
url: &DisplaySafeUrl,
username: Option<&str>,
) -> Option<&Credentials> {
let request_realm = Realm::from(url);
// Perform an exact lookup first
// TODO(zanieb): Consider adding `DisplaySafeUrlRef` so we can avoid this clone
// TODO(zanieb): We could also return early here if we can't normalize to a `Service`
if let Ok(url_service) = Service::try_from(url.clone()) {
if let Some(credential) = self.credentials.get(&(
url_service.clone(),
Username::from(username.map(str::to_string)),
)) {
return Some(credential);
}
}
// If that fails, iterate through to find a prefix match
let mut best: Option<(usize, &Service, &Credentials)> = None;
for ((service, stored_username), credential) in &self.credentials {
let service_realm = Realm::from(service.url().deref());
// Only consider services in the same realm
if service_realm != request_realm {
continue;
}
// Service path must be a prefix of request path
if !url.path().starts_with(service.url().path()) {
continue;
}
// If a username is provided, it must match
if let Some(request_username) = username {
if Some(request_username) != stored_username.as_deref() {
continue;
}
}
// Update our best matching credential based on prefix length
let specificity = service.url().path().len();
if best.is_none_or(|(best_specificity, _, _)| specificity > best_specificity) {
best = Some((specificity, service, credential));
}
}
// Return the most specific match
if let Some((_, _, credential)) = best {
return Some(credential);
}
None
}
/// Store credentials for a given service.
pub fn insert(&mut self, service: Service, credentials: Credentials) -> Option<Credentials> {
let username = match &credentials {
Credentials::Basic { username, .. } => username.clone(),
Credentials::Bearer { .. } => Username::none(),
};
self.credentials.insert((service, username), credentials)
}
/// Remove credentials for a given service.
pub fn remove(&mut self, service: &Service, username: Username) -> Option<Credentials> {
// Remove the specific credential for this service and username
self.credentials.remove(&(service.clone(), username))
}
}
#[cfg(test)]
mod tests {
use std::io::Write;
use std::str::FromStr;
use tempfile::NamedTempFile;
use super::*;
#[test]
fn test_toml_serialization() {
let credentials = TomlCredentials {
credentials: vec![
TomlCredential {
service: Service::from_str("https://example.com").unwrap(),
credentials: Credentials::Basic {
username: Username::new(Some("user1".to_string())),
password: Some(Password::new("pass1".to_string())),
},
},
TomlCredential {
service: Service::from_str("https://test.org").unwrap(),
credentials: Credentials::Basic {
username: Username::new(Some("user2".to_string())),
password: Some(Password::new("pass2".to_string())),
},
},
],
};
let toml_str = toml::to_string_pretty(&credentials).unwrap();
let parsed: TomlCredentials = toml::from_str(&toml_str).unwrap();
assert_eq!(parsed.credentials.len(), 2);
assert_eq!(
parsed.credentials[0].service.to_string(),
"https://example.com/"
);
assert_eq!(
parsed.credentials[1].service.to_string(),
"https://test.org/"
);
}
#[test]
fn test_credential_store_operations() {
let mut store = TextCredentialStore::default();
let credentials = Credentials::basic(Some("user".to_string()), Some("pass".to_string()));
let service = Service::from_str("https://example.com").unwrap();
store.insert(service.clone(), credentials.clone());
let url = DisplaySafeUrl::parse("https://example.com/").unwrap();
assert!(store.get_credentials(&url, None).is_some());
let url = DisplaySafeUrl::parse("https://example.com/path").unwrap();
let retrieved = store.get_credentials(&url, None).unwrap();
assert_eq!(retrieved.username(), Some("user"));
assert_eq!(retrieved.password(), Some("pass"));
assert!(
store
.remove(&service, Username::from(Some("user".to_string())))
.is_some()
);
let url = DisplaySafeUrl::parse("https://example.com/").unwrap();
assert!(store.get_credentials(&url, None).is_none());
}
#[tokio::test]
async fn test_file_operations() {
let mut temp_file = NamedTempFile::new().unwrap();
writeln!(
temp_file,
r#"
[[credential]]
service = "https://example.com"
username = "testuser"
scheme = "basic"
password = "testpass"
[[credential]]
service = "https://test.org"
username = "user2"
password = "pass2"
"#
)
.unwrap();
let store = TextCredentialStore::from_file(temp_file.path()).unwrap();
let url = DisplaySafeUrl::parse("https://example.com/").unwrap();
assert!(store.get_credentials(&url, None).is_some());
let url = DisplaySafeUrl::parse("https://test.org/").unwrap();
assert!(store.get_credentials(&url, None).is_some());
let url = DisplaySafeUrl::parse("https://example.com").unwrap();
let cred = store.get_credentials(&url, None).unwrap();
assert_eq!(cred.username(), Some("testuser"));
assert_eq!(cred.password(), Some("testpass"));
// Test saving
let temp_output = NamedTempFile::new().unwrap();
store
.write(
temp_output.path(),
TextCredentialStore::lock(temp_file.path()).await.unwrap(),
)
.unwrap();
let content = fs::read_to_string(temp_output.path()).unwrap();
assert!(content.contains("example.com"));
assert!(content.contains("testuser"));
}
#[test]
fn test_prefix_matching() {
let mut store = TextCredentialStore::default();
let credentials = Credentials::basic(Some("user".to_string()), Some("pass".to_string()));
// Store credentials for a specific path prefix
let service = Service::from_str("https://example.com/api").unwrap();
store.insert(service.clone(), credentials.clone());
// Should match URLs that are prefixes of the stored service
let matching_urls = [
"https://example.com/api",
"https://example.com/api/v1",
"https://example.com/api/v1/users",
];
for url_str in matching_urls {
let url = DisplaySafeUrl::parse(url_str).unwrap();
let cred = store.get_credentials(&url, None);
assert!(cred.is_some(), "Failed to match URL with prefix: {url_str}");
}
// Should NOT match URLs that are not prefixes
let non_matching_urls = [
"https://example.com/different",
"https://example.com/ap", // Not a complete path segment match
"https://example.com", // Shorter than the stored prefix
];
for url_str in non_matching_urls {
let url = DisplaySafeUrl::parse(url_str).unwrap();
let cred = store.get_credentials(&url, None);
assert!(cred.is_none(), "Should not match non-prefix URL: {url_str}");
}
}
#[test]
fn test_realm_based_matching() {
let mut store = TextCredentialStore::default();
let credentials = Credentials::basic(Some("user".to_string()), Some("pass".to_string()));
// Store by full URL (realm)
let service = Service::from_str("https://example.com").unwrap();
store.insert(service.clone(), credentials.clone());
// Should match URLs in the same realm
let matching_urls = [
"https://example.com",
"https://example.com/path",
"https://example.com/different/path",
"https://example.com:443/path", // Default HTTPS port
];
for url_str in matching_urls {
let url = DisplaySafeUrl::parse(url_str).unwrap();
let cred = store.get_credentials(&url, None);
assert!(
cred.is_some(),
"Failed to match URL in same realm: {url_str}"
);
}
// Should NOT match URLs in different realms
let non_matching_urls = [
"http://example.com", // Different scheme
"https://different.com", // Different host
"https://example.com:8080", // Different port
];
for url_str in non_matching_urls {
let url = DisplaySafeUrl::parse(url_str).unwrap();
let cred = store.get_credentials(&url, None);
assert!(
cred.is_none(),
"Should not match URL in different realm: {url_str}"
);
}
}
#[test]
fn test_most_specific_prefix_matching() {
let mut store = TextCredentialStore::default();
let general_cred =
Credentials::basic(Some("general".to_string()), Some("pass1".to_string()));
let specific_cred =
Credentials::basic(Some("specific".to_string()), Some("pass2".to_string()));
// Store credentials with different prefix lengths
let general_service = Service::from_str("https://example.com/api").unwrap();
let specific_service = Service::from_str("https://example.com/api/v1").unwrap();
store.insert(general_service.clone(), general_cred);
store.insert(specific_service.clone(), specific_cred);
// Should match the most specific prefix
let url = DisplaySafeUrl::parse("https://example.com/api/v1/users").unwrap();
let cred = store.get_credentials(&url, None).unwrap();
assert_eq!(cred.username(), Some("specific"));
// Should match the general prefix for non-specific paths
let url = DisplaySafeUrl::parse("https://example.com/api/v2").unwrap();
let cred = store.get_credentials(&url, None).unwrap();
assert_eq!(cred.username(), Some("general"));
}
#[test]
fn test_username_exact_url_match() {
let mut store = TextCredentialStore::default();
let url = DisplaySafeUrl::parse("https://example.com").unwrap();
let service = Service::from_str("https://example.com").unwrap();
let user1_creds = Credentials::basic(Some("user1".to_string()), Some("pass1".to_string()));
store.insert(service.clone(), user1_creds.clone());
// Should return credentials when username matches
let result = store.get_credentials(&url, Some("user1"));
assert!(result.is_some());
assert_eq!(result.unwrap().username(), Some("user1"));
assert_eq!(result.unwrap().password(), Some("pass1"));
// Should not return credentials when username doesn't match
let result = store.get_credentials(&url, Some("user2"));
assert!(result.is_none());
// Should return credentials when no username is specified
let result = store.get_credentials(&url, None);
assert!(result.is_some());
assert_eq!(result.unwrap().username(), Some("user1"));
}
#[test]
fn test_username_prefix_url_match() {
let mut store = TextCredentialStore::default();
// Add credentials with different usernames for overlapping URL prefixes
let general_service = Service::from_str("https://example.com/api").unwrap();
let specific_service = Service::from_str("https://example.com/api/v1").unwrap();
let general_creds = Credentials::basic(
Some("general_user".to_string()),
Some("general_pass".to_string()),
);
let specific_creds = Credentials::basic(
Some("specific_user".to_string()),
Some("specific_pass".to_string()),
);
store.insert(general_service, general_creds);
store.insert(specific_service, specific_creds);
let url = DisplaySafeUrl::parse("https://example.com/api/v1/users").unwrap();
// Should match specific credentials when username matches
let result = store.get_credentials(&url, Some("specific_user"));
assert!(result.is_some());
assert_eq!(result.unwrap().username(), Some("specific_user"));
// Should match the general credentials when requesting general_user (falls back to less specific prefix)
let result = store.get_credentials(&url, Some("general_user"));
assert!(
result.is_some(),
"Should match general_user from less specific prefix"
);
assert_eq!(result.unwrap().username(), Some("general_user"));
// Should match most specific when no username specified
let result = store.get_credentials(&url, None);
assert!(result.is_some());
assert_eq!(result.unwrap().username(), Some("specific_user"));
}
}

View File

@ -1,13 +1,12 @@
[package]
name = "uv-bench"
version = "0.0.0"
description = "uv Micro-benchmarks"
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,18 +22,19 @@ 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 }
uv-platform-tags = { workspace = true }
uv-preview = { workspace = true }
uv-pypi-types = { workspace = true }
uv-python = { workspace = true }
uv-resolver = { workspace = true }
@ -42,10 +42,7 @@ uv-types = { workspace = true }
uv-workspace = { workspace = true }
anyhow = { workspace = true }
codspeed-criterion-compat = { version = "3.0.2", default-features = false, optional = true }
criterion = { version = "0.7.0", default-features = false, features = [
"async_tokio",
] }
criterion = { version = "4.0.3", default-features = false, package = "codspeed-criterion-compat", features = ["async_tokio"] }
jiff = { workspace = true }
tokio = { workspace = true }
@ -53,5 +50,4 @@ tokio = { workspace = true }
ignored = ["uv-extract"]
[features]
codspeed = ["codspeed-criterion-compat"]
static = ["uv-extract/static"]

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

@ -1,9 +1,9 @@
use std::hint::black_box;
use std::str::FromStr;
use uv_bench::criterion::{Criterion, criterion_group, criterion_main, measurement::WallTime};
use criterion::{Criterion, criterion_group, criterion_main, measurement::WallTime};
use uv_cache::Cache;
use uv_client::RegistryClientBuilder;
use uv_client::{BaseClientBuilder, RegistryClientBuilder};
use uv_distribution_types::Requirement;
use uv_python::PythonEnvironment;
use uv_resolver::Manifest;
@ -59,11 +59,14 @@ 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();
let client = RegistryClientBuilder::new(cache.clone()).build();
let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache.clone()).build();
move |universal| {
runtime
@ -85,19 +88,18 @@ mod resolver {
use uv_cache::Cache;
use uv_client::RegistryClient;
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, IndexStrategy,
PackageConfigSettings, Preview, SourceStrategy,
};
use uv_configuration::{BuildOptions, Concurrency, Constraints, IndexStrategy, SourceStrategy};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution::DistributionDatabase;
use uv_distribution_types::{
DependencyMetadata, ExtraBuildRequires, ExtraBuildVariables, IndexLocations, RequiresPython,
ConfigSettings, DependencyMetadata, ExtraBuildRequires, ExtraBuildVariables,
IndexLocations, PackageConfigSettings, RequiresPython,
};
use uv_install_wheel::LinkMode;
use uv_pep440::Version;
use uv_pep508::{MarkerEnvironment, MarkerEnvironmentBuilder};
use uv_platform_tags::{Arch, Os, Platform, Tags};
use uv_preview::Preview;
use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment};
use uv_python::Interpreter;
use uv_resolver::{
@ -132,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,10 +1 @@
pub mod criterion {
//! This module re-exports the criterion API but picks the right backend depending on whether
//! the benchmarks are built to run locally or with codspeed
#[cfg(not(feature = "codspeed"))]
pub use criterion::*;
#[cfg(feature = "codspeed")]
pub use codspeed_criterion_compat::*;
}

View File

@ -0,0 +1,37 @@
[package]
name = "uv-bin-install"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
[lib]
doctest = false
[lints]
workspace = true
[dependencies]
uv-cache = { workspace = true }
uv-client = { workspace = true }
uv-distribution-filename = { workspace = true }
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 }
reqwest-middleware = { workspace = true }
reqwest-retry = { workspace = true }
tempfile = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tokio-util = { workspace = true }
tracing = { workspace = true }
url = { 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

@ -0,0 +1,438 @@
//! Binary download and installation utilities for uv.
//!
//! These utilities are specifically for consuming distributions that are _not_ Python packages,
//! e.g., `ruff` (which does have a Python package, but also has standalone binaries on GitHub).
use std::path::PathBuf;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, SystemTime};
use futures::TryStreamExt;
use reqwest_retry::RetryPolicy;
use reqwest_retry::policies::ExponentialBackoff;
use std::fmt;
use thiserror::Error;
use tokio::io::{AsyncRead, ReadBuf};
use tokio_util::compat::FuturesAsyncReadCompatExt;
use tracing::debug;
use url::Url;
use uv_distribution_filename::SourceDistExtension;
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;
use uv_platform::Platform;
use uv_redacted::DisplaySafeUrl;
/// Binary tools that can be installed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Binary {
Ruff,
}
impl Binary {
/// Get the default version for this binary.
pub fn default_version(&self) -> Version {
match self {
// TODO(zanieb): Figure out a nice way to automate updating this
Self::Ruff => Version::new([0, 12, 5]),
}
}
/// The name of the binary.
///
/// See [`Binary::executable`] for the platform-specific executable name.
pub fn name(&self) -> &'static str {
match self {
Self::Ruff => "ruff",
}
}
/// Get the download URL for a specific version and platform.
pub fn download_url(
&self,
version: &Version,
platform: &str,
format: ArchiveFormat,
) -> Result<Url, Error> {
match self {
Self::Ruff => {
let url = format!(
"https://github.com/astral-sh/ruff/releases/download/{version}/ruff-{platform}.{}",
format.extension()
);
Url::parse(&url).map_err(|err| Error::UrlParse { url, source: err })
}
}
}
/// Get the executable name
pub fn executable(&self) -> String {
format!("{}{}", self.name(), std::env::consts::EXE_SUFFIX)
}
}
impl fmt::Display for Binary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
/// Archive formats for binary downloads.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArchiveFormat {
Zip,
TarGz,
}
impl ArchiveFormat {
/// Get the file extension for this archive format.
pub fn extension(&self) -> &'static str {
match self {
Self::Zip => "zip",
Self::TarGz => "tar.gz",
}
}
}
impl From<ArchiveFormat> for SourceDistExtension {
fn from(val: ArchiveFormat) -> Self {
match val {
ArchiveFormat::Zip => Self::Zip,
ArchiveFormat::TarGz => Self::TarGz,
}
}
}
/// Errors that can occur during binary download and installation.
#[derive(Debug, Error)]
pub enum Error {
#[error("Failed to download from: {url}")]
Download {
url: Url,
#[source]
source: reqwest_middleware::Error,
},
#[error("Failed to parse URL: {url}")]
UrlParse {
url: String,
#[source]
source: url::ParseError,
},
#[error("Failed to extract archive")]
Extract {
#[source]
source: ExtractError,
},
#[error("Binary not found in archive at expected location: {expected}")]
BinaryNotFound { expected: PathBuf },
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Cache(#[from] CacheError),
#[error("Failed to detect platform")]
Platform(#[from] uv_platform::Error),
#[error("Attempt failed after {retries} {subject}", subject = if *retries > 1 { "retries" } else { "retry" })]
RetriedError {
#[source]
err: Box<Error>,
retries: u32,
},
}
impl Error {
/// Return the number of attempts that were made to complete this request before this error was
/// returned. Note that e.g. 3 retries equates to 4 attempts.
fn attempts(&self) -> u32 {
if let Self::RetriedError { retries, .. } = self {
return retries + 1;
}
1
}
}
/// Install the given binary.
pub async fn bin_install(
binary: Binary,
version: &Version,
client: &BaseClient,
retry_policy: &ExponentialBackoff,
cache: &Cache,
reporter: &dyn Reporter,
) -> Result<PathBuf, Error> {
let platform = Platform::from_env()?;
let platform_name = platform.as_cargo_dist_triple();
let cache_entry = CacheEntry::new(
cache
.bucket(CacheBucket::Binaries)
.join(binary.name())
.join(version.to_string())
.join(&platform_name),
binary.executable(),
);
// Lock the directory to prevent racing installs
let _lock = cache_entry.with_file(".lock").lock().await?;
if cache_entry.path().exists() {
return Ok(cache_entry.into_path_buf());
}
let format = if platform.os.is_windows() {
ArchiveFormat::Zip
} else {
ArchiveFormat::TarGz
};
let download_url = binary.download_url(version, &platform_name, format)?;
let cache_dir = cache_entry.dir();
fs_err::tokio::create_dir_all(&cache_dir).await?;
let path = download_and_unpack_with_retry(
binary,
version,
client,
retry_policy,
cache,
reporter,
&platform_name,
format,
&download_url,
&cache_entry,
)
.await?;
// Add executable bit
#[cfg(unix)]
{
use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt;
let permissions = fs_err::tokio::metadata(&path).await?.permissions();
if permissions.mode() & 0o111 != 0o111 {
fs_err::tokio::set_permissions(
&path,
Permissions::from_mode(permissions.mode() | 0o111),
)
.await?;
}
}
Ok(path)
}
/// Download and unpack a binary with retry on stream failures.
async fn download_and_unpack_with_retry(
binary: Binary,
version: &Version,
client: &BaseClient,
retry_policy: &ExponentialBackoff,
cache: &Cache,
reporter: &dyn Reporter,
platform_name: &str,
format: ArchiveFormat,
download_url: &Url,
cache_entry: &CacheEntry,
) -> Result<PathBuf, Error> {
let mut total_attempts = 0;
let mut retried_here = false;
let start_time = SystemTime::now();
loop {
let result = download_and_unpack(
binary,
version,
client,
cache,
reporter,
platform_name,
format,
download_url,
cache_entry,
)
.await;
let result = match result {
Ok(path) => Ok(path),
Err(err) => {
total_attempts += err.attempts();
let past_retries = total_attempts - 1;
if is_transient_network_error(&err) {
let retry_decision = retry_policy.should_retry(start_time, past_retries);
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
debug!(
"Transient failure while installing {} {}; retrying...",
binary.name(),
version
);
let duration = execute_after
.duration_since(SystemTime::now())
.unwrap_or_else(|_| Duration::default());
tokio::time::sleep(duration).await;
retried_here = true;
continue;
}
}
if retried_here {
Err(Error::RetriedError {
err: Box::new(err),
retries: past_retries,
})
} else {
Err(err)
}
}
};
return result;
}
}
/// Download and unpackage a binary,
///
/// NOTE [`download_and_unpack_with_retry`] should be used instead.
async fn download_and_unpack(
binary: Binary,
version: &Version,
client: &BaseClient,
cache: &Cache,
reporter: &dyn Reporter,
platform_name: &str,
format: ArchiveFormat,
download_url: &Url,
cache_entry: &CacheEntry,
) -> Result<PathBuf, Error> {
// Create a temporary directory for extraction
let temp_dir = tempfile::tempdir_in(cache.bucket(CacheBucket::Binaries))?;
let response = client
.for_host(&DisplaySafeUrl::from_url(download_url.clone()))
.get(download_url.clone())
.send()
.await
.map_err(|err| Error::Download {
url: download_url.clone(),
source: err,
})?;
let inner_retries = response
.extensions()
.get::<reqwest_retry::RetryCount>()
.map(|retries| retries.value());
if let Err(status_error) = response.error_for_status_ref() {
let err = Error::Download {
url: download_url.clone(),
source: reqwest_middleware::Error::from(status_error),
};
if let Some(retries) = inner_retries {
return Err(Error::RetriedError {
err: Box::new(err),
retries,
});
}
return Err(err);
}
// Get the download size from headers if available
let size = response
.headers()
.get(reqwest::header::CONTENT_LENGTH)
.and_then(|val| val.to_str().ok())
.and_then(|val| val.parse::<u64>().ok());
// Stream download directly to extraction
let reader = response
.bytes_stream()
.map_err(std::io::Error::other)
.into_async_read()
.compat();
let id = reporter.on_download_start(binary.name(), version, size);
let mut progress_reader = ProgressReader::new(reader, id, reporter);
stream::archive(&mut progress_reader, format.into(), temp_dir.path())
.await
.map_err(|e| Error::Extract { source: e })?;
reporter.on_download_complete(id);
// Find the binary in the extracted files
let extracted_binary = match format {
ArchiveFormat::Zip => {
// Windows ZIP archives contain the binary directly in the root
temp_dir.path().join(binary.executable())
}
ArchiveFormat::TarGz => {
// tar.gz archives contain the binary in a subdirectory
temp_dir
.path()
.join(format!("{}-{platform_name}", binary.name()))
.join(binary.executable())
}
};
if !extracted_binary.exists() {
return Err(Error::BinaryNotFound {
expected: extracted_binary,
});
}
// Move the binary to its final location before the temp directory is dropped
fs_err::tokio::rename(&extracted_binary, cache_entry.path()).await?;
Ok(cache_entry.path().to_path_buf())
}
/// Progress reporter for binary downloads.
pub trait Reporter: Send + Sync {
/// Called when a download starts.
fn on_download_start(&self, name: &str, version: &Version, size: Option<u64>) -> usize;
/// Called when download progress is made.
fn on_download_progress(&self, id: usize, inc: u64);
/// Called when a download completes.
fn on_download_complete(&self, id: usize);
}
/// An asynchronous reader that reports progress as bytes are read.
struct ProgressReader<'a, R> {
reader: R,
index: usize,
reporter: &'a dyn Reporter,
}
impl<'a, R> ProgressReader<'a, R> {
/// Create a new [`ProgressReader`] that wraps another reader.
fn new(reader: R, index: usize, reporter: &'a dyn Reporter) -> Self {
Self {
reader,
index,
reporter,
}
}
}
impl<R> AsyncRead for ProgressReader<'_, R>
where
R: AsyncRead + Unpin,
{
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.as_mut().reader)
.poll_read(cx, buf)
.map_ok(|()| {
self.reporter
.on_download_progress(self.index, buf.filled().len() as u64);
})
}
}

View File

@ -1,10 +1,10 @@
[package]
name = "uv-build-backend"
version = "0.1.0"
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 }
@ -26,6 +26,7 @@ uv-pypi-types = { workspace = true }
uv-version = { workspace = true }
uv-warnings = { workspace = true }
base64 = { workspace = true }
csv = { workspace = true }
flate2 = { workspace = true, default-features = false }
fs-err = { 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,11 @@ 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};
use std::str::FromStr;
@ -28,20 +32,20 @@ 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),
#[error("Unsupported glob expression in: `{field}`")]
#[error("Unsupported glob expression in: {field}")]
PortableGlob {
field: String,
#[source]
source: PortableGlobError,
},
/// <https://github.com/BurntSushi/ripgrep/discussions/2927>
#[error("Glob expressions caused to large regex in: `{field}`")]
#[error("Glob expressions caused to large regex in: {field}")]
GlobSetTooLarge {
field: String,
#[source]
@ -49,7 +53,7 @@ pub enum Error {
},
#[error("`pyproject.toml` must not be excluded from source distribution build")]
PyprojectTomlExcluded,
#[error("Failed to walk source tree: `{}`", root.user_display())]
#[error("Failed to walk source tree: {}", root.user_display())]
WalkDir {
root: PathBuf,
#[source]
@ -59,14 +63,19 @@ pub enum Error {
Zip(#[from] zip::result::ZipError),
#[error("Failed to write RECORD file")]
Csv(#[from] csv::Error),
#[error("Expected a Python module at: `{}`", _0.user_display())]
#[error("Expected a Python module at: {}", _0.user_display())]
MissingInitPy(PathBuf),
#[error("For namespace packages, `__init__.py[i]` is not allowed in parent directory: `{}`", _0.user_display())]
#[error("For namespace packages, `__init__.py[i]` is not allowed in parent directory: {}", _0.user_display())]
NotANamespace(PathBuf),
/// Either an absolute path or a parent path through `..`.
#[error("Module root must be inside the project: `{}`", _0.user_display())]
#[error("Module root must be inside the project: {}", _0.user_display())]
InvalidModuleRoot(PathBuf),
#[error("Inconsistent metadata between prepare and build step: `{0}`")]
/// Either an absolute path or a parent path through `..`.
#[error("The path for the data directory {} must be inside the project: {}", name, path.user_display())]
InvalidDataRoot { name: String, path: PathBuf },
#[error("Virtual environments must not be added to source distributions or wheels, remove the directory or exclude it from the build: {}", _0.user_display())]
VenvInSourceTree(PathBuf),
#[error("Inconsistent metadata between prepare and build step: {0}")]
InconsistentSteps(&'static str),
#[error("Failed to write to {}", _0.user_display())]
TarWrite(PathBuf, #[source] io::Error),
@ -185,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.
///
@ -207,10 +270,13 @@ 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);
let src_root = source_tree.join(&relative_module_root);
if !src_root.starts_with(source_tree) {
// Check that even if a path contains `..`, we only include files below the module root.
if !uv_fs::normalize_path(&source_tree.join(&relative_module_root))
.starts_with(uv_fs::normalize_path(source_tree))
{
return Err(Error::InvalidModuleRoot(relative_module_root.to_path_buf()));
}
let src_root = source_tree.join(&relative_module_root);
@ -223,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(),
}
@ -242,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 {
@ -347,6 +413,27 @@ fn module_path_from_module_name(src_root: &Path, module_name: &str) -> Result<Pa
Ok(module_relative)
}
/// Error if we're adding a venv to a distribution.
pub(crate) fn error_on_venv(file_name: &OsStr, path: &Path) -> Result<(), Error> {
// On 64-bit Unix, `lib64` is a (compatibility) symlink to lib. If we traverse `lib64` before
// `pyvenv.cfg`, we show a generic error for symlink directories instead.
if !(file_name == "pyvenv.cfg" || file_name == "lib64") {
return Ok(());
}
let Some(parent) = path.parent() else {
return Ok(());
};
if parent.join("bin").join("python").is_symlink()
|| parent.join("Scripts").join("python.exe").is_file()
{
return Err(Error::VenvInSourceTree(parent.to_path_buf()));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
@ -391,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);
@ -417,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.
@ -505,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",
@ -568,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"
@ -622,7 +716,7 @@ mod tests {
// Check that the wheel is reproducible across platforms.
assert_snapshot!(
format!("{:x}", sha2::Sha256::digest(fs_err::read(&wheel_path).unwrap())),
@"342bf60c8406144f459358cde92408686c1631fe22389d042ce80379e589d6ec"
@"319afb04e87caf894b1362b508ec745253c6d241423ea59021694d2015e821da"
);
assert_snapshot!(build.wheel_contents.join("\n"), @r"
built_by_uv-0.1.0.data/data/
@ -665,6 +759,31 @@ mod tests {
built_by_uv-0.1.0.dist-info/entry_points.txt (generated)
built_by_uv-0.1.0.dist-info/METADATA (generated)
");
let mut wheel = zip::ZipArchive::new(File::open(wheel_path).unwrap()).unwrap();
let mut record = String::new();
wheel
.by_name("built_by_uv-0.1.0.dist-info/RECORD")
.unwrap()
.read_to_string(&mut record)
.unwrap();
assert_snapshot!(record, @r###"
built_by_uv/__init__.py,sha256=AJ7XpTNWxYktP97ydb81UpnNqoebH7K4sHRakAMQKG4,44
built_by_uv/arithmetic/__init__.py,sha256=x2agwFbJAafc9Z6TdJ0K6b6bLMApQdvRSQjP4iy7IEI,67
built_by_uv/arithmetic/circle.py,sha256=FYZkv6KwrF9nJcwGOKigjke1dm1Fkie7qW1lWJoh3AE,287
built_by_uv/arithmetic/pi.txt,sha256=-4HqoLoIrSKGf0JdTrM8BTTiIz8rq-MSCDL6LeF0iuU,8
built_by_uv/cli.py,sha256=Jcm3PxSb8wTAN3dGm5vKEDQwCgoUXkoeggZeF34QyKM,44
built_by_uv-0.1.0.dist-info/licenses/LICENSE-APACHE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
built_by_uv-0.1.0.dist-info/licenses/LICENSE-MIT,sha256=F5Z0Cpu8QWyblXwXhrSo0b9WmYXQxd1LwLjVLJZwbiI,1077
built_by_uv-0.1.0.dist-info/licenses/third-party-licenses/PEP-401.txt,sha256=KN-KAx829G2saLjVmByc08RFFtIDWvHulqPyD0qEBZI,270
built_by_uv-0.1.0.data/headers/built_by_uv.h,sha256=p5-HBunJ1dY-xd4dMn03PnRClmGyRosScIp8rT46kg4,144
built_by_uv-0.1.0.data/scripts/whoami.sh,sha256=T2cmhuDFuX-dTkiSkuAmNyIzvv8AKopjnuTCcr9o-eE,20
built_by_uv-0.1.0.data/data/data.csv,sha256=7z7u-wXu7Qr2eBZFVpBILlNUiGSngv_1vYqZHVWOU94,265
built_by_uv-0.1.0.dist-info/WHEEL,sha256=PaG_oOj9G2zCRqoLK0SjWBVZbGAMtIXDmm-MEGw9Wo0,83
built_by_uv-0.1.0.dist-info/entry_points.txt,sha256=-IO6yaq6x6HSl-zWH96rZmgYvfyHlH00L5WQoCpz-YI,50
built_by_uv-0.1.0.dist-info/METADATA,sha256=m6EkVvKrGmqx43b_VR45LHD37IZxPYC0NI6Qx9_UXLE,474
built_by_uv-0.1.0.dist-info/RECORD,,
"###);
}
/// Test that `license = { file = "LICENSE" }` is supported.
@ -702,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());
@ -713,6 +832,7 @@ mod tests {
output_dir.path(),
None,
"0.5.15",
false,
)
.unwrap();
let wheel = output_dir
@ -777,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
@ -915,7 +1036,7 @@ mod tests {
.replace('\\', "/");
assert_snapshot!(
err_message,
@"Expected a Python module at: `[TEMP_PATH]/src/camel_case/__init__.py`"
@"Expected a Python module at: [TEMP_PATH]/src/camel_case/__init__.py"
);
}
@ -980,7 +1101,7 @@ mod tests {
.replace('\\', "/");
assert_snapshot!(
err_message,
@"Expected a Python module at: `[TEMP_PATH]/src/stuffed_bird-stubs/__init__.pyi`"
@"Expected a Python module at: [TEMP_PATH]/src/stuffed_bird-stubs/__init__.pyi"
);
// Create the correct file
@ -1046,7 +1167,7 @@ mod tests {
assert_snapshot!(
build_err(src.path()),
@"Expected a Python module at: `[TEMP_PATH]/src/simple_namespace/part/__init__.py`"
@"Expected a Python module at: [TEMP_PATH]/src/simple_namespace/part/__init__.py"
);
// Create the correct file
@ -1068,7 +1189,7 @@ mod tests {
File::create(&bogus_init_py).unwrap();
assert_snapshot!(
build_err(src.path()),
@"For namespace packages, `__init__.py[i]` is not allowed in parent directory: `[TEMP_PATH]/src/simple_namespace`"
@"For namespace packages, `__init__.py[i]` is not allowed in parent directory: [TEMP_PATH]/src/simple_namespace"
);
fs_err::remove_file(bogus_init_py).unwrap();
@ -1288,7 +1409,7 @@ mod tests {
// The first module is missing an `__init__.py`.
assert_snapshot!(
build_err(src.path()),
@"Expected a Python module at: `[TEMP_PATH]/src/foo/__init__.py`"
@"Expected a Python module at: [TEMP_PATH]/src/foo/__init__.py"
);
// Create the first correct `__init__.py` file
@ -1297,7 +1418,7 @@ mod tests {
// The second module, a namespace, is missing an `__init__.py`.
assert_snapshot!(
build_err(src.path()),
@"Expected a Python module at: `[TEMP_PATH]/src/simple_namespace/part_a/__init__.py`"
@"Expected a Python module at: [TEMP_PATH]/src/simple_namespace/part_a/__init__.py"
);
// Create the other two correct `__init__.py` files
@ -1327,7 +1448,7 @@ mod tests {
File::create(&bogus_init_py).unwrap();
assert_snapshot!(
build_err(src.path()),
@"For namespace packages, `__init__.py[i]` is not allowed in parent directory: `[TEMP_PATH]/src/simple_namespace`"
@"For namespace packages, `__init__.py[i]` is not allowed in parent directory: [TEMP_PATH]/src/simple_namespace"
);
fs_err::remove_file(bogus_init_py).unwrap();
@ -1360,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

@ -3,10 +3,10 @@ use std::ffi::OsStr;
use std::fmt::Display;
use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::str::{self, FromStr};
use itertools::Itertools;
use serde::Deserialize;
use serde::{Deserialize, Deserializer};
use tracing::{debug, trace, warn};
use version_ranges::Ranges;
use walkdir::WalkDir;
@ -21,7 +21,7 @@ use uv_pep508::{
use uv_pypi_types::{Metadata23, VerbatimParsedUrl};
use crate::serde_verbatim::SerdeVerbatim;
use crate::{BuildBackendSettings, Error};
use crate::{BuildBackendSettings, Error, error_on_venv};
/// By default, we ignore generated python files.
pub(crate) const DEFAULT_EXCLUDES: &[&str] = &["__pycache__", "*.pyc", "*.pyo"];
@ -40,7 +40,7 @@ pub enum ValidationError {
UnknownExtension(String),
#[error("Can't infer content type because `{}` does not have an extension. Please use a support extension (`.md`, `.rst`, `.txt`) or set the content type manually.", _0.user_display())]
MissingExtension(PathBuf),
#[error("Unsupported content type: `{0}`")]
#[error("Unsupported content type: {0}")]
UnsupportedContentType(String),
#[error("`project.description` must be a single line")]
DescriptionNewlines,
@ -51,19 +51,29 @@ pub enum ValidationError {
)]
MixedLicenseGenerations,
#[error(
"Entrypoint groups must consist of letters and numbers separated by dots, invalid group: `{0}`"
"Entrypoint groups must consist of letters and numbers separated by dots, invalid group: {0}"
)]
InvalidGroup(String),
#[error("Use `project.scripts` instead of `project.entry-points.console_scripts`")]
ReservedScripts,
#[error("Use `project.gui-scripts` instead of `project.entry-points.gui_scripts`")]
ReservedGuiScripts,
#[error("`project.license` is not a valid SPDX expression: `{0}`")]
#[error("`project.license` is not a valid SPDX expression: {0}")]
InvalidSpdx(String, #[source] spdx::error::ParseError),
#[error("`{field}` glob `{glob}` did not match any files")]
LicenseGlobNoMatches { field: String, glob: String },
#[error("License file `{}` must be UTF-8 encoded", _0)]
LicenseFileNotUtf8(String),
}
/// Check if the build backend is matching the currently running uv version.
pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
struct PyProjectToml {
build_system: BuildSystem,
}
let pyproject_toml: PyProjectToml =
match fs_err::read_to_string(source_tree.join("pyproject.toml"))
.map_err(|err| err.to_string())
@ -73,12 +83,14 @@ pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
Ok(pyproject_toml) => pyproject_toml,
Err(err) => {
debug!(
"Not using uv build backend direct build of {name}, no pyproject.toml: {err}"
"Not using uv build backend direct build for source tree `{name}`, \
failed to parse pyproject.toml: {err}"
);
return false;
}
};
match pyproject_toml
.build_system
.check_build_system(uv_version::version())
.as_slice()
{
@ -87,16 +99,36 @@ pub fn check_direct_build(source_tree: &Path, name: impl Display) -> bool {
// Any warning -> no match
[first, others @ ..] => {
debug!(
"Not using uv build backend direct build of {name}, pyproject.toml does not match: {first}"
"Not using uv build backend direct build of `{name}`, pyproject.toml does not match: {first}"
);
for other in others {
trace!("Further uv build backend direct build of {name} mismatch: {other}");
trace!("Further uv build backend direct build of `{name}` mismatch: {other}");
}
false
}
}
}
/// A package name as provided in a `pyproject.toml`.
#[derive(Debug, Clone)]
struct VerbatimPackageName {
/// The package name as given in the `pyproject.toml`.
given: String,
/// The normalized package name.
normalized: PackageName,
}
impl<'de> Deserialize<'de> for VerbatimPackageName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let given = String::deserialize(deserializer)?;
let normalized = PackageName::from_str(&given).map_err(serde::de::Error::custom)?;
Ok(Self { given, normalized })
}
}
/// A `pyproject.toml` as specified in PEP 517.
#[derive(Deserialize, Debug, Clone)]
#[serde(
@ -115,15 +147,18 @@ pub struct PyProjectToml {
impl PyProjectToml {
pub(crate) fn name(&self) -> &PackageName {
&self.project.name
&self.project.name.normalized
}
pub(crate) fn version(&self) -> &Version {
&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> {
@ -161,83 +196,9 @@ impl PyProjectToml {
self.tool.as_ref()?.uv.as_ref()?.build_backend.as_ref()
}
/// Returns user-facing warnings if the `[build-system]` table looks suspicious.
///
/// Example of a valid table:
///
/// ```toml
/// [build-system]
/// requires = ["uv_build>=0.4.15,<0.5.0"]
/// build-backend = "uv_build"
/// ```
/// See [`BuildSystem::check_build_system`].
pub fn check_build_system(&self, uv_version: &str) -> Vec<String> {
let mut warnings = Vec::new();
if self.build_system.build_backend.as_deref() != Some("uv_build") {
warnings.push(format!(
r#"The value for `build_system.build-backend` should be `"uv_build"`, not `"{}"`"#,
self.build_system.build_backend.clone().unwrap_or_default()
));
}
let uv_version =
Version::from_str(uv_version).expect("uv's own version is not PEP 440 compliant");
let next_minor = uv_version.release().get(1).copied().unwrap_or_default() + 1;
let next_breaking = Version::new([0, next_minor]);
let expected = || {
format!(
"Expected a single uv requirement in `build-system.requires`, found `{}`",
toml::to_string(&self.build_system.requires).unwrap_or_default()
)
};
let [uv_requirement] = &self.build_system.requires.as_slice() else {
warnings.push(expected());
return warnings;
};
if uv_requirement.name.as_str() != "uv-build" {
warnings.push(expected());
return warnings;
}
let bounded = match &uv_requirement.version_or_url {
None => false,
Some(VersionOrUrl::Url(_)) => {
// We can't validate the url
true
}
Some(VersionOrUrl::VersionSpecifier(specifier)) => {
// We don't check how wide the range is (that's up to the user), we just
// check that the current version is compliant, to avoid accidentally using a
// too new or too old uv, and we check that an upper bound exists. The latter
// is very important to allow making breaking changes in uv without breaking
// the existing immutable source distributions on pypi.
if !specifier.contains(&uv_version) {
// This is allowed to happen when testing prereleases, but we should still warn.
warnings.push(format!(
r#"`build_system.requires = ["{uv_requirement}"]` does not contain the
current uv version {uv_version}"#,
));
}
Ranges::from(specifier.clone())
.bounding_range()
.map(|bounding_range| bounding_range.1 != Bound::Unbounded)
.unwrap_or(false)
}
};
if !bounded {
warnings.push(format!(
"`build_system.requires = [\"{}\"]` is missing an \
upper bound on the `uv_build` version such as `<{next_breaking}`. \
Without bounding the `uv_build` version, the source distribution will break \
when a future, breaking version of `uv_build` is released.",
// Use an underscore consistently, to avoid confusing users between a package name with dash and a
// module name with underscore
uv_requirement.verbatim()
));
}
warnings
self.build_system.check_build_system(uv_version)
}
/// Validate and convert a `pyproject.toml` to core metadata.
@ -385,97 +346,7 @@ impl PyProjectToml {
"2.3"
};
// TODO(konsti): Issue a warning on old license metadata once PEP 639 is universal.
let (license, license_expression, license_files) =
if let Some(license_globs) = &self.project.license_files {
let license_expression = match &self.project.license {
None => None,
Some(License::Spdx(license_expression)) => Some(license_expression.clone()),
Some(License::Text { .. } | License::File { .. }) => {
return Err(ValidationError::MixedLicenseGenerations.into());
}
};
let mut license_files = Vec::new();
let mut license_globs_parsed = Vec::new();
for license_glob in license_globs {
let pep639_glob =
PortableGlobParser::Pep639
.parse(license_glob)
.map_err(|err| Error::PortableGlob {
field: license_glob.to_string(),
source: err,
})?;
license_globs_parsed.push(pep639_glob);
}
let license_globs =
GlobDirFilter::from_globs(&license_globs_parsed).map_err(|err| {
Error::GlobSetTooLarge {
field: "tool.uv.build-backend.source-include".to_string(),
source: err,
}
})?;
for entry in WalkDir::new(root)
.sort_by_file_name()
.into_iter()
.filter_entry(|entry| {
license_globs.match_directory(
entry
.path()
.strip_prefix(root)
.expect("walkdir starts with root"),
)
})
{
let entry = entry.map_err(|err| Error::WalkDir {
root: root.to_path_buf(),
err,
})?;
let relative = entry
.path()
.strip_prefix(root)
.expect("walkdir starts with root");
if !license_globs.match_path(relative) {
trace!("Not a license files match: `{}`", relative.user_display());
continue;
}
if !entry.file_type().is_file() {
trace!(
"Not a file in license files match: `{}`",
relative.user_display()
);
continue;
}
debug!("License files match: `{}`", relative.user_display());
license_files.push(relative.portable_display().to_string());
}
// The glob order may be unstable
license_files.sort();
(None, license_expression, license_files)
} else {
match &self.project.license {
None => (None, None, Vec::new()),
Some(License::Spdx(license_expression)) => {
(None, Some(license_expression.clone()), Vec::new())
}
Some(License::Text { text }) => (Some(text.clone()), None, Vec::new()),
Some(License::File { file }) => {
let text = fs_err::read_to_string(root.join(file))?;
(Some(text), None, Vec::new())
}
}
};
// Check that the license expression is a valid SPDX identifier.
if let Some(license_expression) = &license_expression {
if let Err(err) = spdx::Expression::parse(license_expression) {
return Err(ValidationError::InvalidSpdx(license_expression.clone(), err).into());
}
}
let (license, license_expression, license_files) = self.license_metadata(root)?;
// TODO(konsti): https://peps.python.org/pep-0753/#label-normalization (Draft)
let project_urls = self
@ -520,7 +391,7 @@ impl PyProjectToml {
Ok(Metadata23 {
metadata_version: metadata_version.to_string(),
name: self.project.name.to_string(),
name: self.project.name.given.clone(),
version: self.project.version.to_string(),
// Not supported.
platforms: vec![],
@ -545,7 +416,7 @@ impl PyProjectToml {
license_files,
classifiers: self.project.classifiers.clone().unwrap_or_default(),
requires_dist: requires_dist.iter().map(ToString::to_string).collect(),
provides_extras: extras.iter().map(ToString::to_string).collect(),
provides_extra: extras.iter().map(ToString::to_string).collect(),
// Not commonly set.
provides_dist: vec![],
// Not supported.
@ -562,6 +433,156 @@ impl PyProjectToml {
})
}
/// Parse and validate the old (PEP 621) and new (PEP 639) license files.
#[allow(clippy::type_complexity)]
fn license_metadata(
&self,
root: &Path,
) -> Result<(Option<String>, Option<String>, Vec<String>), Error> {
// TODO(konsti): Issue a warning on old license metadata once PEP 639 is universal.
let (license, license_expression, license_files) = if let Some(license_globs) =
&self.project.license_files
{
let license_expression = match &self.project.license {
None => None,
Some(License::Spdx(license_expression)) => Some(license_expression.clone()),
Some(License::Text { .. } | License::File { .. }) => {
return Err(ValidationError::MixedLicenseGenerations.into());
}
};
let mut license_files = Vec::new();
let mut license_globs_parsed = Vec::with_capacity(license_globs.len());
let mut license_glob_matchers = Vec::with_capacity(license_globs.len());
for license_glob in license_globs {
let pep639_glob =
PortableGlobParser::Pep639
.parse(license_glob)
.map_err(|err| Error::PortableGlob {
field: license_glob.to_owned(),
source: err,
})?;
license_glob_matchers.push(pep639_glob.compile_matcher());
license_globs_parsed.push(pep639_glob);
}
// Track whether each user-specified glob matched so we can flag the unmatched ones.
let mut license_globs_matched = vec![false; license_globs_parsed.len()];
let license_globs =
GlobDirFilter::from_globs(&license_globs_parsed).map_err(|err| {
Error::GlobSetTooLarge {
field: "project.license-files".to_string(),
source: err,
}
})?;
for entry in WalkDir::new(root)
.sort_by_file_name()
.into_iter()
.filter_entry(|entry| {
license_globs.match_directory(
entry
.path()
.strip_prefix(root)
.expect("walkdir starts with root"),
)
})
{
let entry = entry.map_err(|err| Error::WalkDir {
root: root.to_path_buf(),
err,
})?;
let relative = entry
.path()
.strip_prefix(root)
.expect("walkdir starts with root");
if !license_globs.match_path(relative) {
trace!("Not a license files match: {}", relative.user_display());
continue;
}
let file_type = entry.file_type();
if !(file_type.is_file() || file_type.is_symlink()) {
trace!(
"Not a file or symlink in license files match: {}",
relative.user_display()
);
continue;
}
error_on_venv(entry.file_name(), entry.path())?;
debug!("License files match: {}", relative.user_display());
for (matched, matcher) in license_globs_matched
.iter_mut()
.zip(license_glob_matchers.iter())
{
if *matched {
continue;
}
if matcher.is_match(relative) {
*matched = true;
}
}
license_files.push(relative.portable_display().to_string());
}
if let Some((pattern, _)) = license_globs_parsed
.into_iter()
.zip(license_globs_matched)
.find(|(_, matched)| !matched)
{
return Err(ValidationError::LicenseGlobNoMatches {
field: "project.license-files".to_string(),
glob: pattern.to_string(),
}
.into());
}
for license_file in &license_files {
let file_path = root.join(license_file);
let bytes = fs_err::read(&file_path)?;
if str::from_utf8(&bytes).is_err() {
return Err(ValidationError::LicenseFileNotUtf8(license_file.clone()).into());
}
}
// The glob order may be unstable
license_files.sort();
(None, license_expression, license_files)
} else {
match &self.project.license {
None => (None, None, Vec::new()),
Some(License::Spdx(license_expression)) => {
(None, Some(license_expression.clone()), Vec::new())
}
Some(License::Text { text }) => (Some(text.clone()), None, Vec::new()),
Some(License::File { file }) => {
let text = fs_err::read_to_string(root.join(file))?;
(Some(text), None, Vec::new())
}
}
};
// Check that the license expression is a valid SPDX identifier.
if let Some(license_expression) = &license_expression {
if let Err(err) = spdx::Expression::parse(license_expression) {
return Err(ValidationError::InvalidSpdx(license_expression.clone(), err).into());
}
}
Ok((license, license_expression, license_files))
}
/// Validate and convert the entrypoints in `pyproject.toml`, including console and GUI scripts,
/// to an `entry_points.txt`.
///
@ -622,7 +643,7 @@ impl PyProjectToml {
{
warn!(
"Entrypoint names should consist of letters, numbers, dots, underscores and \
dashes; non-compliant name: `{name}`"
dashes; non-compliant name: {name}"
);
}
@ -643,7 +664,7 @@ impl PyProjectToml {
#[serde(rename_all = "kebab-case")]
struct Project {
/// The name of the project.
name: PackageName,
name: VerbatimPackageName,
/// The version of the project.
version: Version,
/// The summary description of the project in one line.
@ -780,18 +801,6 @@ pub(crate) enum Contact {
Email { email: String },
}
/// The `[build-system]` section of a pyproject.toml as specified in PEP 517.
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
struct BuildSystem {
/// PEP 508 dependencies required to execute the build system.
requires: Vec<SerdeVerbatim<Requirement<VerbatimParsedUrl>>>,
/// A string naming a Python object that will be used to perform the build.
build_backend: Option<String>,
/// <https://peps.python.org/pep-0517/#in-tree-build-backends>
backend_path: Option<Vec<String>>,
}
/// The `tool` section as specified in PEP 517.
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
@ -808,6 +817,100 @@ pub(crate) struct ToolUv {
build_backend: Option<BuildBackendSettings>,
}
/// The `[build-system]` section of a pyproject.toml as specified in PEP 517.
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
struct BuildSystem {
/// PEP 508 dependencies required to execute the build system.
requires: Vec<SerdeVerbatim<Requirement<VerbatimParsedUrl>>>,
/// A string naming a Python object that will be used to perform the build.
build_backend: Option<String>,
/// <https://peps.python.org/pep-0517/#in-tree-build-backends>
backend_path: Option<Vec<String>>,
}
impl BuildSystem {
/// Check if the `[build-system]` table matches the uv build backend expectations and return
/// a list of warnings if it looks suspicious.
///
/// Example of a valid table:
///
/// ```toml
/// [build-system]
/// requires = ["uv_build>=0.4.15,<0.5.0"]
/// build-backend = "uv_build"
/// ```
pub(crate) fn check_build_system(&self, uv_version: &str) -> Vec<String> {
let mut warnings = Vec::new();
if self.build_backend.as_deref() != Some("uv_build") {
warnings.push(format!(
r#"The value for `build_system.build-backend` should be `"uv_build"`, not `"{}"`"#,
self.build_backend.clone().unwrap_or_default()
));
}
let uv_version =
Version::from_str(uv_version).expect("uv's own version is not PEP 440 compliant");
let next_minor = uv_version.release().get(1).copied().unwrap_or_default() + 1;
let next_breaking = Version::new([0, next_minor]);
let expected = || {
format!(
"Expected a single uv requirement in `build-system.requires`, found `{}`",
toml::to_string(&self.requires).unwrap_or_default()
)
};
let [uv_requirement] = &self.requires.as_slice() else {
warnings.push(expected());
return warnings;
};
if uv_requirement.name.as_str() != "uv-build" {
warnings.push(expected());
return warnings;
}
let bounded = match &uv_requirement.version_or_url {
None => false,
Some(VersionOrUrl::Url(_)) => {
// We can't validate the url
true
}
Some(VersionOrUrl::VersionSpecifier(specifier)) => {
// We don't check how wide the range is (that's up to the user), we just
// check that the current version is compliant, to avoid accidentally using a
// too new or too old uv, and we check that an upper bound exists. The latter
// is very important to allow making breaking changes in uv without breaking
// the existing immutable source distributions on pypi.
if !specifier.contains(&uv_version) {
// This is allowed to happen when testing prereleases, but we should still warn.
warnings.push(format!(
r#"`build_system.requires = ["{uv_requirement}"]` does not contain the
current uv version {uv_version}"#,
));
}
Ranges::from(specifier.clone())
.bounding_range()
.map(|bounding_range| bounding_range.1 != Bound::Unbounded)
.unwrap_or(false)
}
};
if !bounded {
warnings.push(format!(
"`build_system.requires = [\"{}\"]` is missing an \
upper bound on the `uv_build` version such as `<{next_breaking}`. \
Without bounding the `uv_build` version, the source distribution will break \
when a future, breaking version of `uv_build` is released.",
// Use an underscore consistently, to avoid confusing users between a package name with dash and a
// module name with underscore
uv_requirement.verbatim()
));
}
warnings
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -838,6 +941,28 @@ mod tests {
formatted
}
#[test]
fn uppercase_package_name() {
let contents = r#"
[project]
name = "Hello-World"
version = "0.1.0"
[build-system]
requires = ["uv_build>=0.4.15,<0.5.0"]
build-backend = "uv_build"
"#;
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();
assert_snapshot!(metadata.core_metadata_format(), @r"
Metadata-Version: 2.3
Name: Hello-World
Version: 0.1.0
");
}
#[test]
fn valid() {
let temp_dir = TempDir::new().unwrap();
@ -912,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###"
@ -1006,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"
@ -1098,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###"
@ -1159,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"),
@""
@ -1177,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."###
@ -1195,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 ``"
@ -1213,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 ``"
@ -1231,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"`"###
@ -1242,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();
@ -1261,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]
@ -1279,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();
@ -1301,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]
@ -1319,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]
@ -1335,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();
@ -1353,17 +1477,17 @@ 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
Caused by: `project.license` is not a valid SPDX expression: `MIT XOR Apache-2`
assert_snapshot!(format_err(err), @r"
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
"###);
");
}
#[test]
@ -1373,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();
@ -1398,7 +1522,7 @@ mod tests {
foo = "bar"
"#
});
assert_snapshot!(script_error(&contents), @"Entrypoint groups must consist of letters and numbers separated by dots, invalid group: `a@b`");
assert_snapshot!(script_error(&contents), @"Entrypoint groups must consist of letters and numbers separated by dots, invalid group: a@b");
}
#[test]

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use uv_macros::OptionsMetadata;
/// Settings for the uv build backend (`uv_build`).
@ -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]",
@ -204,16 +207,16 @@ pub enum ModuleName {
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct WheelDataIncludes {
purelib: Option<String>,
platlib: Option<String>,
headers: Option<String>,
scripts: Option<String>,
data: Option<String>,
purelib: Option<PathBuf>,
platlib: Option<PathBuf>,
headers: Option<PathBuf>,
scripts: Option<PathBuf>,
data: Option<PathBuf>,
}
impl WheelDataIncludes {
/// Yield all data directories name and corresponding paths.
pub fn iter(&self) -> impl Iterator<Item = (&'static str, &str)> {
pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Path)> {
[
("purelib", self.purelib.as_deref()),
("platlib", self.platlib.as_deref()),

View File

@ -1,7 +1,8 @@
use crate::metadata::DEFAULT_EXCLUDES;
use crate::wheel::build_exclude_matcher;
use crate::{
BuildBackendSettings, DirectoryWriter, Error, FileList, ListWriter, PyProjectToml, find_roots,
BuildBackendSettings, DirectoryWriter, Error, FileList, ListWriter, PyProjectToml,
error_on_venv, find_roots,
};
use flate2::Compression;
use flate2::write::GzEncoder;
@ -9,7 +10,7 @@ use fs_err::File;
use globset::{Glob, GlobSet};
use std::io;
use std::io::{BufReader, Cursor};
use std::path::{Path, PathBuf};
use std::path::{Component, Path, PathBuf};
use tar::{EntryType, Header};
use tracing::{debug, trace};
use uv_distribution_filename::{SourceDistExtension, SourceDistFilename};
@ -23,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(),
@ -33,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)
}
@ -41,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(),
@ -51,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))
}
@ -60,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();
@ -74,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
@ -103,7 +106,7 @@ fn source_dist_matcher(
.and_then(|readme| readme.path())
{
let readme = uv_fs::normalize_path(readme);
trace!("Including readme at: `{}`", readme.user_display());
trace!("Including readme at: {}", readme.user_display());
let readme = readme.portable_display().to_string();
let glob = Glob::new(&globset::escape(&readme)).expect("escaped globset is parseable");
include_globs.push(glob);
@ -111,7 +114,7 @@ fn source_dist_matcher(
// Include the license files
for license_files in pyproject_toml.license_files_source_dist() {
trace!("Including license files at: `{license_files}`");
trace!("Including license files at: {license_files}`");
let glob = PortableGlobParser::Pep639
.parse(license_files)
.map_err(|err| Error::PortableGlob {
@ -123,12 +126,18 @@ fn source_dist_matcher(
// Include the data files
for (name, directory) in settings.data.iter() {
let directory = uv_fs::normalize_path(Path::new(directory));
trace!(
"Including data ({}) at: `{}`",
name,
directory.user_display()
);
let directory = uv_fs::normalize_path(directory);
trace!("Including data ({}) at: {}", name, directory.user_display());
if directory
.components()
.next()
.is_some_and(|component| !matches!(component, Component::CurDir | Component::Normal(_)))
{
return Err(Error::InvalidDataRoot {
name: name.to_string(),
path: directory.to_path_buf(),
});
}
let directory = directory.portable_display().to_string();
let glob = PortableGlobParser::Uv
.parse(&format!("{}/**", globset::escape(&directory)))
@ -140,7 +149,7 @@ fn source_dist_matcher(
}
debug!(
"Source distribution includes: `{:?}`",
"Source distribution includes: {:?}",
include_globs
.iter()
.map(ToString::to_string)
@ -175,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}");
}
@ -211,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)
@ -252,10 +261,12 @@ fn write_source_dist(
.expect("walkdir starts with root");
if !include_matcher.match_path(relative) || exclude_matcher.is_match(relative) {
trace!("Excluding from sdist: `{}`", relative.user_display());
trace!("Excluding from sdist: {}", relative.user_display());
continue;
}
error_on_venv(entry.file_name(), entry.path())?;
let entry_path = Path::new(&top_level)
.join(relative)
.portable_display()
@ -288,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.
@ -301,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

@ -1,10 +1,11 @@
use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD as base64};
use fs_err::File;
use globset::{GlobSet, GlobSetBuilder};
use itertools::Itertools;
use rustc_hash::FxHashSet;
use sha2::{Digest, Sha256};
use std::io::{BufReader, Read, Write};
use std::path::{Path, PathBuf};
use std::path::{Component, Path, PathBuf};
use std::{io, mem};
use tracing::{debug, trace};
use walkdir::WalkDir;
@ -18,7 +19,8 @@ use uv_warnings::warn_user_once;
use crate::metadata::DEFAULT_EXCLUDES;
use crate::{
BuildBackendSettings, DirectoryWriter, Error, FileList, ListWriter, PyProjectToml, find_roots,
BuildBackendSettings, DirectoryWriter, Error, FileList, ListWriter, PyProjectToml,
error_on_venv, find_roots,
};
/// Build a wheel from the source tree and place it in the output directory.
@ -27,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}");
}
@ -56,6 +58,7 @@ pub fn build_wheel(
&filename,
uv_version,
wheel_writer,
show_warnings,
)?;
Ok(filename)
@ -65,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}");
}
@ -85,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))
}
@ -95,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()
@ -130,6 +141,7 @@ fn write_wheel(
&settings.module_root,
settings.module_name.as_ref(),
settings.namespace,
show_warnings,
)?;
let mut files_visited = 0;
@ -175,10 +187,12 @@ fn write_wheel(
.strip_prefix(&src_root)
.expect("walkdir starts with root");
if exclude_matcher.is_match(match_path) {
trace!("Excluding from module: `{}`", match_path.user_display());
trace!("Excluding from module: {}", match_path.user_display());
continue;
}
error_on_venv(entry.file_name(), entry.path())?;
let entry_path = entry_path.portable_display().to_string();
debug!("Adding to wheel: {entry_path}");
wheel_writer.write_dir_entry(&entry, &entry_path)?;
@ -206,7 +220,20 @@ fn write_wheel(
// Add the data files
for (name, directory) in settings.data.iter() {
debug!("Adding {name} data files from: `{directory}`");
debug!(
"Adding {name} data files from: {}",
directory.user_display()
);
if directory
.components()
.next()
.is_some_and(|component| !matches!(component, Component::CurDir | Component::Normal(_)))
{
return Err(Error::InvalidDataRoot {
name: name.to_string(),
path: directory.to_path_buf(),
});
}
let data_dir = format!(
"{}-{}.data/{}/",
pyproject_toml.name().as_dist_info_name(),
@ -242,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}");
}
@ -278,6 +305,7 @@ pub fn build_editable(
&settings.module_root,
settings.module_name.as_ref(),
settings.namespace,
show_warnings,
)?;
wheel_writer.write_bytes(
@ -285,7 +313,7 @@ pub fn build_editable(
src_root.as_os_str().as_encoded_bytes(),
)?;
debug!("Adding metadata files to: `{}`", wheel_path.user_display());
debug!("Adding metadata files to: {}", wheel_path.user_display());
let dist_info_dir = write_dist_info(
&mut wheel_writer,
&pyproject_toml,
@ -304,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}");
}
@ -346,7 +373,7 @@ struct RecordEntry {
///
/// While the spec would allow backslashes, we always use portable paths with forward slashes.
path: String,
/// The SHA256 of the files.
/// The urlsafe-base64-nopad encoded SHA256 of the files.
hash: String,
/// The size of the file in bytes.
size: usize,
@ -381,7 +408,7 @@ fn write_hashed(
}
Ok(RecordEntry {
path: path.to_string(),
hash: format!("{:x}", hasher.finalize()),
hash: base64.encode(hasher.finalize()),
size,
})
}
@ -511,15 +538,17 @@ fn wheel_subdir_from_globs(
.expect("walkdir starts with root");
if !matcher.match_path(relative) {
trace!("Excluding {}: `{}`", globs_field, relative.user_display());
trace!("Excluding {}: {}", globs_field, relative.user_display());
continue;
}
error_on_venv(entry.file_name(), entry.path())?;
let license_path = Path::new(target)
.join(relative)
.portable_display()
.to_string();
debug!("Adding for {}: `{}`", globs_field, relative.user_display());
debug!("Adding for {}: {}", globs_field, relative.user_display());
wheel_writer.write_dir_entry(&entry, &license_path)?;
}
Ok(())
@ -641,7 +670,7 @@ impl DirectoryWriter for ZipDirectoryWriter {
self.writer.start_file(path, options)?;
self.writer.write_all(bytes)?;
let hash = format!("{:x}", Sha256::new().chain_update(bytes).finalize());
let hash = base64.encode(Sha256::new().chain_update(bytes).finalize());
self.record.push(RecordEntry {
path: path.to_string(),
hash,
@ -719,7 +748,7 @@ impl FilesystemWriter {
impl DirectoryWriter for FilesystemWriter {
fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error> {
trace!("Adding {}", path);
let hash = format!("{:x}", Sha256::new().chain_update(bytes).finalize());
let hash = base64.encode(Sha256::new().chain_update(bytes).finalize());
self.record.push(RecordEntry {
path: path.to_string(),
hash,
@ -795,14 +824,14 @@ mod test {
fn test_record() {
let record = vec![RecordEntry {
path: "built_by_uv/__init__.py".to_string(),
hash: "89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865".to_string(),
hash: "ifhp5To6AGGlLAIz5kQtTXLegKii00BtnqC_05fteGU".to_string(),
size: 37,
}];
let mut writer = Vec::new();
write_record(&mut writer, "built_by_uv-0.1.0", record).unwrap();
assert_snapshot!(String::from_utf8(writer).unwrap(), @r"
built_by_uv/__init__.py,sha256=89f869e53a3a0061a52c0233e6442d4d72de80a8a2d3406d9ea0bfd397ed7865,37
built_by_uv/__init__.py,sha256=ifhp5To6AGGlLAIz5kQtTXLegKii00BtnqC_05fteGU,37
built_by_uv-0.1.0/RECORD,,
");
}
@ -811,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())
@ -861,9 +890,9 @@ mod test {
.path()
.join("built_by_uv-0.1.0.dist-info/RECORD");
assert_snapshot!(fs_err::read_to_string(record_file).unwrap(), @r###"
built_by_uv-0.1.0.dist-info/WHEEL,sha256=3da1bfa0e8fd1b6cc246aa0b2b44a35815596c600cb485c39a6f8c106c3d5a8d,83
built_by_uv-0.1.0.dist-info/entry_points.txt,sha256=f883bac9aabac7a1d297ecd61fdeab666818bdfc87947d342f9590a02a73f982,50
built_by_uv-0.1.0.dist-info/METADATA,sha256=9ba12456f2ab1a6ab1e376ff551e392c70f7ec86713d80b4348e90c7dfd45cb1,474
built_by_uv-0.1.0.dist-info/WHEEL,sha256=PaG_oOj9G2zCRqoLK0SjWBVZbGAMtIXDmm-MEGw9Wo0,83
built_by_uv-0.1.0.dist-info/entry_points.txt,sha256=-IO6yaq6x6HSl-zWH96rZmgYvfyHlH00L5WQoCpz-YI,50
built_by_uv-0.1.0.dist-info/METADATA,sha256=m6EkVvKrGmqx43b_VR45LHD37IZxPYC0NI6Qx9_UXLE,474
built_by_uv-0.1.0.dist-info/RECORD,,
"###);

View File

@ -1,11 +1,10 @@
[package]
name = "uv-build-frontend"
version = "0.0.1"
description = "Build wheels from source distributions"
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,13 +16,16 @@ doctest = false
workspace = true
[dependencies]
uv-auth = { workspace = true }
uv-cache-key = { workspace = true }
uv-configuration = { workspace = true }
uv-distribution = { workspace = true }
uv-distribution-types = { workspace = true }
uv-fs = { workspace = true }
uv-normalize = { workspace = true }
uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true }
uv-preview = { workspace = true }
uv-pypi-types = { workspace = true }
uv-python = { workspace = true }
uv-static = { 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

@ -13,8 +13,8 @@ use tracing::error;
use uv_configuration::BuildOutput;
use uv_distribution_types::IsBuildBackendError;
use uv_fs::Simplified;
use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_pep508::PackageName;
use uv_types::AnyErrorBuild;
/// e.g. `pygraphviz/graphviz_wrap.c:3020:10: fatal error: graphviz/cgraph.h: No such file or directory`
@ -46,9 +46,10 @@ static LD_NOT_FOUND_RE: LazyLock<Regex> = LazyLock::new(|| {
static WHEEL_NOT_FOUND_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"error: invalid command 'bdist_wheel'").unwrap());
/// e.g. `ModuleNotFoundError: No module named 'torch'`
static TORCH_NOT_FOUND_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"ModuleNotFoundError: No module named 'torch'").unwrap());
/// e.g. `ModuleNotFoundError`
static MODULE_NOT_FOUND: LazyLock<Regex> = LazyLock::new(|| {
Regex::new("ModuleNotFoundError: No module named ['\"]([^'\"]+)['\"]").unwrap()
});
/// e.g. `ModuleNotFoundError: No module named 'distutils'`
static DISTUTILS_NOT_FOUND_RE: LazyLock<Regex> =
@ -90,6 +91,10 @@ pub enum Error {
NoSourceDistBuilds,
#[error("Cyclic build dependency detected for `{0}`")]
CyclicBuildDependency(PackageName),
#[error(
"Extra build requirement `{0}` was declared with `match-runtime = true`, but `{1}` does not declare static metadata, making runtime-matching impossible"
)]
UnmatchedRuntime(PackageName, PackageName),
}
impl IsBuildBackendError for Error {
@ -105,7 +110,8 @@ impl IsBuildBackendError for Error {
| Self::Virtualenv(_)
| Self::NoSourceDistBuild(_)
| Self::NoSourceDistBuilds
| Self::CyclicBuildDependency(_) => false,
| Self::CyclicBuildDependency(_)
| Self::UnmatchedRuntime(_, _) => false,
Self::CommandFailed(_, _)
| Self::BuildBackend(_)
| Self::MissingHeader(_)
@ -130,6 +136,59 @@ pub struct MissingHeaderCause {
version_id: Option<String>,
}
/// Extract the package name from a version specifier string.
/// Uses PEP 508 naming rules but more lenient for hinting purposes.
fn extract_package_name(version_id: &str) -> &str {
// https://peps.python.org/pep-0508/#names
// ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$ with re.IGNORECASE
// Since we're only using this for a hint, we're more lenient than what we would be doing if this was used for parsing
let end = version_id
.char_indices()
.take_while(|(_, char)| matches!(char, 'A'..='Z' | 'a'..='z' | '0'..='9' | '.' | '-' | '_'))
.last()
.map_or(0, |(i, c)| i + c.len_utf8());
if end == 0 {
version_id
} else {
&version_id[..end]
}
}
/// Write a hint about missing build dependencies.
fn hint_build_dependency(
f: &mut std::fmt::Formatter<'_>,
display_name: &str,
package_name: &str,
package: &str,
) -> std::fmt::Result {
let table_key = if package_name.contains('.') {
format!("\"{package_name}\"")
} else {
package_name.to_string()
};
write!(
f,
"This error likely indicates that `{}` depends on `{}`, but doesn't declare it as a build dependency. \
If `{}` is a first-party package, consider adding `{}` to its `{}`. \
Otherwise, either add it to your `pyproject.toml` under:\n\
\n\
[tool.uv.extra-build-dependencies]\n\
{} = [\"{}\"]\n\
\n\
or `{}` into the environment and re-run with `{}`.",
display_name.cyan(),
package.cyan(),
package_name.cyan(),
package.cyan(),
"build-system.requires".green(),
table_key.cyan(),
package.cyan(),
format!("uv pip install {package}").green(),
"--no-build-isolation".green(),
)
}
impl Display for MissingHeaderCause {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.missing_library {
@ -190,29 +249,15 @@ impl Display for MissingHeaderCause {
if let (Some(package_name), Some(package_version)) =
(&self.package_name, &self.package_version)
{
write!(
hint_build_dependency(
f,
"This error likely indicates that `{}` depends on `{}`, but doesn't declare it as a build dependency. If `{}` is a first-party package, consider adding `{}` to its `{}`. Otherwise, `{}` into the environment and re-run with `{}`.",
format!("{package_name}@{package_version}").cyan(),
package.cyan(),
package_name.cyan(),
package.cyan(),
"build-system.requires".green(),
format!("uv pip install {package}").green(),
"--no-build-isolation".green(),
&format!("{package_name}@{package_version}"),
package_name.as_str(),
package,
)
} else if let Some(version_id) = &self.version_id {
write!(
f,
"This error likely indicates that `{}` depends on `{}`, but doesn't declare it as a build dependency. If `{}` is a first-party package, consider adding `{}` to its `{}`. Otherwise, `{}` into the environment and re-run with `{}`.",
version_id.cyan(),
package.cyan(),
version_id.cyan(),
package.cyan(),
"build-system.requires".green(),
format!("uv pip install {package}").green(),
"--no-build-isolation".green(),
)
let package_name = extract_package_name(version_id);
hint_build_dependency(f, package_name, package_name, package)
} else {
write!(
f,
@ -347,13 +392,22 @@ impl Error {
Some(MissingLibrary::Linker(library.to_string()))
} else if WHEEL_NOT_FOUND_RE.is_match(line.trim()) {
Some(MissingLibrary::BuildDependency("wheel".to_string()))
} else if TORCH_NOT_FOUND_RE.is_match(line.trim()) {
Some(MissingLibrary::BuildDependency("torch".to_string()))
} else if DISTUTILS_NOT_FOUND_RE.is_match(line.trim()) {
Some(MissingLibrary::DeprecatedModule(
"distutils".to_string(),
Version::new([3, 12]),
))
} else if let Some(caps) = MODULE_NOT_FOUND.captures(line.trim()) {
if let Some(module_match) = caps.get(1) {
let module_name = module_match.as_str();
let package_name = match crate::pipreqs::MODULE_MAPPING.lookup(module_name) {
Some(package) => package.to_string(),
None => module_name.to_string(),
};
Some(MissingLibrary::BuildDependency(package_name))
} else {
None
}
} else {
None
}
@ -414,8 +468,8 @@ mod test {
use std::process::ExitStatus;
use std::str::FromStr;
use uv_configuration::BuildOutput;
use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_pep508::PackageName;
#[test]
fn missing_header() {
@ -565,7 +619,7 @@ mod test {
.to_string()
.replace("exit status: ", "exit code: ");
let formatted = anstream::adapter::strip_str(&formatted);
insta::assert_snapshot!(formatted, @r###"
insta::assert_snapshot!(formatted, @r#"
Failed building wheel through setup.py (exit code: 0)
[stderr]
@ -576,8 +630,13 @@ mod test {
error: invalid command 'bdist_wheel'
hint: This error likely indicates that `pygraphviz-1.11` depends on `wheel`, but doesn't declare it as a build dependency. If `pygraphviz-1.11` is a first-party package, consider adding `wheel` to its `build-system.requires`. Otherwise, `uv pip install wheel` into the environment and re-run with `--no-build-isolation`.
"###);
hint: This error likely indicates that `pygraphviz-1.11` depends on `wheel`, but doesn't declare it as a build dependency. If `pygraphviz-1.11` is a first-party package, consider adding `wheel` to its `build-system.requires`. Otherwise, either add it to your `pyproject.toml` under:
[tool.uv.extra-build-dependencies]
"pygraphviz-1.11" = ["wheel"]
or `uv pip install wheel` into the environment and re-run with `--no-build-isolation`.
"#);
}
#[test]

View File

@ -3,6 +3,7 @@
//! <https://packaging.python.org/en/latest/specifications/source-distribution-format/>
mod error;
mod pipreqs;
use std::borrow::Cow;
use std::ffi::OsString;
@ -27,16 +28,19 @@ 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::Preview;
use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy};
use uv_configuration::{BuildKind, BuildOutput, SourceStrategy};
use uv_distribution::BuildRequires;
use uv_distribution_types::{ExtraBuildRequires, IndexLocations, Requirement, Resolution};
use uv_fs::LockedFile;
use uv_distribution_types::{
ConfigSettings, ExtraBuildRequirement, ExtraBuildRequires, IndexLocations, Requirement,
Resolution,
};
use uv_fs::{LockedFile, LockedFileMode};
use uv_fs::{PythonExt, Simplified};
use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_pep508::PackageName;
use uv_preview::Preview;
use uv_pypi_types::VerbatimParsedUrl;
use uv_python::{Interpreter, PythonEnvironment};
use uv_static::EnvVars;
@ -288,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()?;
@ -298,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,
@ -307,7 +311,7 @@ impl SourceBuild {
locations,
source_strategy,
workspace_cache,
&default_backend,
credentials_cache,
)
.await
.map_err(|err| *err)?;
@ -323,13 +327,28 @@ impl SourceBuild {
.or(fallback_package_version)
.cloned();
let extra_build_dependencies: Vec<Requirement> = package_name
let extra_build_dependencies = package_name
.as_ref()
.and_then(|name| extra_build_requires.get(name).cloned())
.unwrap_or_default()
.into_iter()
.map(Requirement::from)
.collect();
.map(|requirement| {
match requirement {
ExtraBuildRequirement {
requirement,
match_runtime: true,
} if requirement.source.is_empty() => {
Err(Error::UnmatchedRuntime(
requirement.name.clone(),
// SAFETY: if `package_name` is `None`, the iterator is empty.
package_name.clone().unwrap(),
))
}
requirement => Ok(requirement),
}
})
.map_ok(Requirement::from)
.collect::<Result<Vec<_>, _>>()?;
// Create a virtual environment, or install into the shared environment if requested.
let venv = if let Some(venv) = build_isolation.shared_environment(package_name.as_ref()) {
@ -340,7 +359,9 @@ impl SourceBuild {
interpreter.clone(),
uv_virtualenv::Prompt::None,
false,
uv_virtualenv::OnExisting::Remove,
uv_virtualenv::OnExisting::Remove(
uv_virtualenv::RemovalReason::TemporaryEnvironment,
),
false,
false,
false,
@ -362,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,
@ -434,6 +454,7 @@ impl SourceBuild {
&environment_variables,
&modified_path,
&temp_dir,
credentials_cache,
)
.await?;
}
@ -472,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)
}
@ -485,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;
@ -499,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())
@ -539,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) => {
@ -568,6 +592,7 @@ impl SourceBuild {
locations,
source_strategy,
workspace_cache,
credentials_cache,
)
.await
.map_err(Error::Lowering)?;
@ -637,7 +662,7 @@ impl SourceBuild {
}
}
default_backend.clone()
DEFAULT_BACKEND.clone()
};
Ok((backend, pyproject_toml.project))
}
@ -653,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())),
}
@ -940,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
@ -1034,6 +1060,7 @@ async fn create_pep517_build_environment(
locations,
source_strategy,
workspace_cache,
credentials_cache,
)
.await
.map_err(Error::Lowering)?;
@ -1140,8 +1167,16 @@ impl PythonRunner {
.envs(environment_variables)
.env(EnvVars::PATH, modified_path)
.env(EnvVars::VIRTUAL_ENV, venv.root())
.env(EnvVars::CLICOLOR_FORCE, "1")
// NOTE: it would be nice to get colored output from build backends,
// but setting CLICOLOR_FORCE=1 changes the output of underlying
// tools, which might mess with wrappers trying to parse their
// output.
.env(EnvVars::PYTHONIOENCODING, "utf-8:backslashreplace")
// Remove potentially-sensitive environment variables.
.env_remove(EnvVars::PYX_API_KEY)
.env_remove(EnvVars::UV_API_KEY)
.env_remove(EnvVars::PYX_AUTH_TOKEN)
.env_remove(EnvVars::UV_AUTH_TOKEN)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()

View File

@ -0,0 +1,32 @@
use std::str::FromStr;
use std::sync::LazyLock;
use rustc_hash::FxHashMap;
use uv_normalize::PackageName;
/// A mapping from module name to PyPI package name.
pub(crate) struct ModuleMap<'a>(FxHashMap<&'a str, PackageName>);
impl<'a> ModuleMap<'a> {
/// Generate a [`ModuleMap`] from a string representation, encoded in `${module}:{package}` format.
fn from_str(source: &'a str) -> Self {
let mut mapping = FxHashMap::default();
for line in source.lines() {
if let Some((module, package)) = line.split_once(':') {
let module = module.trim();
let package = PackageName::from_str(package.trim()).unwrap();
mapping.insert(module, package);
}
}
Self(mapping)
}
/// Look up a PyPI package name for a given module name.
pub(crate) fn lookup(&self, module: &str) -> Option<&PackageName> {
self.0.get(module)
}
}
/// A mapping from module name to PyPI package name.
pub(crate) static MODULE_MAPPING: LazyLock<ModuleMap> =
LazyLock::new(|| ModuleMap::from_str(include_str!("pipreqs/mapping")));

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,22 @@
[package]
name = "uv-build"
version = "0.8.8"
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 }
[dependencies]
uv-build-backend = { workspace = true }
uv-logging = { workspace = true }
uv-version = { workspace = true }
anstream = { workspace = true }
anyhow = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
[lints]
workspace = true

View File

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

View File

@ -7,7 +7,7 @@ def main():
"Use `uv build` or another build frontend instead.",
file=sys.stderr,
)
if "--help" in sys.argv:
if "--help" in sys.argv or "-h" in sys.argv:
sys.exit(0)
else:
sys.exit(1)

View File

@ -1,10 +1,32 @@
use anyhow::{Context, Result, bail};
use std::env;
use std::io::Write;
use std::path::PathBuf;
use anyhow::{Context, Result, bail};
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Layer};
use uv_logging::UvFormat;
/// Entrypoint for the `uv-build` Python package.
fn main() -> Result<()> {
// Support configuring the log level with `RUST_LOG` (shows only the error level by default) and
// color.
//
// This configuration is a simplified version of the uv logging configuration. When using
// uv_build through uv proper, the uv logging configuration applies.
let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::OFF.into())
.from_env()
.context("Invalid RUST_LOG directives")?;
let stderr_layer = tracing_subscriber::fmt::layer()
.event_format(UvFormat::default())
.with_writer(std::sync::Mutex::new(anstream::stderr()))
.with_filter(filter);
tracing_subscriber::registry().with(stderr_layer).init();
// Handrolled to avoid the large clap dependency
let mut args = env::args_os();
// Skip the name of the binary
@ -22,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")?;
@ -34,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")?;
@ -46,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,10 +1,10 @@
[package]
name = "uv-cache-info"
version = "0.0.1"
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 }
@ -16,6 +16,8 @@ doctest = false
workspace = true
[dependencies]
uv-fs = { workspace = true }
fs-err = { workspace = true }
globwalk = { workspace = true }
schemars = { workspace = true, optional = 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,12 @@
use std::borrow::Cow;
use std::cmp::max;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde::Deserialize;
use tracing::{debug, warn};
use uv_fs::Simplified;
use crate::git_info::{Commit, Tags};
use crate::glob::cluster_globs;
use crate::timestamp::Timestamp;
@ -63,7 +64,7 @@ impl CacheInfo {
pub fn from_directory(directory: &Path) -> Result<Self, CacheInfoError> {
let mut commit = None;
let mut tags = None;
let mut timestamp = None;
let mut last_changed: Option<(PathBuf, Timestamp)> = None;
let mut directories = BTreeMap::new();
let mut env = BTreeMap::new();
@ -128,7 +129,12 @@ impl CacheInfo {
);
continue;
}
timestamp = max(timestamp, Some(Timestamp::from_metadata(&metadata)));
let timestamp = Timestamp::from_metadata(&metadata);
if last_changed.as_ref().is_none_or(|(_, prev_timestamp)| {
*prev_timestamp < Timestamp::from_metadata(&metadata)
}) {
last_changed = Some((path, timestamp));
}
}
CacheKey::Directory { dir } => {
// Treat the path as a directory.
@ -258,14 +264,25 @@ impl CacheInfo {
}
continue;
}
timestamp = max(timestamp, Some(Timestamp::from_metadata(&metadata)));
let timestamp = Timestamp::from_metadata(&metadata);
if last_changed.as_ref().is_none_or(|(_, prev_timestamp)| {
*prev_timestamp < Timestamp::from_metadata(&metadata)
}) {
last_changed = Some((entry.into_path(), timestamp));
}
}
}
}
debug!(
"Computed cache info: {timestamp:?}, {commit:?}, {tags:?}, {env:?}, {directories:?}"
);
let timestamp = if let Some((path, timestamp)) = last_changed {
debug!(
"Computed cache info: {timestamp:?}, {commit:?}, {tags:?}, {env:?}, {directories:?}. Most recently modified: {}",
path.user_display()
);
Some(timestamp)
} else {
None
};
Ok(Self {
timestamp,

View File

@ -1,11 +1,10 @@
[package]
name = "uv-cache-key"
version = "0.0.1"
description = "Generic functionality for caching paths, URLs, and other resources across platforms."
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

@ -4,7 +4,7 @@ use std::hash::{Hash, Hasher};
use std::ops::Deref;
use url::Url;
use uv_redacted::DisplaySafeUrl;
use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlError};
use crate::cache_key::{CacheKey, CacheKeyHasher};
@ -98,7 +98,7 @@ impl CanonicalUrl {
Self(url)
}
pub fn parse(url: &str) -> Result<Self, url::ParseError> {
pub fn parse(url: &str) -> Result<Self, DisplaySafeUrlError> {
Ok(Self::new(&DisplaySafeUrl::parse(url)?))
}
}
@ -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, url::ParseError> {
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)
}
}
@ -204,7 +229,7 @@ mod tests {
use super::*;
#[test]
fn user_credential_does_not_affect_cache_key() -> Result<(), url::ParseError> {
fn user_credential_does_not_affect_cache_key() -> Result<(), DisplaySafeUrlError> {
let mut hasher = CacheKeyHasher::new();
CanonicalUrl::parse("https://example.com/pypa/sample-namespace-packages.git@2.0.0")?
.cache_key(&mut hasher);
@ -254,7 +279,7 @@ mod tests {
}
#[test]
fn canonical_url() -> Result<(), url::ParseError> {
fn canonical_url() -> Result<(), DisplaySafeUrlError> {
// Two URLs should be considered equal regardless of the `.git` suffix.
assert_eq!(
CanonicalUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?,
@ -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(
@ -335,7 +368,7 @@ mod tests {
}
#[test]
fn repository_url() -> Result<(), url::ParseError> {
fn repository_url() -> Result<(), DisplaySafeUrlError> {
// Two URLs should be considered equal regardless of the `.git` suffix.
assert_eq!(
RepositoryUrl::parse("git+https://github.com/pypa/sample-namespace-packages.git")?,
@ -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 = "Generate stable hash digests across versions and platforms."
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

@ -7,11 +7,10 @@ use std::str::FromStr;
use std::sync::Arc;
use rustc_hash::FxHashMap;
use tracing::debug;
use tracing::{debug, trace, warn};
pub use archive::ArchiveId;
use uv_cache_info::Timestamp;
use uv_fs::{LockedFile, cachedir, directories};
use uv_fs::{LockedFile, LockedFileError, LockedFileMode, Simplified, cachedir, directories};
use uv_normalize::PackageName;
use uv_pypi_types::ResolutionMetadata;
@ -22,6 +21,7 @@ use crate::removal::Remover;
pub use crate::removal::{Removal, rm_rf};
pub use crate::wheel::WheelCache;
use crate::wheel::WheelCacheKind;
pub use archive::ArchiveId;
mod archive;
mod by_timestamp;
@ -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`].
@ -135,6 +156,8 @@ impl Deref for CacheShard {
}
/// The main cache abstraction.
///
/// While the cache is active, it holds a read (shared) lock that prevents cache cleaning
#[derive(Debug, Clone)]
pub struct Cache {
/// The cache directory.
@ -146,6 +169,9 @@ pub struct Cache {
/// Included to ensure that the temporary directory exists for the length of the operation, but
/// is dropped at the end as appropriate.
temp_dir: Option<Arc<tempfile::TempDir>>,
/// Ensure that `uv cache` operations don't remove items from the cache that are used by another
/// uv process.
lock_file: Option<Arc<LockedFile>>,
}
impl Cache {
@ -155,6 +181,7 @@ impl Cache {
root: root.into(),
refresh: Refresh::None(Timestamp::now()),
temp_dir: None,
lock_file: None,
}
}
@ -165,6 +192,7 @@ impl Cache {
root: temp_dir.path().to_path_buf(),
refresh: Refresh::None(Timestamp::now()),
temp_dir: Some(Arc::new(temp_dir)),
lock_file: None,
})
}
@ -174,6 +202,69 @@ impl Cache {
Self { refresh, ..self }
}
/// Acquire a lock that allows removing entries from the cache.
pub async fn with_exclusive_lock(self) -> Result<Self, LockedFileError> {
let Self {
root,
refresh,
temp_dir,
lock_file,
} = self;
// Release the existing lock, avoid deadlocks from a cloned cache.
if let Some(lock_file) = lock_file {
drop(
Arc::try_unwrap(lock_file).expect(
"cloning the cache before acquiring an exclusive lock causes a deadlock",
),
);
}
let lock_file = LockedFile::acquire(
root.join(".lock"),
LockedFileMode::Exclusive,
root.simplified_display(),
)
.await?;
Ok(Self {
root,
refresh,
temp_dir,
lock_file: Some(Arc::new(lock_file)),
})
}
/// Acquire a lock that allows removing entries from the cache, if available.
///
/// If the lock is not immediately available, returns [`Err`] with self.
pub fn with_exclusive_lock_no_wait(self) -> Result<Self, Self> {
let Self {
root,
refresh,
temp_dir,
lock_file,
} = self;
match LockedFile::acquire_no_wait(
root.join(".lock"),
LockedFileMode::Exclusive,
root.simplified_display(),
) {
Some(lock_file) => Ok(Self {
root,
refresh,
temp_dir,
lock_file: Some(Arc::new(lock_file)),
}),
None => Err(Self {
root,
refresh,
temp_dir,
lock_file,
}),
}
}
/// Return the root of the cache.
pub fn root(&self) -> &Path {
&self.root
@ -310,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)?;
@ -359,21 +448,101 @@ 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(
root.join(".lock"),
LockedFileMode::Shared,
root.simplified_display(),
)
.await
{
Ok(lock_file) => Some(Arc::new(lock_file)),
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`."
);
None
}
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> {
Remover::new(reporter).rm_rf(&self.root)
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
let mut removal = Remover::new(reporter).rm_rf(&self.root, true)?;
let Self {
root, lock_file, ..
} = self;
// Remove the `.lock` file, unlocking it first
if let Some(lock) = lock_file {
drop(lock);
fs_err::remove_file(root.join(".lock"))?;
}
removal.num_files += 1;
// Remove the root directory
match fs_err::remove_dir(root) {
Ok(()) => {
removal.num_dirs += 1;
}
// On Windows, when `--force` is used, the `.lock` file can exist and be unremovable,
// so we make this non-fatal
Err(err) if err.kind() == io::ErrorKind::DirectoryNotEmpty => {
trace!("Failed to remove root cache directory: not empty");
}
Err(err) => return Err(err),
}
Ok(removal)
}
/// 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()?;
@ -407,6 +576,7 @@ impl Cache {
if entry.file_name() == "CACHEDIR.TAG"
|| entry.file_name() == ".gitignore"
|| entry.file_name() == ".git"
|| entry.file_name() == ".lock"
{
continue;
}
@ -987,6 +1157,8 @@ pub enum CacheBucket {
Environments,
/// Cached Python downloads
Python,
/// Downloaded tool binaries (e.g., Ruff).
Binaries,
}
impl CacheBucket {
@ -1000,7 +1172,7 @@ impl CacheBucket {
Self::Interpreter => "interpreter-v4",
// Note that when bumping this, you'll also need to bump it
// in `crates/uv/tests/it/cache_clean.rs`.
Self::Simple => "simple-v16",
Self::Simple => "simple-v18",
// Note that when bumping this, you'll also need to bump it
// in `crates/uv/tests/it/cache_prune.rs`.
Self::Wheels => "wheels-v5",
@ -1010,6 +1182,7 @@ impl CacheBucket {
Self::Builds => "builds-v0",
Self::Environments => "environments-v2",
Self::Python => "python-v0",
Self::Binaries => "binaries-v0",
}
}
@ -1116,7 +1289,8 @@ impl CacheBucket {
| Self::Archive
| Self::Builds
| Self::Environments
| Self::Python => {
| Self::Python
| Self::Binaries => {
// Nothing to do.
}
}
@ -1135,6 +1309,7 @@ impl CacheBucket {
Self::Archive,
Self::Builds,
Self::Environments,
Self::Binaries,
]
.iter()
.copied()
@ -1212,35 +1387,30 @@ impl Refresh {
/// Combine two [`Refresh`] policies, taking the "max" of the two policies.
#[must_use]
pub fn combine(self, other: Self) -> Self {
/// Return the maximum of two timestamps.
fn max(a: Timestamp, b: Timestamp) -> Timestamp {
if a > b { a } else { b }
}
match (self, other) {
// If the policy is `None`, return the existing refresh policy.
// Take the `max` of the two timestamps.
(Self::None(t1), Self::None(t2)) => Self::None(max(t1, t2)),
(Self::None(t1), Self::All(t2)) => Self::All(max(t1, t2)),
(Self::None(t1), Self::None(t2)) => Self::None(t1.max(t2)),
(Self::None(t1), Self::All(t2)) => Self::All(t1.max(t2)),
(Self::None(t1), Self::Packages(packages, paths, t2)) => {
Self::Packages(packages, paths, max(t1, t2))
Self::Packages(packages, paths, t1.max(t2))
}
// If the policy is `All`, refresh all packages.
(Self::All(t1), Self::None(t2)) => Self::All(max(t1, t2)),
(Self::All(t1), Self::All(t2)) => Self::All(max(t1, t2)),
(Self::All(t1), Self::Packages(.., t2)) => Self::All(max(t1, t2)),
(Self::All(t1), Self::None(t2) | Self::All(t2) | Self::Packages(.., t2)) => {
Self::All(t1.max(t2))
}
// If the policy is `Packages`, take the "max" of the two policies.
(Self::Packages(packages, paths, t1), Self::None(t2)) => {
Self::Packages(packages, paths, max(t1, t2))
Self::Packages(packages, paths, t1.max(t2))
}
(Self::Packages(.., t1), Self::All(t2)) => Self::All(max(t1, t2)),
(Self::Packages(.., t1), Self::All(t2)) => Self::All(t1.max(t2)),
(Self::Packages(packages1, paths1, t1), Self::Packages(packages2, paths2, t2)) => {
Self::Packages(
packages1.into_iter().chain(packages2).collect(),
paths1.into_iter().chain(paths2).collect(),
max(t1, t2),
t1.max(t2),
)
}
}

View File

@ -10,7 +10,7 @@ use crate::CleanReporter;
/// Remove a file or directory and all its contents, returning a [`Removal`] with
/// the number of files and directories removed, along with a total byte count.
pub fn rm_rf(path: impl AsRef<Path>) -> io::Result<Removal> {
Remover::default().rm_rf(path)
Remover::default().rm_rf(path, false)
}
/// A builder for a [`Remover`] that can remove files and directories.
@ -29,9 +29,13 @@ impl Remover {
/// Remove a file or directory and all its contents, returning a [`Removal`] with
/// the number of files and directories removed, along with a total byte count.
pub(crate) fn rm_rf(&self, path: impl AsRef<Path>) -> io::Result<Removal> {
pub(crate) fn rm_rf(
&self,
path: impl AsRef<Path>,
skip_locked_file: bool,
) -> io::Result<Removal> {
let mut removal = Removal::default();
removal.rm_rf(path.as_ref(), self.reporter.as_deref())?;
removal.rm_rf(path.as_ref(), self.reporter.as_deref(), skip_locked_file)?;
Ok(removal)
}
}
@ -52,7 +56,12 @@ pub struct Removal {
impl Removal {
/// Recursively remove a file or directory and all its contents.
fn rm_rf(&mut self, path: &Path, reporter: Option<&dyn CleanReporter>) -> io::Result<()> {
fn rm_rf(
&mut self,
path: &Path,
reporter: Option<&dyn CleanReporter>,
skip_locked_file: bool,
) -> io::Result<()> {
let metadata = match fs_err::symlink_metadata(path) {
Ok(metadata) => metadata,
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(()),
@ -64,9 +73,22 @@ impl Removal {
// Remove the file.
self.total_bytes += metadata.len();
if cfg!(windows) && metadata.is_symlink() {
// Remove the junction.
remove_dir(path)?;
if metadata.is_symlink() {
#[cfg(windows)]
{
use std::os::windows::fs::FileTypeExt;
if metadata.file_type().is_symlink_dir() {
remove_dir(path)?;
} else {
remove_file(path)?;
}
}
#[cfg(not(windows))]
{
remove_file(path)?;
}
} else {
remove_file(path)?;
}
@ -87,18 +109,44 @@ impl Removal {
if set_readable(dir).unwrap_or(false) {
// Retry the operation; if we _just_ `self.rm_rf(dir)` and continue,
// `walkdir` may give us duplicate entries for the directory.
return self.rm_rf(path, reporter);
return self.rm_rf(path, reporter, skip_locked_file);
}
}
}
}
let entry = entry?;
if cfg!(windows) && entry.file_type().is_symlink() {
// Remove the junction.
// Remove the exclusive lock last.
if skip_locked_file
&& entry.file_name() == ".lock"
&& entry
.path()
.strip_prefix(path)
.is_ok_and(|suffix| suffix == Path::new(".lock"))
{
continue;
}
if entry.file_type().is_symlink() && {
#[cfg(windows)]
{
use std::os::windows::fs::FileTypeExt;
entry.file_type().is_symlink_dir()
}
#[cfg(not(windows))]
{
false
}
} {
self.num_files += 1;
remove_dir(entry.path())?;
} else if entry.file_type().is_dir() {
// Remove the directory with the exclusive lock last.
if skip_locked_file && entry.path() == path {
continue;
}
self.num_dirs += 1;
// The contents should have been removed by now, but sometimes a race condition is

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 = "The command line interface for the uv binary."
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,12 +16,14 @@ doctest = false
workspace = true
[dependencies]
uv-auth = { workspace = true }
uv-cache = { workspace = true, features = ["clap"] }
uv-configuration = { workspace = true, features = ["clap"] }
uv-distribution-types = { workspace = true }
uv-install-wheel = { workspace = true, features = ["clap"], default-features = false }
uv-normalize = { workspace = true }
uv-pep508 = { workspace = true }
uv-preview = { workspace = true }
uv-pypi-types = { workspace = true }
uv-python = { workspace = true, features = ["clap", "schemars"]}
uv-redacted = { 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

@ -1,7 +1,8 @@
use anstream::eprintln;
use uv_cache::Refresh;
use uv_configuration::{ConfigSettings, PackageConfigSettings};
use uv_configuration::{BuildIsolation, Reinstall, Upgrade};
use uv_distribution_types::{ConfigSettings, PackageConfigSettings, Requirement};
use uv_resolver::{ExcludeNewer, ExcludeNewerPackage, PrereleaseMode};
use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};
use uv_warnings::owo_colors::OwoColorize;
@ -333,8 +334,10 @@ pub fn resolver_options(
.filter_map(Maybe::into_option)
.collect()
}),
upgrade: flag(upgrade, no_upgrade, "no-upgrade"),
upgrade_package: Some(upgrade_package),
upgrade: Upgrade::from_args(
flag(upgrade, no_upgrade, "no-upgrade"),
upgrade_package.into_iter().map(Requirement::from).collect(),
),
index_strategy,
keyring_provider,
resolution,
@ -352,8 +355,10 @@ pub fn resolver_options(
.into_iter()
.collect::<PackageConfigSettings>()
}),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: Some(no_build_isolation_package),
build_isolation: BuildIsolation::from_args(
flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package,
),
extra_build_dependencies: None,
extra_build_variables: None,
exclude_newer: ExcludeNewer::from_args(
@ -361,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"),
@ -442,18 +448,14 @@ pub fn resolver_installer_options(
.filter_map(Maybe::into_option)
.collect()
}),
upgrade: flag(upgrade, no_upgrade, "upgrade"),
upgrade_package: if upgrade_package.is_empty() {
None
} else {
Some(upgrade_package)
},
reinstall: flag(reinstall, no_reinstall, "reinstall"),
reinstall_package: if reinstall_package.is_empty() {
None
} else {
Some(reinstall_package)
},
upgrade: Upgrade::from_args(
flag(upgrade, no_upgrade, "upgrade"),
upgrade_package.into_iter().map(Requirement::from).collect(),
),
reinstall: Reinstall::from_args(
flag(reinstall, no_reinstall, "reinstall"),
reinstall_package,
),
index_strategy,
keyring_provider,
resolution,
@ -471,12 +473,10 @@ pub fn resolver_installer_options(
.into_iter()
.collect::<PackageConfigSettings>()
}),
no_build_isolation: flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package: if no_build_isolation_package.is_empty() {
None
} else {
Some(no_build_isolation_package)
},
build_isolation: BuildIsolation::from_args(
flag(no_build_isolation, build_isolation, "build-isolation"),
no_build_isolation_package,
),
extra_build_dependencies: None,
extra_build_variables: None,
exclude_newer,
@ -496,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

@ -3,7 +3,9 @@
use std::fmt;
use serde::Serialize;
use uv_pep508::{PackageName, uv_pep440::Version};
use uv_normalize::PackageName;
use uv_pep508::uv_pep440::Version;
/// Information about the git repository where uv was built from.
#[derive(Serialize)]

View File

@ -1,7 +1,13 @@
[package]
name = "uv-client"
version = "0.0.1"
version = "0.0.8"
description = "This is an internal component crate of uv"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
[lib]
doctest = false
@ -22,6 +28,7 @@ uv-normalize = { workspace = true }
uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true }
uv-platform-tags = { workspace = true }
uv-preview = { workspace = true }
uv-pypi-types = { workspace = true }
uv-small-str = { workspace = true }
uv-redacted = { workspace = true }
@ -31,12 +38,14 @@ uv-version = { workspace = true }
uv-warnings = { workspace = true }
anyhow = { workspace = true }
astral-tl = { workspace = true }
async-trait = { workspace = true }
async_http_range_reader = { workspace = true }
async_zip = { workspace = true }
bytecheck = { workspace = true }
fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true }
h2 = { workspace = true }
html-escape = { workspace = true }
http = { workspace = true }
itertools = { workspace = true }
@ -52,7 +61,6 @@ serde = { workspace = true }
serde_json = { workspace = true }
sys-info = { workspace = true }
thiserror = { workspace = true }
tl = { workspace = true }
tokio = { workspace = true }
tokio-util = { workspace = true }
tracing = { workspace = true }
@ -64,5 +72,9 @@ http-body-util = { workspace = true }
hyper = { workspace = true }
hyper-util = { workspace = true }
insta = { workspace = true }
rcgen = { workspace = true }
rustls = { workspace = true }
tokio = { workspace = true }
tokio-rustls = { workspace = true }
wiremock = { workspace = true }
tempfile = { workspace = true }

View File

@ -1,5 +1,13 @@
# `pypi-client`
<!-- This file is generated. DO NOT EDIT -->
A general-use client for interacting with PyPI.
# uv-client
Loosely modeled after Orogene's `oro-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

@ -21,35 +21,36 @@ use reqwest_middleware::{ClientWithMiddleware, Middleware};
use reqwest_retry::policies::ExponentialBackoff;
use reqwest_retry::{
DefaultRetryableStrategy, RetryTransientMiddleware, Retryable, RetryableStrategy,
default_on_request_error,
};
use thiserror::Error;
use tracing::{debug, trace};
use url::ParseError;
use url::Url;
use uv_auth::Credentials;
use uv_auth::{AuthMiddleware, Indexes};
use uv_auth::{AuthMiddleware, Credentials, CredentialsCache, Indexes, PyxTokenStore};
use uv_configuration::{KeyringProviderType, TrustedHost};
use uv_fs::Simplified;
use uv_pep508::MarkerEnvironment;
use uv_platform_tags::Platform;
use uv_preview::Preview;
use uv_redacted::DisplaySafeUrl;
use uv_redacted::DisplaySafeUrlError;
use uv_static::EnvVars;
use uv_version::version;
use uv_warnings::warn_user_once;
use crate::Connectivity;
use crate::linehaul::LineHaul;
use crate::middleware::OfflineMiddleware;
use crate::tls::read_identity;
use crate::{Connectivity, WrappedReqwestError};
/// Do not use this value directly outside tests, use [`retries_from_env`] instead.
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)]
@ -68,6 +69,7 @@ pub enum AuthIntegration {
#[derive(Debug, Clone)]
pub struct BaseClientBuilder<'a> {
keyring: KeyringProviderType,
preview: Preview,
allow_insecure_host: Vec<TrustedHost>,
native_tls: bool,
built_in_root_certs: bool,
@ -76,8 +78,10 @@ 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,
default_timeout: Duration,
timeout: Duration,
extra_middleware: Option<ExtraMiddleware>,
proxies: Vec<Proxy>,
redirect_policy: RedirectPolicy,
@ -85,6 +89,10 @@ pub struct BaseClientBuilder<'a> {
///
/// A policy allowing propagation is insecure and should only be available for test code.
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.
@ -96,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 {
@ -103,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(),
}
}
}
@ -121,14 +132,9 @@ impl Debug for ExtraMiddleware {
impl Default for BaseClientBuilder<'_> {
fn default() -> Self {
Self::new()
}
}
impl BaseClientBuilder<'_> {
pub fn new() -> Self {
Self {
keyring: KeyringProviderType::default(),
preview: Preview::default(),
allow_insecure_host: vec![],
native_tls: false,
built_in_root_certs: false,
@ -137,17 +143,50 @@ impl BaseClientBuilder<'_> {
markers: None,
platform: None,
auth_integration: AuthIntegration::default(),
credentials_cache: Arc::new(CredentialsCache::default()),
indexes: Indexes::new(),
default_timeout: Duration::from_secs(30),
timeout: Duration::from_secs(30),
extra_middleware: None,
proxies: vec![],
redirect_policy: RedirectPolicy::default(),
cross_origin_credential_policy: CrossOriginCredentialsPolicy::Secure,
custom_client: None,
subcommand: None,
}
}
}
impl<'a> BaseClientBuilder<'a> {
pub fn new(
connectivity: Connectivity,
native_tls: bool,
allow_insecure_host: Vec<TrustedHost>,
preview: Preview,
timeout: Duration,
retries: u32,
) -> Self {
Self {
preview,
allow_insecure_host,
native_tls,
retries,
connectivity,
timeout,
..Self::default()
}
}
/// Use a custom reqwest client instead of creating a new one.
///
/// This allows you to provide your own reqwest client with custom configuration.
/// Note that some configuration options from this builder will still be applied
/// to the client via middleware.
#[must_use]
pub fn custom_client(mut self, client: Client) -> Self {
self.custom_client = Some(client);
self
}
#[must_use]
pub fn keyring(mut self, keyring_type: KeyringProviderType) -> Self {
self.keyring = keyring_type;
@ -172,15 +211,6 @@ impl<'a> BaseClientBuilder<'a> {
self
}
/// Read the retry count from [`EnvVars::UV_HTTP_RETRIES`] if set, otherwise use the default
/// retries.
///
/// Errors when [`EnvVars::UV_HTTP_RETRIES`] is not a valid u32.
pub fn retries_from_env(mut self) -> Result<Self, RetryParsingError> {
self.retries = retries_from_env()?;
Ok(self)
}
#[must_use]
pub fn native_tls(mut self, native_tls: bool) -> Self {
self.native_tls = native_tls;
@ -218,8 +248,8 @@ impl<'a> BaseClientBuilder<'a> {
}
#[must_use]
pub fn default_timeout(mut self, default_timeout: Duration) -> Self {
self.default_timeout = default_timeout;
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
@ -253,12 +283,36 @@ 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
}
pub fn is_offline(&self) -> bool {
matches!(self.connectivity, Connectivity::Offline)
}
/// Create a [`RetryPolicy`] for the client.
fn retry_policy(&self) -> ExponentialBackoff {
pub fn retry_policy(&self) -> ExponentialBackoff {
let mut builder = ExponentialBackoff::builder();
if env::var_os(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY).is_some() {
builder = builder.retry_bounds(Duration::from_millis(0), Duration::from_millis(0));
@ -267,63 +321,14 @@ impl<'a> BaseClientBuilder<'a> {
}
pub fn build(&self) -> BaseClient {
// Create user agent.
let mut user_agent_string = format!("uv/{}", version());
// Add linehaul metadata.
if let Some(markers) = self.markers {
let linehaul = LineHaul::new(markers, self.platform);
if let Ok(output) = serde_json::to_string(&linehaul) {
let _ = write!(user_agent_string, " {output}");
}
}
// Check for the presence of an `SSL_CERT_FILE`.
let ssl_cert_file_exists = env::var_os(EnvVars::SSL_CERT_FILE).is_some_and(|path| {
let path_exists = Path::new(&path).exists();
if !path_exists {
warn_user_once!(
"Ignoring invalid `SSL_CERT_FILE`. File does not exist: {}.",
path.simplified_display().cyan()
);
}
path_exists
});
// Timeout options, matching https://doc.rust-lang.org/nightly/cargo/reference/config.html#httptimeout
// `UV_REQUEST_TIMEOUT` is provided for backwards compatibility with v0.1.6
let timeout = env::var(EnvVars::UV_HTTP_TIMEOUT)
.or_else(|_| env::var(EnvVars::UV_REQUEST_TIMEOUT))
.or_else(|_| env::var(EnvVars::HTTP_TIMEOUT))
.and_then(|value| {
value.parse::<u64>()
.map(Duration::from_secs)
.or_else(|_| {
// On parse error, warn and use the default timeout
warn_user_once!("Ignoring invalid value from environment for `UV_HTTP_TIMEOUT`. Expected an integer number of seconds, got \"{value}\".");
Ok(self.default_timeout)
})
})
.unwrap_or(self.default_timeout);
let timeout = self.timeout;
debug!("Using request timeout of {}s", timeout.as_secs());
// Create a secure client that validates certificates.
let raw_client = self.create_client(
&user_agent_string,
timeout,
ssl_cert_file_exists,
Security::Secure,
self.redirect_policy,
);
// Create an insecure client that accepts invalid certificates.
let raw_dangerous_client = self.create_client(
&user_agent_string,
timeout,
ssl_cert_file_exists,
Security::Insecure,
self.redirect_policy,
);
// Use the custom client if provided, otherwise create a new one
let (raw_client, raw_dangerous_client) = match &self.custom_client {
Some(client) => (client.clone(), client.clone()),
None => self.create_secure_and_insecure_clients(timeout),
};
// Wrap in any relevant middleware and handle connectivity.
let client = RedirectClientWithMiddleware {
@ -346,6 +351,7 @@ impl<'a> BaseClientBuilder<'a> {
dangerous_client,
raw_dangerous_client,
timeout,
credentials_cache: self.credentials_cache.clone(),
}
}
@ -372,14 +378,112 @@ 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(),
}
}
fn create_secure_and_insecure_clients(&self, timeout: Duration) -> (Client, Client) {
// Create user agent.
let mut user_agent_string = format!("uv/{}", version());
// Add linehaul metadata.
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}");
}
// Checks for the presence of `SSL_CERT_FILE`.
// Certificate loading support is delegated to `rustls-native-certs`.
// See https://github.com/rustls/rustls-native-certs/blob/813790a297ad4399efe70a8e5264ca1b420acbec/src/lib.rs#L118-L125
let ssl_cert_file_exists = env::var_os(EnvVars::SSL_CERT_FILE).is_some_and(|path| {
let path_exists = Path::new(&path).exists();
if !path_exists {
warn_user_once!(
"Ignoring invalid `SSL_CERT_FILE`. File does not exist: {}.",
path.simplified_display().cyan()
);
}
path_exists
});
// Checks for the presence of `SSL_CERT_DIR`.
// Certificate loading support is delegated to `rustls-native-certs`.
// See https://github.com/rustls/rustls-native-certs/blob/813790a297ad4399efe70a8e5264ca1b420acbec/src/lib.rs#L118-L125
let ssl_cert_dir_exists = env::var_os(EnvVars::SSL_CERT_DIR)
.filter(|v| !v.is_empty())
.is_some_and(|dirs| {
// Parse `SSL_CERT_DIR`, with support for multiple entries using
// a platform-specific delimiter (`:` on Unix, `;` on Windows)
let (existing, missing): (Vec<_>, Vec<_>) =
env::split_paths(&dirs).partition(|p| p.exists());
if existing.is_empty() {
let end_note = if missing.len() == 1 {
"The directory does not exist."
} else {
"The entries do not exist."
};
warn_user_once!(
"Ignoring invalid `SSL_CERT_DIR`. {end_note}: {}.",
missing
.iter()
.map(Simplified::simplified_display)
.join(", ")
.cyan()
);
return false;
}
// Warn on any missing entries
if !missing.is_empty() {
let end_note = if missing.len() == 1 {
"The following directory does not exist:"
} else {
"The following entries do not exist:"
};
warn_user_once!(
"Invalid entries in `SSL_CERT_DIR`. {end_note}: {}.",
missing
.iter()
.map(Simplified::simplified_display)
.join(", ")
.cyan()
);
}
// Proceed while ignoring missing entries
true
});
// Create a secure client that validates certificates.
let raw_client = self.create_client(
&user_agent_string,
timeout,
ssl_cert_file_exists,
ssl_cert_dir_exists,
Security::Secure,
self.redirect_policy,
);
// Create an insecure client that accepts invalid certificates.
let raw_dangerous_client = self.create_client(
&user_agent_string,
timeout,
ssl_cert_file_exists,
ssl_cert_dir_exists,
Security::Insecure,
self.redirect_policy,
);
(raw_client, raw_dangerous_client)
}
fn create_client(
&self,
user_agent: &str,
timeout: Duration,
ssl_cert_file_exists: bool,
ssl_cert_dir_exists: bool,
security: Security,
redirect_policy: RedirectPolicy,
) -> Client {
@ -398,7 +502,7 @@ impl<'a> BaseClientBuilder<'a> {
Security::Insecure => client_builder.danger_accept_invalid_certs(true),
};
let client_builder = if self.native_tls || ssl_cert_file_exists {
let client_builder = if self.native_tls || ssl_cert_file_exists || ssl_cert_dir_exists {
client_builder.tls_built_in_native_certs(true)
} else {
client_builder.tls_built_in_webpki_certs(true)
@ -432,6 +536,30 @@ impl<'a> BaseClientBuilder<'a> {
fn apply_middleware(&self, client: Client) -> ClientWithMiddleware {
match self.connectivity {
Connectivity::Online => {
// Create a base client to using in the authentication middleware.
let base_client = {
let mut client = reqwest_middleware::ClientBuilder::new(client.clone());
// Avoid uncloneable errors with a streaming body during publish.
if self.retries > 0 {
// Initialize the retry strategy.
let retry_strategy = RetryTransientMiddleware::new_with_policy_and_strategy(
self.retry_policy(),
UvRetryableStrategy,
);
client = client.with(retry_strategy);
}
// When supplied, add the extra middleware.
if let Some(extra_middleware) = &self.extra_middleware {
for middleware in &extra_middleware.0 {
client = client.with_arc(middleware.clone());
}
}
client.build()
};
let mut client = reqwest_middleware::ClientBuilder::new(client);
// Avoid uncloneable errors with a streaming body during publish.
@ -444,20 +572,38 @@ impl<'a> BaseClientBuilder<'a> {
client = client.with(retry_strategy);
}
// When supplied, add the extra middleware.
if let Some(extra_middleware) = &self.extra_middleware {
for middleware in &extra_middleware.0 {
client = client.with_arc(middleware.clone());
}
}
// Initialize the authentication middleware to set headers.
match self.auth_integration {
AuthIntegration::Default => {
let auth_middleware = AuthMiddleware::new()
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());
.with_keyring(self.keyring.to_provider())
.with_preview(self.preview);
if let Ok(token_store) = PyxTokenStore::from_settings() {
auth_middleware = auth_middleware.with_pyx_token_store(token_store);
}
client = client.with(auth_middleware);
}
AuthIntegration::OnlyAuthenticated => {
let auth_middleware = AuthMiddleware::new()
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())
.with_preview(self.preview)
.with_only_authenticated(true);
if let Ok(token_store) = PyxTokenStore::from_settings() {
auth_middleware = auth_middleware.with_pyx_token_store(token_store);
}
client = client.with(auth_middleware);
}
AuthIntegration::NoAuthMiddleware => {
@ -465,13 +611,6 @@ impl<'a> BaseClientBuilder<'a> {
}
}
// When supplied add the extra middleware
if let Some(extra_middleware) = &self.extra_middleware {
for middleware in &extra_middleware.0 {
client = client.with_arc(middleware.clone());
}
}
client.build()
}
Connectivity::Offline => reqwest_middleware::ClientBuilder::new(client)
@ -500,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)]
@ -522,7 +663,7 @@ impl BaseClient {
/// Executes a request, applying redirect policy.
pub async fn execute(&self, req: Request) -> reqwest_middleware::Result<Response> {
let client = self.for_host(&DisplaySafeUrl::from(req.url().clone()));
let client = self.for_host(&DisplaySafeUrl::from_url(req.url().clone()));
client.execute(req).await
}
@ -545,7 +686,15 @@ impl BaseClient {
/// The [`RetryPolicy`] for the client.
pub fn retry_policy(&self) -> ExponentialBackoff {
ExponentialBackoff::builder().build_with_max_retries(self.retries)
let mut builder = ExponentialBackoff::builder();
if env::var_os(EnvVars::UV_TEST_NO_HTTP_RETRY_DELAY).is_some() {
builder = builder.retry_bounds(Duration::from_millis(0), Duration::from_millis(0));
}
builder.build_with_max_retries(self.retries)
}
pub fn credentials_cache(&self) -> &CredentialsCache {
&self.credentials_cache
}
}
@ -583,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,
}
}
@ -648,7 +798,7 @@ fn request_into_redirect(
res: &Response,
cross_origin_credentials_policy: CrossOriginCredentialsPolicy,
) -> reqwest_middleware::Result<Option<Request>> {
let original_req_url = DisplaySafeUrl::from(req.url().clone());
let original_req_url = DisplaySafeUrl::from_url(req.url().clone());
let status = res.status();
let should_redirect = match status {
StatusCode::MOVED_PERMANENTLY
@ -701,7 +851,7 @@ fn request_into_redirect(
let mut redirect_url = match DisplaySafeUrl::parse(location) {
Ok(url) => url,
// Per RFC 7231, URLs should be resolved against the request URL.
Err(ParseError::RelativeUrlWithoutBase) => original_req_url.join(location).map_err(|err| {
Err(DisplaySafeUrlError::Url(ParseError::RelativeUrlWithoutBase)) => original_req_url.join(location).map_err(|err| {
reqwest_middleware::Error::Middleware(anyhow!(
"Invalid HTTP {status} 'Location' value `{location}` relative to `{original_req_url}`: {err}"
))
@ -879,7 +1029,7 @@ impl RetryableStrategy for UvRetryableStrategy {
None | Some(Retryable::Fatal)
if res
.as_ref()
.is_err_and(|err| is_extended_transient_error(err)) =>
.is_err_and(|err| is_transient_network_error(err)) =>
{
Some(Retryable::Transient)
}
@ -907,12 +1057,15 @@ impl RetryableStrategy for UvRetryableStrategy {
}
}
/// Check for additional transient error kinds not supported by the default retry strategy in `reqwest_retry`.
/// Whether the error looks like a network error that should be retried.
///
/// These cases should be safe to retry with [`Retryable::Transient`].
pub fn is_extended_transient_error(err: &dyn Error) -> bool {
/// There are two cases that the default retry strategy is missing:
/// * Inside the reqwest or reqwest-middleware error is an `io::Error` such as a broken pipe
/// * When streaming a response, a reqwest error may be hidden several layers behind errors
/// of different crates processing the stream, including `io::Error` layers.
pub fn is_transient_network_error(err: &(dyn Error + 'static)) -> bool {
// First, try to show a nice trace log
if let Some((Some(status), Some(url))) = find_source::<crate::WrappedReqwestError>(&err)
if let Some((Some(status), Some(url))) = find_source::<WrappedReqwestError>(&err)
.map(|request_err| (request_err.status(), request_err.url()))
{
trace!("Considering retry of response HTTP {status} for {url}");
@ -920,38 +1073,88 @@ pub fn is_extended_transient_error(err: &dyn Error) -> bool {
trace!("Considering retry of error: {err:?}");
}
// IO Errors may be nested through custom IO errors.
let mut has_io_error = false;
for io_err in find_sources::<io::Error>(&err) {
has_io_error = true;
let retryable_io_err_kinds = [
// https://github.com/astral-sh/uv/issues/12054
io::ErrorKind::BrokenPipe,
// From reqwest-middleware
io::ErrorKind::ConnectionAborted,
// https://github.com/astral-sh/uv/issues/3514
io::ErrorKind::ConnectionReset,
// https://github.com/astral-sh/uv/issues/14699
io::ErrorKind::InvalidData,
// https://github.com/astral-sh/uv/issues/9246
io::ErrorKind::UnexpectedEof,
];
if retryable_io_err_kinds.contains(&io_err.kind()) {
trace!("Retrying error: `{}`", io_err.kind());
let mut has_known_error = false;
// IO Errors or reqwest errors may be nested through custom IO errors or stream processing
// crates
let mut current_source = Some(err);
while let Some(source) = current_source {
if let Some(reqwest_err) = source.downcast_ref::<WrappedReqwestError>() {
has_known_error = true;
if let reqwest_middleware::Error::Reqwest(reqwest_err) = &**reqwest_err {
if default_on_request_error(reqwest_err) == Some(Retryable::Transient) {
trace!("Retrying nested reqwest middleware error");
return true;
}
if is_retryable_status_error(reqwest_err) {
trace!("Retrying nested reqwest middleware status code error");
return true;
}
}
trace!("Cannot retry nested reqwest middleware error");
} else if let Some(reqwest_err) = source.downcast_ref::<reqwest::Error>() {
has_known_error = true;
if default_on_request_error(reqwest_err) == Some(Retryable::Transient) {
trace!("Retrying nested reqwest error");
return true;
}
if is_retryable_status_error(reqwest_err) {
trace!("Retrying nested reqwest status code error");
return true;
}
trace!("Cannot retry nested reqwest error");
} else if source.downcast_ref::<h2::Error>().is_some() {
// All h2 errors look like errors that should be retried
// https://github.com/astral-sh/uv/issues/15916
trace!("Retrying nested h2 error");
return true;
} else if let Some(io_err) = source.downcast_ref::<io::Error>() {
has_known_error = true;
let retryable_io_err_kinds = [
// https://github.com/astral-sh/uv/issues/12054
io::ErrorKind::BrokenPipe,
// From reqwest-middleware
io::ErrorKind::ConnectionAborted,
// https://github.com/astral-sh/uv/issues/3514
io::ErrorKind::ConnectionReset,
// https://github.com/astral-sh/uv/issues/14699
io::ErrorKind::InvalidData,
// https://github.com/astral-sh/uv/issues/9246
io::ErrorKind::UnexpectedEof,
];
if retryable_io_err_kinds.contains(&io_err.kind()) {
trace!("Retrying error: `{}`", io_err.kind());
return true;
}
trace!(
"Cannot retry IO error `{}`, not a retryable IO error kind",
io_err.kind()
);
}
trace!(
"Cannot retry IO error `{}`, not a retryable IO error kind",
io_err.kind()
);
current_source = source.source();
}
if !has_io_error {
trace!("Cannot retry error: not an extended IO error");
if !has_known_error {
trace!("Cannot retry error: Neither an IO error nor a reqwest error");
}
false
}
/// Whether the error is a status code error that is retryable.
///
/// Port of `reqwest_retry::default_on_request_success`.
fn is_retryable_status_error(reqwest_err: &reqwest::Error) -> bool {
let Some(status) = reqwest_err.status() else {
return false;
};
status.is_server_error()
|| status == StatusCode::REQUEST_TIMEOUT
|| status == StatusCode::TOO_MANY_REQUESTS
}
/// Find the first source error of a specific type.
///
/// See <https://github.com/seanmonstar/reqwest/issues/1602#issuecomment-1220996681>
@ -966,15 +1169,6 @@ fn find_source<E: Error + 'static>(orig: &dyn Error) -> Option<&E> {
None
}
/// Return all errors in the chain of a specific type.
///
/// This handles cases such as nested `io::Error`s.
///
/// See <https://github.com/seanmonstar/reqwest/issues/1602#issuecomment-1220996681>
fn find_sources<E: Error + 'static>(orig: &dyn Error) -> impl Iterator<Item = &E> {
iter::successors(find_source::<E>(orig), |&err| find_source(err))
}
// TODO(konsti): Remove once we find a native home for `retries_from_env`
#[derive(Debug, Error)]
pub enum RetryParsingError {
@ -982,26 +1176,14 @@ pub enum RetryParsingError {
ParseInt(#[from] ParseIntError),
}
/// Read the retry count from [`EnvVars::UV_HTTP_RETRIES`] if set, otherwise, make no change.
///
/// Errors when [`EnvVars::UV_HTTP_RETRIES`] is not a valid u32.
pub fn retries_from_env() -> Result<u32, RetryParsingError> {
// TODO(zanieb): We should probably parse this in another layer, but there's not a natural
// fit for it right now
if let Some(value) = env::var_os(EnvVars::UV_HTTP_RETRIES) {
Ok(value.to_string_lossy().as_ref().parse::<u32>()?)
} else {
Ok(DEFAULT_RETRIES)
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
use anyhow::Result;
use insta::assert_debug_snapshot;
use reqwest::{Client, Method};
use wiremock::matchers::method;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use crate::base_client::request_into_redirect;
@ -1194,4 +1376,71 @@ mod tests {
Ok(())
}
/// Enumerate which status codes we are retrying.
#[tokio::test]
async fn retried_status_codes() -> Result<()> {
let server = MockServer::start().await;
let client = Client::default();
let middleware_client = ClientWithMiddleware::default();
let mut retried = Vec::new();
for status in 100..599 {
// Test all standard status codes and and example for a non-RFC code used in the wild.
if StatusCode::from_u16(status)?.canonical_reason().is_none() && status != 420 {
continue;
}
Mock::given(path(format!("/{status}")))
.respond_with(ResponseTemplate::new(status))
.mount(&server)
.await;
let response = middleware_client
.get(format!("{}/{}", server.uri(), status))
.send()
.await;
let middleware_retry =
DefaultRetryableStrategy.handle(&response) == Some(Retryable::Transient);
let response = client
.get(format!("{}/{}", server.uri(), status))
.send()
.await?;
let uv_retry = match response.error_for_status() {
Ok(_) => false,
Err(err) => is_transient_network_error(&err),
};
// Ensure we're retrying the same status code as the reqwest_retry crate. We may choose
// to deviate from this later.
assert_eq!(middleware_retry, uv_retry);
if uv_retry {
retried.push(status);
}
}
assert_debug_snapshot!(retried, @r"
[
100,
102,
408,
429,
500,
501,
502,
503,
504,
505,
506,
507,
508,
510,
511,
]
");
Ok(())
}
}

View File

@ -14,13 +14,33 @@ use uv_fs::write_atomic;
use uv_redacted::DisplaySafeUrl;
use crate::BaseClient;
use crate::base_client::is_extended_transient_error;
use crate::base_client::is_transient_network_error;
use crate::error::ProblemDetails;
use crate::{
Error, ErrorKind,
httpcache::{AfterResponse, BeforeRequest, CachePolicy, CachePolicyBuilder},
rkyvutil::OwnedArchive,
};
/// Extract problem details from an HTTP response if it has the correct content type
///
/// Note: This consumes the response body, so it should only be called when there's an error status.
async fn extract_problem_details(response: Response) -> Option<ProblemDetails> {
match response.bytes().await {
Ok(bytes) => match serde_json::from_slice(&bytes) {
Ok(details) => Some(details),
Err(err) => {
warn!("Failed to parse problem details: {err}");
None
}
},
Err(err) => {
warn!("Failed to read response body for problem details: {err}");
None
}
}
}
/// A trait the generalizes (de)serialization at a high level.
///
/// The main purpose of this trait is to make the `CachedClient` work for
@ -141,7 +161,7 @@ impl<CallbackError: std::error::Error + 'static> CachedClientError<CallbackError
}
}
fn error(&self) -> &dyn std::error::Error {
fn error(&self) -> &(dyn std::error::Error + 'static) {
match self {
Self::Client { err, .. } => err,
Self::Callback { err, .. } => err,
@ -452,7 +472,8 @@ impl CachedClient {
.await
}
#[instrument(name="read_and_parse_cache", skip_all, fields(file = %cache_entry.path().display()))]
#[instrument(name = "read_and_parse_cache", skip_all, fields(file = %cache_entry.path().display()
))]
async fn read_cache(cache_entry: &CacheEntry) -> Option<DataWithCachePolicy> {
match DataWithCachePolicy::from_path_async(cache_entry.path()).await {
Ok(data) => Some(data),
@ -536,16 +557,36 @@ impl CachedClient {
cached: DataWithCachePolicy,
new_cache_policy_builder: CachePolicyBuilder,
) -> Result<CachedResponse, Error> {
let url = DisplaySafeUrl::from(req.url().clone());
let url = DisplaySafeUrl::from_url(req.url().clone());
debug!("Sending revalidation request for: {url}");
let mut response = self
.0
.execute(req)
.instrument(info_span!("revalidation_request", url = url.as_str()))
.await
.map_err(|err| ErrorKind::from_reqwest_middleware(url.clone(), err))?
.error_for_status()
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
.map_err(|err| ErrorKind::from_reqwest_middleware(url.clone(), err))?;
// Check for HTTP error status and extract problem details if available
if let Err(status_error) = response.error_for_status_ref() {
// Clone the response to extract problem details before the error consumes it
let problem_details = if response
.headers()
.get("content-type")
.and_then(|ct| ct.to_str().ok())
.map(|ct| ct == "application/problem+json")
.unwrap_or(false)
{
extract_problem_details(response).await
} else {
None
};
return Err(ErrorKind::from_reqwest_with_problem_details(
url.clone(),
status_error,
problem_details,
)
.into());
}
// If the user set a custom `Cache-Control` header, override it.
if let CacheControl::Override(header) = cache_control {
@ -586,7 +627,7 @@ impl CachedClient {
req: Request,
cache_control: CacheControl<'_>,
) -> Result<(Response, Option<Box<CachePolicy>>), Error> {
let url = DisplaySafeUrl::from(req.url().clone());
let url = DisplaySafeUrl::from_url(req.url().clone());
trace!("Sending fresh {} request for {}", req.method(), url);
let cache_policy_builder = CachePolicyBuilder::new(&req);
let mut response = self
@ -610,9 +651,25 @@ impl CachedClient {
.map(|retries| retries.value());
if let Err(status_error) = response.error_for_status_ref() {
let problem_details = if response
.headers()
.get("content-type")
.and_then(|ct| ct.to_str().ok())
.map(|ct| ct.starts_with("application/problem+json"))
.unwrap_or(false)
{
extract_problem_details(response).await
} else {
None
};
return Err(CachedClientError::<Error>::Client {
retries: retry_count,
err: ErrorKind::from_reqwest(url, status_error).into(),
err: ErrorKind::from_reqwest_with_problem_details(
url,
status_error,
problem_details,
)
.into(),
}
.into());
}
@ -680,19 +737,21 @@ impl CachedClient {
if result
.as_ref()
.is_err_and(|err| is_extended_transient_error(err.error()))
.is_err_and(|err| is_transient_network_error(err.error()))
{
// If middleware already retried, consider that in our retry budget
let total_retries = past_retries + middleware_retries;
let retry_decision = retry_policy.should_retry(start_time, total_retries);
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
debug!(
"Transient failure while handling response from {}; retrying...",
req.url(),
);
let duration = execute_after
.duration_since(SystemTime::now())
.unwrap_or_else(|_| Duration::default());
debug!(
"Transient failure while handling response from {}; retrying after {:.1}s...",
req.url(),
duration.as_secs_f32(),
);
tokio::time::sleep(duration).await;
past_retries += 1;
continue;
@ -739,18 +798,19 @@ impl CachedClient {
if result
.as_ref()
.err()
.is_some_and(|err| is_extended_transient_error(err.error()))
.is_some_and(|err| is_transient_network_error(err.error()))
{
let total_retries = past_retries + middleware_retries;
let retry_decision = retry_policy.should_retry(start_time, total_retries);
if let reqwest_retry::RetryDecision::Retry { execute_after } = retry_decision {
debug!(
"Transient failure while handling response from {}; retrying...",
req.url(),
);
let duration = execute_after
.duration_since(SystemTime::now())
.unwrap_or_else(|_| Duration::default());
debug!(
"Transient failure while handling response from {}; retrying after {}s...",
req.url(),
duration.as_secs(),
);
tokio::time::sleep(duration).await;
past_retries += 1;
continue;

View File

@ -1,9 +1,11 @@
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use async_http_range_reader::AsyncHttpRangeReaderError;
use async_zip::error::ZipError;
use serde::Deserialize;
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;
@ -11,6 +13,61 @@ use uv_redacted::DisplaySafeUrl;
use crate::middleware::OfflineError;
use crate::{FlatIndexError, html};
/// RFC 9457 Problem Details for HTTP APIs
///
/// This structure represents the standard format for machine-readable details
/// of errors in HTTP response bodies as defined in RFC 9457.
#[derive(Debug, Clone, Deserialize)]
pub struct ProblemDetails {
/// A URI reference that identifies the problem type.
/// When dereferenced, it SHOULD provide human-readable documentation for the problem type.
#[serde(rename = "type", default = "default_problem_type")]
pub problem_type: String,
/// A short, human-readable summary of the problem type.
pub title: Option<String>,
/// The HTTP status code generated by the origin server for this occurrence of the problem.
pub status: Option<u16>,
/// A human-readable explanation specific to this occurrence of the problem.
pub detail: Option<String>,
/// A URI reference that identifies the specific occurrence of the problem.
pub instance: Option<String>,
}
/// Default problem type URI as per RFC 9457
#[inline]
fn default_problem_type() -> String {
"about:blank".to_string()
}
impl ProblemDetails {
/// Get a human-readable description of the problem
pub fn description(&self) -> Option<String> {
match self {
Self {
title: Some(title),
detail: Some(detail),
..
} => Some(format!("Server message: {title}, {detail}")),
Self {
title: Some(title), ..
} => Some(format!("Server message: {title}")),
Self {
detail: Some(detail),
..
} => Some(format!("Server message: {detail}")),
Self {
status: Some(status),
..
} => Some(format!("HTTP error {status}")),
_ => None,
}
}
}
#[derive(Debug)]
pub struct Error {
kind: Box<ErrorKind>,
@ -22,8 +79,9 @@ impl Display for Error {
if self.retries > 0 {
write!(
f,
"Request failed after {retries} retries",
retries = self.retries
"Request failed after {retries} {subject}",
retries = self.retries,
subject = if self.retries > 1 { "retries" } else { "retry" }
)
} else {
Display::fmt(&self.kind, f)
@ -75,6 +133,11 @@ impl Error {
ErrorKind::BadHtml { source: err, url }.into()
}
/// Create a new error from a `MessagePack` parsing error.
pub(crate) fn from_msgpack_err(err: rmp_serde::decode::Error, url: DisplaySafeUrl) -> Self {
ErrorKind::BadMessagePack { source: err, url }.into()
}
/// Returns `true` if this error corresponds to an offline error.
pub(crate) fn is_offline(&self) -> bool {
matches!(&*self.kind, ErrorKind::Offline(_))
@ -210,11 +273,15 @@ pub enum ErrorKind {
/// Make sure the package name is spelled correctly and that you've
/// configured the right registry to fetch it from.
#[error("Package `{0}` was not found in the registry")]
PackageNotFound(String),
RemotePackageNotFound(PackageName),
/// The package was not found in the local (file-based) index.
#[error("Package `{0}` was not found in the local index")]
FileNotFound(String),
LocalPackageNotFound(PackageName),
/// The root was not found in the local (file-based) index.
#[error("Local index not found at: `{}`", _0.display())]
LocalIndexNotFound(PathBuf),
/// The metadata file could not be parsed.
#[error("Couldn't parse metadata of {0} from {1}")]
@ -224,16 +291,12 @@ pub enum ErrorKind {
#[source] Box<uv_pypi_types::MetadataError>,
),
/// The metadata file was not found in the wheel.
#[error("Metadata file `{0}` was not found in {1}")]
MetadataNotFound(WheelFilename, String),
/// An error that happened while making a request or in a reqwest middleware.
#[error("Failed to fetch: `{0}`")]
WrappedReqwestError(DisplaySafeUrl, #[source] WrappedReqwestError),
/// Add the number of failed retries to the error.
#[error("Request failed after {retries} retries")]
#[error("Request failed after {retries} {subject}", subject = if *retries > 1 { "retries" } else { "retry" })]
RequestWithRetries {
source: Box<ErrorKind>,
retries: u32,
@ -251,6 +314,12 @@ pub enum ErrorKind {
url: DisplaySafeUrl,
},
#[error("Received some unexpected MessagePack from {}", url)]
BadMessagePack {
source: rmp_serde::decode::Error,
url: DisplaySafeUrl,
},
#[error("Failed to read zip with range requests: `{0}`")]
AsyncHttpRangeReader(DisplaySafeUrl, #[source] AsyncHttpRangeReaderError),
@ -269,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),
@ -319,7 +391,19 @@ impl ErrorKind {
}
}
Self::WrappedReqwestError(url, WrappedReqwestError(err))
Self::WrappedReqwestError(url, WrappedReqwestError::from(err))
}
/// Create an [`ErrorKind`] from a [`reqwest::Error`] with problem details.
pub(crate) fn from_reqwest_with_problem_details(
url: DisplaySafeUrl,
error: reqwest::Error,
problem_details: Option<ProblemDetails>,
) -> Self {
Self::WrappedReqwestError(
url,
WrappedReqwestError::with_problem_details(error.into(), problem_details),
)
}
}
@ -329,12 +413,26 @@ impl ErrorKind {
/// Wraps a [`reqwest_middleware::Error`] instead of an [`reqwest::Error`] since the actual reqwest
/// error may be below some context in the [`anyhow::Error`].
#[derive(Debug)]
pub struct WrappedReqwestError(reqwest_middleware::Error);
pub struct WrappedReqwestError {
error: reqwest_middleware::Error,
problem_details: Option<Box<ProblemDetails>>,
}
impl WrappedReqwestError {
/// Create a new `WrappedReqwestError` with optional problem details
pub fn with_problem_details(
error: reqwest_middleware::Error,
problem_details: Option<ProblemDetails>,
) -> Self {
Self {
error,
problem_details: problem_details.map(Box::new),
}
}
/// Return the inner [`reqwest::Error`] from the error chain, if it exists.
fn inner(&self) -> Option<&reqwest::Error> {
match &self.0 {
match &self.error {
reqwest_middleware::Error::Reqwest(err) => Some(err),
reqwest_middleware::Error::Middleware(err) => err.chain().find_map(|err| {
if let Some(err) = err.downcast_ref::<reqwest::Error>() {
@ -396,13 +494,19 @@ impl WrappedReqwestError {
impl From<reqwest::Error> for WrappedReqwestError {
fn from(error: reqwest::Error) -> Self {
Self(error.into())
Self {
error: error.into(),
problem_details: None,
}
}
}
impl From<reqwest_middleware::Error> for WrappedReqwestError {
fn from(error: reqwest_middleware::Error) -> Self {
Self(error)
Self {
error,
problem_details: None,
}
}
}
@ -410,7 +514,7 @@ impl Deref for WrappedReqwestError {
type Target = reqwest_middleware::Error;
fn deref(&self) -> &Self::Target {
&self.0
&self.error
}
}
@ -419,9 +523,15 @@ impl Display for WrappedReqwestError {
if self.is_likely_offline() {
// Insert an extra hint, we'll show the wrapped error through `source`
f.write_str("Could not connect, are you offline?")
} else if let Some(problem_details) = &self.problem_details {
// Show problem details if available
match problem_details.description() {
None => Display::fmt(&self.error, f),
Some(message) => f.write_str(&message),
}
} else {
// Show the wrapped error
Display::fmt(&self.0, f)
Display::fmt(&self.error, f)
}
}
}
@ -430,10 +540,117 @@ impl std::error::Error for WrappedReqwestError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
if self.is_likely_offline() {
// `Display` is inserting an extra message, so we need to show the wrapped error
Some(&self.0)
Some(&self.error)
} else if self.problem_details.is_some() {
// `Display` is showing problem details, so show the wrapped error as source
Some(&self.error)
} else {
// `Display` is showing the wrapped error, continue with its source
self.0.source()
self.error.source()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_problem_details_parsing() {
let json = r#"{
"type": "https://example.com/probs/out-of-credit",
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"status": 403,
"instance": "/account/12345/msgs/abc"
}"#;
let problem_details: ProblemDetails = serde_json::from_slice(json.as_bytes()).unwrap();
assert_eq!(
problem_details.problem_type,
"https://example.com/probs/out-of-credit"
);
assert_eq!(
problem_details.title,
Some("You do not have enough credit.".to_string())
);
assert_eq!(
problem_details.detail,
Some("Your current balance is 30, but that costs 50.".to_string())
);
assert_eq!(problem_details.status, Some(403));
assert_eq!(
problem_details.instance,
Some("/account/12345/msgs/abc".to_string())
);
}
#[test]
fn test_problem_details_default_type() {
let json = r#"{
"detail": "Something went wrong",
"status": 500
}"#;
let problem_details: ProblemDetails = serde_json::from_slice(json.as_bytes()).unwrap();
assert_eq!(problem_details.problem_type, "about:blank");
assert_eq!(
problem_details.detail,
Some("Something went wrong".to_string())
);
assert_eq!(problem_details.status, Some(500));
}
#[test]
fn test_problem_details_description() {
let json = r#"{
"detail": "Detailed error message",
"title": "Error Title",
"status": 400
}"#;
let problem_details: ProblemDetails = serde_json::from_slice(json.as_bytes()).unwrap();
assert_eq!(
problem_details.description().unwrap(),
"Server message: Error Title, Detailed error message"
);
let json_no_detail = r#"{
"title": "Error Title",
"status": 400
}"#;
let problem_details: ProblemDetails =
serde_json::from_slice(json_no_detail.as_bytes()).unwrap();
assert_eq!(
problem_details.description().unwrap(),
"Server message: Error Title"
);
let json_minimal = r#"{
"status": 400
}"#;
let problem_details: ProblemDetails =
serde_json::from_slice(json_minimal.as_bytes()).unwrap();
assert_eq!(problem_details.description().unwrap(), "HTTP error 400");
}
#[test]
fn test_problem_details_with_extensions() {
let json = r#"{
"type": "https://example.com/probs/out-of-credit",
"title": "You do not have enough credit.",
"detail": "Your current balance is 30, but that costs 50.",
"status": 403,
"balance": 30,
"accounts": ["/account/12345", "/account/67890"]
}"#;
let problem_details: ProblemDetails = serde_json::from_slice(json.as_bytes()).unwrap();
assert_eq!(
problem_details.title,
Some("You do not have enough credit.".to_string())
);
}
}

View File

@ -14,7 +14,7 @@ use uv_redacted::DisplaySafeUrl;
use uv_small_str::SmallString;
use crate::cached_client::{CacheControl, CachedClientError};
use crate::html::SimpleHtml;
use crate::html::SimpleDetailHTML;
use crate::{CachedClient, Connectivity, Error, ErrorKind, OwnedArchive};
#[derive(Debug, thiserror::Error)]
@ -189,13 +189,13 @@ impl<'a> FlatIndexClient<'a> {
async {
// Use the response URL, rather than the request URL, as the base for relative URLs.
// This ensures that we handle redirects and other URL transformations correctly.
let url = DisplaySafeUrl::from(response.url().clone());
let url = DisplaySafeUrl::from_url(response.url().clone());
let text = response
.text()
.await
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
let SimpleHtml { base, files } = SimpleHtml::parse(&text, &url)
let SimpleDetailHTML { base, files } = SimpleDetailHTML::parse(&text, &url)
.map_err(|err| Error::from_html_err(err, url.clone()))?;
// Convert to a reference-counted string.
@ -204,7 +204,7 @@ impl<'a> FlatIndexClient<'a> {
let unarchived: Vec<File> = files
.into_iter()
.filter_map(|file| {
match File::try_from(file, &base) {
match File::try_from_pypi(file, &base) {
Ok(file) => Some(file),
Err(err) => {
// Ignore files with unparsable version specifiers.
@ -305,6 +305,7 @@ impl<'a> FlatIndexClient<'a> {
upload_time_utc_ms: None,
url: FileLocation::AbsoluteUrl(UrlString::from(url)),
yanked: None,
zstd: None,
};
let Some(filename) = DistFilename::try_from_normalized_filename(filename) else {
@ -320,6 +321,63 @@ impl<'a> FlatIndexClient<'a> {
index: flat_index.clone(),
});
}
dists.sort_by(|a, b| {
a.filename
.cmp(&b.filename)
.then_with(|| a.index.cmp(&b.index))
});
Ok(FlatIndexEntries::from_entries(dists))
}
}
#[cfg(test)]
mod tests {
use super::*;
use fs_err::File;
use std::io::Write;
use tempfile::tempdir;
#[test]
fn read_from_directory_sorts_distributions() {
let dir = tempdir().unwrap();
let filenames = [
"beta-2.0.0-py3-none-any.whl",
"alpha-1.0.0.tar.gz",
"alpha-1.0.0-py3-none-any.whl",
];
for name in &filenames {
let mut file = File::create(dir.path().join(name)).unwrap();
file.write_all(b"").unwrap();
}
let entries = FlatIndexClient::read_from_directory(
dir.path(),
&IndexUrl::parse(&dir.path().to_string_lossy(), None).unwrap(),
)
.unwrap();
let actual = entries
.entries
.iter()
.map(|entry| entry.filename.to_string())
.collect::<Vec<_>>();
let mut expected = filenames
.iter()
.map(|name| DistFilename::try_from_normalized_filename(name).unwrap())
.collect::<Vec<_>>();
expected.sort();
let expected = expected
.into_iter()
.map(|filename| filename.to_string())
.collect::<Vec<_>>();
assert_eq!(actual, expected);
}
}

View File

@ -3,32 +3,32 @@ use std::str::FromStr;
use jiff::Timestamp;
use tl::HTMLTag;
use tracing::{debug, instrument, warn};
use url::Url;
use uv_normalize::PackageName;
use uv_pep440::VersionSpecifiers;
use uv_pypi_types::{BaseUrl, CoreMetadata, File, Hashes, Yanked};
use uv_pypi_types::{BaseUrl, CoreMetadata, Hashes, PypiFile, Yanked};
use uv_pypi_types::{HashError, LenientVersionSpecifiers};
use uv_redacted::DisplaySafeUrl;
use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlError};
/// A parsed structure from PyPI "HTML" index format for a single package.
#[derive(Debug, Clone)]
pub(crate) struct SimpleHtml {
pub(crate) struct SimpleDetailHTML {
/// The [`BaseUrl`] to which all relative URLs should be resolved.
pub(crate) base: BaseUrl,
/// The list of [`File`]s available for download sorted by filename.
pub(crate) files: Vec<File>,
/// The list of [`PypiFile`]s available for download sorted by filename.
pub(crate) files: Vec<PypiFile>,
}
impl SimpleHtml {
/// Parse the list of [`File`]s from the simple HTML page returned by the given URL.
impl SimpleDetailHTML {
/// Parse the list of [`PypiFile`]s from the simple HTML page returned by the given URL.
#[instrument(skip_all, fields(url = % url))]
pub(crate) fn parse(text: &str, url: &Url) -> Result<Self, Error> {
pub(crate) fn parse(text: &str, url: &DisplaySafeUrl) -> Result<Self, Error> {
let dom = tl::parse(text, tl::ParserOptions::default())?;
// Parse the first `<base>` tag, if any, to determine the base URL to which all
// relative URLs should be resolved. The HTML spec requires that the `<base>` tag
// appear before other tags with attribute values of URLs.
let base = BaseUrl::from(DisplaySafeUrl::from(
let base = BaseUrl::from(
dom.nodes()
.iter()
.filter_map(|node| node.as_tag())
@ -38,10 +38,10 @@ impl SimpleHtml {
.transpose()?
.flatten()
.unwrap_or_else(|| url.clone()),
));
);
// Parse each `<a>` tag, to extract the filename, hash, and URL.
let mut files: Vec<File> = dom
let mut files: Vec<PypiFile> = dom
.nodes()
.iter()
.filter_map(|node| node.as_tag())
@ -67,19 +67,20 @@ impl SimpleHtml {
}
/// Parse the `href` from a `<base>` tag.
fn parse_base(base: &HTMLTag) -> Result<Option<Url>, Error> {
fn parse_base(base: &HTMLTag) -> Result<Option<DisplaySafeUrl>, Error> {
let Some(Some(href)) = base.attributes().get("href") else {
return Ok(None);
};
let href = std::str::from_utf8(href.as_bytes())?;
let url = Url::parse(href).map_err(|err| Error::UrlParse(href.to_string(), err))?;
let url =
DisplaySafeUrl::parse(href).map_err(|err| Error::UrlParse(href.to_string(), err))?;
Ok(Some(url))
}
/// Parse a [`File`] from an `<a>` tag.
/// Parse a [`PypiFile`] from an `<a>` tag.
///
/// Returns `None` if the `<a>` don't doesn't have an `href` attribute.
fn parse_anchor(link: &HTMLTag) -> Result<Option<File>, Error> {
/// Returns `None` if the `<a>` doesn't have an `href` attribute.
fn parse_anchor(link: &HTMLTag) -> Result<Option<PypiFile>, Error> {
// Extract the href.
let Some(href) = link
.attributes()
@ -212,7 +213,7 @@ impl SimpleHtml {
.map(|upload_time| html_escape::decode_html_entities(upload_time))
.and_then(|upload_time| Timestamp::from_str(&upload_time).ok());
Ok(Some(File {
Ok(Some(PypiFile {
core_metadata,
yanked,
requires_python,
@ -225,6 +226,56 @@ impl SimpleHtml {
}
}
/// A parsed structure from PyPI "HTML" index format listing all available packages.
#[derive(Debug, Clone)]
pub(crate) struct SimpleIndexHtml {
/// The list of project names available in the index.
pub(crate) projects: Vec<PackageName>,
}
impl SimpleIndexHtml {
/// Parse the list of project names from the Simple API index HTML page.
pub(crate) fn parse(text: &str) -> Result<Self, Error> {
let dom = tl::parse(text, tl::ParserOptions::default())?;
// Parse each `<a>` tag to extract the project name.
let parser = dom.parser();
let mut projects = dom
.nodes()
.iter()
.filter_map(|node| node.as_tag())
.filter(|link| link.name().as_bytes() == b"a")
.filter_map(|link| Self::parse_anchor_project_name(link, parser))
.collect::<Vec<_>>();
// Sort for deterministic ordering.
projects.sort_unstable();
Ok(Self { projects })
}
/// Parse a project name from an `<a>` tag.
///
/// Returns `None` if the `<a>` doesn't have an `href` attribute or text content.
fn parse_anchor_project_name(link: &HTMLTag, parser: &tl::Parser) -> Option<PackageName> {
// Extract the href.
link.attributes()
.get("href")
.flatten()
.filter(|bytes| !bytes.as_bytes().is_empty())?;
// Extract the text content, which should be the project name.
let inner_text = link.inner_text(parser);
let project_name = inner_text.trim();
if project_name.is_empty() {
return None;
}
PackageName::from_str(project_name).ok()
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
@ -234,7 +285,7 @@ pub enum Error {
FromUtf8(#[from] std::string::FromUtf8Error),
#[error("Failed to parse URL: {0}")]
UrlParse(String, #[source] url::ParseError),
UrlParse(String, #[source] DisplaySafeUrlError),
#[error(transparent)]
HtmlParse(#[from] tl::ParseError),
@ -274,10 +325,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -296,7 +347,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2-py3-none-any.whl",
hashes: Hashes {
@ -331,10 +382,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -353,7 +404,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2-py3-none-any.whl",
hashes: Hashes {
@ -391,10 +442,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -413,7 +464,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2-py3-none-any.whl",
hashes: Hashes {
@ -448,10 +499,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -470,7 +521,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2+233fca715f49-py3-none-any.whl",
hashes: Hashes {
@ -505,10 +556,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -527,7 +578,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2-py3-none-any.whl",
hashes: Hashes {
@ -562,10 +613,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -584,7 +635,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "torchtext-0.17.0+cpu-cp39-cp39-win_amd64.whl",
hashes: Hashes {
@ -617,10 +668,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -639,7 +690,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2-py3-none-any.whl",
hashes: Hashes {
@ -672,10 +723,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
";
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -710,10 +761,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -748,10 +799,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -770,7 +821,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2-py3-none-any.whl",
hashes: Hashes {
@ -803,10 +854,10 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -825,7 +876,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2-py3-none-any.whl",
hashes: Hashes {
@ -858,11 +909,11 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base);
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base);
insta::assert_debug_snapshot!(result, @r#"
Ok(
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -881,7 +932,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2-py3-none-any.whl",
hashes: Hashes {
@ -915,11 +966,11 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base);
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base);
insta::assert_debug_snapshot!(result, @r#"
Ok(
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -938,7 +989,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2-py3-none-any.whl",
hashes: Hashes {
@ -972,8 +1023,8 @@ mod tests {
</html>
<!--TIMESTAMP 1703347410-->
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap_err();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap_err();
insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected one of: `md5`, `sha256`, `sha384`, `sha512`, or `blake2b`) on: `blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61`");
}
@ -989,11 +1040,13 @@ mod tests {
</body>
</html>
"#;
let base = Url::parse("https://storage.googleapis.com/jax-releases/jax_cuda_releases.html")
.unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse(
"https://storage.googleapis.com/jax-releases/jax_cuda_releases.html",
)
.unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -1012,7 +1065,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl",
hashes: Hashes {
@ -1028,7 +1081,7 @@ mod tests {
url: "https://storage.googleapis.com/jax-releases/cuda100/jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl",
yanked: None,
},
File {
PypiFile {
core_metadata: None,
filename: "jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl",
hashes: Hashes {
@ -1071,11 +1124,11 @@ mod tests {
</body>
</html>
"#;
let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/")
let base = DisplaySafeUrl::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/")
.unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -1094,7 +1147,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Flask-0.1.tar.gz",
hashes: Hashes {
@ -1112,7 +1165,7 @@ mod tests {
url: "0.1/Flask-0.1.tar.gz",
yanked: None,
},
File {
PypiFile {
core_metadata: None,
filename: "Flask-0.10.1.tar.gz",
hashes: Hashes {
@ -1130,7 +1183,7 @@ mod tests {
url: "0.10.1/Flask-0.10.1.tar.gz",
yanked: None,
},
File {
PypiFile {
core_metadata: None,
filename: "flask-3.0.1.tar.gz",
hashes: Hashes {
@ -1175,10 +1228,10 @@ mod tests {
</body>
</html>
"#;
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let base = DisplaySafeUrl::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -1197,7 +1250,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: None,
filename: "Jinja2-3.1.2-py3-none-any.whl",
hashes: Hashes {
@ -1247,11 +1300,11 @@ mod tests {
</body>
</html>
"#;
let base = Url::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/")
let base = DisplaySafeUrl::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask/")
.unwrap();
let result = SimpleHtml::parse(text, &base).unwrap();
let result = SimpleDetailHTML::parse(text, &base).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleHtml {
SimpleDetailHTML {
base: BaseUrl(
DisplaySafeUrl {
scheme: "https",
@ -1270,7 +1323,7 @@ mod tests {
},
),
files: [
File {
PypiFile {
core_metadata: Some(
Bool(
true,
@ -1290,7 +1343,7 @@ mod tests {
url: "/whl/Jinja2-3.1.2-py3-none-any.whl",
yanked: None,
},
File {
PypiFile {
core_metadata: Some(
Bool(
true,
@ -1310,7 +1363,7 @@ mod tests {
url: "/whl/Jinja2-3.1.3-py3-none-any.whl",
yanked: None,
},
File {
PypiFile {
core_metadata: Some(
Bool(
false,
@ -1330,7 +1383,7 @@ mod tests {
url: "/whl/Jinja2-3.1.4-py3-none-any.whl",
yanked: None,
},
File {
PypiFile {
core_metadata: Some(
Bool(
false,
@ -1350,7 +1403,7 @@ mod tests {
url: "/whl/Jinja2-3.1.5-py3-none-any.whl",
yanked: None,
},
File {
PypiFile {
core_metadata: Some(
Bool(
true,
@ -1374,4 +1427,180 @@ mod tests {
}
"#);
}
/// Test parsing Simple API index (root) HTML.
#[test]
fn parse_simple_index() {
let text = r#"
<!DOCTYPE html>
<html>
<head>
<title>Simple Index</title>
</head>
<body>
<h1>Simple Index</h1>
<a href="/simple/flask/">flask</a><br/>
<a href="/simple/jinja2/">jinja2</a><br/>
<a href="/simple/requests/">requests</a><br/>
</body>
</html>
"#;
let result = SimpleIndexHtml::parse(text).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleIndexHtml {
projects: [
PackageName(
"flask",
),
PackageName(
"jinja2",
),
PackageName(
"requests",
),
],
}
"#);
}
/// Test that project names are sorted.
#[test]
fn parse_simple_index_sorted() {
let text = r#"
<!DOCTYPE html>
<html>
<body>
<a href="/simple/zebra/">zebra</a><br/>
<a href="/simple/apple/">apple</a><br/>
<a href="/simple/monkey/">monkey</a><br/>
</body>
</html>
"#;
let result = SimpleIndexHtml::parse(text).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleIndexHtml {
projects: [
PackageName(
"apple",
),
PackageName(
"monkey",
),
PackageName(
"zebra",
),
],
}
"#);
}
/// Test that links without `href` attributes are ignored.
#[test]
fn parse_simple_index_missing_href() {
let text = r#"
<!DOCTYPE html>
<html>
<body>
<h1>Simple Index</h1>
<a href="/simple/flask/">flask</a><br/>
<a>no-href-project</a><br/>
<a href="/simple/requests/">requests</a><br/>
</body>
</html>
"#;
let result = SimpleIndexHtml::parse(text).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleIndexHtml {
projects: [
PackageName(
"flask",
),
PackageName(
"requests",
),
],
}
"#);
}
/// Test that links with empty `href` attributes are ignored.
#[test]
fn parse_simple_index_empty_href() {
let text = r#"
<!DOCTYPE html>
<html>
<body>
<a href="">empty-href</a><br/>
<a href="/simple/flask/">flask</a><br/>
</body>
</html>
"#;
let result = SimpleIndexHtml::parse(text).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleIndexHtml {
projects: [
PackageName(
"flask",
),
],
}
"#);
}
/// Test that links with empty text content are ignored.
#[test]
fn parse_simple_index_empty_text() {
let text = r#"
<!DOCTYPE html>
<html>
<body>
<a href="/simple/empty/"></a><br/>
<a href="/simple/flask/">flask</a><br/>
<a href="/simple/whitespace/"> </a><br/>
</body>
</html>
"#;
let result = SimpleIndexHtml::parse(text).unwrap();
insta::assert_debug_snapshot!(result, @r#"
SimpleIndexHtml {
projects: [
PackageName(
"flask",
),
],
}
"#);
}
/// Test parsing with case variations and normalization.
#[test]
fn parse_simple_index_case_variations() {
let text = r#"
<!DOCTYPE html>
<html>
<body>
<a href="/simple/Flask/">Flask</a><br/>
<a href="/simple/django/">django</a><br/>
<a href="/simple/PyYAML/">PyYAML</a><br/>
</body>
</html>
"#;
let result = SimpleIndexHtml::parse(text).unwrap();
// Note: We preserve the case as returned by the server
insta::assert_debug_snapshot!(result, @r#"
SimpleIndexHtml {
projects: [
PackageName(
"django",
),
PackageName(
"flask",
),
PackageName(
"pyyaml",
),
],
}
"#);
}
}

View File

@ -1,15 +1,15 @@
pub use base_client::{
AuthIntegration, BaseClient, BaseClientBuilder, DEFAULT_RETRIES, ExtraMiddleware,
RedirectClientWithMiddleware, RequestBuilder, RetryParsingError, UvRetryableStrategy,
is_extended_transient_error, retries_from_env,
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};
pub use flat_index::{FlatIndexClient, FlatIndexEntries, FlatIndexEntry, FlatIndexError};
pub use linehaul::LineHaul;
pub use registry_client::{
Connectivity, MetadataFormat, RegistryClient, RegistryClientBuilder, SimpleMetadata,
SimpleMetadatum, VersionFiles,
Connectivity, MetadataFormat, RegistryClient, RegistryClientBuilder, SimpleDetailMetadata,
SimpleDetailMetadatum, SimpleIndexMetadata, VersionFiles,
};
pub use rkyvutil::{Deserializer, OwnedArchive, Serializer, Validator};

View File

@ -5,12 +5,14 @@ use tracing::instrument;
use uv_pep508::MarkerEnvironment;
use uv_platform_tags::{Os, Platform};
use uv_static::EnvVars;
use uv_version::version;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Installer {
pub name: Option<String>,
pub version: Option<String>,
pub subcommand: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
@ -62,11 +64,20 @@ pub struct LineHaul {
impl LineHaul {
/// Initializes Linehaul information based on PEP 508 markers.
#[instrument(name = "linehaul", skip_all)]
pub fn new(markers: &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 = ["BUILD_BUILDID", "BUILD_ID", "CI", "PIP_IS_CI"]
.iter()
.find_map(|&var_name| env::var(var_name).ok().map(|_| true));
let looks_like_ci = [
EnvVars::BUILD_BUILDID,
EnvVars::BUILD_ID,
EnvVars::CI,
EnvVars::PIP_IS_CI,
]
.iter()
.find_map(|&var_name| env::var(var_name).ok().map(|_| true));
let libc = match platform.map(Platform::os) {
Some(Os::Manylinux { major, minor }) => Some(Libc {
@ -117,18 +128,19 @@ impl LineHaul {
installer: Option::from(Installer {
name: Some("uv".to_string()),
version: Some(version().to_string()),
subcommand,
}),
python: Some(markers.python_full_version().version.to_string()),
python: markers.map(|markers| markers.python_full_version().version.to_string()),
implementation: Option::from(Implementation {
name: Some(markers.platform_python_implementation().to_string()),
version: Some(markers.python_full_version().version.to_string()),
name: markers.map(|markers| markers.platform_python_implementation().to_string()),
version: markers.map(|markers| markers.python_full_version().version.to_string()),
}),
distro,
system: Option::from(System {
name: Some(markers.platform_system().to_string()),
release: Some(markers.platform_release().to_string()),
name: markers.map(|markers| markers.platform_system().to_string()),
release: markers.map(|markers| markers.platform_release().to_string()),
}),
cpu: Some(markers.platform_machine().to_string()),
cpu: markers.map(|markers| markers.platform_machine().to_string()),
// Should probably always be None in uv.
openssl_version: None,
// Should probably always be None in uv.

View File

@ -43,7 +43,7 @@ impl Middleware for OfflineMiddleware {
) -> reqwest_middleware::Result<Response> {
Err(reqwest_middleware::Error::Middleware(
OfflineError {
url: DisplaySafeUrl::from(req.url().clone()),
url: DisplaySafeUrl::from_url(req.url().clone()),
}
.into(),
))

View File

@ -15,10 +15,10 @@ use tokio::sync::{Mutex, Semaphore};
use tracing::{Instrument, debug, info_span, instrument, trace, warn};
use url::Url;
use uv_auth::Indexes;
use uv_auth::{CredentialsCache, Indexes, PyxTokenStore};
use uv_cache::{Cache, CacheBucket, CacheEntry, WheelCache};
use uv_configuration::IndexStrategy;
use uv_configuration::KeyringProviderType;
use uv_configuration::{IndexStrategy, TrustedHost};
use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
use uv_distribution_types::{
BuiltDist, File, IndexCapabilities, IndexFormat, IndexLocations, IndexMetadataRef,
@ -29,7 +29,9 @@ use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_pep508::MarkerEnvironment;
use uv_platform_tags::Platform;
use uv_pypi_types::{ResolutionMetadata, SimpleJson};
use uv_pypi_types::{
PypiSimpleDetail, PypiSimpleIndex, PyxSimpleDetail, PyxSimpleIndex, ResolutionMetadata,
};
use uv_redacted::DisplaySafeUrl;
use uv_small_str::SmallString;
use uv_torch::TorchStrategy;
@ -37,7 +39,7 @@ use uv_torch::TorchStrategy;
use crate::base_client::{BaseClientBuilder, ExtraMiddleware, RedirectPolicy};
use crate::cached_client::CacheControl;
use crate::flat_index::FlatIndexEntry;
use crate::html::SimpleHtml;
use crate::html::SimpleDetailHTML;
use crate::remote_metadata::wheel_metadata_from_remote_zip;
use crate::rkyvutil::OwnedArchive;
use crate::{
@ -48,32 +50,33 @@ use crate::{
/// A builder for an [`RegistryClient`].
#[derive(Debug, Clone)]
pub struct RegistryClientBuilder<'a> {
index_urls: IndexUrls,
index_locations: IndexLocations,
index_strategy: IndexStrategy,
torch_backend: Option<TorchStrategy>,
cache: Cache,
base_client_builder: BaseClientBuilder<'a>,
}
impl RegistryClientBuilder<'_> {
pub fn new(cache: Cache) -> Self {
impl<'a> RegistryClientBuilder<'a> {
pub fn new(base_client_builder: BaseClientBuilder<'a>, cache: Cache) -> Self {
Self {
index_urls: IndexUrls::default(),
index_locations: IndexLocations::default(),
index_strategy: IndexStrategy::default(),
torch_backend: None,
cache,
base_client_builder: BaseClientBuilder::new(),
base_client_builder,
}
}
}
impl<'a> RegistryClientBuilder<'a> {
#[must_use]
pub fn index_locations(mut self, index_locations: &IndexLocations) -> Self {
self.index_urls = index_locations.index_urls();
self.base_client_builder = self
.base_client_builder
.indexes(Indexes::from(index_locations));
pub fn with_reqwest_client(mut self, client: reqwest::Client) -> Self {
self.base_client_builder = self.base_client_builder.custom_client(client);
self
}
#[must_use]
pub fn index_locations(mut self, index_locations: IndexLocations) -> Self {
self.index_locations = index_locations;
self
}
@ -95,37 +98,6 @@ impl<'a> RegistryClientBuilder<'a> {
self
}
#[must_use]
pub fn allow_insecure_host(mut self, allow_insecure_host: Vec<TrustedHost>) -> Self {
self.base_client_builder = self
.base_client_builder
.allow_insecure_host(allow_insecure_host);
self
}
#[must_use]
pub fn connectivity(mut self, connectivity: Connectivity) -> Self {
self.base_client_builder = self.base_client_builder.connectivity(connectivity);
self
}
#[must_use]
pub fn retries(mut self, retries: u32) -> Self {
self.base_client_builder = self.base_client_builder.retries(retries);
self
}
pub fn retries_from_env(mut self) -> anyhow::Result<Self> {
self.base_client_builder = self.base_client_builder.retries_from_env()?;
Ok(self)
}
#[must_use]
pub fn native_tls(mut self, native_tls: bool) -> Self {
self.base_client_builder = self.base_client_builder.native_tls(native_tls);
self
}
#[must_use]
pub fn built_in_root_certs(mut self, built_in_root_certs: bool) -> Self {
self.base_client_builder = self
@ -176,10 +148,36 @@ impl<'a> RegistryClientBuilder<'a> {
self
}
pub fn build(self) -> RegistryClient {
/// 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
let builder = self
.base_client_builder
.indexes(Indexes::from(&self.index_locations))
.redirect(RedirectPolicy::RetriggerMiddleware);
let client = builder.build();
@ -191,7 +189,7 @@ impl<'a> RegistryClientBuilder<'a> {
let client = CachedClient::new(client);
RegistryClient {
index_urls: self.index_urls,
index_urls,
index_strategy: self.index_strategy,
torch_backend: self.torch_backend,
cache: self.cache,
@ -199,13 +197,20 @@ impl<'a> RegistryClientBuilder<'a> {
client,
timeout,
flat_indexes: Arc::default(),
pyx_token_store: PyxTokenStore::from_settings().ok(),
}
}
/// Share the underlying client between two different middleware configurations.
pub fn wrap_existing(self, existing: &BaseClient) -> RegistryClient {
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.
let client = self.base_client_builder.wrap_existing(existing);
let client = self
.base_client_builder
.indexes(Indexes::from(&self.index_locations))
.wrap_existing(existing);
let timeout = client.timeout();
let connectivity = client.connectivity();
@ -214,7 +219,7 @@ impl<'a> RegistryClientBuilder<'a> {
let client = CachedClient::new(client);
RegistryClient {
index_urls: self.index_urls,
index_urls,
index_strategy: self.index_strategy,
torch_backend: self.torch_backend,
cache: self.cache,
@ -222,24 +227,11 @@ impl<'a> RegistryClientBuilder<'a> {
client,
timeout,
flat_indexes: Arc::default(),
pyx_token_store: PyxTokenStore::from_settings().ok(),
}
}
}
impl<'a> TryFrom<BaseClientBuilder<'a>> for RegistryClientBuilder<'a> {
type Error = std::io::Error;
fn try_from(value: BaseClientBuilder<'a>) -> Result<Self, Self::Error> {
Ok(Self {
index_urls: IndexUrls::default(),
index_strategy: IndexStrategy::default(),
torch_backend: None,
cache: Cache::temp()?,
base_client_builder: value,
})
}
}
/// A client for fetching packages from a `PyPI`-compatible index.
#[derive(Debug, Clone)]
pub struct RegistryClient {
@ -259,13 +251,16 @@ pub struct RegistryClient {
timeout: Duration,
/// The flat index entries for each `--find-links`-style index URL.
flat_indexes: Arc<Mutex<FlatIndexCache>>,
/// The pyx token store to use for persistent credentials.
// TODO(charlie): The token store is only needed for `is_known_url`; can we avoid storing it here?
pyx_token_store: Option<PyxTokenStore>,
}
/// The format of the package metadata returned by querying an index.
#[derive(Debug)]
pub enum MetadataFormat {
/// The metadata adheres to the Simple Repository API format.
Simple(OwnedArchive<SimpleMetadata>),
Simple(OwnedArchive<SimpleDetailMetadata>),
/// The metadata consists of a list of distributions from a "flat" index.
Flat(Vec<FlatIndexEntry>),
}
@ -296,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,
@ -333,7 +332,7 @@ impl RegistryClient {
/// and [PEP 691 JSON-based Simple API for Python Package Indexes](https://peps.python.org/pep-0691/),
/// which the PyPI JSON API implements.
#[instrument(skip_all, fields(package = % package_name))]
pub async fn package_metadata<'index>(
pub async fn simple_detail<'index>(
&'index self,
package_name: &PackageName,
index: Option<IndexMetadataRef<'index>>,
@ -364,7 +363,7 @@ impl RegistryClient {
let status_code_strategy =
self.index_urls.status_code_strategy_for(index.url);
match self
.simple_single_index(
.simple_detail_single_index(
package_name,
index.url,
capabilities,
@ -410,7 +409,7 @@ impl RegistryClient {
let status_code_strategy =
IndexStatusCodeStrategy::ignore_authentication_error_codes();
let metadata = match self
.simple_single_index(
.simple_detail_single_index(
package_name,
index.url,
capabilities,
@ -444,7 +443,7 @@ impl RegistryClient {
if results.is_empty() {
return match self.connectivity {
Connectivity::Online => {
Err(ErrorKind::PackageNotFound(package_name.to_string()).into())
Err(ErrorKind::RemotePackageNotFound(package_name.clone()).into())
}
Connectivity::Offline => Err(ErrorKind::Offline(package_name.to_string()).into()),
};
@ -493,11 +492,11 @@ impl RegistryClient {
Ok(package_entries)
}
/// Fetch the [`SimpleMetadata`] from a single index for a given package.
/// Fetch the [`SimpleDetailMetadata`] from a single index for a given package.
///
/// The index can either be a PEP 503-compatible remote repository, or a local directory laid
/// out in the same format.
async fn simple_single_index(
async fn simple_detail_single_index(
&self,
package_name: &PackageName,
index: &IndexUrl,
@ -540,13 +539,13 @@ 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(_)) {
self.fetch_local_index(package_name, &url).await
self.fetch_local_simple_detail(package_name, &url).await
} else {
self.fetch_remote_index(package_name, &url, &cache_entry, cache_control)
self.fetch_remote_simple_detail(package_name, &url, index, &cache_entry, cache_control)
.await
};
@ -575,33 +574,46 @@ impl RegistryClient {
ErrorKind::Offline(_) => Ok(SimpleMetadataSearchOutcome::NotFound),
// The package could not be found in the local index.
ErrorKind::FileNotFound(_) => Ok(SimpleMetadataSearchOutcome::NotFound),
ErrorKind::LocalPackageNotFound(_) => Ok(SimpleMetadataSearchOutcome::NotFound),
_ => Err(err),
},
}
}
/// Fetch the [`SimpleMetadata`] from a remote URL, using the PEP 503 Simple Repository API.
async fn fetch_remote_index(
/// Fetch the [`SimpleDetailMetadata`] from a remote URL, using the PEP 503 Simple Repository API.
async fn fetch_remote_simple_detail(
&self,
package_name: &PackageName,
url: &DisplaySafeUrl,
index: &IndexUrl,
cache_entry: &CacheEntry,
cache_control: CacheControl<'_>,
) -> Result<OwnedArchive<SimpleMetadata>, Error> {
) -> Result<OwnedArchive<SimpleDetailMetadata>, Error> {
// In theory, we should be able to pass `MediaType::all()` to all registries, and as
// unsupported media types should be ignored by the server. For now, we implement this
// defensively to avoid issues with misconfigured servers.
let accept = if self
.pyx_token_store
.as_ref()
.is_some_and(|token_store| token_store.is_known_url(index.url()))
{
MediaType::all()
} else {
MediaType::pypi()
};
let simple_request = self
.uncached_client(url)
.get(Url::from(url.clone()))
.header("Accept-Encoding", "gzip, deflate, zstd")
.header("Accept", MediaType::accepts())
.header("Accept", accept)
.build()
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
let parse_simple_response = |response: Response| {
async {
// Use the response URL, rather than the request URL, as the base for relative URLs.
// This ensures that we handle redirects and other URL transformations correctly.
let url = DisplaySafeUrl::from(response.url().clone());
let url = DisplaySafeUrl::from_url(response.url().clone());
let content_type = response
.headers()
@ -619,22 +631,53 @@ impl RegistryClient {
})?;
let unarchived = match media_type {
MediaType::Json => {
MediaType::PyxV1Msgpack => {
let bytes = response
.bytes()
.await
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
let data: SimpleJson = serde_json::from_slice(bytes.as_ref())
let data: PyxSimpleDetail = rmp_serde::from_slice(bytes.as_ref())
.map_err(|err| Error::from_msgpack_err(err, url.clone()))?;
SimpleDetailMetadata::from_pyx_files(
data.files,
data.core_metadata,
package_name,
&url,
)
}
MediaType::PyxV1Json => {
let bytes = response
.bytes()
.await
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
let data: PyxSimpleDetail = serde_json::from_slice(bytes.as_ref())
.map_err(|err| Error::from_json_err(err, url.clone()))?;
SimpleMetadata::from_files(data.files, package_name, &url)
SimpleDetailMetadata::from_pyx_files(
data.files,
data.core_metadata,
package_name,
&url,
)
}
MediaType::Html => {
MediaType::PypiV1Json => {
let bytes = response
.bytes()
.await
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
let data: PypiSimpleDetail = serde_json::from_slice(bytes.as_ref())
.map_err(|err| Error::from_json_err(err, url.clone()))?;
SimpleDetailMetadata::from_pypi_files(data.files, package_name, &url)
}
MediaType::PypiV1Html | MediaType::TextHtml => {
let text = response
.text()
.await
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
SimpleMetadata::from_html(&text, package_name, &url)?
SimpleDetailMetadata::from_html(&text, package_name, &url)?
}
};
OwnedArchive::from_unarchived(&unarchived)
@ -654,13 +697,13 @@ impl RegistryClient {
Ok(simple)
}
/// Fetch the [`SimpleMetadata`] from a local file, using a PEP 503-compatible directory
/// Fetch the [`SimpleDetailMetadata`] from a local file, using a PEP 503-compatible directory
/// structure.
async fn fetch_local_index(
async fn fetch_local_simple_detail(
&self,
package_name: &PackageName,
url: &DisplaySafeUrl,
) -> Result<OwnedArchive<SimpleMetadata>, Error> {
) -> Result<OwnedArchive<SimpleDetailMetadata>, Error> {
let path = url
.to_file_path()
.map_err(|()| ErrorKind::NonFileUrl(url.clone()))?
@ -668,15 +711,185 @@ impl RegistryClient {
let text = match fs_err::tokio::read_to_string(&path).await {
Ok(text) => text,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Err(Error::from(ErrorKind::FileNotFound(
package_name.to_string(),
return Err(Error::from(ErrorKind::LocalPackageNotFound(
package_name.clone(),
)));
}
Err(err) => {
return Err(Error::from(ErrorKind::Io(err)));
}
};
let metadata = SimpleMetadata::from_html(&text, package_name, url)?;
let metadata = SimpleDetailMetadata::from_html(&text, package_name, url)?;
OwnedArchive::from_unarchived(&metadata)
}
/// Fetch the list of projects from a Simple API index at a remote URL.
///
/// This fetches the root of a Simple API index (e.g., `https://pypi.org/simple/`)
/// which returns a list of all available projects.
pub async fn fetch_simple_index(
&self,
index_url: &IndexUrl,
) -> Result<SimpleIndexMetadata, Error> {
// Format the URL for PyPI.
let mut url = index_url.url().clone();
url.path_segments_mut()
.map_err(|()| ErrorKind::CannotBeABase(index_url.url().clone()))?
.pop_if_empty()
// The URL *must* end in a trailing slash for proper relative path behavior
// ref https://github.com/servo/rust-url/issues/333
.push("");
if url.scheme() == "file" {
let archived = self.fetch_local_simple_index(&url).await?;
Ok(OwnedArchive::deserialize(&archived))
} else {
let archived = self.fetch_remote_simple_index(&url, index_url).await?;
Ok(OwnedArchive::deserialize(&archived))
}
}
/// Fetch the list of projects from a remote Simple API index.
async fn fetch_remote_simple_index(
&self,
url: &DisplaySafeUrl,
index: &IndexUrl,
) -> Result<OwnedArchive<SimpleIndexMetadata>, Error> {
// In theory, we should be able to pass `MediaType::all()` to all registries, and as
// unsupported media types should be ignored by the server. For now, we implement this
// defensively to avoid issues with misconfigured servers.
let accept = if self
.pyx_token_store
.as_ref()
.is_some_and(|token_store| token_store.is_known_url(index.url()))
{
MediaType::all()
} else {
MediaType::pypi()
};
let cache_entry = self.cache.entry(
CacheBucket::Simple,
WheelCache::Index(index).root(),
"index.html.rkyv",
);
let cache_control = match self.connectivity {
Connectivity::Online => {
if let Some(header) = self.index_urls.simple_api_cache_control_for(index) {
CacheControl::Override(header)
} else {
CacheControl::from(
self.cache
.freshness(&cache_entry, None, None)
.map_err(ErrorKind::Io)?,
)
}
}
Connectivity::Offline => CacheControl::AllowStale,
};
let parse_simple_response = |response: Response| {
async {
// Use the response URL, rather than the request URL, as the base for relative URLs.
// This ensures that we handle redirects and other URL transformations correctly.
let url = DisplaySafeUrl::from_url(response.url().clone());
let content_type = response
.headers()
.get("content-type")
.ok_or_else(|| Error::from(ErrorKind::MissingContentType(url.clone())))?;
let content_type = content_type.to_str().map_err(|err| {
Error::from(ErrorKind::InvalidContentTypeHeader(url.clone(), err))
})?;
let media_type = content_type.split(';').next().unwrap_or(content_type);
let media_type = MediaType::from_str(media_type).ok_or_else(|| {
Error::from(ErrorKind::UnsupportedMediaType(
url.clone(),
media_type.to_string(),
))
})?;
let metadata = match media_type {
MediaType::PyxV1Msgpack => {
let bytes = response
.bytes()
.await
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
let data: PyxSimpleIndex = rmp_serde::from_slice(bytes.as_ref())
.map_err(|err| Error::from_msgpack_err(err, url.clone()))?;
SimpleIndexMetadata::from_pyx_index(data)
}
MediaType::PyxV1Json => {
let bytes = response
.bytes()
.await
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
let data: PyxSimpleIndex = serde_json::from_slice(bytes.as_ref())
.map_err(|err| Error::from_json_err(err, url.clone()))?;
SimpleIndexMetadata::from_pyx_index(data)
}
MediaType::PypiV1Json => {
let bytes = response
.bytes()
.await
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
let data: PypiSimpleIndex = serde_json::from_slice(bytes.as_ref())
.map_err(|err| Error::from_json_err(err, url.clone()))?;
SimpleIndexMetadata::from_pypi_index(data)
}
MediaType::PypiV1Html | MediaType::TextHtml => {
let text = response
.text()
.await
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
SimpleIndexMetadata::from_html(&text, &url)?
}
};
OwnedArchive::from_unarchived(&metadata)
}
};
let simple_request = self
.uncached_client(url)
.get(Url::from(url.clone()))
.header("Accept-Encoding", "gzip, deflate, zstd")
.header("Accept", accept)
.build()
.map_err(|err| ErrorKind::from_reqwest(url.clone(), err))?;
let index = self
.cached_client()
.get_cacheable_with_retry(
simple_request,
&cache_entry,
cache_control,
parse_simple_response,
)
.await?;
Ok(index)
}
/// Fetch the list of projects from a local Simple API index.
async fn fetch_local_simple_index(
&self,
url: &DisplaySafeUrl,
) -> Result<OwnedArchive<SimpleIndexMetadata>, Error> {
let path = url
.to_file_path()
.map_err(|()| ErrorKind::NonFileUrl(url.clone()))?
.join("index.html");
let text = match fs_err::tokio::read_to_string(&path).await {
Ok(text) => text,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Err(Error::from(ErrorKind::LocalIndexNotFound(path)));
}
Err(err) => {
return Err(Error::from(ErrorKind::Io(err)));
}
};
let metadata = SimpleIndexMetadata::from_html(&text, url)?;
OwnedArchive::from_unarchived(&metadata)
}
@ -818,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| {
@ -902,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.
@ -1033,7 +1246,7 @@ impl RegistryClient {
#[derive(Debug)]
pub(crate) enum SimpleMetadataSearchOutcome {
/// Simple metadata was found
Found(OwnedArchive<SimpleMetadata>),
Found(OwnedArchive<SimpleDetailMetadata>),
/// Simple metadata was not found
NotFound,
/// A status code failure was encountered when searching for
@ -1114,24 +1327,71 @@ pub struct VersionSourceDist {
pub file: File,
}
/// The list of projects available in a Simple API index.
#[derive(Default, Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[rkyv(derive(Debug))]
pub struct SimpleMetadata(Vec<SimpleMetadatum>);
pub struct SimpleIndexMetadata {
/// The list of project names available in the index.
projects: Vec<PackageName>,
}
impl SimpleIndexMetadata {
/// Iterate over the projects in the index.
pub fn iter(&self) -> impl Iterator<Item = &PackageName> {
self.projects.iter()
}
/// Create a [`SimpleIndexMetadata`] from a [`PypiSimpleIndex`].
fn from_pypi_index(index: PypiSimpleIndex) -> Self {
Self {
projects: index.projects.into_iter().map(|entry| entry.name).collect(),
}
}
/// Create a [`SimpleIndexMetadata`] from a [`PyxSimpleIndex`].
fn from_pyx_index(index: PyxSimpleIndex) -> Self {
Self {
projects: index.projects.into_iter().map(|entry| entry.name).collect(),
}
}
/// Create a [`SimpleIndexMetadata`] from HTML content.
fn from_html(text: &str, url: &DisplaySafeUrl) -> Result<Self, Error> {
let html = crate::html::SimpleIndexHtml::parse(text).map_err(|err| {
Error::from(ErrorKind::BadHtml {
source: err,
url: url.clone(),
})
})?;
Ok(Self {
projects: html.projects,
})
}
}
#[derive(Default, Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[rkyv(derive(Debug))]
pub struct SimpleDetailMetadata(Vec<SimpleDetailMetadatum>);
#[derive(Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[rkyv(derive(Debug))]
pub struct SimpleMetadatum {
pub struct SimpleDetailMetadatum {
pub version: Version,
pub files: VersionFiles,
pub metadata: Option<ResolutionMetadata>,
}
impl SimpleMetadata {
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &SimpleMetadatum> {
impl SimpleDetailMetadata {
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &SimpleDetailMetadatum> {
self.0.iter()
}
fn from_files(files: Vec<uv_pypi_types::File>, package_name: &PackageName, base: &Url) -> Self {
let mut map: BTreeMap<Version, VersionFiles> = BTreeMap::default();
fn from_pypi_files(
files: Vec<uv_pypi_types::PypiFile>,
package_name: &PackageName,
base: &Url,
) -> Self {
let mut version_map: BTreeMap<Version, VersionFiles> = BTreeMap::default();
// Convert to a reference-counted string.
let base = SmallString::from(base.as_str());
@ -1143,11 +1403,7 @@ impl SimpleMetadata {
warn!("Skipping file for {package_name}: {}", file.filename);
continue;
};
let version = match filename {
DistFilename::SourceDistFilename(ref inner) => &inner.version,
DistFilename::WheelFilename(ref inner) => &inner.version,
};
let file = match File::try_from(file, &base) {
let file = match File::try_from_pypi(file, &base) {
Ok(file) => file,
Err(err) => {
// Ignore files with unparsable version specifiers.
@ -1155,7 +1411,7 @@ impl SimpleMetadata {
continue;
}
};
match map.entry(version.clone()) {
match version_map.entry(filename.version().clone()) {
std::collections::btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().push(filename, file);
}
@ -1166,67 +1422,161 @@ impl SimpleMetadata {
}
}
}
Self(
map.into_iter()
.map(|(version, files)| SimpleMetadatum { version, files })
version_map
.into_iter()
.map(|(version, files)| SimpleDetailMetadatum {
version,
files,
metadata: None,
})
.collect(),
)
}
/// Read the [`SimpleMetadata`] from an HTML index.
fn from_pyx_files(
files: Vec<uv_pypi_types::PyxFile>,
mut core_metadata: FxHashMap<Version, uv_pypi_types::CoreMetadatum>,
package_name: &PackageName,
base: &Url,
) -> Self {
let mut version_map: BTreeMap<Version, VersionFiles> = BTreeMap::default();
// Convert to a reference-counted string.
let base = SmallString::from(base.as_str());
// Group the distributions by version and kind
for file in files {
let file = match File::try_from_pyx(file, &base) {
Ok(file) => file,
Err(err) => {
// Ignore files with unparsable version specifiers.
warn!("Skipping file for {package_name}: {err}");
continue;
}
};
let Some(filename) = DistFilename::try_from_filename(&file.filename, package_name)
else {
warn!("Skipping file for {package_name}: {}", file.filename);
continue;
};
match version_map.entry(filename.version().clone()) {
std::collections::btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().push(filename, file);
}
std::collections::btree_map::Entry::Vacant(entry) => {
let mut files = VersionFiles::default();
files.push(filename, file);
entry.insert(files);
}
}
}
Self(
version_map
.into_iter()
.map(|(version, files)| {
let metadata =
core_metadata
.remove(&version)
.map(|metadata| ResolutionMetadata {
name: package_name.clone(),
version: version.clone(),
requires_dist: metadata.requires_dist,
requires_python: metadata.requires_python,
provides_extra: metadata.provides_extra,
dynamic: false,
});
SimpleDetailMetadatum {
version,
files,
metadata,
}
})
.collect(),
)
}
/// Read the [`SimpleDetailMetadata`] from an HTML index.
fn from_html(
text: &str,
package_name: &PackageName,
url: &DisplaySafeUrl,
) -> Result<Self, Error> {
let SimpleHtml { base, files } =
SimpleHtml::parse(text, url).map_err(|err| Error::from_html_err(err, url.clone()))?;
let SimpleDetailHTML { base, files } = SimpleDetailHTML::parse(text, url)
.map_err(|err| Error::from_html_err(err, url.clone()))?;
Ok(Self::from_files(files, package_name, base.as_url()))
Ok(Self::from_pypi_files(files, package_name, base.as_url()))
}
}
impl IntoIterator for SimpleMetadata {
type Item = SimpleMetadatum;
type IntoIter = std::vec::IntoIter<SimpleMetadatum>;
impl IntoIterator for SimpleDetailMetadata {
type Item = SimpleDetailMetadatum;
type IntoIter = std::vec::IntoIter<SimpleDetailMetadatum>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl ArchivedSimpleMetadata {
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &rkyv::Archived<SimpleMetadatum>> {
impl ArchivedSimpleDetailMetadata {
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &rkyv::Archived<SimpleDetailMetadatum>> {
self.0.iter()
}
pub fn datum(&self, i: usize) -> Option<&rkyv::Archived<SimpleMetadatum>> {
pub fn datum(&self, i: usize) -> Option<&rkyv::Archived<SimpleDetailMetadatum>> {
self.0.get(i)
}
}
#[derive(Debug)]
enum MediaType {
Json,
Html,
PyxV1Msgpack,
PyxV1Json,
PypiV1Json,
PypiV1Html,
TextHtml,
}
impl MediaType {
/// Parse a media type from a string, returning `None` if the media type is not supported.
fn from_str(s: &str) -> Option<Self> {
match s {
"application/vnd.pypi.simple.v1+json" => Some(Self::Json),
"application/vnd.pypi.simple.v1+html" | "text/html" => Some(Self::Html),
"application/vnd.pyx.simple.v1+msgpack" => Some(Self::PyxV1Msgpack),
"application/vnd.pyx.simple.v1+json" => Some(Self::PyxV1Json),
"application/vnd.pypi.simple.v1+json" => Some(Self::PypiV1Json),
"application/vnd.pypi.simple.v1+html" => Some(Self::PypiV1Html),
"text/html" => Some(Self::TextHtml),
_ => None,
}
}
/// Return the `Accept` header value for all supported media types.
/// Return the `Accept` header value for all PyPI media types.
#[inline]
const fn accepts() -> &'static str {
const fn pypi() -> &'static str {
// See: https://peps.python.org/pep-0691/#version-format-selection
"application/vnd.pypi.simple.v1+json, application/vnd.pypi.simple.v1+html;q=0.2, text/html;q=0.01"
}
/// Return the `Accept` header value for all supported media types.
#[inline]
const fn all() -> &'static str {
// See: https://peps.python.org/pep-0691/#version-format-selection
"application/vnd.pyx.simple.v1+msgpack, application/vnd.pyx.simple.v1+json;q=0.9, application/vnd.pypi.simple.v1+json;q=0.8, application/vnd.pypi.simple.v1+html;q=0.2, text/html;q=0.01"
}
}
impl std::fmt::Display for MediaType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::PyxV1Msgpack => write!(f, "application/vnd.pyx.simple.v1+msgpack"),
Self::PyxV1Json => write!(f, "application/vnd.pyx.simple.v1+json"),
Self::PypiV1Json => write!(f, "application/vnd.pypi.simple.v1+json"),
Self::PypiV1Html => write!(f, "application/vnd.pypi.simple.v1+html"),
Self::TextHtml => write!(f, "text/html"),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
@ -1255,10 +1605,12 @@ mod tests {
use url::Url;
use uv_normalize::PackageName;
use uv_pypi_types::SimpleJson;
use uv_pypi_types::PypiSimpleDetail;
use uv_redacted::DisplaySafeUrl;
use crate::{SimpleMetadata, SimpleMetadatum, html::SimpleHtml};
use crate::{
BaseClientBuilder, SimpleDetailMetadata, SimpleDetailMetadatum, html::SimpleDetailHTML,
};
use crate::RegistryClientBuilder;
use uv_cache::Cache;
@ -1307,7 +1659,7 @@ mod tests {
let redirect_server_url = DisplaySafeUrl::parse(&redirect_server.uri())?;
let cache = Cache::temp()?;
let registry_client = RegistryClientBuilder::new(cache)
let registry_client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache)
.allow_cross_origin_credentials()
.build();
let client = registry_client.cached_client().uncached();
@ -1367,7 +1719,7 @@ mod tests {
let redirect_server_url = DisplaySafeUrl::parse(&redirect_server.uri())?.join("foo/")?;
let cache = Cache::temp()?;
let registry_client = RegistryClientBuilder::new(cache)
let registry_client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache)
.allow_cross_origin_credentials()
.build();
let client = registry_client.cached_client().uncached();
@ -1415,7 +1767,7 @@ mod tests {
.await;
let cache = Cache::temp()?;
let registry_client = RegistryClientBuilder::new(cache)
let registry_client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache)
.allow_cross_origin_credentials()
.build();
let client = registry_client.cached_client().uncached();
@ -1474,16 +1826,16 @@ mod tests {
]
}
"#;
let data: SimpleJson = serde_json::from_str(response).unwrap();
let data: PypiSimpleDetail = serde_json::from_str(response).unwrap();
let base = DisplaySafeUrl::parse("https://pypi.org/simple/pyflyby/").unwrap();
let simple_metadata = SimpleMetadata::from_files(
let simple_metadata = SimpleDetailMetadata::from_pypi_files(
data.files,
&PackageName::from_str("pyflyby").unwrap(),
&base,
);
let versions: Vec<String> = simple_metadata
.iter()
.map(|SimpleMetadatum { version, .. }| version.to_string())
.map(|SimpleDetailMetadatum { version, .. }| version.to_string())
.collect();
assert_eq!(versions, ["1.7.8".to_string()]);
}
@ -1514,7 +1866,7 @@ mod tests {
// Note the lack of a trailing `/` here is important for coverage of url-join behavior
let base = DisplaySafeUrl::parse("https://account.d.codeartifact.us-west-2.amazonaws.com/pypi/shared-packages-pypi/simple/flask")
.unwrap();
let SimpleHtml { base, files } = SimpleHtml::parse(text, &base).unwrap();
let SimpleDetailHTML { base, files } = SimpleDetailHTML::parse(text, &base).unwrap();
let base = SmallString::from(base.as_str());
// Test parsing of the file urls

View File

@ -0,0 +1,382 @@
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{Context, Result};
use futures::future;
use http_body_util::combinators::BoxBody;
use http_body_util::{BodyExt, Full};
use hyper::body::{Bytes, Incoming};
use hyper::header::USER_AGENT;
use hyper::service::service_fn;
use hyper::{Request, Response};
use hyper_util::rt::{TokioExecutor, TokioIo};
use hyper_util::server::conn::auto::Builder;
use rcgen::{
BasicConstraints, Certificate, CertificateParams, DnType, ExtendedKeyUsagePurpose, IsCa,
Issuer, KeyPair, KeyUsagePurpose, SanType, date_time_ymd,
};
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls::server::WebPkiClientVerifier;
use rustls::{RootCertStore, ServerConfig};
use tokio::net::TcpListener;
use tokio::task::JoinHandle;
use tokio_rustls::TlsAcceptor;
use uv_fs::Simplified;
/// An issued certificate, together with the subject keypair.
#[derive(Debug)]
pub(crate) struct SelfSigned {
/// An issued certificate.
pub public: Certificate,
/// The certificate's subject signing key.
pub private: KeyPair,
}
/// Defines the base location for temporary generated certs.
///
/// See [`TestContext::test_bucket_dir`] for implementation rationale.
pub(crate) fn test_cert_dir() -> PathBuf {
std::env::temp_dir()
.simple_canonicalize()
.expect("failed to canonicalize temp dir")
.join("uv")
.join("tests")
.join("certs")
}
/// Generates a self-signed server certificate for `uv-test-server`, `localhost` and `127.0.0.1`.
/// This certificate is standalone and not issued by a self-signed Root CA.
///
/// Use sparingly as generation of certs is a slow operation.
pub(crate) fn generate_self_signed_certs() -> Result<SelfSigned> {
let mut params = CertificateParams::default();
params.is_ca = IsCa::NoCa;
params.not_before = date_time_ymd(1975, 1, 1);
params.not_after = date_time_ymd(4096, 1, 1);
params.key_usages.push(KeyUsagePurpose::DigitalSignature);
params.key_usages.push(KeyUsagePurpose::KeyEncipherment);
params
.extended_key_usages
.push(ExtendedKeyUsagePurpose::ServerAuth);
params
.distinguished_name
.push(DnType::OrganizationName, "Astral Software Inc.");
params
.distinguished_name
.push(DnType::CommonName, "uv-test-server");
params
.subject_alt_names
.push(SanType::DnsName("uv-test-server".try_into()?));
params
.subject_alt_names
.push(SanType::DnsName("localhost".try_into()?));
params
.subject_alt_names
.push(SanType::IpAddress("127.0.0.1".parse()?));
let private = KeyPair::generate()?;
let public = params.self_signed(&private)?;
Ok(SelfSigned { public, private })
}
/// Generates a self-signed root CA, server certificate, and client certificate.
/// There are no intermediate certs generated as part of this function.
/// The server certificate is for `uv-test-server`, `localhost` and `127.0.0.1` issued by this CA.
/// The client certificate is for `uv-test-client` issued by this CA.
///
/// Use sparingly as generation of these certs is a very slow operation.
pub(crate) fn generate_self_signed_certs_with_ca() -> Result<(SelfSigned, SelfSigned, SelfSigned)> {
// Generate the CA
let mut ca_params = CertificateParams::default();
ca_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); // root cert
ca_params.not_before = date_time_ymd(1975, 1, 1);
ca_params.not_after = date_time_ymd(4096, 1, 1);
ca_params.key_usages.push(KeyUsagePurpose::DigitalSignature);
ca_params.key_usages.push(KeyUsagePurpose::KeyCertSign);
ca_params.key_usages.push(KeyUsagePurpose::CrlSign);
ca_params
.distinguished_name
.push(DnType::OrganizationName, "Astral Software Inc.");
ca_params
.distinguished_name
.push(DnType::CommonName, "uv-test-ca");
ca_params
.subject_alt_names
.push(SanType::DnsName("uv-test-ca".try_into()?));
let ca_private_key = KeyPair::generate()?;
let ca_public_cert = ca_params.self_signed(&ca_private_key)?;
let ca_cert_issuer = Issuer::new(ca_params, &ca_private_key);
// Generate server cert issued by this CA
let mut server_params = CertificateParams::default();
server_params.is_ca = IsCa::NoCa;
server_params.not_before = date_time_ymd(1975, 1, 1);
server_params.not_after = date_time_ymd(4096, 1, 1);
server_params.use_authority_key_identifier_extension = true;
server_params
.key_usages
.push(KeyUsagePurpose::DigitalSignature);
server_params
.key_usages
.push(KeyUsagePurpose::KeyEncipherment);
server_params
.extended_key_usages
.push(ExtendedKeyUsagePurpose::ServerAuth);
server_params
.distinguished_name
.push(DnType::OrganizationName, "Astral Software Inc.");
server_params
.distinguished_name
.push(DnType::CommonName, "uv-test-server");
server_params
.subject_alt_names
.push(SanType::DnsName("uv-test-server".try_into()?));
server_params
.subject_alt_names
.push(SanType::DnsName("localhost".try_into()?));
server_params
.subject_alt_names
.push(SanType::IpAddress("127.0.0.1".parse()?));
let server_private_key = KeyPair::generate()?;
let server_public_cert = server_params.signed_by(&server_private_key, &ca_cert_issuer)?;
// Generate client cert issued by this CA
let mut client_params = CertificateParams::default();
client_params.is_ca = IsCa::NoCa;
client_params.not_before = date_time_ymd(1975, 1, 1);
client_params.not_after = date_time_ymd(4096, 1, 1);
client_params.use_authority_key_identifier_extension = true;
client_params
.key_usages
.push(KeyUsagePurpose::DigitalSignature);
client_params
.extended_key_usages
.push(ExtendedKeyUsagePurpose::ClientAuth);
client_params
.distinguished_name
.push(DnType::OrganizationName, "Astral Software Inc.");
client_params
.distinguished_name
.push(DnType::CommonName, "uv-test-client");
client_params
.subject_alt_names
.push(SanType::DnsName("uv-test-client".try_into()?));
let client_private_key = KeyPair::generate()?;
let client_public_cert = client_params.signed_by(&client_private_key, &ca_cert_issuer)?;
let ca_self_signed = SelfSigned {
public: ca_public_cert,
private: ca_private_key,
};
let server_self_signed = SelfSigned {
public: server_public_cert,
private: server_private_key,
};
let client_self_signed = SelfSigned {
public: client_public_cert,
private: client_private_key,
};
Ok((ca_self_signed, server_self_signed, client_self_signed))
}
// Plain is fine for now; Arc/Box could be used later if we need to support move.
type ServerSvcFn =
fn(
Request<Incoming>,
) -> future::Ready<Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error>>;
#[derive(Default)]
pub(crate) struct TestServerBuilder<'a> {
// Custom server response function
svc_fn: Option<ServerSvcFn>,
// CA certificate
ca_cert: Option<&'a SelfSigned>,
// Server certificate
server_cert: Option<&'a SelfSigned>,
// Enable mTLS Verification
mutual_tls: bool,
}
impl<'a> TestServerBuilder<'a> {
pub(crate) fn new() -> Self {
Self {
svc_fn: None,
server_cert: None,
ca_cert: None,
mutual_tls: false,
}
}
#[expect(unused)]
/// Provide a custom server response function.
pub(crate) fn with_svc_fn(mut self, svc_fn: ServerSvcFn) -> Self {
self.svc_fn = Some(svc_fn);
self
}
/// Provide the server certificate. This will enable TLS (HTTPS).
pub(crate) fn with_server_cert(mut self, server_cert: &'a SelfSigned) -> Self {
self.server_cert = Some(server_cert);
self
}
/// CA certificate used to build the `RootCertStore` for client verification.
/// Requires `with_server_cert`.
pub(crate) fn with_ca_cert(mut self, ca_cert: &'a SelfSigned) -> Self {
self.ca_cert = Some(ca_cert);
self
}
/// Enforce mutual TLS (client cert auth).
/// Requires `with_server_cert` and `with_ca_cert`.
pub(crate) fn with_mutual_tls(mut self, mutual: bool) -> Self {
self.mutual_tls = mutual;
self
}
/// Starts the HTTP(S) server with optional mTLS enforcement.
pub(crate) async fn start(self) -> Result<(JoinHandle<Result<()>>, SocketAddr)> {
// Validate builder input combinations
if self.ca_cert.is_some() && self.server_cert.is_none() {
anyhow::bail!("server certificate is required when CA certificate is provided");
}
if self.mutual_tls && (self.ca_cert.is_none() || self.server_cert.is_none()) {
anyhow::bail!("ca certificate is required for mTLS");
}
// Set up the TCP listener on a random available port
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
// Setup TLS Config (if any)
let tls_acceptor = if let Some(server_cert) = self.server_cert {
// Prepare Server Cert and KeyPair
let server_key = PrivateKeyDer::try_from(server_cert.private.serialize_der()).unwrap();
let server_cert = vec![CertificateDer::from(server_cert.public.der().to_vec())];
// Setup CA Verifier
let client_verifier = if let Some(ca_cert) = self.ca_cert {
let mut root_store = RootCertStore::empty();
root_store
.add(CertificateDer::from(ca_cert.public.der().to_vec()))
.expect("failed to add CA cert");
if self.mutual_tls {
// Setup mTLS CA config
WebPkiClientVerifier::builder(root_store.into())
.build()
.expect("failed to setup client verifier")
} else {
// Only load the CA roots
WebPkiClientVerifier::builder(root_store.into())
.allow_unauthenticated()
.build()
.expect("failed to setup client verifier")
}
} else {
WebPkiClientVerifier::no_client_auth()
};
let mut tls_config = ServerConfig::builder()
.with_client_cert_verifier(client_verifier)
.with_single_cert(server_cert, server_key)?;
tls_config.alpn_protocols = vec![b"http/1.1".to_vec(), b"http/1.0".to_vec()];
Some(TlsAcceptor::from(Arc::new(tls_config)))
} else {
None
};
// Setup Response Handler
let svc_fn = if let Some(custom_svc_fn) = self.svc_fn {
custom_svc_fn
} else {
|req: Request<Incoming>| {
// Get User Agent Header and send it back in the response
let user_agent = req
.headers()
.get(USER_AGENT)
.and_then(|v| v.to_str().ok())
.map(ToString::to_string)
.unwrap_or_default(); // Empty Default
let response_content = Full::new(Bytes::from(user_agent))
.map_err(|_| unreachable!())
.boxed();
// If we ever want a true echo server, we can use instead
// let response_content = req.into_body().boxed();
// although uv-client doesn't expose post currently.
future::ok::<_, hyper::Error>(Response::new(response_content))
}
};
// Spawn the server loop in a background task
let server_task = tokio::spawn(async move {
let svc = service_fn(move |req: Request<Incoming>| svc_fn(req));
let (tcp_stream, _remote_addr) = listener
.accept()
.await
.context("Failed to accept TCP connection")?;
// Start Server (not wrapped in loop {} since we want a single response server)
// If we want server to accept multiple connections, we can wrap it in loop {}
// but we'll need to ensure to handle termination signals in the tests otherwise
// it may never stop.
if let Some(tls_acceptor) = tls_acceptor {
let tls_stream = tls_acceptor
.accept(tcp_stream)
.await
.context("Failed to accept TLS connection")?;
let socket = TokioIo::new(tls_stream);
tokio::task::spawn(async move {
Builder::new(TokioExecutor::new())
.serve_connection(socket, svc)
.await
.expect("HTTPS Server Started");
});
} else {
let socket = TokioIo::new(tcp_stream);
tokio::task::spawn(async move {
Builder::new(TokioExecutor::new())
.serve_connection(socket, svc)
.await
.expect("HTTP Server Started");
});
}
Ok(())
});
Ok((server_task, addr))
}
}
/// Single Request HTTP server that echoes the User Agent Header.
pub(crate) async fn start_http_user_agent_server() -> Result<(JoinHandle<Result<()>>, SocketAddr)> {
TestServerBuilder::new().start().await
}
/// Single Request HTTPS server that echoes the User Agent Header.
pub(crate) async fn start_https_user_agent_server(
server_cert: &SelfSigned,
) -> Result<(JoinHandle<Result<()>>, SocketAddr)> {
TestServerBuilder::new()
.with_server_cert(server_cert)
.start()
.await
}
/// Single Request HTTPS mTLS server that echoes the User Agent Header.
pub(crate) async fn start_https_mtls_user_agent_server(
ca_cert: &SelfSigned,
server_cert: &SelfSigned,
) -> Result<(JoinHandle<Result<()>>, SocketAddr)> {
TestServerBuilder::new()
.with_ca_cert(ca_cert)
.with_server_cert(server_cert)
.with_mutual_tls(true)
.start()
.await
}

View File

@ -1,2 +1,4 @@
mod http_util;
mod remote_metadata;
mod ssl_certs;
mod user_agent_version;

View File

@ -3,7 +3,7 @@ use std::str::FromStr;
use anyhow::Result;
use uv_cache::Cache;
use uv_client::RegistryClientBuilder;
use uv_client::{BaseClientBuilder, RegistryClientBuilder};
use uv_distribution_filename::WheelFilename;
use uv_distribution_types::{BuiltDist, DirectUrlBuiltDist, IndexCapabilities};
use uv_pep508::VerbatimUrl;
@ -11,8 +11,8 @@ use uv_redacted::DisplaySafeUrl;
#[tokio::test]
async fn remote_metadata_with_and_without_cache() -> Result<()> {
let cache = Cache::temp()?.init()?;
let client = RegistryClientBuilder::new(cache).build();
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
// first run.
@ -21,11 +21,11 @@ async fn remote_metadata_with_and_without_cache() -> Result<()> {
let filename = WheelFilename::from_str(url.rsplit_once('/').unwrap().1)?;
let dist = BuiltDist::DirectUrl(DirectUrlBuiltDist {
filename,
location: Box::new(DisplaySafeUrl::parse(url).unwrap()),
url: VerbatimUrl::from_str(url).unwrap(),
location: Box::new(DisplaySafeUrl::parse(url)?),
url: VerbatimUrl::from_str(url)?,
});
let capabilities = IndexCapabilities::default();
let metadata = client.wheel_metadata(&dist, &capabilities).await.unwrap();
let metadata = client.wheel_metadata(&dist, &capabilities).await?;
assert_eq!(metadata.version.to_string(), "4.66.1");
}

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