Commit graph

894 commits

Author SHA1 Message Date
konsti
2e0ce70d13
Initial windows support (#940)
## Summary

First batch of changes for windows support. Notable changes:

* Fixes all compile errors and added windows specific paths.
* Working venv creation on windows, both from a base interpreter and
from a venv. This requires querying `stdlib` from the sysconfig paths to
find the launcher.
* Basic url/path conversion handling for windows.
* `if cfg!(...)` instead of `#[cfg()]`. This should make it easier to
keep everything compiling across platforms.

## Outlook

Test summary: 402 tests run: 299 passed (15 slow), 103 failed, 1 skipped

There are various reason for the remaining test failure:
* Windows-specific colorama and tzdata dependencies that change the
snapshot slightly. This is by far the biggest batch.
* Some url-path handling issues. I fixed some in the PR, some remain.
* Lack of the latest python patch versions for older pythons on my
machine, since there are no builds for windows and we need to register
them in the registry for them to be picked up for `py --list-paths` (CC
@zanieb RE #1070).
* Lack of entrypoint launchers.
* ... likely more
2024-01-24 18:27:49 +01:00
Zanie Blue
ea4ab29bad
Prefer target Python version over current version for builds (#1040)
Extends #1029 
Closes https://github.com/astral-sh/puffin/issues/1038

Instead of always using the current Python version for builds when a
target version is provided, we will do our best to use a compatible
Python version for builds.

Removes behavior where Python versions without patch versions were
always assumed to be the latest known patch version (previously
discussed in https://github.com/astral-sh/puffin/pull/534). While this
was convenient for resolutions which include packages which require
minimum patch versions e.g. `requires-python=">=3.7.4"`, it conflicts
with the idea that the target Python version you provide is the
_minimum_ compatible version. Additionally, it complicates interpreter
lookup as we cannot tell if the user has asked for that specific patch
version or not.
2024-01-24 11:12:02 -06:00
konsti
77dcb2421a
Add some instructions about build dependencies (#1075)
You need to install cmake on windows, so i added a hint about using
`pipx install cmake`, and some more general notes on building and
testing puffin.

See #817
2024-01-24 17:03:55 +00:00
Charlie Marsh
0519375bd6
Remove some unused dependencies (#1077) 2024-01-24 11:58:21 -05:00
Charlie Marsh
afb571643f
Avoid unzipping local wheels when fresh (#1076)
Since the archive is a single file in this case, we can rely on the
modification timestamp to check for freshness.
2024-01-24 15:01:16 +00:00
konsti
411613a24e
No python prefix in packse scenarios (#1066)
In windows, `python3.9` and `python3.11` are not in `PATH`. Instead, we
should pass only the python version to `puffin venv -p` in packse
scenarios (#1039).
2024-01-24 11:22:48 +00:00
Charlie Marsh
63f3434b21
Use nanoid instead of uuid (#1074)
## Summary

Gives us equivalent randomness with ~half as many characters.
2024-01-24 05:05:14 +00:00
Andrew Gallant
eebc2f340a
make some things guaranteed to be deterministic (#1065)
This PR replaces a few uses of hash maps/sets with btree maps/sets and
index maps/sets. This has the benefit of guaranteeing a deterministic
order of iteration.

I made these changes as part of looking into a flaky test.
Unfortunately, I'm not optimistic that anything here will actually fix
the flaky test, since I don't believe anything was actually dependent
on the order of iteration.
2024-01-23 20:30:33 -05:00
Charlie Marsh
1b3a3f4e80
Add --refresh behavior to the cache (#1057)
## Summary

This PR is an alternative approach to #949 which should be much safer.
As in #949, we add a `Refresh` policy to the cache. However, instead of
deleting entries from the cache the first time we read them, we now
check if the entry is sufficiently new (created after the start of the
command) if the refresh policy applies. If the entry is stale, then we
avoid reading it and continue onward, relying on the cache to
appropriately overwrite based on "new" data. (This relies on the
preceding PRs, which ensure the cache is append-only, and ensure that we
can atomically overwrite.)

Unfortunately, there are just a lot of paths through the cache, and
didn't data is handled with different policies, so I really had to go
through and consider the "right" behavior for each case. For example,
the HTTP requests can use `max-age=0, must-revalidate`. But for the
routes that are based on filesystem modification, we need to do
something slightly different.

Closes #945.
2024-01-23 18:30:26 -05:00
Charlie Marsh
cf8b452414
Track HTTP caches for URL wheels (#1071)
## Summary

This PR ensures that we store HTTP caching information for wheels.
Previously, we only stored these for source distributions. This will be
helpful for refresh, since we can avoid re-downloading wheels that are
unchanged per HTTP caching semantics.

There should be zero performance hit here for warm installs, and only an
extremely small hit for cold installs (writing the HTTP cache data to
disk). The hyperfine benchmarks reflect this.
2024-01-23 17:31:42 -05:00
Charlie Marsh
09f5884f28
Avoid revalidating immutable HTTP responses (#1069)
## Summary

If you send a revalidation request to a resource that returns an
`immutable` directive, the server apparently returns a 200 instead of a
304? In other words, the server can ignore the revalidation request.
This PR adds handling on top of the HTTP cache semantics to respect
immutable resources, which is especially useful since all PyPI files are
immutable.
2024-01-23 16:22:21 -05:00
Charlie Marsh
5621c414cf
Use symlinks for directories entries in cache (#1037)
## Summary

One problem we have in the cache today is that we can't overwrite
entries atomically, because we store unzipped _directories_ in the cache
(which makes installation _much_ faster than storing zipped
directories). So, if you ignore the existing contents of the cache when
writing, you might run into an error, because you might attempt to write
a directory where a directory already exists.

This is especially annoying for cache refresh, because in order to
refresh the cache, we have to purge it (i.e., delete a bunch of stuff),
which is also highly unsafe if Puffin is running across multiple threads
or multiple processes.

The solution I'm proposing here is that whenever we persist a
_directory_ to the cache, we persist it to a special "archive" bucket.
Then, within the other buckets, directory entries are actually symlinks
into that "archive" bucket. With symlinks, we can atomically replace,
which means we can easily overwrite cache entries without having to
delete from the cache.

The main downside is that we'll now accumulate dangling entries in the
"archive" bucket, and so we'll need to implement some form of garbage
collection to ensure that we remove entries with no symlinks. Another
downside is that cache reads and writes will be a bit slower, since we
need to deal with creating and resolving these symlinks.

As an example... after this change, the cache entry for this unzipped
wheel is actually a symlink:

![Screenshot 2024-01-22 at 11 56
18 AM](99ff6940-5096-4246-8d16-2a7bdcdd8d4b)

Then, within the archive directory, we actually have two unique entries
(since I intentionally ran the command twice to ensure overwrites were
safe):

![Screenshot 2024-01-22 at 11 56
22 AM](717d04e2-25d9-4225-b190-bad1441868c6)
2024-01-23 19:52:37 +00:00
Charlie Marsh
556080225d
Use ctime for interpreter timestamps (#1067)
Per https://apenwarr.ca/log/20181113, `ctime` should be a lot more
conservative, and should detect things like the issue we see with the
python-build-standalone builds, where the `mtime` is identical across
builds.

On Windows, I'm just using `last_write_time`. But we should probably add
`volume_serial_number` and other attributes via
[`winapi_util`](https://docs.rs/winapi-util/latest/winapi_util/index.html).
2024-01-23 19:52:20 +00:00
Charlie Marsh
6561617c56
Store source distribution builds under a unique manifest ID (#1051)
## Summary

This is a refactor of the source distribution cache that again aims to
make the cache purely additive. Instead of deleting all built wheels
when the cache gets invalidated (e.g., because the source distribution
changed on PyPI or something), we now treat each invalidation as its own
cache directory. The manifest inside of the source distribution
directory now becomes a pointer to the "latest" version of the source
distribution cache.

Here's a visual example:

![Screenshot 2024-01-22 at 5 35
41 PM](ca103c83-e116-4956-b91c-8434fe62cffe)

With this change, we avoid deleting built distributions that might be
relied on elsewhere and maintain our invariant that the cache is purely
additive. The cost is that we now preserve stale wheels, but we should
add a garbage collection mechanism to deal with that.
2024-01-23 19:49:11 +00:00
Charlie Marsh
e32027e384
Avoid persisting manifest data in standalone file (#1044)
## Summary

This PR gets rid of the manifest that we store for source distributions.
Historically, that manifest included the source distribution metadata,
plus a list of built wheels.

The problem with the manifest is that it duplicates state, since we now
have to look at both the manifest and the filesystem to understand the
cache state. Instead, I think we should treat the cache as the source of
truth, and get rid of the duplicated state in the manifest.

Now, we store the manifest (which is merely used to check for cache
freshness -- in future PRs, I will repurpose it though, so I left it
around), then the distribution metadata as its own file, then any
distributions in the same directory. When we want to see if there are
any valid distributions, we `readdir` on the directory. This is also
much more consistent with how the install plan works.
2024-01-23 19:46:48 +00:00
Zanie Blue
19c5cc8aba
Improve retry of pip install scenario tests (#1063)
Follow-up to #996 with retries of the full `pip_install_scenario` module
since there are a few tests that flake and tracking down the
non-determinism is proving to be quite complicated.

Adds a profile that can be used to disable retries, e.g. `--profile
no-retry` when you expect the scenario to change.

As before, hopefully this is a short-term measure.
2024-01-23 10:35:16 -06:00
Zanie Blue
1f0a21d127
Write an Into<anstream::ColorChoice> implementation for more idiomatic code (#1064)
Follow-up to #1049
2024-01-23 15:43:16 +00:00
konsti
1131341cbc
Support more formats in puffin venv, incl. windows support (#1039)
Mirroring `virtualenv -p` and driven by the lack of `pythonx.y` in
`PATH` on windows, this PR adds `-p x.y` support to `puffin venv` (first
commit).

Supported formats:
* NEW: `-p 3.10` searches for an installed Python 3.10 (Looking for
`python3.10` on linux/mac).
  Specifying a patch version is not supported
* `-p python3.10` or `-p python.exe` looks for a binary in `PATH`
* `-p /home/ferris/.local/bin/python3.10` uses this exact Python

In the second commit, we add python interpreter search on windows using
`py --list-paths`. On windows, all python are called `python.exe` so the
unix trick of looking for `python{}.{}` in `PATH` doesn't work. Instead,
we ask the python launcher for windows to tell us about all installed
packages. We should eventually migrate this to [PEP
514](https://peps.python.org/pep-0514/) by reading the registry entries
ourselves.
2024-01-23 15:35:07 +00:00
Charlie Marsh
cb04fa4496
Hide --exclude-newer from the command line (#1058)
This exists for our own test suite.
2024-01-23 00:29:47 -05:00
Zanie Blue
5db81c7caa
Add --color always|never|auto interface (#1049)
Extends #1048 interface providing a more general interface that I think
should be standard.

Allows forcing colors to be on _or_ off. e.g. `NO_COLOR=1 pip install
pip-tools --color always` would be colored.

Hides the `--no-color` option as it only exists for compatibility (and
seems better than throwing an error when people assume it will exist).

Has a nice side-effect of documenting our coloring behaviors e.g.

```
--color <COLOR>
    Control colors in output
    
    [default: auto]

    Possible values:
    - auto:   Enables colored output only when the output is going to a terminal or TTY with support
    - always: Enables colored output regardless of the detected environment
    - never:  Disables colored output
```
2024-01-22 23:01:36 -06:00
Zanie Blue
a9a7b0069b
Add --force-reinstall alias for --reinstall to match pip interface (#1045)
Tested with `cargo run -- pip install pip-tools --force-reinstall`.

The alias is hidden.
2024-01-22 22:59:43 -06:00
Zanie Blue
a87e071b5e
Add --no-color support for pip compatibility (#1048)
Adds `--no-color` as provided by `pip`.

See #1049 for follow-up.
2024-01-22 22:56:51 -06:00
Charlie Marsh
81401a17e5
Use archive_mtime in another call site (#1056)
_Not_ using this was an oversight.
2024-01-23 04:51:18 +00:00
Charlie Marsh
9fd3b8298d
Use fs_err::tokio consistently in distribution database (#1055) 2024-01-22 19:14:29 -05:00
Zanie Blue
b5463e2a5b
Pin dependencies in scenario requirements to allow install with puffin (#1050) 2024-01-22 17:11:11 -06:00
Zanie Blue
f3562e5a25
Canonicalize paths to interpreter executables before checking modified time (#1046)
If the executable is a symbolic link, checking the modified time will
not reflect changes to the source file e.g.

```
❯ touch foo
❯ ln -s foo foobar
❯ gstat -c %Y foo
1705958431
❯ gstat -c %Y foobar
1705958438
❯ touch foo
❯ gstat -c %Y foobar
1705958438
```

This can result in a stale cache being treated as fresh; for example,
when Rye changes the interpreter linked in a virtual environment.
2024-01-22 15:44:22 -06:00
Zanie Blue
c06bc335c4
Fix failing test cases (#1047)
These tests from #1041 failing on `main`
2074501921
due to conflict with #1042
2024-01-22 21:31:12 +00:00
Zanie Blue
a2efd74209
Add complex Python requirement scenarios (#1041)
Follows #1011 with some more scenarios
2024-01-22 14:31:06 -06:00
Charlie Marsh
c8941d4799
Rename metadata.msgpack to manifest.msgpack (#1043)
We store the `Manifest` at this path, so this name feels more
appropriate.
2024-01-22 15:00:41 -05:00
Zanie Blue
89eb8547ce
Fix missing comma before conclusions (#1042)
Closes https://github.com/astral-sh/puffin/issues/1010
2024-01-22 13:31:09 -06:00
Zanie Blue
e21948f353
Improve display of Python versions (#1029)
In https://github.com/astral-sh/puffin/pull/986 there was some confusion
about what these values are set to and I noticed that we never actually
display the target version being used for a resolution.

- Consistently display the Python interpreter being used, i.e. make it
clear that we are referring the the interpreter/installed Python version
and always show the version number
- Display the target Python version during solving
2024-01-22 18:46:18 +00:00
Charlie Marsh
e6f5c8360c
Use a separate memory index for each requirement (#1036)
Closes #1005.
2024-01-22 16:22:03 +00:00
Charlie Marsh
b0e73d796c
Add support for PyPy wheels (#1028)
## Summary

This PR adds support for PyPy wheels by changing the compatible tags
based on the implementation name and version of the current interpreter.

For now, we only support CPython and PyPy, and explicitly error out when
given other interpreters. (Is this right? Should we just fallback to
CPython tags...? Or skip the ABI-specific tags for unknown
interpreters?)

The logic is based on
4d85340613/src/packaging/tags.py (L247).
Note, however, that `packaging` uses the `EXT_SUFFIX` variable from
`sysconfig`... Instead, I looked at the way that PyPy formats the tags,
and recreated them based on the Python and implementation version. For
example, PyPy wheels look like
`cchardet-2.1.7-pp37-pypy37_pp73-win_amd64.whl` -- so that's `pp37` for
PyPy with Python version 3.7, and then `pypy37_pp73` for PyPy with
Python version 3.7 and PyPy version 7.3.

Closes https://github.com/astral-sh/puffin/issues/1013.

## Test Plan

I tested this manually, but I couldn't find macOS universal PyPy
wheels... So instead I added `cchardet` to a `requirements.in`, ran
`cargo run pip sync requirements.in --index-url
https://pypy.kmtea.eu/simple --verbose`, and added logging to verify
that the platform tags matched (even if the architecture didn't).
2024-01-22 14:22:27 +00:00
Charlie Marsh
145ba0e5ab
Allow relative paths in requirements.txt (#1027)
This PR attempts to fix a common footgun in `requirements.txt` files.
Previously, to provide a file, you had to use `package_name @
file:///Users/crmarsh/...` -- in other words, an absolute path.

Now, these requirements follow the exact same rules as editables, so you
can do:
```
package_name @ ./file.zip
```

And similar.

The way the parsing is setup, this is intentionally _not_ supported when
reading metadata -- only when parsing `requirements.txt` directly.

Closes #984.
2024-01-22 14:20:30 +00:00
Charlie Marsh
e09a51653e
Propagate cancellation errors in OnceMap (#1032)
## Summary

Ensures that if an operation is cancelled in one thread, we propagate it
to others rather than panicking.

Related to https://github.com/astral-sh/puffin/issues/1005.
2024-01-22 09:00:21 -05:00
Charlie Marsh
db0c76c4ba
Improve requirements-txt error formatting (#1026)
- Wrap filename in quotes
- Only show the start position (I think the end is a bit noisy)
2024-01-22 13:42:17 +00:00
konsti
765e3175e1
Make windows compile (#1035)
Minimal changes to make `cargo check`/`cargo run` work to unblock the
remaining PR stacking
2024-01-22 13:11:20 +00:00
Charlie Marsh
b9bee013ce
Use full Python version for installed version (#1033)
## Summary

`interpreter.version()` returns the `python_full_version`, but the
marker variant uses `python_version` instead of `python_full_version` --
so it's omitting the patch.
2024-01-22 00:44:39 -06:00
Zanie Blue
6202c9e1b5
Use current and requested Python versions in requires-python incompatibility errors (#986)
Closes https://github.com/astral-sh/puffin/issues/806
2024-01-22 00:32:02 -06:00
Charlie Marsh
23f73592b1
Add test to avoid invalidating virtualenv (#1031)
## Summary

I think if we used symlinks (instead of hardlinks), this test would fail
-- so it's worth including.
2024-01-21 19:53:58 -05:00
Charlie Marsh
540442b8de
Treat missing package name error as an unsupported requirement (#1025)
## Summary

Based on user feedback. Calling it a "parse error" is misleading, since
this is really something we don't support, but that users can work
around.
2024-01-21 19:53:10 -05:00
Zanie Blue
4026710189
Add scenario tests for pip-compile (#1011)
e.g. for scenarios that test resolution _without_ installation.

This refactors the `update` script to generate scenario test files for
`pip compile` _and_ `pip install`. We don't overlap scenarios to save
time. We only generate `pip compile` test cases for scenarios we cannot
represent with `pip install` e.g. a `--python-version` override.

The _one_ scenario I added happened to reveal a bug in our resolver
where we were incorrectly filtering versions by the installed version
when wheels were available. Per the comment at
https://github.com/astral-sh/puffin/issues/883#issuecomment-1890773112,
we should _only_ need to check for a compatible installed Python version
when using a different _target_ Python version if we need to build a
source distribution.
53bce68400
resolves this by removing the excessive constraints — the correct Python
version incompatibilities are applied elsewhere.
2024-01-21 17:47:42 -06:00
Charlie Marsh
d9cc9dbf88
Improve error message when editable requirement doesn't exist (#1024)
Making these a lot clearer in the common case by reducing the depth of
the error.
2024-01-20 12:59:18 -05:00
Charlie Marsh
53b7e3cb4f
Update list of expected architectures for cargo-dist (#1021) 2024-01-19 22:32:01 +00:00
Charlie Marsh
69d2791a43
Remove URL clone in requirements-txt parser (#1020) 2024-01-19 17:30:17 -05:00
Charlie Marsh
b3954f2449
Enable PowerPC builds (#1017)
Closes #1015.
2024-01-19 17:29:11 -05:00
Charlie Marsh
459c2abc81
Avoid canonicalizing paths in requirements-txt (#1019)
## Summary

When you specify an editable that doesn't exist, it should error, but
not in the parser -- the error should be downstream.
2024-01-19 16:28:04 -05:00
Charlie Marsh
d55e34c310
Make editable URL parsing more robust (#1018)
This just generalizes the parsing to handle arbitrary schemes instead of
encoding a fixed list.
2024-01-19 16:01:33 -05:00
Charlie Marsh
72924935f8
Upgrade cargo-dist (#1016) 2024-01-19 20:19:02 +00:00
Charlie Marsh
c66395977d
Rename pep440-rs to Readme.md (#1014)
This is due to a bug in Maturin
(https://github.com/PyO3/maturin/pull/1915), so I'll just fix our setup
to work with existing versions.

Closes https://github.com/astral-sh/puffin/issues/991.
2024-01-19 15:16:12 -05:00