## Summary
If you `uv run` from the same directory via multiple processes at the
same time, some of them will fail as they'll see an "incomplete" virtual
environment.
Closes https://github.com/astral-sh/uv/issues/11219.
I think `UV_PROJECT_ENVIRONMENT` is too complicated for use-cases where
the user wants to sync to the active environment. I don't see a
compelling reason not to make opt-in easier. I see a lot of questions
about how to deal with this warning in the issue tracker, but it seems
painful to collect them here for posterity.
A notable behavior here — we'll treat this as equivalent to
`UV_PROJECT_ENVIRONMENT` so... if you point us to a valid virtual
environment that needs to be recreated for some reason (e.g., new Python
version request), we'll happily delete it and start over.
## Summary
This PR removes the ephemeral `.pth` overlay when using a cached
environment. This solution isn't _completely_ safe, since we could
remove the `.pth` file just as another process is starting the
environment... But that risk already exists today, since we could
_overwrite_ the `.pth` file just as another process is starting the
environment, so I think what I've added here is a strict improvement.
Ideally, we wouldn't write this file at all, and we'd instead somehow
(e.g.) pass a file to the interpreter to run at startup? Or find some
other solution that doesn't require poisoning the cache like this.
Closes https://github.com/astral-sh/uv/issues/11117.
# Test Plan
Ran through the great reproduction steps from the linked issue.
Before:

After:

## Summary
I'm not sure that this has much of an effect in practice, but currently,
when we return a virtual environment, the `sys_base_executable ` of the
parent ends up being retained as `sys_base_executable` of the created
environment. But these can be, like, subtly different? If you have a
symlink to a Python, then for the symlink, `sys_base_executable` will be
equal to `sys_executable`. But when you create a virtual environment for
that interpreter, we'll set `home` to the resolved symlink, and so
`sys_base_executable` will be the resolved symlink too, in general.
Anyway, this means that we should now have a consistent value between
(1) returning `Virtualenv` from the creation routine and (2) querying
the created interpreter.
## Summary
It turns out that we were returning slightly different interpreter paths
on repeated `uv run --with` commands. This likely didn't affect many (or
any?) users, but it does affect our test suite, since in the test suite,
we use a symlinked interpreter.
The issue is that on first invocation, we create the virtual
environment, and that returns the path to the `python` executable in the
environment. On second invocation, we return the `python3` executable,
since that gets priority during discovery. This on its own is
potentially ok. The issue is that these resolve to different
`sys._base_executable` values in these flows... The latter gets the
correct value (since it's read from the `home` key), but the former gets
the incorrect value (since it's just the `base_executable` of the
executable that created the virtualenv, which is the symlink).
We now use the same logic to determine the "cached interpreter" as in
virtual environment creation, to ensure consistency between those paths.
Closes https://github.com/astral-sh/uv/issues/11214
Special-cases the first Python executable we find on the `PATH`,
allowing it to be considered during searches for virtual environments.
For some context, there are two stages to Python interpreter discovery
1. We find possible Python executables in various sources
2. We query the executables to determine canonical metadata about the
interpreter
We can't really be "sure" if an executable is a complaint virtual
environment during (1), we need to query the interpreter first. This
means that if you're only allowed to installed into virtual
environments, we'll query every interpreter on your PATH. This is not
performant, and causes confusion for users. Notably, I recently improved
error messaging when we can't find any valid interpreters, by showing
the error message we encounter while querying an interpreter (if any).
However, this is problematic when there's an error for an interpreter
that is not relevant to your search. In
https://github.com/astral-sh/uv/pull/11143, I added filtering to avoid
querying additional interpreters, but that regressed some user
experiences where they were relying on us finding implicitly active
virtual environments via the PATH.
In https://github.com/astral-sh/tokio-tar/pull/2, we accidentally
changed the `target_base` from the target base to the parent of the
file. This would cause hardlink unpacking to fail.
Example: A hardlink at `hardlinked-0.1.0/pyproject.toml` pointing to
`hardlinked-0.1.0/pyproject.toml.real` would try pointing to
`hardlinked-0.1.0/hardlinked-0.1.0/pyproject.toml.real` instead and fail
the unpacking.
The actual fix is in astral-tokio-tar, on the uv side there are only tests.
Fixes#11213
<!--
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 got a bit confused when testing `[dependency-groups]` because uv's
error message had the same typo I did in my `pyproject.toml`.
I tried to fix it, as well as a few comment I found along the way.
These are noisy relative to the effect they have on the user. It seems
better to prioritize hints on poor resolutions. Notably, it seems hard
to make these "not noisy" ref #11091.
Does not include the "lowest" resolution mode, in which lower bounds are
critical.
With the parallel simple index fetching, we would only acquire one
download concurrency token, meaning that we could in the worst case make
times the number of indexes more requests than the user requested limit.
We fix this by passing the semaphore down to the simple API method.
Looks like the set based prioritize tracking from
https://github.com/pubgrub-rs/pubgrub/pull/313 is a slight speedup.
I assume the changed derivation tree in the error snapshot is due to
out-of-sync virtual package priorities, while the main package priority
defining the solution remains stable.
```
$ hyperfine --warmup 2 "./uv-main pip compile --no-progress scripts/requirements/airflow.in --universal" "./uv-branch pip compile --no-progress scripts/requirements/airflow.in --universal"
Benchmark 1: ./uv-main pip compile --no-progress scripts/requirements/airflow.in --universal
Time (mean ± σ): 115.0 ms ± 4.8 ms [User: 131.0 ms, System: 113.6 ms]
Range (min … max): 108.1 ms … 125.8 ms 25 runs
Benchmark 2: ./uv-branch pip compile --no-progress scripts/requirements/airflow.in --universal
Time (mean ± σ): 105.4 ms ± 2.6 ms [User: 118.5 ms, System: 113.5 ms]
Range (min … max): 101.1 ms … 111.9 ms 28 runs
Summary
./uv-branch pip compile --no-progress scripts/requirements/airflow.in --universal ran
1.09 ± 0.05 times faster than ./uv-main pip compile --no-progress scripts/requirements/airflow.in --universal
```
uv-install-wheel had the logic for laying out the installation and for
linking a directory in the same module. We split them up to isolate each
module's logic and tighten the crate's interface to only expose top
level members.
No logic changes, only moving code around.
As before, these are fine-grained PATs and will expire in 366 days.
They're generated by splitting the token into three parts (by `_`) and
base64 encoding.
## Summary
This lets us drop a dependency entirely. `percent-encoding` is used by
`url` and so is already in the graph, whereas `urlencoding` isn't used
by anything else.
## Summary
This PR adds an additional normalization step to `CanonicalUrl` whereby
we now percent-decode the path, to ensure that (e.g.)
`torch-2.5.1%2Bcpu.cxx11.abi-cp39-cp39-linux_x86_64.whl` and
`torch-2.5.1+cpu.cxx11.abi-cp39-cp39-linux_x86_64.whl` are considered
equal. Further, when generating the "reinstall" report, we use the
canonical URL rather than the verbatim URL.
In making this change, I also learned that we don't apply any of the
normalization passes to `file://` URLs. I inadvertently removed it in
93d606aba2,
since setting the password or URL on ` file://` URL errors -- but now
suppress those errors anyway.
Closes https://github.com/astral-sh/uv/issues/11082.
## Test Plan
- Downloaded a [PyTorch
wheel](https://download.pytorch.org/whl/cpu-cxx11-abi/torch-2.5.1%2Bcpu.cxx11.abi-cp39-cp39-linux_x86_64.whl)
- `python3.9 -m pip install
torch-2.5.1+cpu.cxx11.abi-cp39-cp39-linux_x86_64.whl --platform
linux_x86_64 --target foo --no-deps`
- `cargo run pip install
torch-2.5.1+cpu.cxx11.abi-cp39-cp39-linux_x86_64.whl --python-platform
linux --python-version 3.9 --target foo --no-deps`
- Verified that the package had the `~` symbol for the reinstall.
## Summary
We now show a custom error if (1) the file doesn't exist at all, or (2)
it's not a PEP 723 script.
In the future, `uv lock --script` should probably initialize the script,
but that requires a more extensive refactor. At present, we just
silently lock the project instead, which is pretty bad!
Closes https://github.com/astral-sh/uv/issues/10979.
## Summary
I'm not a fan of registries including fragments here that aren't hashes,
but the spec doesn't expressly forbid it. I think it's reasonable to
ignore them.
Specifically, the spec is here:
https://packaging.python.org/en/latest/specifications/simple-repository-api/.
It says that:
> The URL **SHOULD** include a hash in the form of a URL fragment with
the following syntax: `#<hashname>=<hashvalue>`, where `<hashname>`he
lowercase name of the hash function (such as sha256) and `<hashvalue>`
is the hex encoded digest.
But it doesn't mention other fragments.
Closes https://github.com/astral-sh/uv/issues/7257.
## Summary
If we fail to deserialize cached metadata in the cache, we should just
ignore it, rather than failing.
Ideally, this never happens. If it does, it means we missed a cache
version bump. But if it does happen, it should still be non-fatal.
Closes https://github.com/astral-sh/uv/issues/11043.
Closes https://github.com/astral-sh/uv/issues/11101.
## Test Plan
Prior to this PR, the following would fail:
- `uvx uv@0.5.25 venv --python 3.12 --cache-dir foo`
- `uvx uv@0.5.25 pip install ./scripts/packages/hatchling_dynamic
--no-deps --python 3.12 --cache-dir foo`
- `uvx uv@0.5.18 venv --python 3.12 --cache-dir foo`
- `uvx uv@0.5.18 pip install ./scripts/packages/hatchling_dynamic
--no-deps --python 3.12 --cache-dir foo`
We can't go back and fix 0.5.18, but this will prevent such regressions
in the future.
Closes https://github.com/astral-sh/uv/issues/11048
This brings the `PythonEnvironment::from_root` behavior in-line with the
rest of uv Python discovery behavior (and in-line with pip). It's not
clear why we were canonicalizing the path in the first place here.
## Summary
This PR migrates all of our PyTorch tests to use our own mirror, which
includes upload timestamps that we can use to enforce
`--excludes-newer`, making the tests far more stable over time. (Today,
if you checkout old versions of `uv`, many of the PyTorch tests will
fail, since the index contents drift over time.)
Some snapshots changed in this PR (see, e.g.,
`universal_nested_overlapping_local_requirement`). The underlying reason
is that I used the current timestamp when setting upload times in the
PyTorch mirror, but those tests read from both the PyTorch
`--find-links` index _and_ PyPI. I guess we don't omit `--find-links`
entries based on `--excludes-newer`? That might be a bug. But I had to
_increase_ the `--excludes-newer` to include the PyTorch mirror's
`--find-links`, which meant pulling in some newer packages from PyPI
too. This is fine: it's a one-time churn, and they'll be stable going
forward.
In #10875, I relaxed the error checking during resolution to permit
dependencies like `foo[x1]`, where `x1` was defined to be conflicting.
In exchange, the error was, roughly speaking, moved to installation
time. This was achieved by looking at the full set of enabled extras
and checking whether any conflicts occurred. If so, an error was
reported. This ends up being more expressive and permits more valid
configurations.
However, in so doing, there was a bug in how the accumulated extras
were being passed to conflict marker evaluation. Namely, we weren't
accounting for the fact that if `foo[x1]` was enabled, then that fact
should be carried through to all conflict marker evaluations. This is
because some of those will use things like `extra != 'x1'` to indicate
that it should only be included if an extra *isn't* enabled.
In #10985, this manifested with PyTorch where `torch==2.4.1` and
`torch==2.4.1+cpu` were being installed simultaneously. Namely, the
choice to install `torch==2.4.1` was not taking into account that
the `cpu` extra has been enabled. If it did, then it's conflict
marker would evaluate to `false`. Since it didn't, and since
`torch==2.4.1+cpu` was also being included, we ended up installing both
versions.
The approach I took in this PR was to add a second breadth first
traversal (which comes first) over the dependency tree to accumulate all
of the activated extras. Then, only in the second traversal do we
actually build up the resolution graph.
Unfortunately, I have no automatic regression test to include here. The
regression test we _ought_ to include involves `torch`. And while we are
generally find to use those in tests that only generate a lock file, the
regression test here actually requires running installation. And
downloading and installing `torch` in tests is bad juju. So adding a
regression test for this is blocked on better infrastructure for PyTorch
tests. With that said, I did manually verify that the test case in #10985
no longer installs multiple versions of `torch`.
Fixes#10985
## Summary
Fixes a recurring typo.
## Details
There's a typo appearing in a particular sentence...
> Ignore package dependencies, instead only add those packages
explicitly listed on the command line to the resulting **the**
requirements file.
... used in:
* `crates/uv-cli/src/lib.rs`
* `crates/uv-settings-src-settings.rs`
* `docs/reference/settings.md`
* `uv.schem.json`
Docs, comments and a CLI command description seem affected.
This PR fixes it.
---------
Co-authored-by: bujnok01 <bujnok01@heiway.net>
I'm sorry, but I was writing some new content here and the inconsistent
wrapping was very hard to maintain and I didn't want to muddy the diff
there with reflowing.
I don't think we need to be strict about the reflow (I'm not sure we
even can be) but some of these were very far off from our typical wrap
length.
## Summary
This is a really subtle issue. I'm actually having trouble writing a
test for it, though the problem makes sense. In short, we're sharing the
`SharedState` between the `BuildContext` and the universal resolver. The
`SharedState` includes `VersionMap`, which tracks incompatibilities...
The incompatibilities use the platform tags, which are only present when
resolving from the `BuildContext` (i.e., when resolving build
dependencies). The universal resolver then fails because it sees a bunch
of "incompatible" wheels that are incompatible with the current platform
(i.e., the current Python interpreter).
In short, we _cannot_ share a `SharedState` across two operations that
perform a universal and then a platform-specific resolution. So this PR
adds separate types and fixes up any overlapping usages.
A better setup, for the future, would be to somehow share the underlying
simple metadata, and only track separate `VersionMap` -- since there
_is_ a bunch of data we can share. But that's a larger change.
Closes https://github.com/astral-sh/uv/issues/10977.
## Summary
The issue here boils down to: when we write metadata that came from
building the wheel itself, we aren't setting the `dynamic` field.
We now _always_ set the dynamic field when reading, even when we read
cached data.
Closes https://github.com/astral-sh/uv/issues/11047.
<!--
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? -->
## Summary
On Windows, we have a lot of issues with atomic replacement and such.
There are a bunch of different failure modes, but they generally
involve: trying to persist a fail to a path at which the file already
exists, trying to replace or remove a file while someone else is reading
it, etc.
This PR adds locks to all of the relevant database paths. We already use
these advisory locks when building source distributions; now we use them
when unzipping wheels, storing metadata, etc.
Closes#11002.
## Test Plan
I ran the following script:
```shell
# Define the cache directory path
$cacheDir = "C:\Users\crmar\workspace\uv\cache"
# Clear the cache directory if it exists
if (Test-Path $cacheDir) {
Remove-Item -Recurse -Force $cacheDir
}
# Create the cache directory again
New-Item -ItemType Directory -Force -Path $cacheDir
# Define the command to run with --cache-dir flag
$command = {
param ($venvPath)
# Create a virtual environment in the specified path with --python
uv venv $venvPath
# Run the pip install command with --cache-dir flag
C:\Users\crmar\workspace\uv\target\profiling\uv.exe pip install flask==1.0.4 --no-binary flask --cache-dir C:\Users\crmar\workspace\uv\cache -v --python $venvPath
}
# Define the paths for the different virtual environments
$venv1 = "C:\Users\crmar\workspace\uv\venv1"
$venv2 = "C:\Users\crmar\workspace\uv\venv2"
$venv3 = "C:\Users\crmar\workspace\uv\venv3"
$venv4 = "C:\Users\crmar\workspace\uv\venv4"
$venv5 = "C:\Users\crmar\workspace\uv\venv5"
# Start the command in parallel five times using Start-Job, each with a different venv
$job1 = Start-Job -ScriptBlock $command -ArgumentList $venv1
$job2 = Start-Job -ScriptBlock $command -ArgumentList $venv2
$job3 = Start-Job -ScriptBlock $command -ArgumentList $venv3
$job4 = Start-Job -ScriptBlock $command -ArgumentList $venv4
$job5 = Start-Job -ScriptBlock $command -ArgumentList $venv5
# Wait for all jobs to complete
$jobs = @($job1, $job2, $job3, $job4, $job5)
$jobs | ForEach-Object { Wait-Job $_ }
# Retrieve the results (optional)
$jobs | ForEach-Object { Receive-Job -Job $_ }
# Clean up the jobs
$jobs | ForEach-Object { Remove-Job -Job $_ }
```
And ensured it succeeded in five straight invocations (whereas on
`main`, it consistently fails with a variety of different traces).
There should be two functional changes here:
- If we receive SIGINT twice, forward it to the child process
- If the `uv run` child process changes its PGID, then forward SIGINT
Previously, we never forwarded SIGINT to a child process. Instead, we
relied on shell to do so.
On Windows, we still do nothing but eat the Ctrl-C events we receive.
I cannot see an easy way to send them to the child.
The motivation for these changes should be explained in the comments.
Closes https://github.com/astral-sh/uv/issues/10952 (in which Ray
changes its PGID)
Replaces the (much simpler) #10989 with a more comprehensive approach.
See https://github.com/astral-sh/uv/pull/6738#issuecomment-2315451358
for some previous context.
## Summary
When a `pyproject.toml` `[tool.uv.sources.(package)]` section specifies
`workspace` and one or more of (`index`, `git`, `url`, `path`, `rev`,
`tag`, `branch`, `editable`), running `uv` to build or sync the package
gives the error:
```
cannot specify both `index` and `(parameter name)`
```
The error should actually say:
```
cannot specify both `workspace` and `(parameter name)`
```
## Test Plan
I ran `cargo test`, and all tests still passed.
## Summary
I think the "available versions" may not filter on `--exclude-newer`,
since it's marked as an incompatibility? In which case, this error
message can change as versions are published.
First of all, I want to test automatic managed installs (see #10913) and
need to set that up. Second of all, some tests were _implicitly_
downloading interpreters instead of using the one from their context —
which is unexpected and naughty and very slow.
We'll probably end up shipping but we were moving ahead with this on the
basis that pip may not even ship this, so let's play it safe and wait
for a bit.
This was an oversight in the implementation, thankfully it appears to be
a simple fix? (My only hesitation is this implementation essentially
claims that --only-group is defacto incompatible with --extra and I
*think* that's the case but I'm not certain.)
Shoves a broken `git` executable onto the front of the `PATH` in the
test context when the `git` feature is disabled so they fail if they're
missing the feature-gate.
## Summary
I'm open to not merging this -- I was kind of just interested in what
the API looked like. But the idea is: we can avoid hashing values twice
and unnecessarily cloning within the priority map by using the raw entry
API.
## Summary
In preview mode on windows, register und un-register the managed python build standalone installations in the Windows registry following PEP 514.
We write the values defined in the PEP plus the download URL and hash. We add an entry when installing a version, remove an entry when uninstalling and removing all values when uninstalling with `--all`. We update entries only by overwriting existing values, there is no "syncing" involved.
Since they are not official builds, pbs gets a prefix. `py -V:Astral/CPython3.13.1` works, `py -3.13` doesn't.
```
$ py --list-paths
-V:3.12 * C:\Users\Konsti\AppData\Local\Programs\Python\Python312\python.exe
-V:3.11.9 C:\Users\Konsti\.pyenv\pyenv-win\versions\3.11.9\python.exe
-V:3.11 C:\Users\micro\AppData\Local\Programs\Python\Python311\python.exe
-V:3.8 C:\Users\micro\AppData\Local\Programs\Python\Python38\python.exe
-V:Astral/CPython3.13.1 C:\Users\Konsti\AppData\Roaming\uv\data\python\cpython-3.13.1-windows-x86_64-none\python.exe
```
Registry errors are reported but not fatal, except for operations on the company key since it's not bound to any specific python interpreter.
On uninstallation, we prune registry entries that have no matching Python installation (i.e. broken entries).
The code uses the official `windows_registry` crate of the `winreg` crate.
Best reviewed commit-by-commit.
## Test Plan
We're reusing an existing system check to test different (un)installation scenarios.
Ultimately this is a lot of settings plumbing and a couple minor pieces
of Actual Logic (which are so simple I have to assume there's something
missing, but maybe not!).
Note this "needlessly" use DevDependencyGroup since it costs nothing, is
more futureproof, and lets us maintain one primary interface (we just
pass `false` for all the dev arguments).
Fixes#8590Fixes#8969
This is the test we tweaked a few commits back when we first removed the
error checking in the resolver. We now add in some `uv sync` commands,
including one that should fail.
This collects ALL activated extras while traversing the lock file to
produce a `Resolution` for installation. If any two extras are activated
that are conflicting, then an error is produced.
We add a couple of tests to demonstrate the behavior. One case is
desirable (where we conditionally depend on `package[extra]`) and the
other case is undesirable (where we create an uninstallable lock file).
Fixes#9942, Fixes#10590
This will make `package[extra]` work even when `extra` is declared as a
conflicting extra.
Note that this isn't relevant for dependency groups since AFAIK those
can actually only be enabled on the CLI. There is no `package:group`
dependency syntax.
With the previous commit loosening a restriction in the resolver, it
reveals a bug: a `uv sync` won't install a `package[extra]` dependency.
This occurs because `extra` isn't treated as activated during install,
and thus `package[extra]`'s conflict marker isn't satisfied.
In other words, the way we dealt with conflict markers previously
assumed that conflicting extras could _only_ be activated via
`--extra foo`. And while that used to be true, after the previous
commit, it no longer is.
We'll fix this bug in the next commit. I added this test in a separate
commit to make the problem and resulting fix clearer.
This removes the error that was causing folks problems.
This does result in some snapshot updates that are arguably wrong, or at
least sub-optimal. However, it's actually intended. Because the approach
we're going to take is going to permit the creation of uninstallable
lock files as a side effect. In the future, we will modify this test to
check that, while `uv lock` succeeds, `uv sync` will always fail.
## One-liner
Relative find-links configuration to local path from a pyproject.toml or
uv.toml is now relative to the config file
## Summary
### Background
One can configure find-links in a `pyproject.toml` or `uv.toml` file,
which are located from the cli arg, system directory, user directory, or
by traversing parent directories until one is encountered.
This PR addresses the following scenario:
- A project directory which includes a `pyproject.toml` or `uv.toml`
file
- The config file includes a `find-links` option. (eg under `[tool.uv]`
for `pyproject.toml`)
- The `find-links` option is configured to point to a local subdirectory
in the project: `packages/`
- There is a subdirectory called `subdir`, which is the current working
directory
- I run `uv run my_script.py`. This will locate the `pyproject.toml` in
the parent directory
### Current Behavior
- uv tries to use the path `subdir/packages/` to find packages, and
fails.
### New Behavior
- uv tries to use the path `packages/` to find the packages, and
succeeds
- Specifically, any relative local find-links path will resolve to be
relative to the configuration file.
### Why is this behavior change OK?
- I believe no one depends on the behavior that a relative find-links
when running in a subdir will refer to different directories each time
- Thus this change only allows a more common use case which didn't work
previously.
## Test Plan
- I re-created the setup mentioned above:
```
UvTest/
├── packages/
│ ├── colorama-0.4.6-py2.py3-none-any.whl
│ └── tqdm-4.67.1-py3-none-any.whl
├── subdir/
│ └── my_script.py
└── pyproject.toml
```
```toml
# pyproject.toml
[project]
name = "uvtest"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"tqdm>=4.67.1",
]
[tool.uv]
offline = true
no-index = true
find-links = ["packages/"]
```
- With working directory under `subdir`, previously, running `uv sync
--offline` would fail resolving the tdqm package, and after the change
it succeeds.
- Additionally, one can use `uv sync --show-settings` to show the
actually-resolved settings - now having the desired path in
`flat_index.url.path`
## Alternative designs considered
- I considered modifying the `impl Deserialize for IndexUrl` to parse
ahead of time directly with a base directory by having a custom
`Deserializer` with a base dir field, but it seems to contradict the
design of the serde `Deserialize` trait - which should work with all
`Deserializer`s
## Future work
- Support for adjusting all other local-relative paths in `Options`
would be desired, but is out of scope for the current PR.
---------
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
## Summary
These tests don't need a build backend. If we omit it, the project is
treated as virtual, and we avoid building and installing it.
The only changes in the snapshots should be a decrement in resolve or
install count, since we're often now omitting the project itself.
I left the build backend for anything borderline, including workspace
members within tests.
## Summary
We should only be ignoring changes in `version` for dynamic projects;
for static projects, it should still be enforced. We should also be
invalidating the lockfile if a project goes from static to dynamic or
vice versa.
Closes#10852.
## Summary
If members define disjoint Python requirements, we should error. Right
now, it seems that it maps to unbounded and leads to weird behavior.
Closes https://github.com/astral-sh/uv/issues/10835.
## Summary
This PR reverts https://github.com/astral-sh/uv/pull/10441 and applies a
different fix for https://github.com/astral-sh/uv/issues/10425.
In #10441, I changed prioritization to visit proxies eagerly. I think
this is actually wrong, since it means we prioritize proxy packages
above _everything_ else. And while a proxy only depends on itself, it
does mean we're selecting a _version_ for the proxy package earlier than
anything else. So, if you look at #10828, we end up choosing a version
for `async-timeout` before we choose a version for `langchain`, despite
the latter being a first-party dependency. (`async-timeout` has a marker
on it, so it has a proxy package, so we solve for it first.)
To fix#10425, we instead need to make sure we visit proxies in the
order we see them. I think the virtual tiebreaker for proxies is
reversed? We want to visit the package we see first, first.
So, in short: this reverts #10441, then corrects the ordering for
visiting proxies.
Closes https://github.com/astral-sh/uv/issues/10828.
## Summary
The linked issue actually isn't a bug on main anymore, but it does
require us to take the "slow" path, since setuptools seems to reorder
the extras. This PR adds another normalization step which lets us take
the fast path: https://github.com/astral-sh/uv/issues/10855.
## Summary
For example: in the linked issue, the user has a symlink at
`pyproject.toml`. The GitHub CDN doesn't give us any way to determine
whether a file is a symlink, so we should just log the error and move on
to the slow path.
Closes https://github.com/astral-sh/uv/issues/10857
## Summary
I noticed that we're only handling `Error::WheelMetadataNameMismatch`
here; but `Error::WheelMetadataVersionMismatch` should also be treated
as non-fatal.
## Summary
Relates to #10273.
This doesn't solve what is highlighted in
https://github.com/astral-sh/uv/issues/10273#issuecomment-2569515066,
but I believe this is still an improvement for users not setting
`upgrade = true` in `[tool.uv]`.
## Test Plan
Ran commands locally:
```shell
$ cargo run --quiet -- lock --locked --upgrade
error: the argument '--check' cannot be used with '--upgrade'
Usage: uv lock --check
For more information, try '--help'.
```
from https://github.com/astral-sh/uv/issues/9742
```
❯ cargo run -q --bin uvx
Provide a command to run with `uvx <command>`.
The following tools are installed:
- ansible-core v2.17.5
- black v24.10.0
- rooster-blue v0.0.0
See `uvx --help` for more information.
❯ rm target/debug/uv
❯ cargo run -q --bin uvx
error: Could not find the `uv` binary at /Users/zb/workspace/uv/target/debug/uv
```
Previously, these errors would only be visible in the debug logs as
"Skipping bad interpreter ..." which can lead us to making some
ridiculous claims like "There is no virtual environment" or "Python is
not installed" when really we just failed to query the interpreter for
some reason.
We show the first error, sort of arbitrarily — but I think it matches
user expectation, i.e., this would be the first Python on your PATH.
Related to #10713
## Summary
I needed this for https://github.com/astral-sh/uv/pull/10794, but it
makes sense as a standalone change, since it's much more testable. We
can also reuse this in at least one more place.
When support for conflicting extras/groups was initially added, I
stopped short of including the conflict markers in uv's "fork markers"
in the lock file. That is, the fork markers are markers that indicate
the different splits uv took during resolution, which we record, I
believe, to avoid spurious updates to the lock file as a result of
using them as preferences.
One interesting result of omitting the conflict markers from the fork
markers is that sometimes this would result in duplicate markers. In
response, I wrote a function that stripped off the conflict markers and
deduplicated the remainder. My thinking at the time was that it wasn't
clear whether we needed to keep conflict markers around.
It looks like #10783 demonstrates a case where we do, seemingly, need
them. Namely, it's a case where after stripping conflict markers, you
don't end up with duplicate markers, but you do end up with overlapping
markers. Overlapping fork markers are bad juju for the same reason that
overlapping resolver forks are bad juju: you can end up with multiple
versions of the same package in the same environment.
I don't know how to fix overlapping markers without just including the
conflict markers. So that's what this PR does. Because of this, there
will be some churn in lock files, but this only applies to projects that
define conflicting extras.
This PR includes a regression test from #10783. I also manually tried
the original reproduction in #10772 (where adding `numpy<2` caused `uv
sync` to fail), and things worked.
Fixes#10772, Fixes#10783
## Summary
Replacing the large `pybabel` in tests with
[`executable-application`](https://pypi.org/project/executable-application/)
(1.7 KB).
We may want a separate test package with an executable that _does_ match
the name? This one intentionally does _not_. It would make it much
easier for us to rewrite the other tests in bulk, since we can do a
find-and-replace on `black`, etc.
Closes https://github.com/astral-sh/uv/issues/10646.
## Summary
This is a smaller alternative to #10794. If the `Requires-Dist` that we
extract statically doesn't match the lockfile metadata, we now go back
to the distribution database to double-check. Checking the
`Requires-Dist` is itself very cheap, so in the worst case, we're just
paying the same cost as prior to this optimization.
Closes https://github.com/astral-sh/uv/issues/10776.
## Summary
When resolving Git metadata, we may be able to fetch the metadata from
GitHub directly in some cases. This is _way_ faster, since we don't need
to perform many Git operations and, in particular, don't need to clone
the repo.
This only works in the following cases:
- The Git repository is public. Otherwise, I believe you need an access
token, which we don't have.
- The `pyproject.toml` has static metadata.
- The `pyproject.toml` has no `tool.uv.sources`. Otherwise, we need to
lower them... And, if there are any paths or workspace sources, that
requires an install path (i.e., we need the content on-disk).
- The project is in the repo root. If it's in a subdirectory, it could
be a workspace member. And if it's a workspace member, there could be
sources defined in the workspace root. But we can't know without
fetching the workspace root -- and we need the workspace in order to
find the root...
Closes#10568.
## Summary
These are very similar to (and computed in the same way as) the hints we
should during a failed resolution, but for install-time.
Closes#10635.
## Test Plan
As an example, when installing PyTorch on macOS with Python 3.13 (wheels
exist for Linux):
```
error: Distribution `torch==2.5.1 @ registry+https://pypi.org/simple` can't be installed because it doesn't have a source distribution or wheel for the current platform
hint: You're on macOS (`macosx_14_0_arm64`), but `torch` (v2.5.1) only has wheels for the following platform: `manylinux1_x86_64`
```
See https://github.com/astral-sh/uv/issues/4204 for motivation
This doesn't really reach the user experience I'd expect — i.e., we end
up saying a virtual environment "does not exist" which is a little
silly. However, I think improving the error messaging on interpreter
queries in general should be solved separately. I did one small
"general" change in
89e11d0222
— otherwise we don't show the message at all.
---------
Co-authored-by: konsti <konstin@mailbox.org>
## Summary
The fix I shipped in https://github.com/astral-sh/uv/pull/10690
regressed an important case. If we solve a PyPI branch before a PyTorch
branch, we'll end up respecting the preference, and choosing `2.2.2`
instead of `2.2.2+cpu`.
This PR goes back to ignoring preferences that don't map to the current
index. However, to solve https://github.com/astral-sh/uv/issues/10383,
we need to special-case `requirements.txt`, which can't provide explicit
indexes. So, if a preference comes from `requirements.txt`, we still
respect it.
Closes https://github.com/astral-sh/uv/issues/10772.
## Summary
For example, `cargo run python install
cpython-3.12.8-linux-x86_64_v3-gnu` (on macOS) shouldn't attempt to
patch the dylib. At present, it leads to this warning:
```
warning: Failed to patch the install name of the dynamic library for /Users/crmarsh/.local/share/uv/python/cpython-3.12.8-linux-x86_64_v3-gnu/bin/python3.12. This may cause issues when building Python native extensions.
Underlying error: Failed to update the install name of the Python dynamic library located at `/Users/crmarsh/.local/share/uv/python/cpython-3.12.8-linux-x86_64_v3-gnu/lib/libpython3.12.dylib`
```
## Summary
We now respect environment variable-based authentication when the
explicit index is defined outside of the workspace root. This applies to
both local and Git-based projects.
Closes https://github.com/astral-sh/uv/issues/10680.
<!--
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 new ARM runners report a permission error:
```
Run uvx twine check wheelhouse/*
error: failed to open file `/home/runneradmin/.config/uv/uv.toml`: Permission denied (os error 13)
```
In this PR, a PermissionsError is treated like not finding the file.
I reworked the structure just a bit to avoid calling `err.kind()`
multiple times.
## Test Plan
<!-- How was it tested? -->
Added a UNIX only test where I set the permissions of the folder
containing the file and try to find it.
---------
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
## Summary
This has a few effects:
1. We only call `preferences` once, which should be more efficient.
2. We collect `preferences` into a vector when there are multiple. Less
efficient, but pretty rare?
3. We now correctly prefer preferences from the same index.
## Summary
A bug in `requires_python` (which infers the Python requirement from a
marker) was leading us to break an invariant around the relationship
between the marker environment and the Python requirement. This, in
turn, was leading us to drop parts of the environment space when
solving.
Specifically, in the linked example, we generated a fork for
`python_full_version < '3.10' or platform_python_implementation !=
'CPython'`, which was later split into `python_full_version == '3.8.*'`
and `python_full_version == '3.9.*'`, losing the
`platform_python_implementation != 'CPython'` portion.
Closes https://github.com/astral-sh/uv/issues/10669.
## Summary
We can retain the small-size advantage of our new tags by moving the
"unknown tag" case into `WheelTagLarge`. This ensures that we can still
represent unknown tags, but avoid paying the cost for them.
Log the file that failed to bytecode compile when encountering a timeout
for debugging #6105 better.
[sysinfo](https://lib.rs/crates/sysinfo) would give us the option to
report memory usage too, but i'm hesitant to add a dependency just for
the error path.
These were introduced in https://github.com/astral-sh/uv/pull/587 but
are now showing up in our slow test list (#878) and we previously pared
down the `poetry_editable` test case dependencies — I think these were
just missed.
## Summary
I'm inferring that these are like... the older tag format? See, e.g.:
```
soxbindings-0.0.1-pp27-pypy_73-macosx_10_9_x86_64.whl
soxbindings-0.0.1-pp27-pypy_73-manylinux2010_x86_64.whl
soxbindings-0.0.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl
soxbindings-0.0.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl
```
## Summary
Fixes#10598
## Test Plan
Looking for input here @zanieb. How/where would you include tests for
this?
More broadly: do we want a failure to perform the rename to be a hard
error? Or should it start out as a warning?
---------
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
This log message is shown every time a script including a uv
shebang is run. After installing all dependencies, printing this log
message every time does not add any relevant information for the user. I
would say it could even be misleading and motivate the user to debug his
own program searching for this log message.
As a consequence, reduce the log level of this message to debug.
## Test Plan
uv run was called with default settings and the log message didn't show
up.
cargo test was run and I tried to fix the issues.
## Summary
This PR modifies the lockfile to omit versions for source trees that use
`dynamic` versioning, thereby enabling projects to use dynamic
versioning with `uv.lock`.
Prior to this change, dynamic versioning was largely incompatible with
locking, especially for popular tools like `setuptools_scm` -- in that
case, every commit bumps the version, so every commit invalidates the
committed lockfile.
Closes https://github.com/astral-sh/uv/issues/7533.
## Summary
I previously made this required, but we now need to be able to create
these from a lockfile that _omits_ versions for dynamic source trees.
They should still be present in most cases, but it's best-effort.
<!--
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 use `uv` for automation on remote hosts and it would be useful to have
it be able to tell me the supported versions of python (for the remote
machine) in a machine readable manner so I do not need to parse `uv
python list`.
This change adds `--format (json|text)` to `uv python list` to make it's
output machine readable
Loosely related:
- https://github.com/astral-sh/uv/issues/411
## Test Plan
Manually tested via
```
# quick inspection without pretty print
cargo run -- python list --format json
```
### Short example of output (trimmed down)
Cmd: `cargo run -- python list --format json | jq '.[:2]'`
```json
[
{
"key": "cpython-3.13.1+freethreaded-linux-x86_64-gnu",
"version": "3.13.1",
"version_parts": {
"major": 3,
"minor": 13,
"patch": 1
},
"path": null,
"symlink": null,
"url": "https://github.com/astral-sh/python-build-standalone/releases/download/20241219/cpython-3.13.1%2B20241219-x86_64-unknown-linux-gnu-freethreaded%2Bpgo%2Blto-full.tar.zst",
"os": "linux",
"variant": "freethreaded",
"implementation": "cpython",
"arch": "x86_64",
"libc": "gnu"
},
{
"key": "cpython-3.13.1-linux-x86_64-gnu",
"version": "3.13.1",
"version_parts": {
"major": 3,
"minor": 13,
"patch": 1
},
"path": "/usr/bin/python3.13",
"symlink": null,
"url": null,
"os": "linux",
"variant": "default",
"implementation": "cpython",
"arch": "x86_64",
"libc": "gnu"
}
]
```
---------
Co-authored-by: John Zlotek <jzlotek@gmail.com>
## Summary
I don't think this had an impact in practice, but it is "wrong" to omit
these. Confirmed that the cache (for example) now includes the build tag
(as in, `mkl_fft-1.3.8-72-cp310-cp310-manylinux2014_x86_64`).
## Summary
* Closes https://github.com/astral-sh/uv/pull/10515
* Bumps Rust Nightly to 1.85 Beta
* Removes old dev dependencies
## Test Plan
Existing tests.
Note, binaries need to be rebuilt for integrity before merging.
---------
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
After we resolve, we filter out any wheels that aren't applicable for
the target platforms. So, e.g., we remove macOS wheels if we find that
the user only asked to solve for Windows.
This PR extends the same logic to architectures, so that we filter out
ARM-only wheels when the user is only solving for x86, etc.
Closes#10571.
## Summary
This PR extends the thinking in #10525 to platform tags, and then uses
the structured tag enums everywhere, rather than passing around strings.
I think this is a big improvement! It means we're no longer doing ad hoc
tag parsing all over the place.
## Summary
The idea here is to show both (1) an example of a compatible tag and (2)
the tags that were available, whenever we fail to resolve due to an
abscence of matching wheels.
Closes https://github.com/astral-sh/uv/issues/2777.
## Summary
I need to be able to do non-lexicographic comparisons between tags
(e.g., so I can sort `cp313` as greater than `cp39`). It ended up being
easiest to just create structured types for all the tags we support,
with `FromStr` and `Display` implementations.
We don't currently store these in `Tags` or in `WheelFilename`. We may
want to, since they're really small (and `Copy`), but I need to
benchmark to determine whether parsing these in `WheelFilename` is
prohibitively slow.
## Summary
Resolves#5952
Add a `--path` option to `uv pip freeze` to be compatible with `pip
freeze`
## Test Plan
New snapshot tests
---------
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
## Summary
Closes#3312.
This PR adds Git LFS support to the `uv-git` crate by using the
`git-lfs` CLI to fetch required LFS objects for a revision following the
call to `git fetch`.
The LFS fetch step is disabled by default and only enabled if the
environment variable `UV_GIT_LFS` is set.
When enabled, the LFS fetch step is run for all repositories regardless
of whether they have associated LFS objects. The step is skipped if the
`git-lfs` CLI tool isn't installed.
## Test Plan
I verified that the minimal example in the linked issue passes, i.e.
this command now succeeds:
```sh
UV_GIT_LFS=1 uv pip install git+https://github.com/grebnetiew/lfs-py.git
```
I also verified that non-LFS repositories still work, with or without
`git-lfs` installed.
### To Replicate
Attempt to use uv to install a Git dependency that contains LFS objects
(e.g. `uv pip install git+https://github.com/grebnetiew/lfs-py.git`).
This should fail with a smudge filter error.
Re-run the same command with the added environment variable
`UV_GIT_LFS=1`. The install should now succeed.
## Potential Changes / Improvements
~With this change LFS objects in a given revision will always be
downloaded if the user has Git LFS installed, which may not always be
desired behavior. It might be helpful to add a field to the `uv`
settings and/or an environment variable so that the LFS step can be
disabled if needed.~
Enabling/disabled via environment variable has now been implemented.
---------
Co-authored-by: Sydney Duckworth <sydduckworth@users.noreply.github.com>
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
Closes https://github.com/astral-sh/uv/issues/10522.
## Test Plan
```
❯ cargo run venv
warning: Failed to parse `pyproject.toml` during environment creation:
TOML parse error at line 1, column 1
|
1 | [project]
| ^^^^^^^^^
`pyproject.toml` is using the `[project]` table, but the required `project.version` field is neither set nor present in the `project.dynamic` list
Using CPython 3.13.0
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
```
## Summary
The assumption that all tags are listed under a flat `.git/ref/tags`
structure was wrong. Git creates a hierarchy of directories for tags
containing slashes. To fix the cache key calculation, we need to
recursively traverse all files under that folder instead.
## Test Plan
1. Create an `uv` project with git-tag cache-keys;
2. Add any tag with slash;
3. Run `uv sync` and see uv_cache_info error in verbose log;
4. `uv sync` doesn't trigger reinstall on next tag addition or removal;
5. With fix applied, reinstall triggers on every tag update and there
are no errors in the log.
Fixes#10467
---------
Co-authored-by: Sergei Nizovtsev <sergei.nizovtsev@eqvilent.com>
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
## Summary
Closes https://github.com/astral-sh/uv/issues/10493.
## Test Plan
Run `cargo test --profile fast-build --no-fail-fast -p uv
username_password_sources` from a terminal.
## Summary
If you have a dependency with a marker, and you add a constraint, it
causes us to _always_ fork, because we represent the constraint as a
second dependency with the marker repeated (and, therefore, we have two
requirements of the same name, both with markers). I don't think we
should fork here -- and in the end it's leading to this undesirable
resolution: #10481.
I tried to change constraints such that we just _reuse_ and augment the
initial requirement, but that has a fairly negative effect on error
messages: #10489. So this fix seems a bit better to me.
Closes https://github.com/astral-sh/uv/issues/10481.
## Summary
Fixes a bug when there are only comments in the dependencies section.
Basically, after one removes all dependencies, if there are remaining
comments then the value unwrapped here
c198e2233e/crates/uv-workspace/src/pyproject_mut.rs (L1309)
is never properly initialized.
It's initialized to `None`, here
c198e2233e/crates/uv-workspace/src/pyproject_mut.rs (L1256),
but doesn't get set to `Some(...)` until the first dependency here
c198e2233e/crates/uv-workspace/src/pyproject_mut.rs (L1276)
and since we remove them all... there are none.
## Test Plan
Manually induced bug with
```
[project]
name = "t1"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"duct>=0.6.4",
"minilog>=2.3.1",
# comment
]
```
Then running
```
$ RUST_LOG=trace RUST_BACKTRACE=full uv remove duct minilog
DEBUG uv 0.5.8
DEBUG Found project root: `/home/bnorick/dev/workspace/t1`
DEBUG No workspace root found, using project root
thread 'main' panicked at crates/uv-workspace/src/pyproject_mut.rs:1294:73:
called `Option::unwrap()` on a `None` value
stack backtrace:
0: 0x5638d7bed6ba - <unknown>
1: 0x5638d783760b - <unknown>
2: 0x5638d7bae232 - <unknown>
3: 0x5638d7bf0f07 - <unknown>
4: 0x5638d7bf215c - <unknown>
5: 0x5638d7bf1972 - <unknown>
6: 0x5638d7bf1909 - <unknown>
7: 0x5638d7bf18f4 - <unknown>
8: 0x5638d75087d2 - <unknown>
9: 0x5638d750896b - <unknown>
10: 0x5638d7508d68 - <unknown>
11: 0x5638d8dcf1bb - <unknown>
12: 0x5638d76be271 - <unknown>
13: 0x5638d75ef1f9 - <unknown>
14: 0x5638d75fc3cd - <unknown>
15: 0x5638d772d9de - <unknown>
16: 0x5638d8476812 - <unknown>
17: 0x5638d83e1894 - <unknown>
18: 0x5638d84722d3 - <unknown>
19: 0x5638d83e1372 - <unknown>
20: 0x7f851cfc7d90 - <unknown>
21: 0x7f851cfc7e40 - __libc_start_main
22: 0x5638d758e992 - <unknown>
23: 0x0 - <unknown>
```
N.B. After fixing #10430, `ArcStr` became the fastest implementation
(and the gains were significantly reduced, down to 1-2%). See:
https://github.com/astral-sh/uv/pull/10453#issuecomment-2583344414.
## Summary
I tried out a variety of small string crates, but `Arc<str>`
outperformed them, giving a ~10% speed-up:
```console
❯ hyperfine "../arcstr lock" "../flexstr lock" "uv lock" "../arc lock" "../compact_str lock" --prepare "rm -f uv.lock" --min-runs 50 --warmup 20
Benchmark 1: ../arcstr lock
Time (mean ± σ): 304.6 ms ± 2.3 ms [User: 302.9 ms, System: 117.8 ms]
Range (min … max): 299.0 ms … 311.3 ms 50 runs
Benchmark 2: ../flexstr lock
Time (mean ± σ): 319.2 ms ± 1.7 ms [User: 317.7 ms, System: 118.2 ms]
Range (min … max): 316.8 ms … 323.3 ms 50 runs
Benchmark 3: uv lock
Time (mean ± σ): 330.6 ms ± 1.5 ms [User: 328.1 ms, System: 139.3 ms]
Range (min … max): 326.6 ms … 334.2 ms 50 runs
Benchmark 4: ../arc lock
Time (mean ± σ): 303.0 ms ± 1.2 ms [User: 301.6 ms, System: 118.4 ms]
Range (min … max): 300.3 ms … 305.3 ms 50 runs
Benchmark 5: ../compact_str lock
Time (mean ± σ): 320.4 ms ± 2.0 ms [User: 318.7 ms, System: 120.8 ms]
Range (min … max): 317.3 ms … 326.7 ms 50 runs
Summary
../arc lock ran
1.01 ± 0.01 times faster than ../arcstr lock
1.05 ± 0.01 times faster than ../flexstr lock
1.06 ± 0.01 times faster than ../compact_str lock
1.09 ± 0.01 times faster than uv lock
```
## Summary
We can read from the slice directly. I don't think this will affect
performance today, because `from_str` will then allocate, but it
_should_ be a speedup once #10475 merges, since we can then avoid
allocating a `String` and go straight from `str` to `ArcStr`.
#8061 incorrectly claims to change the delimiter for `UV_FIND_LINKS`
from spaces to commas. In reality, it prevents `UV_FIND_LINKS` from
being split. This commit fixes that.
## Summary
This appears to be a consistent 1% performance improvement and should
also reduce memory quite a bit. We've also decided to use these for
markers, so it's nice to use the same optimization here.
```
❯ hyperfine "./uv pip compile --universal scripts/requirements/airflow.in" "./arcstr pip compile --universal scripts/requirements/airflow.in" --min-runs 50 --warmup 20
Benchmark 1: ./uv pip compile --universal scripts/requirements/airflow.in
Time (mean ± σ): 136.3 ms ± 4.0 ms [User: 139.1 ms, System: 241.9 ms]
Range (min … max): 131.5 ms … 149.5 ms 50 runs
Benchmark 2: ./arcstr pip compile --universal scripts/requirements/airflow.in
Time (mean ± σ): 134.9 ms ± 3.2 ms [User: 137.6 ms, System: 239.0 ms]
Range (min … max): 130.1 ms … 151.8 ms 50 runs
Summary
./arcstr pip compile --universal scripts/requirements/airflow.in ran
1.01 ± 0.04 times faster than ./uv pip compile --universal scripts/requirements/airflow.in
```
It turns out that we use `UniversalMarker::pep508` quite a bit. To the
point that it makes sense to pre-compute it when constructing a
`UniversalMarker`.
This still isn't necessarily the fastest thing we can do, but this
results in a major speed-up and `without_extras` no longer shows up for
me in a profile.
Motivating benchmarks. First, from #10430:
```
$ hyperfine 'rm -f uv.lock && uv lock' 'rm -f uv.lock && uv-ag-optimize-without-extras lock'
Benchmark 1: rm -f uv.lock && uv lock
Time (mean ± σ): 408.3 ms ± 276.6 ms [User: 333.6 ms, System: 111.1 ms]
Range (min … max): 316.9 ms … 1195.3 ms 10 runs
Warning: The first benchmarking run for this command was significantly slower than the rest (1.195 s). This could be caused by (filesystem) caches that were not filled until after the first run. You should consider using the '--warmup' option to fill those caches before the actual benchmark. Alternatively, use the '--prepare' option to clear the caches before each timing run.
Benchmark 2: rm -f uv.lock && uv-ag-optimize-without-extras lock
Time (mean ± σ): 209.4 ms ± 2.2 ms [User: 209.8 ms, System: 103.8 ms]
Range (min … max): 206.1 ms … 213.4 ms 14 runs
Summary
rm -f uv.lock && uv-ag-optimize-without-extras lock ran
1.95 ± 1.32 times faster than rm -f uv.lock && uv lock
```
And now from #10438:
```
$ hyperfine 'uv pip compile requirements.in -c constraints.txt --universal --no-progress --python-version 3.8 --offline > /dev/null' 'uv-ag-optimize-without-extras pip compile requirements.in -c constraints.txt --universal --no-progress --python-version 3.8 --offline > /dev/null'
Benchmark 1: uv pip compile requirements.in -c constraints.txt --universal --no-progress --python-version 3.8 --offline > /dev/null
Time (mean ± σ): 12.718 s ± 0.052 s [User: 12.818 s, System: 0.140 s]
Range (min … max): 12.650 s … 12.815 s 10 runs
Benchmark 2: uv-ag-optimize-without-extras pip compile requirements.in -c constraints.txt --universal --no-progress --python-version 3.8 --offline > /dev/null
Time (mean ± σ): 419.5 ms ± 6.7 ms [User: 434.7 ms, System: 100.6 ms]
Range (min … max): 412.7 ms … 434.3 ms 10 runs
Summary
uv-ag-optimize-without-extras pip compile requirements.in -c constraints.txt --universal --no-progress --python-version 3.8 --offline > /dev/null ran
30.32 ± 0.50 times faster than uv pip compile requirements.in -c constraints.txt --universal --no-progress --python-version 3.8 --offline > /dev/null
```
Fixes#10430, Fixes#10438
## Summary
We shouldn't consider incompatible distributions (e.g., those that don't
match the required Python version) when determining the implied markers.
For some reason this was banned when originally added (I did not see
discussion about it). I think it's fine to allow. With `uv run`, there's
a bit of nuance because we also allow the script to be read from stdin.