Commit Graph

5369 Commits

Author SHA1 Message Date
Arjun Munji 3ed386c0c5
Remove setuptools & wheel from seed packages (#1602) (#1613)
## Summary
Removed `wheel` and `setuptools` from seed packages list when creating a
virtual environment

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

## Test Plan
Ran the command `cargo nextest run` :
<img width="564" alt="image"
src="https://github.com/astral-sh/uv/assets/6116387/14ed2da6-1b3e-4598-a49f-29dd8c4cb19b">

---------

Co-authored-by: Zanie <contact@zanie.dev>
2024-02-18 21:20:21 +00:00
Zanie Blue 63c313425f
Build source distributions in the cache directory instead of the global temporary directory (#1628)
Addresses report in https://github.com/astral-sh/uv/issues/1444 where a
temporary directory is created outside of the cache directory or current
virtual environment.

There is one additional usage of bare `tempdir` outside of tests we may
want to change:


2586f655bb/crates/install-wheel-rs/src/wheel.rs (L567)
2024-02-18 15:05:39 -06:00
Charlie Marsh 5cdc6de4a9
Add `CACHEDIR.TAG` to uv-created virtualenvs (#1653)
## Summary

Just as we mark virtualenvs as `gitignore`d by default, we should also
mark them as `CACHEDIR.TAG`, to ensure that they aren't included in
backups, etc.

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

## Test Plan

Ran `cargo run venv` and:

```
❯ ls .venv
CACHEDIR.TAG bin          lib          pyvenv.cfg
```
2024-02-18 13:32:11 -05:00
Alexander Gherm c04f597fae
Do not remove uv itself on pip sync (#1649)
## Summary

Added `uv` to the list of the preserved packages when building the
installer plan. In that case `uv` is not going to be removed when, for
example, using `python -m uv pip sync requirements.txt` when
requirements.txt does not contain `uv`, but `uv` is installed in that
venv.

Closes #1631 

## Test Plan

Got through the example attached to
https://github.com/astral-sh/uv/issues/1631 and did see the uv deletion
in the output
```
$ python -m uv pip sync requirements.txt
Installed 1 package in 20ms
 + ruff==0.2.2
```
2024-02-18 14:39:51 +00:00
Charlie Marsh fef1956c62
Bump simple metadata cache version (#1617)
## Summary

We made a breaking change to the cache representation, so some folks
have had to `uv clean`. Let's bump it for the next release.
2024-02-17 22:10:44 +00:00
Micha Reiser b296c04a67
Add CMD support (#1523)
## Sumamry

This PR adds the `activation.bat`, `deactivation.bat` and `pyenv.bat`
files to add support for using uv from CMD.

This PR further fixes an issue with our trampoline implementation where
calling an executable like `black` failed:

```
(venv) C:\Users\Micha\astral\test>where black
C:\Users\Micha\astral\test\.venv\Scripts\black.exe

(venv) C:\Users\Micha\astral\test>black
C:\Users\Micha\AppData\Local\Programs\Python\Python312\python.exe: can't open file 'C:\\Users\\Micha\\astral\\test\\black': [Errno 2] No such file or directory
```

The issue was that CMD doesn't extend `black` to its full path before
passing it to the trampoline and our trampoline generated the command
`<python> black` instead of `<python> .venv/Scripts/black`, and Python
can't find `black` in the project directory.

This PR fixes this by using the full executable name (that we already
parsed out to discover the Python version). This adds one complication,
we need to preserve the arguments without repeating the executable name
that is the first argument.
One option is to use
[`CommandLineToArgvW`](https://learn.microsoft.com/de-de/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw)
and then serialize the arguments 1.. to a string again. I decided
against that. Win32 API calls are easy to get wrong. That's why I
implemented the parsing rules specified in
[`CommandLineToArgvW`](https://learn.microsoft.com/de-de/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw)
to skip the first argument.

Fixes https://github.com/astral-sh/uv/issues/1471

## Test Plan


https://github.com/astral-sh/uv/assets/1203881/bdb537b6-97c8-4f7e-bb4a-3a614eb5e0f6

Powershell continues to work


https://github.com/astral-sh/uv/assets/1203881/6c806477-a7c6-4047-9ffc-5ed91c6f1c84

I haven't been able to test the aarch binaries.
2024-02-17 16:47:40 -05:00
Charlie Marsh ea62ae4ebd
Bump version to v0.1.4 (#1608) 2024-02-17 15:22:07 -05:00
Charlie Marsh 5c4cecaa85
Allow URL requirements in editable installs (#1614)
## Summary

If an editable package declares a direct URL requirement, we currently
error since it's not considered an "allowed" requirement. We need to add
those URLs to the allow-list.

Closes https://github.com/astral-sh/uv/issues/1603.
2024-02-17 15:20:23 -05:00
Micha Reiser 8675f66e74
Add license to activator scripts (#1610) 2024-02-17 20:37:26 +01:00
Charlie Marsh b5617198f3
Avoid propagating top-level options to sub-resolutions (#1607)
## Summary

It's incorrect to pass the resolution and dependency mode down to the
`BuildDispatch`, since it means that we'll use `--no-deps` when building
source distributions. If you set resolution to `lowest`, it also means
we end up using (e.g.) the lowest version of `wheel`, which also doesn't
make sense.

It's fine to pass `--exclude-newer`.

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

Closes https://github.com/astral-sh/uv/issues/1563.
2024-02-17 18:53:38 +00:00
Aarni Koskela bc14ed1613
Fix typos & add pre-commit configuration (#1487)
Co-authored-by: Zanie Blue <contact@zanie.dev>
2024-02-17 18:16:50 +01:00
Dhruv Manilawala e4389e537d
Consistent use of `BIN_NAME` in activation scripts (#1577)
This PR fixes the bug where the `BIN_NAME` replacement field wasn't
being used in the activator scripts.

fixes: #1518 

## Test plan

As I don't have a Windows machine, I switched the `bin_name` value here
to point to `Scripts` on `unix` platform:


2a76c59084/crates/gourgeist/src/bare.rs (L99-L105)

<details><summary>Code diff</summary>
<p>

```diff
```diff
diff --git a/crates/gourgeist/src/bare.rs b/crates/gourgeist/src/bare.rs
index 4c7808d3..0e0b41cf 100644
--- a/crates/gourgeist/src/bare.rs
+++ b/crates/gourgeist/src/bare.rs
@@ -97,9 +97,9 @@ pub fn create_bare_venv(location: &Utf8Path,
interpreter: &Interpreter) -> io::R
// TODO(konstin): I bet on windows we'll have to strip the prefix again
     let location = location.canonicalize_utf8()?;
     let bin_name = if cfg!(unix) {
-        "bin"
-    } else if cfg!(windows) {
         "Scripts"
+    } else if cfg!(windows) {
+        "bin"
     } else {
         unimplemented!("Only Windows and Unix are supported")
     };
```

</p>
</details> 

I then created the virtual environment as usual and tested out that the path modifications were correct:

```console
$ cargo run --bin uv -- venv
    Finished dev [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/uv venv`
Using Python 3.12.1 interpreter at
/Users/dhruv/.pyenv/versions/3.12.1/bin/python3.12
Creating virtualenv at: .venv

$ source .venv/Scripts/activate

$ echo $PATH
/Users/dhruv/work/astral/uv/.venv/Scripts:[...]

$ which python
/Users/dhruv/work/astral/uv/.venv/Scripts/python
```

I'm not sure how else to test this without having access to a Windows machine
2024-02-17 22:09:41 +05:30
Charlie Marsh facc60f3a8
Add graceful fallback for Artifactory indexes (#1574)
## Summary

There are more details in https://github.com/astral-sh/uv/issues/1370,
but it looks like Artifactory servers have incorrect behavior when it
comes to HTTP range requests, in that they return `Accept-Ranges:
bytes`, but then incorrectly return 200 requests when you actually ask
for a given range.

This PR ensures that we fallback gracefully in this case. It's built on
https://github.com/prefix-dev/async_http_range_reader/pull/5. Assuming
that gets merged upstream, we can then remove the Git dependency.

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

## Test Plan

`cargo run pip install requests -i
https://killjoyuvbug.jfrog.io/artifactory/api/pypi/pypi/simple
--verbose`
2024-02-17 14:37:06 +00:00
Charlie Marsh 12fea1d058
Always run `get_requires_for_build_wheel` (#1590)
## Summary

I want to revisit this as I think it's still skippable in some cases,
but for now, let's be more conservative.

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

## Test Plan

Cloned
`3aea423569/setup/mis_builder/setup.py (L4)`,
and ran `cargo run pip install -e mis_builder`.
2024-02-17 09:24:58 -05:00
Zanie Blue 563c636aa0
Improve tracing when encountering invalid `requires-python` values (#1568)
Unsure what the easiest way to test this is, it is hard to publish
invalid requires-python specifiers with hatchling
2024-02-17 07:40:13 -05:00
Shantanu 5d58d4fd2e
Better error messages on expect failures in resolver (#1583)
I ran into some (tricky to reproduce) panics while using uv, would be
useful to have a better error message to track down the source of the
problem
2024-02-17 07:39:10 -05:00
Zanie Blue bb7c3e6b58
Avoid using `white` coloring in terminal output (#1576)
Closes https://github.com/astral-sh/uv/issues/1489
2024-02-17 01:59:20 -06:00
Charlie Marsh 340cb67a8b
Allow non-nested archives for `hexdump` and others (#1564)
## Summary#1562 

It turns out that `hexdump` uses an invalid source distribution format
whereby the contents aren't nested in a top-level directory -- instead,
they're all just flattened at the top-level. In looking at pip's source
(51de88ca64/src/pip/_internal/utils/unpacking.py (L62)),
it only strips the top-level directory if all entries have the same
directory prefix (i.e., if it's the only thing in the directory). This
PR accommodates these "invalid" distributions.

I can't find any history on this method in `pip`. It looks like it dates
back over 15 years ago, to before `pip` was even called `pip`.

Closes https://github.com/astral-sh/uv/issues/1376.
2024-02-16 23:17:36 -05:00
Charlie Marsh 4a09889c80
Enforce URL constraints for non-URL dependencies (#1565)
## Summary

This was just a missing line -- we have `dependencies.remove(&package);`
in the ~identical branch above, but it must've been an oversight to omit
it here.

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

## Test Plan

`cargo test`
2024-02-17 03:11:28 +00:00
Charlie Marsh f897ee3f88
Allow repeated dependencies when installing (#1558)
## Summary

It turns out that it's not uncommon to end up with repeated packages in
requirements files when running `pip-sync`, e.g., you might have
`anyio==4.0.0` specified multiple times. This PR relaxes our assertions
in the install plan to allow such repeated packages, as long as the
requirement markers are exactly the same (i.e., they are truly
duplicates).

Closes https://github.com/astral-sh/uv/issues/1552.
2024-02-17 01:33:40 +00:00
Charlie Marsh 1110489c29
Bump version to v0.1.3 (#1557) 2024-02-16 19:45:29 -05:00
Charlie Marsh c1eb6130e1
Support MD5 hashes (#1556)
## Summary

We can add other hashes if necessary, but I don't know that they're
really used in practice.

Closes https://github.com/astral-sh/uv/issues/1547.
2024-02-17 00:25:16 +00:00
Charlie Marsh 9e0336c28a
Remove URL encoding when determining file name (#1555)
## Summary

Closes https://github.com/astral-sh/uv/issues/1553.
2024-02-16 19:15:24 -05:00
Charlie Marsh 6392961f44
Add support for extras in editable requirements (#1531)
## Summary

If you're developing on a package like `attrs` locally, and it has a
recursive extra like `attrs[dev]`, it turns out that we then try to find
the `attrs` in `attrs[dev]` from the registry, rather than recognizing
that it's part of the editable.

This PR fixes the issue by making editables slightly more first-class
throughout the resolver. Instead of mocking metadata, we explicitly
check for extras in various places. Part of the problem here is that we
treated editables as URL dependencies, but when we saw an _extra_ like
`attrs[dev]`, we didn't map that back to the URL. So now, we treat them
as registry dependencies, but with the appropriate guardrails
throughout.

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

## Test Plan

- Cloned `attrs`.
- Ran `cargo run venv && cargo run pip install -e ".[dev]" -v`.
2024-02-16 18:48:35 -05:00
Charlie Marsh 4e0b6f8f84
Avoid attempting rename in copy fallback path (#1546)
## Summary

This _could_ fix https://github.com/astral-sh/uv/issues/1454, but I'm
not sure. I was able to replicate by forcing a bunch of error states.
But, in short, if we fail to hardlink on the initial copy due to a file
existing, and then fail _again_, we fallback to copying. But if we copy,
then the tempfile doesn't exist, and so the `fs_err::rename(&tempfile,
&out_path)?;` will fail with "File not found".

This PR just ensures that the cases are explicitly mutually exclusive:
we only attempt to rename if the hardlink succeeded.
2024-02-16 17:08:49 -05:00
David Szotten 8050370717
Fix trailing commas on `Requires-Python` in HTML indexes (#1507)
illustration and suggested fix for #1464
2024-02-16 22:05:28 +00:00
Charlie Marsh 4f216f3a74
Apply percent-decoding to filepaths in HTML find-links (#1544)
## Summary

Closes https://github.com/astral-sh/uv/issues/1542.
2024-02-16 16:47:04 -05:00
Andrew Gallant 3aa7a6b796
fix OS detection for Alpine Linux (#1545)
This PR fixes the OS detection for Alpine Linux such that the version
of musl available is correctly determined. The issue boiled down to
a regex that required 2 digits for each version component. But a
valid musl version is 1.2.4, which only has a single digit for each
component.

It's unclear how this was working for musl before this change. My
theory is that our other methods of OS detection were somehow working.

The first commit in this PR cleans up our Linux detection logic and adds
lots of tracing calls to make debugging issues like this easier in the
future. To do so, one can run:

    $ RUST_LOG=trace uv pip install -v whatever

The second commit has the actual fix.

Fixes #1427
2024-02-16 16:37:18 -05:00
Charlie Marsh b4ea48955b
Use comparable representation for `PackageId` (#1543)
## Summary

By using the display representation of `Version` to form a `PackageId`,
we run the risk (as seen in the linked issue) of thinking that versions
like `2021.1` and `2021.1.0` are not equivalent.

Closes https://github.com/astral-sh/uv/issues/1536
2024-02-16 16:30:54 -05:00
Charlie Marsh 01ffc36520
Apply percent-decoding to file-based URLs (#1541)
## Summary

Closes https://github.com/astral-sh/uv/issues/1537.
2024-02-16 16:11:16 -05:00
Andrew Gallant a97c207674
pypi-types: fix lenient requirement parsing (#1529)
This fixes a bug where `uv pip install` failed to install `polars`:

```
$ uv pip install polars==0.14.0
error: Failed to download: polars==0.14.0
  Caused by: Couldn't parse metadata of polars-0.14.0-cp37-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl from 749022b096cb7c1c2cc32b7f433c4f/polars-0.14.0-cp37-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl
  Caused by: Operator >= cannot be used with a wildcard version specifier
pyarrow>=4.0.*; extra == 'pyarrow'
       ^^^^^^^
```

Since `pyarrow>=4.0.*; extra == 'pyarrow'` is invalid *and* it comes
from the metadata of a dependency (that isn't under the control of the
end user), we actually attempt to "fix" it. Namely, wildcard
dependency specifications are only allowed with `==` and `!=`, as per
the [Version Specifiers spec]. (They aren't explicitly forbidden in
these cases, but instead only have specified behavior for the `==` and
`!=` operators.)

This is all fine, but it turns out that when we fix the `>=4.0.*`
component, we also strip the quotes around `pyarrow`. (Because some
dependency specifications include stray quotes.) We fix this by making
our quote stripping a bit more selective. (We require that it appear
adjacent to a digit or a `*`.)

Note that #1477 also reports this error:

```
$ uv pip install 'requests>=2.30.*'
error: Failed to parse `requests>=2.30.*`
  Caused by: Operator >= cannot be used with a wildcard version specifier
requests>=2.30.*
```

However, we specifically keep that error message since it's something
under the end user's control. And similarly for a dependency
specification in a `requirements.txt` file.

Fixes #1477

[Version Specifiers spec]:
https://packaging.python.org/en/latest/specifications/version-specifiers/
2024-02-16 15:52:44 -05:00
Zanie Blue d5e8531ae3
Add support for `UV_EXTRA_INDEX_URL` (#1515)
Closes https://github.com/astral-sh/uv/issues/1450
2024-02-16 12:54:58 -06:00
Zanie Blue 2ea44d863a
Add warning for empty requirements files (#1519)
Also, improve tracing of requirements file parsing.

Per my confusion in #1334
2024-02-16 18:19:09 +00:00
Zanie Blue 89ad1c6fa1
Add `pip install --constraint` test coverage (#1334)
Exploring behavior reported in
https://github.com/astral-sh/uv/issues/1332
2024-02-16 17:39:39 +00:00
Charlie Marsh f25781ff6c
Support recursive extras (#1435)
## Summary

We had a guard in the resolve to avoid "self-dependencies" (as in
`gps3==0.33.3`), but this guard was _unintentionally_ filtering out
recursive extras.

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

## Test Plan

Taken from https://github.com/astral-sh/uv/pull/1352.
2024-02-16 11:42:04 -05:00
Zanie Blue e6c4c77ba1
Use string display instead of debug for url parse trace (#1498)
e.g. 

`uv_client::html::parse
url=https://download.pytorch.org/whl/torch_stable.html`

instead of

`uv_client::html::parse url=Url { scheme: "https", cannot_be_a_base:
false, username: "", password: None, host:
Some(Domain("download.pytorch.org")), port: None, path:
"/whl/torch_stable.html", query: None, fragment: None }`
2024-02-16 15:13:16 +00:00
Andrew Gallant 67cde15420
only parse /bin/sh (not /bin/ls) (#1493)
It turns out that /bin/ls can sometimes be plain text file. For
example, in Rocky Linux 9:

```
$ cat /bin/ls
#!/usr/bin/coreutils --coreutils-prog-shebang=ls
```

However, `/bin/sh` is an ELF binary:

```
$ file /bin/sh
/bin/sh: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=7acbb41bf6f1b7d977f1b44675bf3ed213776835, for GNU/Linux 3.2.0, stripped
```

In a related issue (#1433), @zanieb fixed #1395 where, on NixOS,
`/bin/ls` doesn't exist but `/bin/sh` does. However, the fix attempts
`/bin/ls` first and only tries `/bin/sh` if `/bin/ls` doesn't exist. If
`/bin/ls` exists but isn't a valid ELF file, then the entire enterprise
gives up and `uv` fails to detect the version of `libc` that is
installed.

Instead of tweaking the logic to keep trying `/bin/ls` and then
`/bin/sh` after even if parsing `/bin/ls` fails, we just switch over to
reading `/bin/sh` only. It seems like a more fundamental thing to sniff
and likely less error prone.

We can adjust this heuristic as needed if it provdes to be problematic.

I tested this fix manually on Rocky Linux 9 via Docker:

```
$ cross b -r -p uv --target x86_64-unknown-linux-musl
$ cp target/x86_64-unknown-linux-musl/release/uv ~/astral/issues/uv/i1486/uv
$ docker run --rm -it --mount type=bind,src=/home/andrew/astral/issues/uv/i1486,dst=/host rockylinux:9 bash
[root@df2baa65d2f8 /]# /host/uv venv
Using Python 3.9.18 interpreter at /usr/bin/python3.9
Creating virtualenv at: .venv
[root@df2baa65d2f8 /]#
```

Fixes #1486, Ref #1433
2024-02-16 09:44:47 -05:00
Micha Reiser e913167849
Fix list rendering in `venv --help` output (#1459) 2024-02-16 15:36:36 +01:00
Aarni Koskela 3280562e3a
Loosen package script regexp to match spec (#1482)
Fixes #1479.
2024-02-16 09:25:32 -05:00
Zanie Blue d99c4cacdf
Read from `/bin/sh` if `/bin/ls` cannot be found when determing libc path (#1433)
I'm not sure if we should just switch to _always_ reading from sh
instead? I don't love that all these errors are strings and I if
`/bin/ls` exists but can't be parsed we still won't try `/bin/sh`. We
may want to address these things in the future.

Closes https://github.com/astral-sh/uv/issues/1395
2024-02-16 07:51:34 -05:00
Charlie Marsh c474370064
Allow empty fragments in HTML parser (#1443)
## Summary

It looks like `devpi` might add an empty fragment (`#`) at the end of
the URL. We expect it to contain the hash; this just makes
empty-fragment map to "no hash".

Closes https://github.com/astral-sh/uv/issues/1441.
2024-02-16 06:42:21 +00:00
Charlie Marsh 659327f24a
Bump version to v0.1.2 (#1439) 2024-02-16 01:17:19 -05:00
Charlie Marsh 0d005a2a71
Decode HTML escapes when extracting SHA (#1440)
## Summary

If a distribution contains a `+`, it'll be HTML-escaped; so when we try
to identify the `#`, we'll split in the wrong location.

Closes https://github.com/astral-sh/uv/issues/1338.
2024-02-16 06:15:51 +00:00
Charlie Marsh 958e88ddbf
Ignore invalid extra named `.none` (#1428)
## Summary

Some packages erroneously include an extra named `.none`. It turns out
that certain versions of `flit` included this by accident:
https://github.com/pypa/flit/issues/228/.

This PR adds leniency for that specific extra name.

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

Closes https://github.com/astral-sh/uv/issues/1399.
2024-02-16 05:01:21 +00:00
Zanie Blue 0bfce353fb
Fix broken URLs parsed from relative paths in registries (#1413)
Closes https://github.com/astral-sh/uv/issues/1388

Fixes incorrect handling of relative paths returned by indexes without
an explicit `<base>`.

`Url.join` will drop the last segment in an url e.g. `http://foo/bar` ->
`http://foo/baz` if there is not a trailing slash but what we want is
`http://foo/bar/baz`. We don't add the trailing `/` in
`base_url_join_relative` because flat indexes are `http://foo/bar.html`
and we _want_ `bar.html` to be replaced.
2024-02-15 22:37:09 -06:00
Charlie Marsh e48edf02fa
Parse `-r` and `-c` entries as relative to containing file (#1421)
## Summary

In a `requirements.txt` file, it turns out that the `-c` and `-r`
entries should be interpreted as relative to the file in which they're
declared, while the `-e` entries should be interpreted as relative to
the current working directory, no matter where they're defined.

Previously, we always used the current working directory; now, we use
the declaring file's directory for `-c` and `-r`.

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

Closes https://github.com/astral-sh/uv/issues/1416.
2024-02-15 23:19:43 -05:00
Charlie Marsh 1837641138
Add fix-up for invalid star comparison with major-only version (#1410)
## Summary

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

## Test Plan

Ran `cargo run pip install junos-eznc==2.6.5`, which still fails for me,
but fails identically to `pip` (and not on the `requires-python`):

```
/private/var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp7mxT9L/built-wheels-v0/pypi/ncclient/0.6.13/4vvPwmDC_CL2OUXd68Zqb/ncclient-0.6.13.tar.gz/versioneer.py:421: SyntaxWarning: invalid escape sequence '\s'
  LONG_VERSION_PY['git'] = '''
Traceback (most recent call last):
  File "<string>", line 10, in <module>
  File "/private/var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmplD5mMO/.venv/lib/python3.12/site-packages/setuptools/build_meta.py", line 366, in prepare_metadata_for_build_wheel
    self.run_setup()
  File "/private/var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmplD5mMO/.venv/lib/python3.12/site-packages/setuptools/build_meta.py", line 480, in run_setup
    super().run_setup(setup_script=setup_script)
  File "/private/var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmplD5mMO/.venv/lib/python3.12/site-packages/setuptools/build_meta.py", line 311, in run_setup
    exec(code, locals())
  File "<string>", line 45, in <module>
  File "/private/var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp7mxT9L/built-wheels-v0/pypi/ncclient/0.6.13/4vvPwmDC_CL2OUXd68Zqb/ncclient-0.6.13.tar.gz/versioneer.py", line 1480, in get_version
    return get_versions()["version"]
           ^^^^^^^^^^^^^^
  File "/private/var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp7mxT9L/built-wheels-v0/pypi/ncclient/0.6.13/4vvPwmDC_CL2OUXd68Zqb/ncclient-0.6.13.tar.gz/versioneer.py", line 1412, in get_versions
    cfg = get_config_from_root(root)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/private/var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp7mxT9L/built-wheels-v0/pypi/ncclient/0.6.13/4vvPwmDC_CL2OUXd68Zqb/ncclient-0.6.13.tar.gz/versioneer.py", line 342, in get_config_from_root
    parser = configparser.SafeConfigParser()
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: module 'configparser' has no attribute 'SafeConfigParser'. Did you mean: 'RawConfigParser'?
```
2024-02-16 02:12:10 +00:00
Charlie Marsh 7994b68654
Add fix-up for trailing comma with trailing space (#1409)
## Summary

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

## Test Plan

```text
Resolved 3 packages in 243ms
Downloaded 3 packages in 193ms
Installed 3 packages in 6ms
 + et-xmlfile==1.1.0
 + jdcal==1.4.1
 + openpyxl==3.0.5
```
2024-02-16 02:08:05 +00:00
Zanie Blue 0f554b0913
Add `-U`/`-P` short flags for `--upgrade`/`--upgrade-package` (#1394)
Closes https://github.com/astral-sh/uv/issues/1340
2024-02-16 01:34:19 +00:00
Zanie Blue 896ab1c54f
Add `--upgrade` support to `pip install` (#1379)
Adds support for `--upgrade` — similar to `--reinstall`.

Closes https://github.com/astral-sh/uv/issues/1391
2024-02-15 19:25:28 -06:00
Shantanu e9d82cf0fa
Avoid import contextlib in `_virtualenv` (#1406)
No need to pay 3ms on basically every Python invocation. I opened a PR
upstream last week: https://github.com/pypa/virtualenv/pull/2688
2024-02-15 20:23:05 -05:00
Robin Krahl f9a9f53476
Improve error message for invalid sdist archives (#1389)
This PR improves the error message for the problem described in
https://github.com/astral-sh/uv/issues/1376. The original output
duplicates the actual error message and includes lots of noise
(`DirEntry { inner: DirEntry(...) }`).

```
$ uv pip install hexdump==3.3
error: Failed to download and build: hexdump==3.3
  Caused by: Failed to extract source distribution: The top level of the archive must only contain a list directory, but it contains: [DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/__main__.py") }, DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/hexdump.py") }, DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/data") }, DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/PKG-INFO") }, DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/setup.py") }, DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/README.txt") }]
  Caused by: The top level of the archive must only contain a list directory, but it contains: [DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/__main__.py") }, DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/hexdump.py") }, DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/data") }, DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/PKG-INFO") }, DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/setup.py") }, DirEntry { inner: DirEntry("/home/robin/.cache/uv/.tmpgSvTCk/README.txt") }]
```

This PR removes the duplication and `DirEntry` internals so that the
error message is easier to grasp:

```
$ uv pip install hexdump==3.3
error: Failed to download and build: hexdump==3.3
  Caused by: Failed to extract source distribution
  Caused by: The top level of the archive must only contain a list directory, but it contains: ["__main__.py", "hexdump.py", "data", "PKG-INFO", "setup.py", "README.txt"]
```
2024-02-15 18:03:23 -06:00
Zanie Blue c6a43e92f9
Add `UV_NO_CACHE` environment variable (#1383)
It's a little picky about the value, but that seems okay.

```
❯ ./target/debug/uv pip install trio
Audited 1 package in 4ms
❯ UV_NO_CACHE=true ./target/debug/uv pip install trio
Audited 1 package in 50ms
```

Closes #1382
2024-02-15 23:34:42 +00:00
mikcl fbd6d87214
uv-cache: Add hidden alias for --no-cache-dir (#1380)
This is for compatability with pip install --no-cache-dir

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

Signed-off-by: mikcl <mikesmikes400@gmail.com>
2024-02-15 23:26:16 +00:00
Zanie Blue 18a7c079de
Fix search for `python.exe` on Windows (#1381)
A la #1351
2024-02-15 17:25:20 -06:00
Zanie Blue e0885b7c8e
Bump version to 0.1.1 (#1359) 2024-02-15 15:38:22 -06:00
Yannik Sander 36544e1678
Fix diagram alignment (#1354)
Drive by alignment PR for the trait structure diagram
2024-02-15 15:32:33 -06:00
Zanie Blue 102e5ddfbe
Fix bug where `python3` is not found in the global path (#1351)
When we refactored handling for Windows tests, we accidentally dropped
`python3` from path searches.
2024-02-15 15:29:33 -06:00
Charlie Marsh 27177613d4
Bump version to v0.1.0 (#1325) 2024-02-15 14:12:23 -05:00
Zanie Blue 0780afff95
Rename `PUFFIN` environment variables to `UV` (#1319)
A couple of these are actually user-facing although undocumented
2024-02-15 12:49:27 -06:00
Charlie Marsh 0579a04014
Bump to v0.0.5 for pre-release (#1324)
This is easier than figuring out the version parsing.
2024-02-15 18:33:34 +00:00
Charlie Marsh ad12d97e71
Set crate to prerelease (#1320) 2024-02-15 18:21:09 +00:00
Charlie Marsh 06f2b6eee2
Bump version and update pyproject.toml metadata (#1316)
Also ensures that we no longer clear the README when uploading to PyPI
:)
2024-02-15 18:03:35 +00:00
Charlie Marsh 55808a451f
Regenerate benchmarks (#1305) 2024-02-15 17:54:04 +00:00
Zanie Blue 2586f655bb
Rename to `uv` (#1302)
First, replace all usages in files in-place. I used my editor for this.
If someone wants to add a one-liner that'd be fun.

Then, update directory and file names:

```
# Run twice for nested directories
find . -type d -print0 | xargs -0 rename s/puffin/uv/g
find . -type d -print0 | xargs -0 rename s/puffin/uv/g

# Update files
find . -type f -print0 | xargs -0 rename s/puffin/uv/g
```

Then add all the files again

```
# Add all the files again
git add crates
git add python/uv

# This one needs a force-add
git add -f crates/uv-trampoline
```
2024-02-15 11:19:46 -06:00
Zanie Blue e9e3e573a2
Report incompatible distributions to users (#1293)
Instead of dropping versions without a compatible distribution, we track
them as incompatibilities in the solver. This implementation follows
patterns established in https://github.com/astral-sh/puffin/pull/1290.

This required some significant refactoring of how we track incompatible
distributions. Notably:

- `Option<TagPriority>` is now `WheelCompatibility` which allows us to
track the reason a wheel is incompatible instead of just `None`.
- `Candidate` now has a `CandidateDist` with `Compatible` and
`Incompatibile` variants instead of just `ResolvableDist`; candidates
are not strictly compatible anymore
- `ResolvableDist` was renamed to `CompatibleDist`
- `IncompatibleWheel` was given an ordering implementation so we can
track the "most compatible" (but still incompatible) wheel. This allows
us to collapse the reason a version cannot be used to a single
incompatibility.
- The filtering in the `VersionMap` is retained, we still only store one
incompatible wheel per version. This is sufficient for error reporting.
- A `TagCompatibility` type was added for tracking which part of a wheel
tag is incompatible
- `Candidate::validate_python` moved to
`PythonRequirement::validate_dist`

I am doing more refactoring in #1298 — I think a couple passes will be
necessary to clarify the relationships of these types.

Includes improved error message snapshots for multiple incompatible
Python tag types from #1285 — we should add more scenarios for coverage
of behavior when multiple tags with different levels are present.
2024-02-15 10:48:15 -06:00
Andrew Gallant b6fba00153
cli: conventionally treat `-` as "read from stdin" (#1314)
Basically, when a path to a requirements file is `-`, then we should
read its contents from `stdin` instead of the file path named `-`.

Fixes #1313
2024-02-15 11:25:56 -05:00
Armin Ronacher bf2ee6bc31
Adds support for --no-deps to pip compile (#1311)
Mostly throwing this up here as a discussion topic. Having something
like this is primarily useful for enabling use cases similar to `rye
add` where I want to use this currently. One can accomplish something
similar with `unearth` today or by abusing regular `pip install`:

```
$ ~/.rye/self/bin/pip install --no-deps --dry-run flask --report - -q | jq '.install[0].metadata | {name, version}'
{
  "name": "Flask",
  "version": "3.0.2"
}
```

Another option would be to have a `puffin resolve` command or similar
that works like `pip compile` without dependencies, takes the
requirements as arguments and returns a line for each resolution. That
would be a larger change.
2024-02-15 09:01:31 -05:00
Andrew Gallant 94437175c7 puffin-resolver: make VersionMap::iter even lazier
This rollbacks the optimization in the previous commit to be more
general. That is, instead of specializing the case of a range for a
singleton version, we make iteration over the distributions in a
`VersionMap` more explicitly lazy. Iteration now provides a `Version`
(like it did previously) and a _handle_ to a distribution that can be
turned into a `ResolvableDist`.

Doing things this way permits callers to iterate over the versions and
only materialize a distribution if they actually need one. In cases like
candidate selection, one can often rule out use of a distribution
through its version alone, and thus skip construction of that
distribution entirely.
2024-02-15 08:10:32 -05:00
Andrew Gallant ed000d0dd5 puffin-resolver: add singleton fast path
In many cases, version ranges are actually just pins to a
specific and single version. And we can detect that statically
by examining the range. If we do have a range that is just one
version, then we can ask a `VersionMap` for just that version
instead of iterating over what's in the map until we find one
that satisfies the range.

I had tried this before making `VersionMap` construction lazy,
but it didn't seem to matter much. But helps a lot more now
with a lazy `VersionMap` because it lets us avoid creating a
lot of distributions in memory that we won't ultimately use.
2024-02-15 08:10:32 -05:00
Andrew Gallant 8102980192 puffin-resolver: make VersionMap construction lazy
That is, a `PrioritizedDistribution` for a specific version of a
package is not actually materialized in memory until a corresponding
`VersionMap::get` call is made for that version. Similarly, iteration
lazily materializes distributions as it moves through the map. It
specifically does not materialize everything first.

The main reason why this is effective is that an
`OwnedArchive<SimpleMetadata>` represents a zero-copy (other than
reading the source file) version of `SimpleMetadata` that is really just
a `Vec<u8>` internally. The problem with `VersionMap` construction
previously is that it had to eagerly materialize a `SimpleMetadata` in
memory before anything else, which defeats a large part of the purpose
of zero-copy deserialization. By making more of `VersionMap`
construction itself lazy, we permit doing some parts of resolution
without necessarily fully deserializing a `SimpleMetadata` into memory.
Indeed, with this commit, in the warm cached case, a `SimpleMetadata` is
itself never materialized fully in memory.

This does not completely and totally fully realize the benefits of
zero-copy deserialization. For example, we are likely still building
lots of distributions in memory that we don't actually need in some
cases. Perhaps in cases where no resolution exists, or when one needs to
iterate over large portions of the total versions published for a
package.
2024-02-15 08:10:32 -05:00
Andrew Gallant e2f3ad0e28 puffin-resolver: add some trace calls
This commit adds some logging to candidate selection during
resolution. The idea with these logs is to get a signal on
how much "exploring" the resolver does in specific examples.

For example, this logs helped me realize that at least in
some cases, candidate selection was looking through a long list
of versions even when its range consisted of exactly one
version. We'll use this fact in a later commit.
2024-02-15 08:10:32 -05:00
Andrew Gallant 1cff7c3774 platform-tags: make Tags use an Arc internally
This makes cloning and thus sharing across multiple threads much
cheaper. Since Tags is conceptually immutable once it is constructed,
this doesn't pose an issue and shouldn't introduce any additional
costs.
2024-02-15 08:10:32 -05:00
Charlie Marsh e4fffc15f5
Remove Cargo-specific error messages (#1306)
We're leveraging Cargo's git implementation, but we left in some
Cargo-specific error messages for features we don't yet support.
2024-02-15 06:04:22 +00:00
Zanie Blue 9808c6b500
Reset all of the snapshots for consistent indentation (#1300)
This is really annoying, but the snapshots keep changing indentation
when updated.

I could not get insta to update them. So I added a print statement to
`main` and updated the snapshots, then removed the statement and updated
the snapshots again to force them all to refresh.
2024-02-14 12:50:28 -06:00
Charlie Marsh 40b74fb0fb
Replace `MarkupSafe` for no-binary tests (#1296) 2024-02-14 04:44:07 +00:00
Zanie Blue 7fec2a311a
Refactor storage of distribution metadata needed in resolver (#1291)
Follows #1290 and https://github.com/astral-sh/puffin/pull/912 with some
minor clean-up.
2024-02-13 04:19:21 +00:00
Zanie Blue 3bff8d5f79
Add scenario coverage for wheels with incompatible ABI and Python tags (#1285)
We use

- An arbitrary ABI hash: `MMMMMM` (six base64 characters)
- An unlikely Jython27 Python tag

For cases that are valid but are never going to be available during
tests.

See https://github.com/zanieb/packse/pull/109
2024-02-12 22:14:38 -06:00
Zanie Blue b5dd8b7de2
Track yanked versions as incompatibilities (#1290)
Moves yanked version filtering from `VersionMap::from_metadata` to the
resolver and tracks it as a PubGrub unavailable incompatibility so
yanked versions are reflected in error messages.

e.g. before
```
╰─▶ Because only albatross<=0.1.0 is available and you require albatross>0.1.0, 
       we can conclude that the requirements are unsatisfiable.
```

after

```
╰─▶ Because only the following versions of albatross are available:
            albatross<=0.1.0
            albatross==1.0.0
      and albatross==1.0.0 is unusable because it was yanked, we can conclude that albatross>0.1.0 cannot be used.
      And because you require albatross>0.1.0, we can conclude that the requirements are unsatisfiable.
```
2024-02-12 22:01:17 -06:00
Charlie Marsh d8619f668a
Surface errors for offline `--find-links` URLs (#1271)
## Summary

Ensures that if the user passes `--no-index` with `--find-links`, and
we're unable to access the HTML page, we show an appropriate hint.
2024-02-13 03:41:00 +00:00
Charlie Marsh 16bb80132f
Add an `--offline` mode (#1270)
## Summary

This PR adds an `--offline` flag to Puffin that disables network
requests (implemented as a Reqwest middleware on our registry client).
When `--offline` is provided, we also allow the HTTP cache to return
stale data.

Closes #942.
2024-02-13 03:35:23 +00:00
Zanie Blue 0cd6b7be8c
Fix incompatible wheel test scenarios (#1284)
I had specified the tags incorrectly
https://github.com/zanieb/packse/pull/105
2024-02-12 18:51:49 +00:00
Zanie Blue 6d24d998e0
Add scenarios for yanked packages (#1283) 2024-02-12 12:44:59 -06:00
Zanie Blue 336d12556c
Add scenario tests for `--only-binary` and `--no-binary` (#1279) 2024-02-12 11:21:14 -06:00
Charlie Marsh b386590b3c
Add some compatibility arguments to `puffin venv` (#1282)
See: https://github.com/astral-sh/puffin/issues/1276.
2024-02-12 03:19:55 +00:00
Charlie Marsh 93b7a1140f
Allow virtualenv creation at existing, empty directories (#1281)
## Summary

If the directory exists but is empty, we should allow `puffin venv`
without erroring.

Also adds test cases for a variety of error cases.
2024-02-12 03:13:13 +00:00
Charlie Marsh b7e3933fe7
Place editable requirements before non-editable requirements (#1278)
## Summary

`pip-compile` puts the editable requirements first.

Closes https://github.com/astral-sh/puffin/issues/1275.
2024-02-12 02:26:40 +00:00
Charlie Marsh a16ec45d1f
Set an exclude cutoff for virtualenv tests (#1280)
## Summary

This test is failing since a new version of one of the seed packages was
uploaded.
2024-02-12 02:21:05 +00:00
Zanie Blue a37b08808e
Implement pip compatible `--no-binary` and `--only-binary` options (#1268)
Updates our `--no-binary` option and adds a `--only-binary` option for
compatibility with `pip` which uses `:all:`, `:none:` and `<name>` for
specifying packages.

This required adding support for `--only-binary <name>` into our
resolver, previously it was only a boolean toggle.

Retains`--no-build` which is equivalent to `--only-binary :all:`. This
is common enough for safety that I would prefer it is available without
pip's awkward `:all:` syntax.

---------

Co-authored-by: konsti <konstin@mailbox.org>
2024-02-11 19:31:41 -06:00
Charlie Marsh d98b3c3070
Strip UNC prefix when setting working directory (#1277)
## Summary

For PEP 517 builds, the current working directory needs to be set to the
directory of the source distribution. It turns out that on Windows, if
you use a UNC path for the working directory, then relative paths are
interpreted relative to the root of the current drive
([source](https://www.fileside.app/blog/2023-03-17_windows-file-paths/#paths-relative-to-the-root-of-the-current-drive)).
So, when builds attempted to resolve relative paths, they always
errored...

This PR ensures that we remove the UNC prefix when setting the current
working directory.

Closes #1238.

## Test Plan

I tested this on my Windows machine by installing `ujson` with
`--no-binary ujson`. (I don't want to add that specific test, since it's
really slow to build.)
2024-02-12 00:51:36 +00:00
Charlie Marsh ba4c6e1a55
Remove unused deps (#1273) 2024-02-11 18:53:58 +00:00
Charlie Marsh 32aacc35a9
Bump version to v0.0.4 (#1269) 2024-02-09 16:42:17 -05:00
konsti 561e33e353
Validate instead of discovering python patch version (#1266)
Contrary to our prior assumption, we can't reliably select a specific
patch version. With the deadsnakes PPA for example, `python3.12` is
installed into `PATH`, but `python3.12.1` isn't. Based on the assumption
(or rather, observation) that users have a single python patch version
per python minor version installed, generally the latest, we only check
if the installed patch version matches the selected patch version, if
any, instead of search for one.

In the process, i deduplicated the python discovery logic.
2024-02-08 22:38:00 +01:00
konsti 1dc9904f8c
Run the test suite on windows in CI (#1262)
Run `cargo test` on windows in CI, pulling the switch on tier 1 windows
support.

These changes make the bootstrap script virtually required for running
the tests. This gives us consistency between and CI, but it also locks
our tests to python-build-standalone and an articificial `PATH`.

I've deleted the shell bootstrap script in favor of only the python one,
which also runs on windows. I've left the (sym)link creation of the
bootstrap in place, even though it is not used by the tests anymore.

I've reactivated the three tests that would previously stack overflow by
doubling their stack sizes. The stack overflows only happen in debug
mode, so this is neither a user facing problem nor an actual problem
with our code and this workaround seems better than optimizing our code
for case that the (release) compiler can optimize much better for.

The handling of patch versions will be fixed in a follow-up PR.

Closes #1160 
Closes #1161

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2024-02-08 22:09:55 +01:00
Andrew Gallant 96276d9e3e
puffin-resolver: simplify version map construction (#1267)
In the process of making VersionMap construction lazy, I realized this
refactoring would be useful to me. It also simplifies a fair bit of case
analysis and does fewer BTreeMap lookups during construction. With that
said, this doesn't seem to matter for perf:

```
$ hyperfine -w10 --runs 50 \
    "puffin-main pip compile --cache-dir ~/astral/tmp/cache-main ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null" \
    "puffin-test pip compile --cache-dir ~/astral/tmp/cache-test ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null"
Benchmark 1: puffin-main pip compile --cache-dir ~/astral/tmp/cache-main ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null
  Time (mean ± σ):     146.8 ms ±   4.1 ms    [User: 350.1 ms, System: 314.2 ms]
  Range (min … max):   140.7 ms … 158.0 ms    50 runs

Benchmark 2: puffin-test pip compile --cache-dir ~/astral/tmp/cache-test ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null
  Time (mean ± σ):     146.8 ms ±   4.5 ms    [User: 359.8 ms, System: 308.3 ms]
  Range (min … max):   138.2 ms … 160.1 ms    50 runs

Summary
  puffin-main pip compile --cache-dir ~/astral/tmp/cache-main ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null ran
    1.00 ± 0.04 times faster than puffin-test pip compile --cache-dir ~/astral/tmp/cache-test ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null
```

But the simplification is still nice, and will decrease the delta
between what we have now and a lazy version map.
2024-02-08 15:33:33 -05:00
Zanie Blue fc2ab611d5
Use "locations" instead of "listings" for find links errors (#1263) 2024-02-07 10:28:22 -06:00
konsti ab45485eb5
Reduce stack sizes further and ignore remaining tests (#1261)
This PR reduces the stack sizes a windows a little further using the
stack traces from stack overflows combined with looking at the type
sizes. Ultimately, it ignore the three remaining tests failing in debug
on windows due to stack overflows to unblock `cargo test` for windows on
CI.

444 tests run: 444 passed (39 slow), 1 skipped
2024-02-06 23:08:18 +01:00
konsti e0cdf1a16f
Use anstream consistently and remove clippy lints (#1260)
We need to use the anstream print macros instead of the std print
macros, otherwise we risk wrong color behavior
(https://github.com/astral-sh/puffin/pull/1258#discussion_r1480428236).
Luckily, the `print_stderr` and `print_stdout` lints catch usages of the
std prints.

This PR switches over to anstream consistently and removes the now
redundant clippy lints. The lints should catch missing anstream usage in
the future.
2024-02-06 22:16:26 +01:00
konsti f4ca175df4
Search and replace windows specific tests in deps (#1255)
Remove windows-only dependencies from the snapshot output using regex.
We now do the filtering entirely on our without relying on insta
settings.

435 tests run: 430 passed (30 slow), 5 failed, 1 skipped
2024-02-06 19:31:42 +00:00
konsti ac49dec4a2
Multiple entries in PUFFIN_PYTHON_PATH for windows tests (#1254)
There are no binary installers for the latests patch versions of cpython
for windows, and building them is hard. As an alternative, we download
python-build-standanlone cpythons and put them into `<project
root>/bin`. On unix, we can symlink `pythonx.y.z` into this directory
and point `PUFFIN_PYTHON_PATH` to it. On windows, all pythons are called
`python.exe` and they don't like being linked. Instead, we add the path
to each directory containing a `python.exe` to `PUFFIN_PYTHON_PATH`,
similar to the regular `PATH`. The python discovery on windows was
extended to respect `PUFFIN_PYTHON_PATH` where needed.

These changes mean that we don't need to (sym)link pythons anymore and
could drop that part to the script.

435 tests run: 389 passed (21 slow), 46 failed, 1 skipped
2024-02-06 20:28:30 +01:00
Charlie Marsh 91118a962a
Offer tip when users omits pip prefix (#1257)
## Summary

Open to other opinions here. We could just continue (and warn), prompt
the user with a confirmation, etc.

(The weird thing about those two options is we might need to validate
the command-line arguments _before_ we do that -- so you could get
errors for bad arguments, and then get a warning that your subcommand is
wrong. I can probably avoid that with more work if it feels like a
better out come though.)

Closes https://github.com/astral-sh/puffin/issues/1256.
2024-02-06 19:25:07 +00:00
Charlie Marsh 62416286e2
Remove `add` and `remove` commands (#1259)
## Summary

These add and remove dependencies from a `pyproject.toml` -- but they're
currently hidden, and don't match the rest of the workflow. We can
re-add them when the time is right.
2024-02-06 14:18:27 -05:00
Zanie Blue d4bbaf1755
Add hint for `--no-index` without `--find-links` (#1258)
Since unavailable packages with `--no-index` can be confusing when the
user does not also provide `--find-links` we add a hint for this case.
Required some plumbing to get the required information to the
`NoSolution` error.

---------

Co-authored-by: konstin <konstin@mailbox.org>
2024-02-06 11:04:14 -06:00
konsti b2a810fe37
Add windows specific filters for tests (#1231)
Add more windows specific filters in various places.

435 tests run: 333 passed (21 slow), 102 failed, 1 skipped
2024-02-06 15:58:16 +01:00
Andrew Gallant d4b4c21133
initial implementation of zero-copy deserialization for SimpleMetadata (#1249)
(Please review this PR commit by commit.)

This PR closes an initial loop on zero-copy deserialization. That
is, provides a way to get a `Archived<SimpleMetadata>` (spelled
`OwnedArchive<SimpleMetadata>` in the code) from a `CachedClient`. The
main benefit of zero-copy deserialization is that we can read bytes
from a file, cast those bytes to a structured representation without
cost, and then start using that type as any other Rust type. The
"catch" is that the structured representation is not the actual type
you started with, but the "archived" version of it.

In order to make all this work, we ended up needing to shave a rather
large yak: we had to re-implement HTTP cache semantics. Previously,
we were using the `http-cache-semantics` crate. While it does support
Serde, it doesn't support `rkyv`. Moreover, even simple support for
`rkyv` wouldn't be enough. What we actually want is for the HTTP cache
semantics to be implemented on the *archived* type so that we can
decide whether our cached response is stale or not without needing to
do a full deserialization into the unarchived type. This is why, in
this PR, you'll see `impl ArchivedCachePolicy { ... }` instead of
`impl CachePolicy { ... }`. (The `derive(rkyv::Archive)` macro
automatically introduces the `ArchivedCachePolicy` type into the
current namespace.)

Unfortunately, this PR does not fully realize the dream that is
zero-copy deserialization. Namely, while a `CachedClient` can now
provide an `OwnedArchive<SimpleMetadata>`, the rest of our code
doesn't really make use of it. Indeed, as soon as we go to build a
`VersionMap`, we eagerly convert our archived metadata into an owned
`SimpleMetadata` via deserialization (that *isn't* zero-copy). After
this change, a lot of the work now shifts to `rkyv` deserialization
and `VersionMap` construction. More precisely, the main thing we drop
here is `CachePolicy` deserialization (which is now truly zero-copy)
and the parsing of the MessagePack format for `SimpleMetadata`. But we
are still paying for deserialization. We're just paying for it in a
different place.

This PR does seem to bring a speed-up, but it is somewhat underwhelming.
My measurements have been pretty noisy, but I get a 1.1x speedup fairly
often:

```
$ hyperfine -w5 "puffin-main pip compile --cache-dir ~/astral/tmp/cache-main ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null" "puffin-test pip compile --cache-dir ~/astral/tmp/cache-test ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null" ; A kang
Benchmark 1: puffin-main pip compile --cache-dir ~/astral/tmp/cache-main ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null
  Time (mean ± σ):     164.4 ms ±  18.8 ms    [User: 427.1 ms, System: 348.6 ms]
  Range (min … max):   131.1 ms … 190.5 ms    18 runs

Benchmark 2: puffin-test pip compile --cache-dir ~/astral/tmp/cache-test ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null
  Time (mean ± σ):     148.3 ms ±  10.2 ms    [User: 357.1 ms, System: 319.4 ms]
  Range (min … max):   136.8 ms … 184.4 ms    19 runs

Summary
  puffin-test pip compile --cache-dir ~/astral/tmp/cache-test ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null ran
    1.11 ± 0.15 times faster than puffin-main pip compile --cache-dir ~/astral/tmp/cache-main ~/astral/tmp/reqs/home-assistant-reduced.in -o /dev/null
```

One downside is that this does increase cache size (`rkyv`'s
serialization format is not as compact as MessagePack). On disk size
increases by about 1.8x for our `simple-v0` cache.

```
$ sort-filesize cache-main
4.0K    cache-main/CACHEDIR.TAG
4.0K    cache-main/.gitignore
8.0K    cache-main/interpreter-v0
8.7M    cache-main/wheels-v0
18M     cache-main/archive-v0
59M     cache-main/simple-v0
109M    cache-main/built-wheels-v0
193M    cache-main
193M    total

$ sort-filesize cache-test
4.0K    cache-test/CACHEDIR.TAG
4.0K    cache-test/.gitignore
8.0K    cache-test/interpreter-v0
8.7M    cache-test/wheels-v0
18M     cache-test/archive-v0
107M    cache-test/simple-v0
109M    cache-test/built-wheels-v0
242M    cache-test
242M    total
```

Also, while I initially intended to do a simplistic implementation of
HTTP cache semantics, I found that everything was somewhat
inter-connected. I could have wrote code that _specifically_ only worked
with the present behavior of PyPI, but then it would need to be special
cased and everything else would need to continue to use
`http-cache-sematics`. By implementing what we need based on what Puffin
actually is (which is still less than what `http-cache-semantics` does),
we can avoid special casing and use zero-copy deserialization for our
cache policy in _all_ cases.
2024-02-05 16:47:53 -05:00
Charlie Marsh 398659a9b0
Show yank warnings for `pip install` (#1253)
Closes https://github.com/astral-sh/puffin/issues/1252.
2024-02-05 17:15:44 +00:00
Zanie Blue d090acf13d
Improve error messaging when a dependency is not found (#1241)
Previously, whenever we encountered a missing package we would throw an
error without information about why the package was requested. This
meant that if a transitive dependency required a missing package, the
user would have no idea why it was even selected. Here, we track
`NotFound` and `NoIndex` errors as `NoVersions` incompatibilities with
an attached reason. Improves our test coverage for `--no-index` without
`--find-links`.

The
[snapshots](https://github.com/astral-sh/puffin/pull/1241/files#diff-3eea1658f165476252f1f061d0aa9f915aabdceafac21611cdf45019447f60ec)
show a nice improvement.

I think this will also enable backtracking to another version if some
version of transitive dependency has a missing dependency. I'll write a
scenario for that next.

Requires https://github.com/zanieb/pubgrub/pull/22
2024-02-05 08:43:05 -06:00
Charlie Marsh be9125b0f0
Remove unnecessary `is_dir` in `clone_recursive` (#1247) 2024-02-04 23:54:22 +00:00
Charlie Marsh 9d42cfd09b
Clarify documentation for `--no-index` (#1243)
Closes #1242.
2024-02-04 18:46:01 -05:00
Andrew Gallant 586eeb6eca
tests: update snapshot for new `pip` release (#1245)
See: https://pypi.org/project/pip/#history
2024-02-03 15:44:13 -05:00
Zanie Blue bc2f8f5b1e
Fix direct use of `range.simplify` (#1236) 2024-02-02 18:08:25 +00:00
Zanie Blue 6db9db0079
Invert display of "no versions" incompatibilities with multiple ranges (#1233)
Closes #884 

e.g.

```
❯ cargo run -q -- pip compile --python-version 3.12 requirements.in
  × No solution found when resolving dependencies:
  ╰─▶ Because the requested Python version (3.12) does not satisfy Python>=3.6,<3.10 and recommenders==1.0.0 depends on Python>=3.6,<3.9, we can conclude that recommenders==1.0.0 cannot be used.
      And because only the following versions of recommenders are available:
          recommenders<=0.7
          recommenders==1.0.0
          recommenders==1.1.0
          recommenders==1.1.1
      we can conclude that recommenders>0.7,<1.1.0 cannot be used. (1)

      Because the requested Python version (3.12) does not satisfy Python>=3.6,<3.10 and recommenders>=1.1.0 depends on Python>=3.6,<3.10, we can conclude that recommenders>=1.1.0 cannot be used.
      And because we know from (1) that recommenders>0.7,<1.1.0 cannot be used, we can conclude that recommenders>0.7 cannot be used.
      And because you require recommenders>0.7, we can conclude that the requirements are unsatisfiable.
```
2024-02-02 12:00:25 -06:00
konsti f10f902570
Yield after channel send and move cpu tasks to thread (#1163)
## Summary

Previously, we were blocking operations that could run in parallel. We
would send request through our main requests channel, but not yield so
that the receiver could only start processing requests much later than
necessary. We solve this by switching to the async
`tokio::sync::mpsc::channel`, where send is an async functions that
yields.

Due to the increased parallelism cache deserialization and the
conversion from simple api request to version map became bottlenecks, so
i moved them to `spawn_blocking`. Together these result in a 30-60%
speedup for larger warm cache resolution. Small cases such as black
already resolve in 5.7 ms on my machine so there's no speedup to be
gained, refresh and no cache were to noisy to get signal from.

Note for the future: Revisit the bounded channel if we want to produce
requests from `process_request`, too, (this would be good for
prefetching) to avoid deadlocks.

## Details

We can look at the behavior change through the spans:

```
RUST_LOG=puffin=info TRACING_DURATIONS_FILE=target/traces/jupyter-warm-branch.ndjson cargo run --features tracing-durations-export --bin puffin-dev --profile profiling -- resolve jupyter 2> /dev/null
```

Below, you can see how on main, we have discrete phases: All (cached)
simple api requests in parallel, then all (cached) metadata requests in
parallel, repeat until done. The solver is mostly waiting until it has
it's version map from the simple API query to be able to choose a
version. The main thread is blocked by process requests.

In the PR branch, the simple api requests succeeds much earlier,
allowing the solver to advance and also to schedule more prefetching.
Due to that `parse_cache` and `from_metadata` became bottlenecks, so i
moved them off the main thread (green color, and their spans can now
overlap because they can run on multiple threads in parallel). The main
thread isn't blocked on `process_request` anymore, instead it has
frequent idle times. The spans are all much shorter, which indicates
that on main they could have finished much earlier, but a task didn't
yield so they weren't scheduled to finish (though i haven't dug deep
enough to understand the exact scheduling between the process request
stream and the solver here).

**main**


![jupyter-warm-main](https://github.com/astral-sh/puffin/assets/6826232/693c53cc-1090-41b7-b02a-a607fcd2cd99)

**PR**


![jupyter-warm-branch](https://github.com/astral-sh/puffin/assets/6826232/33435f34-b39b-4b0a-a9d7-4bfc22f55f05)

## Benchmarks

```
$ hyperfine --warmup 3 "target/profiling/main-dev resolve jupyter" "target/profiling/branch-dev resolve jupyter"
Benchmark 1: target/profiling/main-dev resolve jupyter
  Time (mean ± σ):      29.1 ms ±   0.7 ms    [User: 22.9 ms, System: 11.1 ms]
  Range (min … max):    27.7 ms …  32.2 ms    103 runs
 
Benchmark 2: target/profiling/branch-dev resolve jupyter
  Time (mean ± σ):      18.8 ms ±   1.1 ms    [User: 37.0 ms, System: 22.7 ms]
  Range (min … max):    16.5 ms …  21.9 ms    154 runs
 
Summary
  target/profiling/branch-dev resolve jupyter ran
    1.55 ± 0.10 times faster than target/profiling/main-dev resolve jupyter

$ hyperfine --warmup 3 "target/profiling/main-dev resolve meine_stadt_transparent" "target/profiling/branch-dev resolve meine_stadt_transparent"
Benchmark 1: target/profiling/main-dev resolve meine_stadt_transparent
  Time (mean ± σ):      37.8 ms ±   0.9 ms    [User: 30.7 ms, System: 14.1 ms]
  Range (min … max):    36.6 ms …  41.5 ms    79 runs
 
Benchmark 2: target/profiling/branch-dev resolve meine_stadt_transparent
  Time (mean ± σ):      24.7 ms ±   1.5 ms    [User: 47.0 ms, System: 39.3 ms]
  Range (min … max):    21.5 ms …  28.7 ms    113 runs
 
Summary
  target/profiling/branch-dev resolve meine_stadt_transparent ran
    1.53 ± 0.10 times faster than target/profiling/main-dev resolve meine_stadt_transparent

$ hyperfine --warmup 3 "target/profiling/main pip compile scripts/requirements/home-assistant.in" "target/profiling/branch pip compile scripts/requirements/home-assistant.in"
Benchmark 1: target/profiling/main pip compile scripts/requirements/home-assistant.in
  Time (mean ± σ):     229.0 ms ±   2.8 ms    [User: 197.3 ms, System: 63.7 ms]
  Range (min … max):   225.8 ms … 234.0 ms    13 runs
 
Benchmark 2: target/profiling/branch pip compile scripts/requirements/home-assistant.in
  Time (mean ± σ):      91.4 ms ±   5.3 ms    [User: 289.2 ms, System: 176.9 ms]
  Range (min … max):    81.0 ms … 104.7 ms    32 runs
 
Summary
  target/profiling/branch pip compile scripts/requirements/home-assistant.in ran
    2.50 ± 0.15 times faster than target/profiling/main pip compile scripts/requirements/home-assistant.in
```
2024-02-02 18:18:24 +01:00
konsti 3771f6656e
Allow additional assertions on command output (#1226)
In the scenario tests, we want to make sure we're actually conforming to
the scenario's expectations, so we now have an extra assertion on
whether resolution failed or succeeded as well as that it includes the
given packages.

Closes #1112
Closes #1030
2024-02-02 09:41:35 +00:00
konsti b16422a108
Remove insta_cmd (#1225)
We need more flexible filters than those `inta` offers, and `insta_cmd`
makes it impossible to plug in programmatic filters. At the same time we
use barely any of `insta_cmd`'s features. We can replace the subset we
need in about 50 loc.
2024-02-02 09:37:04 +00:00
konsti 0925e446a8
Refactor remaining integration tests (#1220)
Mostly a mechanical refactor to use the `puffin_snapshot!` and
`TestContext` infrastructure in the add, remove, venv and pip uninstall
tests, in preparation for adding programmatic windows testing filters.
The is only one remaining usage of `assert_cmd_snapshot!` now in the
`puffin_snapshot!` macro.
2024-02-02 10:26:59 +01:00
konsti 6b050a1972
Refactor pip install and sync tests (#1213)
Mostly a mechanical refactor to use the `puffin_snapshot!` and
`TestContext` infrastructure in the pip install and pip sync tests, in
preparation for adding programmatic windows testing filters.
2024-02-02 10:26:31 +01:00
Charlie Marsh d77d129e8d
Run `cargo update` (#1230) 2024-02-01 11:14:38 -05:00
Charlie Marsh bb49ebee1e
Avoid race condition in clone file replacement (#1229)
## Summary

I've never seen this in practice but in theory it is possible, and we
have the same guardrail in the hardlink path.
2024-02-01 10:55:23 -05:00
konsti 809c6d676f
Use normalized display in tests and other small windows fixes (#1228)
Split out from the large test refactoring PR. Use `normalized_display`
in tests and two more thiserror derives to match snapshots and output,
and other small windows fixes.
2024-02-01 16:12:30 +01:00
Charlie Marsh 9487378ef9
Avoid TOCTOU errors in data directory installations (#1227)
## Summary

See: https://github.com/astral-sh/puffin/issues/1224

## Test Plan

Ran `python -m scripts.bench --puffin
scripts/requirements/compiled/jupyter.txt --min-runs 100 --benchmark
install-warm --verbose` several times, which failed eventually on `main`
but not on this branch.
2024-02-01 14:55:29 +00:00
konsti ea0bfc565d
Refactor pip scenario tests (#1212)
Mostly a mechanical refactor to use the `puffin_snapshot!` and
`TestContext` infrastructure in the pip compile and pip install
scenarios, in preparation for adding programmatic windows testing
filters.
2024-02-01 10:31:40 +01:00
Charlie Marsh 0757862a7a
Accommodate minute-level filters in Insta (#1219)
I don't know why `compile_editable` took over a minute in this case, but
seems like it did? Hard to test this fix.


https://github.com/astral-sh/puffin/actions/runs/7734769259/job/21089338951?pr=1216
2024-02-01 09:43:09 +01:00
Charlie Marsh 8cbe1d220c
Remove double-download for source distributions (#1218)
## Summary

Oops -- this was using a different cache key than the route above (this
is the wheel _metadata_ route vs. the wheel build route), so we were
saving and building source distributions twice in `pip install`.
2024-02-01 04:41:29 +00:00
Charlie Marsh 51e8609ee8
Use Python 3.12 in benchmarks (#1215)
I originally used Python 3.10, since 3.10 and 3.11 are by far the most
common (at least for [Ruff](https://pypistats.org/packages/ruff)). But
3.12 should give Python tools the most favorable benchmarks.
2024-01-31 15:51:13 -05:00
Charlie Marsh c4bfb6efee
Add a `BENCHMARKS.md` with rendered benchmarks (#1211)
As a precursor to the release, I want to include a structured document
with detailed benchmarks.

Closes https://github.com/astral-sh/puffin/issues/1210.
2024-01-31 20:11:52 +00:00
Andrew Gallant b9d89e7624
puffin-client: generalize SimpleMetadaRaw into OwnedArchive<A> (#1208)
It turns out that the pattern I coded up for SimpleMetadataRaw is
generally useful when working with rkyv. This commit makes it generic by
supporting any type that implements rkyv's traits, and makes a few
simplifying assumptions by picking a concrete serializer, validator and
deserializer. In effect, this lets use own any archived value.

We also rejigger the API a little bit and double-down on
`OwnedArchive<A>` just being a owned wrapper for `Archived<A>`. Namely,
we implement `Deref` and turn its inherent methods into methods that
require fully qualified syntax. (As is standard for things that
implement `Deref` to avoid ambiguity with the deref target's methods.)

(This PR also makes a couple small simplifications to our custom rkyv
serializer since we no longer need to use it directly. We do still need
to name the type in trait bounds, so it has to be public.)
2024-01-31 11:56:34 -05:00
konsti 234e8d0bb7
Abstract away test duplication in pip-compile (#1187)
In preparation for the new windows handling, i want to introduce a
`TestContext` and `puffin_snapshot!` abstraction. This PR applies those
changes for pip-compile. My plan is to use those for all venv-based
integration tests and build the custom windows filters on top of
`puffin_snapshot!`.
2024-01-31 16:11:10 +00:00
Charlie Marsh 01258c1bb3
Report number of bytes deleted when clearing cache (#1203)
## Summary

This is based on Cargo's `clean` implementation, with modifications
based on some of my own preferences, and to better adhere to patterns we
use in our codebase:

![Screenshot 2024-01-31 at 1 31
10 AM](https://github.com/astral-sh/puffin/assets/1309177/38704798-b17f-4972-ab67-00484ce63d62)
2024-01-31 10:48:28 -05:00
Charlie Marsh 8f9258fae3
Invert default feature for testing (#1200)
## Summary

We have some flags in Puffin that enable us to opt-in to certain tests.
To date, they've been opt-in, so we've run our tests with
`--all-features`. This PR makes them opt-out, and we now run tests with
default features.

The main motivation here is that I want to get tests working for macOS
on CI, but for unknown reasons, macOS can't compile the PyO3 features at
the same time as everything else due to strange linker issues. By
avoiding `--all-features` for tests, we thus avoid unnecessarily
including features that we don't actually use in Puffin.

I verified that the exact same number of tests (439) are run before and
after this change. For users, the primary difference is that you now
need to specify `--no-default-features --features pypi --features
python` to avoid (e.g.) including the Git tests.
2024-01-31 09:44:26 -05:00
Charlie Marsh b2f1bbaa63
Add a Ctrl+C handler to the confirm workflow (#1202)
Fixes an issue whereby exiting the confirmation prompt can lead to your
cursor disappearing: https://github.com/console-rs/dialoguer/issues/294.

See:
b839a2c5b7/rye/src/main.rs (L36-L48).
2024-01-31 02:08:27 +00:00
Charlie Marsh 262f29b558
Add missing `--exclude-newer` to executable tests (#1201)
A new version of `platformdirs` came out, which broke these.
2024-01-30 20:26:11 -05:00
Charlie Marsh b88b9e1f3d
Remove dedicated `flate2` features from Puffin (#1199)
We should be able to enable and disable these without crate-internal
features.
2024-01-30 19:41:08 -05:00
Andrew Gallant b47f70917f
puffin-client: simplify use of http-cache-semantics (#1197)
The `http-cache-semantics` crate is polymorphic on the types of requests
and responses it accepts. We had previously been explicitly converting
between `http` and `reqwest` types, but this isn't necessary. We can
provide impls of the traits in `http-cache-semantics` for `reqwest`'s
types (via a wrapper). This saves us from the awkward request/response
type conversions.

While this does clone the request, this is:

1. Not new. We were previously cloning the request to do the conversion.
2. An artifact (I believe) of http-cache-semantics API. (It kind of
   seems like an API bug to me?)

There is also a little bit of messiness around inter-operating between
http::uri::Uri and url::Url. But overall shouldn't be a big deal.
2024-01-30 18:20:44 -05:00
Charlie Marsh 3f5e7306a5
Remove `WaitMap` dependency (#1183)
## Summary

This is an attempt to https://github.com/astral-sh/puffin/pull/1163 by
removing the `WaitMap` and gaining more granular control over the values
that we hold over `await` boundaries.
2024-01-30 15:25:22 -05:00
Charlie Marsh c129717b41
Add support for `--no-deps` to `pip install` (#1191)
## Summary

Closes https://github.com/astral-sh/puffin/issues/1188.
2024-01-30 19:54:57 +00:00
Charlie Marsh 8305acc584
Add a builder for resolution options (#1192) 2024-01-30 19:50:16 +00:00
Charlie Marsh aa3b79ec63
Prompt user for missing `-r` and `-e` flags in `pip install` (#1180)
## Summary

If the user runs a command like `pip install requirements.txt`, we now
prompt them to ask if they meant to include the `-r` flag:

![Screenshot 2024-01-29 at 8 38
29 PM](https://github.com/astral-sh/puffin/assets/1309177/82b9f7a2-2526-4144-b200-a5015e5b8a4b)

![Screenshot 2024-01-29 at 8 38
33 PM](https://github.com/astral-sh/puffin/assets/1309177/bd8ebb51-2537-4540-a0e0-718e66a1c69c)

The specific logic is: if the requirement ends in `.txt` or `.in`, and
the file exists locally, prompt the user for `-r`. If the requirement
contains a directory separator, and the directory exists locally, prompt
the user for `-e`.

Closes #1166.
2024-01-30 18:58:45 +00:00
Charlie Marsh 7a937e0f60
Error when parsing `requirements.txt`-like packages in `requirements.txt` file (#1179)
## Summary

Like https://github.com/astral-sh/puffin/pull/1180, this PR adds logic
for `requirements.txt` parsing whereby if a requirement _looks like_ a
local requirements file or an editable directory, we prompt the user to
correct the error (typically, by adding `-r`).
2024-01-30 18:55:11 +00:00
konsti 4ad0dc8b9e
Add windows aarch64 trampolines (#1190)
Lacking windows compatible aarch64 hardware, i cross compiled the
trampoline from x86_64 linux to aarch64-pc-windows-msvc; I added the
instructions to the puffin-trampoline readme. With some testing on an
aarch64 windows machine, this should be sufficient to build working
win_arm64 tagged wheels.

i686-pc-windows-msvc is failing with an error:

```
error: linking with `lld-link` failed: exit status: 1
  = note: lld-link: error: undefined symbol: __aulldiv
          >>> referenced by libcompiler_builtins-2fb09dee087e9f64.rlib(compiler_builtins-2fb09dee087e9f64.compiler_builtins.597f0152646f1b8-cgu.0.rcgu.o):(compiler_builtins::int::specialized_div_rem::u128_div_rem::h06aed1e23a3f8f5c)
          >>> referenced by libcompiler_builtins-2fb09dee087e9f64.rlib(compiler_builtins-2fb09dee087e9f64.compiler_builtins.597f0152646f1b8-cgu.0.rcgu.o):(compiler_builtins::int::specialized_div_rem::u128_div_rem::h06aed1e23a3f8f5c)
          >>> referenced by libcompiler_builtins-2fb09dee087e9f64.rlib(compiler_builtins-2fb09dee087e9f64.compiler_builtins.597f0152646f1b8-cgu.0.rcgu.o):(compiler_builtins::int::specialized_div_rem::u128_div_rem::h06aed1e23a3f8f5c)
          >>> referenced 4 more times

          lld-link: error: undefined symbol: __aullrem
          >>> referenced by libcompiler_builtins-2fb09dee087e9f64.rlib(compiler_builtins-2fb09dee087e9f64.compiler_builtins.597f0152646f1b8-cgu.0.rcgu.o):(compiler_builtins::int::specialized_div_rem::u128_div_rem::h06aed1e23a3f8f5c)
          >>> referenced by libcompiler_builtins-2fb09dee087e9f64.rlib(compiler_builtins-2fb09dee087e9f64.compiler_builtins.597f0152646f1b8-cgu.0.rcgu.o):(compiler_builtins::int::specialized_div_rem::u128_div_rem::h06aed1e23a3f8f5c)
          >>> referenced by libcompiler_builtins-2fb09dee087e9f64.rlib(compiler_builtins-2fb09dee087e9f64.compiler_builtins.597f0152646f1b8-cgu.0.rcgu.o):(compiler_builtins::int::specialized_div_rem::u128_div_rem::h06aed1e23a3f8f5c)
          >>> referenced 4 more times
```
2024-01-30 17:51:27 +00:00
Charlie Marsh c479c26cab
Add compatibility arguments for `pip sync` (#1185)
## Summary

As with `pip compile`, we can provide useful error messages and warnings
when people pass `pip sync` arguments.

Closes https://github.com/astral-sh/puffin/issues/1184.
2024-01-30 08:48:55 -05:00
konsti ab27913f68
Instrument the main function and add jupyter.in (#1186)
Instrument the main function as anchor span for checking overhead and
update tracing-durations-export to 0.2.0 for differentiating
blocking/non-blocking tasks.

Add a `jupyter.in` requirement since `pip install jupyter` is a common
operation. I tried `jupyterlab` too but there is no difference in
performance (1.00 ± 0.07).
2024-01-30 11:03:24 +00:00
konsti a6c4cbfe55
Cleanup puffin interpreter errors (#1169)
Use `virtualenv` consistently, remove unused error variants and hint the
user towards installing missing python versions.

I didn't touch the Readme but i replaced `virtualenv environment` with
`virtualenv` in the strings i found.

Fixes https://github.com/astral-sh/puffin/issues/1167
2024-01-30 10:52:46 +01:00
Charlie Marsh bd934207e4
Accept relative file paths in CLI requirements (#1182)
## Summary

See: https://github.com/astral-sh/puffin/issues/1181.

## Test Plan

```
❯ cargo run -- pip install packse@../../zanieb/packse
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s
     Running `target/debug/puffin pip install 'packse@../../zanieb/packse'`
error: Distribution not found at: file:///Users/crmarsh/zanieb/packse
```
2024-01-30 03:31:24 +00:00
konsti d4ed5ea858
Fix the `compile_python_37` test with python 3.7 installed (#1172)
Make the test `compile_python_37` pass whether python 3.7 is installed
or not by muting the warning for a missing 3.7. The resolution error is
independent of whether 3.7 is installed or not.
2024-01-29 18:59:28 +01:00
Charlie Marsh 67a09649f2
Support parsing `--find-links`, `--index-url`, and `--extra-index-url` in `requirements.txt` (#1146)
## Summary

This PR adds support for `--find-links`, `--index-url`, and
`--extra-index-url` arguments when specified in a `requirements.txt`.

It's a mostly-straightforward change. The only uncertain piece is what
to do when multiple files include these flags, and/or when we include
them on the CLI and in other files.

In general:

- If _anything_ specifies `--no-index`, we respect it.
- We combine all `--extra-index-url` and `--find-links` across all
sources, since those are just vectors.
- If we see multiple `--index-url` in requirements files, we error.
- We respect the `--index-url` from the command line over any provided
in a requirements file.

(`pip-compile` seems to just pick one semi-arbitrarily when multiple are
provided.)

Closes https://github.com/astral-sh/puffin/issues/1143.
2024-01-29 15:06:40 +00:00
Charlie Marsh 4b9daf9604
Use tokio_tar instead of async_tar (#1170)
## Summary

`tokio_tar` is a fork of `async_tar` that uses Tokio instead of
`async-std`. Using it removes a significant dependency from our tree.

(There is an open PR
(https://github.com/dignifiedquire/async-tar/pull/41) in `async-tar` to
add Tokio support, but it's over a year old.)

See:
https://github.com/astral-sh/puffin/pull/1157#discussion_r1469190249.
2024-01-29 10:00:30 -05:00
Andrew Gallant a42b385e9b
puffin-client: add SimpleMetadataRaw (#1150)
This adds what is effectively an owned wrapper around
`Archived<SimpleMetadata>`. Normally, an `Archived<SimpleMetadata>`
has to be used behind a pointer (since it has a lifetime
attached to its underlying byte buffer), but we create a
wrapper around it that owns the underlying buffer and provides
free access to the archived type.

This in effect creates an anchor point for the archived type
and lets us pass it around easily. (There has to be an anchor
point for it somewhere.)

An alternative to this approach would be to store it as a file
backed memory map. But in practice, we're dealing with small
files, and just reading them on to the heap is likely to be
faster. (Memory maps also have wildly different perf characteristics
across platforms.)

Note that this commit just defines the type. It isn't actually
used anywhere yet.
2024-01-29 09:37:06 -05:00
konsti be48200642
Small instrumentation improvements (#1164)
Less verbose span fields for `Dist`s by using the display impl and no
more min length in the tracing durations plot config for comparability
(we lose spans due to a speedup otherwise). Both wait points in the
solver loop are now instrumented so we can inspect what we're waiting
for to progress in the solver.
2024-01-29 10:55:19 +00:00
konsti 8bfc3c1b37
Trim `get_cached_with_callback` and `send_cached` down some more. (#1128)
I noticed that `get_cached_with_callback` and `send_cached` are large
both in terms of llvm lines and in terms of types (and large types can
cause buffer overflows on windows). `get_cached_with_callback`
specifically is large because it's monomorphized for each callback. I've
split both functions into smaller units and boxed the callback.

llvm lines, before:

```
  Lines                 Copies               Function name
  -----                 ------               -------------
  909511                21625                (TOTAL)
   36026 (4.0%,  4.0%)     33 (0.2%,  0.2%)  <&mut rmp_serde::decode::Deserializer<R,C> as serde:🇩🇪:Deserializer>::deserialize_any
   14688 (1.6%,  5.6%)      8 (0.0%,  0.2%)  puffin_client::cached_client::CachedClient::get_cached_with_callback::{{closure}}::{{closure}}
   13748 (1.5%,  7.1%)      5 (0.0%,  0.2%)  puffin_client::cached_client::CachedClient::send_cached::{{closure}}
   12460 (1.4%,  8.5%)     35 (0.2%,  0.4%)  alloc::raw_vec::RawVec<T,A>::grow_amortized
   10731 (1.2%,  9.6%)    122 (0.6%,  0.9%)  <alloc::boxed::Box<T,A> as core::ops::drop::Drop>::drop
    8952 (1.0%, 10.6%)      9 (0.0%,  1.0%)  core::slice::sort::partition_in_blocks
    8216 (0.9%, 11.5%)    323 (1.5%,  2.5%)  <core::result::Result<T,E> as core::ops::try_trait::Try>::branch
    7745 (0.9%, 12.4%)    205 (0.9%,  3.4%)  core::result::Result<T,E>::map_err
    6862 (0.8%, 13.1%)     54 (0.2%,  3.7%)  <alloc::vec::Vec<T> as alloc::vec::spec_from_iter_nested::SpecFromIterNested<T,I>>::from_iter
    6720 (0.7%, 13.9%)    133 (0.6%,  4.3%)  std::panicking::try
    6600 (0.7%, 14.6%)     45 (0.2%,  4.5%)  <alloc::sync::Weak<T,A> as core::ops::drop::Drop>::drop
    5899 (0.6%, 15.2%)     33 (0.2%,  4.6%)  rmp_serde::decode::Deserializer<R,C>::read_str_data
    5610 (0.6%, 15.9%)     33 (0.2%,  4.8%)  alloc::raw_vec::RawVec<T,A>::allocate_in
    5187 (0.6%, 16.4%)    133 (0.6%,  5.4%)  std::panicking::try::do_catch
    4740 (0.5%, 17.0%)    268 (1.2%,  6.7%)  core::ops::function::FnOnce::call_once
    4670 (0.5%, 17.5%)     40 (0.2%,  6.8%)  puffin_client::cached_client::CachedClient::get_cached_with_callback::{{closure}}::{{closure}}::{{closure}}
    4527 (0.5%, 18.0%)     54 (0.2%,  7.1%)  core::iter::traits::iterator::Iterator::try_fold
```

after:

```
  Lines                 Copies               Function name
  -----                 ------               -------------
  910275                21712                (TOTAL)
   36026 (4.0%,  4.0%)     33 (0.2%,  0.2%)  <&mut rmp_serde::decode::Deserializer<R,C> as serde:🇩🇪:Deserializer>::deserialize_any
   12460 (1.4%,  5.3%)     35 (0.2%,  0.3%)  alloc::raw_vec::RawVec<T,A>::grow_amortized
   10935 (1.2%,  6.5%)    124 (0.6%,  0.9%)  <alloc::boxed::Box<T,A> as core::ops::drop::Drop>::drop
    8952 (1.0%,  7.5%)      9 (0.0%,  0.9%)  core::slice::sort::partition_in_blocks
    8714 (1.0%,  8.5%)      5 (0.0%,  0.9%)  puffin_client::cached_client::CachedClient::send_cached_handle_stale::{{closure}}
    8216 (0.9%,  9.4%)    323 (1.5%,  2.4%)  <core::result::Result<T,E> as core::ops::try_trait::Try>::branch
    8192 (0.9%, 10.3%)      8 (0.0%,  2.5%)  puffin_client::cached_client::CachedClient::get_cached_with_callback::{{closure}}::{{closure}}
    7745 (0.9%, 11.1%)    205 (0.9%,  3.4%)  core::result::Result<T,E>::map_err
    6862 (0.8%, 11.9%)     54 (0.2%,  3.7%)  <alloc::vec::Vec<T> as alloc::vec::spec_from_iter_nested::SpecFromIterNested<T,I>>::from_iter
    6778 (0.7%, 12.6%)      5 (0.0%,  3.7%)  puffin_client::cached_client::CachedClient::send_cached::{{closure}}
    6720 (0.7%, 13.4%)    133 (0.6%,  4.3%)  std::panicking::try
    6600 (0.7%, 14.1%)     45 (0.2%,  4.5%)  <alloc::sync::Weak<T,A> as core::ops::drop::Drop>::drop
    5899 (0.6%, 14.7%)     33 (0.2%,  4.7%)  rmp_serde::decode::Deserializer<R,C>::read_str_data
    5610 (0.6%, 15.3%)     33 (0.2%,  4.8%)  alloc::raw_vec::RawVec<T,A>::allocate_in
    5187 (0.6%, 15.9%)    133 (0.6%,  5.4%)  std::panicking::try::do_catch
    4740 (0.5%, 16.4%)    268 (1.2%,  6.7%)  core::ops::function::FnOnce::call_once
    4527 (0.5%, 16.9%)     54 (0.2%,  6.9%)  core::iter::traits::iterator::Iterator::try_fold
```

Stack sizes diff:
https://gist.github.com/konstin/a3f38276aacf1170038a756c8c49793c
2024-01-29 08:31:27 +00:00
Charlie Marsh fa3f0d7a55
Remove cache `purge` methods to `clean` (#1159)
This is more consistent with the public interface.
2024-01-28 21:15:11 -05:00
Charlie Marsh d88ce76979
Stream unpacking of source distribution downloads (#1157)
This PR migrates our source distribution downloads to unzip as we
stream, similar to our approach for wheels.

In my testing, this showed a consistent speedup (e.g., 6% here for a few
representative source distributions):

```text
❯ python -m scripts.bench --puffin-path ./target/release/main --puffin-path ./target/release/puffin --benchmark install-cold requirements.in
Benchmark 1: ./target/release/main (install-cold)
  Time (mean ± σ):      1.503 s ±  0.039 s    [User: 1.479 s, System: 0.537 s]
  Range (min … max):    1.466 s …  1.605 s    10 runs

Benchmark 2: ./target/release/puffin (install-cold)
  Time (mean ± σ):      1.421 s ±  0.024 s    [User: 1.505 s, System: 0.593 s]
  Range (min … max):    1.381 s …  1.454 s    10 runs

Summary
  './target/release/puffin (install-cold)' ran
    1.06 ± 0.03 times faster than './target/release/main (install-cold)'
```
2024-01-28 20:09:24 -05:00
Andrew Gallant 5219d37250
add initial rkyv support (#1135)
This PR adds initial support for [rkyv] to puffin. In particular,
the main aim here is to make puffin-client's `SimpleMetadata` type
possible to deserialize from a `&[u8]` without doing any copies. This
PR **stops short of actuallying doing that zero-copy deserialization**.
Instead, this PR is about adding the necessary trait impls to a variety
of types, along with a smattering of small refactorings to make rkyv
possible to use.

For those unfamiliar, rkyv works via the interplay of three traits:
`Archive`, `Serialize` and `Deserialize`. The usual flow of things is
this:

* Make a type `T` implement `Archive`, `Serialize` and `Deserialize`.
rkyv
helpfully provides `derive` macros to make this pretty painless in most
  cases.
* The process of implementing `Archive` for `T` *usually* creates an
entirely
new distinct type within the same namespace. One can refer to this type
without naming it explicitly via `Archived<T>` (where `Archived` is a
clever
  type alias defined by rkyv).
* Serialization happens from `T` to (conceptually) a `Vec<u8>`. The
serialization format is specifically designed to reflect the in-memory
layout
  of `Archived<T>`. Notably, *not* `T`. But `Archived<T>`.
* One can then get an `Archived<T>` with no copying (albeit, we will
likely
need to incur some cost for validation) from the previously created
`&[u8]`.
This is quite literally [implemented as a pointer cast][rkyv-ptr-cast].
* The problem with an `Archived<T>` is that it isn't your `T`. It's
something
  else. And while there is limited interoperability between a `T` and an
`Archived<T>`, the main issue is that the surrounding code generally
demands
a `T` and not an `Archived<T>`. **This is at the heart of the tension
for
  introducing zero-copy deserialization, and this is mostly an intrinsic
problem to the technique and not an rkyv-specific issue.** For this
reason,
  given an `Archived<T>`, one can get a `T` back via an explicit
deserialization step. This step is like any other kind of
deserialization,
although generally faster since no real "parsing" is required. But it
will
  allocate and create all necessary objects.

This PR largely proceeds by deriving the three aforementioned traits
for `SimpleMetadata`. And, of course, all of its type dependencies. But
we stop there for now.

The main issue with carrying this work forward so that rkyv is actually
used to deserialize a `SimpleMetadata` is figuring out how to deal
with `DataWithCachePolicy` inside of the cached client. Ideally, this
type would itself have rkyv support, but adding it is difficult. The
main difficulty lay in the fact that its `CachePolicy` type is opaque,
not easily constructable and is internally the tip of the iceberg of
a rat's nest of types found in more crates such as `http`. While one
"dumb"-but-annoying approach would be to fork both of those crates
and add rkyv trait impls to all necessary types, it is my belief that
this is the wrong approach. What we'd *like* to do is not just use
rkyv to deserialize a `DataWithCachePolicy`, but we'd actually like to
get an `Archived<DataWithCachePolicy>` and make actual decisions used
the archived type directly. Doing that will require some work to make
`Archived<DataWithCachePolicy>` directly useful.

My suspicion is that, after doing the above, we may want to mush
forward with a similar approach for `SimpleMetadata`. That is, we want
`Archived<SimpleMetadata>` to be as useful as possible. But right
now, the structure of the code demands an eager conversion (and thus
deserialization) into a `SimpleMetadata` and then into a `VersionMap`.
Getting rid of that eagerness is, I think, the next step after dealing
with `DataWithCachePolicy` to unlock bigger wins here.

There are many commits in this PR, but most are tiny. I still encourage
review to happen commit-by-commit.

[rkyv]: https://rkyv.org/
[rkyv-ptr-cast]:
https://docs.rs/rkyv/latest/src/rkyv/util/mod.rs.html#63-68
2024-01-28 12:14:59 -05:00
Charlie Marsh a25a1f2958
Avoid re-creating directories in async unzip (#1155)
This PR extends the optimizations from #1154 to other unzip paths.
2024-01-28 14:30:38 +00:00
Charlie Marsh 3d10f344f3
Only include visited packages in error message derivation (#1144)
## Summary

This is my guess as to the source of the resolver flake, based on
information and extensive debugging from @zanieb. In short, if we rely
on `self.index.packages` as a source of truth during error reporting, we
open ourselves up to a source of non-determinism, because we fetch
package metadata asynchronously in the background while we solve -- so
packages _could_ be included in or excluded from the index depending on
the order in which those requests are returned.

So, instead, we now track the set of packages that _were_ visited by the
solver. Visiting a package _requires_ that we wait for its metadata to
be available. By limiting analysis to those packages that were visited
during solving, we are faithfully representing the state of the solver
at the time of failure.

Closes #863
2024-01-28 09:27:22 -05:00
Charlie Marsh 6f2c235d21
Avoid re-creating directories during unzip (#1154)
## Summary

We have this optimization in `wheel.rs`, in the installer, but it makes
a huge difference for zips with many small files:

```
Benchmarking file_reader/Django-5.0.1-py3-none-any.whl: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 74.2s, or reduce sample count to 10.
file_reader/Django-5.0.1-py3-none-any.whl
                        time:   [751.63 ms 757.78 ms 764.27 ms]
                        change: [-1.0290% +0.0841% +1.2289%] (p = 0.88 > 0.05)
                        No change in performance detected.
Found 4 outliers among 100 measurements (4.00%)
  4 (4.00%) high mild

Benchmarking buffered_reader/Django-5.0.1-py3-none-any.whl: Warming up for 3.0000 s
Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 53.4s, or reduce sample count to 10.
buffered_reader/Django-5.0.1-py3-none-any.whl
                        time:   [529.86 ms 536.44 ms 543.35 ms]
                        change: [+0.0293% +1.5543% +3.1426%] (p = 0.05 > 0.05)
                        No change in performance detected.
Found 3 outliers among 100 measurements (3.00%)
  3 (3.00%) high mild
```

That's almost 30% faster...
2024-01-28 00:07:54 -05:00
Charlie Marsh 888a9e6f53
Remove an unnecessary `Path` clone (#1153) 2024-01-28 03:16:51 +00:00
Charlie Marsh d243250dec
Avoid unnecessary permissions changes for copy paths (#1152)
In Rust, `fs::copy` automatically preserves permissions (see:
https://doc.rust-lang.org/std/fs/fn.copy.html).

Elsewhere, when copying from the zip archive out to the cache, we can
set permissions during file creation, rather than as a separate call.

Both of these should be slightly more efficient.
2024-01-27 22:11:55 -05:00
Charlie Marsh d6795da0ea
Set permissions after streaming unzip (#1151)
## Summary

When we migrated to an "unzip while we stream" solution, we lost the
logic to set permissions on the extracted files, so executables in
wheels were no longer executable. It turns out this is a little tricky,
since the permissions metadata is in the central directory at the _end_
of the zip file, and the async ZIP reader explicitly stops iteration
once it hits the central directory. (Specifically, it goes 4 bytes into
the central directory, since it sees the 4-byte signature header and
then stops.)

So, to solve that, I've added a `CentralDirectoryReader` that continues
where that iterator left off. This required forking the async zip crate:
https://github.com/charliermarsh/rs-async-zip/pull/1. It took a lot of
fiddling but I'm quite confident in the code now, especially since the
async zip crate validates the signature kind on every read.

The central directory is typically quite small (even for the Zig wheel,
which is enormous, it's just around 1MB), so I don't expect this to have
a high cost.

Closes https://github.com/astral-sh/puffin/issues/1148.
2024-01-27 19:22:44 -05:00
Charlie Marsh 15ca17a68d
Support relative `file:` paths for `--find-links` (#1147)
Just for consistency.
2024-01-27 03:48:25 +00:00
Charlie Marsh 4e19e6846d
Accept long form of pip arguments in `requirements.txt` (#1145) 2024-01-26 21:56:10 -05:00
Charlie Marsh addb94fbd6
Add support for emitting index URLs and --find-links (#1142)
Closes https://github.com/astral-sh/puffin/issues/1140.
2024-01-27 01:37:55 +00:00
Charlie Marsh a2ef2010d2
Add arguments for pip-compile compatibility (#1139)
## Summary

This ensures that we warn when redundant options are passed (like
`--allow-unsafe`, which is really common for forwards compatibility
since it's going to be the default in a future release), and errors when
known variants are passed that we _don't_ support (like
`--resolver=backtracking`).

Closes https://github.com/astral-sh/puffin/issues/1127.
2024-01-26 16:54:02 -05:00
Charlie Marsh 06024653f9
Reduce visibility of some methods in `wheel.rs` (#1125) 2024-01-26 16:34:51 -05:00
Zanie Blue 5cc4e5d31e
Add `pip compile` test where specific Python versions are available on the system (#1111)
Extends https://github.com/astral-sh/puffin/pull/1106 with the scenario
from https://github.com/zanieb/packse/pull/95 which tests that `pip
compile` will use the matching system Python version for builds when
available
2024-01-26 18:38:24 +00:00
Zanie Blue 91f421cf97
Do not allow `pip compile` scenario tests to discover other Python versions (#1106)
In https://github.com/astral-sh/puffin/pull/1040 we broke the pip
compile scenarios designed to test failure when a required Python
version is not available — resolution succeeded because all of the
Python versions were available in CI. Following #1105 we have the
ability to isolate tests from Python versions available in the system.
Here, we limit the scenarios to only the Python version in the current
environment, restoring our ability to test the error messages.

With https://github.com/zanieb/packse/pull/95, we will be able to
specify scenarios with access to additional system Python versions. This
will allow us to include test coverage where resolution can succeed by
using a version available elsewhere on the system. See #1111 for this
follow-up.
2024-01-26 18:18:15 +00:00
Zanie Blue 21577ad002
Add bootstrapping and isolation of development Python versions (#1105)
Replaces https://github.com/astral-sh/puffin/pull/1068 and #1070 which
were more complicated than I wanted.

- Introduces a `.python-versions` file which defines the Python versions
needed for development
- Adds a Bash script at `scripts/bootstrap/install` which installs the
required Python versions from `python-build-standalone` to `./bin`
- Checks in a `versions.json` file with metadata about available
versions on each platform and a `fetch-version` Python script derived
from `rye` for updating the versions
- Updates CI to use these Python builds instead of the `setup-python`
action
- Updates to the latest packse scenarios which require Python 3.8+
instead of 3.7+ since we cannot use 3.7 anymore and includes new test
coverage of patch Python version requests
- Adds a `PUFFIN_PYTHON_PATH` variable to prevent lookup of system
Python versions for isolation during development

Tested on Linux (via CI) and macOS (locally) — presumably it will be a
bit more complicated to do proper Windows support.
2024-01-26 12:12:48 -06:00
Charlie Marsh cc0e211074
Avoid embedding launcher scripts on non-Windows (#1124)
Just to reduce binary size on all other platforms.
2024-01-26 17:19:05 +01:00
Charlie Marsh f946d46273
Avoid allocating a max-size buffer (#1123)
This seems potentially-dangerous with no upside.
2024-01-26 14:27:19 +00:00
konsti 39021263dd
Windows launchers using posy trampolines (#1092)
## Background

In virtual environments, we want to install python programs as console
commands, e.g. `black .` over `python -m black .`. They may be called
[entrypoints](https://packaging.python.org/en/latest/specifications/entry-points/)
or scripts. For entrypoints, we're given a module name and function to
call in that module.

On Unix, we generate a minimal python script launcher. Text files are
runnable on unix by adding a shebang at their top, e.g.

```python
#!/usr/bin/env python
```

will make the operating system run the file with the current python
interpreter. A venv launcher for black in `/home/ferris/colorize/.venv`
(module name: `black`, function to call: `patched_main`) would look like
this:

```python
#!/home/ferris/colorize/.venv/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from black import patched_main
if __name__ == "__main__":
    sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
    sys.exit(patched_main())
```

On windows, this doesn't work, we can only rely on launching `.exe`
files.

## Summary

We use posy's rust implementation of a trampoline, which is based on
distlib's c++ implementation. We pre-build a minimal exe and append the
launcher script as stored zip archive behind it. The exe will look for
the venv python interpreter next to it and use it to execute the
appended script.

The changes in this PR make the `black` entrypoint work:

```powershell
cargo run -- venv .venv
cargo run -q -- pip install black
.\.venv\Scripts\black --version
```

Integration with our existing tests will be done in follow-up PRs.

## Implementation and Details

I've vendored the posy trampoline crate. It is a formatted, renamed and
slightly changed for embedding version of
https://github.com/njsmith/posy/pull/28.

The posy launchers are smaller than the distlib launchers, 16K vs 106K
for black. Currently only `x86_64-pc-windows-msvc` is supported. The
crate requires a nightly compiler for its no-std binary size tricks.

On windows, an application can be launched with a console or without (to
create windows instead), which needs two different launchers. The gui
launcher will subsequently use `pythonw.exe` while the console launcher
uses `python.exe`.
2024-01-26 13:54:11 +00:00
konsti f1d3b08c12
Add missing version to pip sync test (#1121)
The test started failing due to a newer version on pypi.
2024-01-26 13:36:25 +00:00
Charlie Marsh 361a2039d2
Add `--no-annotate` and `--no-header` flags (#1117)
Closes #1107.
Closes #1108.
2024-01-26 12:14:18 +00:00
Charlie Marsh 7755f986c3
Support extras in editable requirements (#1113)
## Summary

This PR adds support for requirements like `-e .[d]`.

Closes #1091.
2024-01-26 12:07:51 +00:00
Charlie Marsh f593b65447
Remove refresh checks from the install plan (#1119)
## Summary

Rather than checking cache freshness in the install plan, it's a lot
simple to have the install plan _never_ return cached data when the
refresh policy is in place, and then rely on the distribution database
to check for freshness. The original implementation didn't support this,
since the distribution database was rebuilding things too often. Now, it
rarely rebuilds (it's much better about this), so it seems conceptually
much simpler to split up the responsibilities like this.
2024-01-25 22:48:16 -05:00
Charlie Marsh 50057cd5f2
Re-add Cargo's known hosts checking (#1118)
## Summary

This ensures that (like Cargo) we don't suffer from
https://github.com/advisories/GHSA-r5w3-xm58-jv6j, by way of checking
known hosts when fetching via `libgit2`.

The implementation is taken from Cargo itself, modified to remove all
configuration, since we don't yet support configuration for known hosts,
etc.

Closes #285.
2024-01-25 22:29:36 -05:00
Charlie Marsh 67b41427cc
Store source distribution directly in the cache (#1116)
I want to move towards using the archive bucket exclusively for wheels.
We never overwrite source distributions, so there's no need to symlink
them.
2024-01-25 20:52:31 -05:00
Charlie Marsh 77351c7874
Use snapshots for requirements.txt error tests (#1115)
## Summary

I find these too difficult to edit and maintain. This brings them closer
to the rest of our testing setups.
2024-01-25 20:35:52 -05:00
Charlie Marsh 57c116ee9a
Move Black editable to flit backend (#1114)
I ran into a bug in PDM that's making it impossible to use the Black
example for extras: https://github.com/pdm-project/pdm/issues/2591.

I've confirmed that Flit handles it correctly.
2024-01-25 19:54:54 -05:00
Zanie Blue 3a05ef5285
Add venv tests for missing Python versions (#1096)
These demonstrate some lackluster error messages.
2024-01-25 13:57:05 -06:00
Charlie Marsh f36c167982
Use a consolidated error for distribution failures (#1104)
## Summary

Use a single error type in `puffin_distribution`, rather than two
confusingly similar types between `DistributionDatabase` and the source
distribution module.

Also removes the `#[from]` for IO errors and replaces with explicit
wrapping, which is verbose but removes a bunch of incorrect error
messages.
2024-01-25 14:49:11 -05:00
Charlie Marsh 8ef819e07e
Remove `Option` wrapper from requirement extras (#1103)
There's no semantic difference between `None` and empty, so seems
simpler to represent this way.
2024-01-25 13:21:53 -05:00
Andrew Gallant 067acfe79e
puffin-client: rejigger error type (#1102)
This PR changes the error type to be boxed internally so that it uses
less size on the stack. This makes functions returning `Result<T,
Error>`, in particular, return something much smaller.

The specific thing that motivated this was Clippy lints firing when I
tried to refactor code in this crate.

I chose to achieve boxing by splitting the enum out into a separate
type, and then wiring up the necessary `From` impl to make error
conversions easy, and then making `Error` itself opaque. We could expose
the `Box`, but there isn't a ton of benefit in doing so because one
cannot pattern match through a `Box`.

This required using more explicit error conversions in several places.
And as a result, I was able to remove all `#[from]` attributes on
non-transparent error variants.
2024-01-25 13:13:21 -05:00
Charlie Marsh 3e86c80874
Set buffer size when unzipping (#1101)
The zip archive includes an uncompressed size header, which we can use
to preallocate.
2024-01-25 17:58:36 +00:00
Charlie Marsh e0902d7d5a
Make `puffin-fs` `tokio` dependency opt-in (#1100) 2024-01-25 12:47:46 -05:00
Charlie Marsh 5ad2e60561
Use `same-file` to detect interpreter shims (#1099)
Our existing detection doesn't work on Windows, because we canoncalize
the interpreter path but not `info.sys_executable`, so the former
includes the UNC prefix, etc. This is cross-platform and gets at the
intent of the check.
2024-01-25 12:27:49 -05:00
Charlie Marsh f4939e50a6
Remove UNC prefixes on Windows (#1086)
## Summary

This PR adds a `NormalizedDisplay` trait that we can use for user-facing
paths, to strip the UNC prefix on Windows.

On other platforms, the implementation is a no-op (vs. `Display`).

I audited all usages of `.display()`, and changed any that were
user-facing, either via `println!` or `eprintln!`, or by way of being
included in error messages. I did _not_ change uses that were only in
tests or only went to tracing.

Closes https://github.com/astral-sh/puffin/issues/1084.
2024-01-25 11:44:22 -05:00
konsti 035cd81ac8
Fix venv PATH on windows (#1095)
Windows uses `;` instead of `:` to separate `PATH` entries. This pull
request switches from manually using `:` to the `std::env` functions.
This fixes

```
puffin pip install -e scripts/editable-installs/maturin_editable
```

on windows.
2024-01-25 15:40:52 +00:00
Charlie Marsh 904db967af
Use junctions instead of symlinks on Windows (#1087)
## Summary

When we unzip wheels in the cache, we write the directories out to an
`archive-v0` bucket, and then symlink into that bucket from the
`wheels-v0` and `built-wheels-v0` buckets.

On Windows, symlinks are not well supported. Specifically, they need to
be explicitly enabled by the user. So, instead of symlinks, we now use
junctions, which are well-supported on Windows, and allow you to
(effectively) symlink a directory to another directory. This PR
implements said junction support, which gets the core installer working
on Windows.

In the past, we also used symlinks to implement another primitive: we
wanted to be able to replace a directory "atomically" (I put
"atomically" in quotes because I don't know if it's actually a
guaranteed atomic operation), in case someone was trying to use the
directory while we were replacing it (as opposed to deleting the
directory, then moving it into place).

On Windows, it doesn't appear to be possible to atomically replace a
junction. So instead, I'm using a new design, whereby the cache always
returns canonicalized paths. We know these canonicalized paths are
unique and won't be replaced, so they're safe for writers to rely on. In
general, when we write new data to the cache, we now return the
canonicalized path. When we read from the cache, and try to identify
(e.g.) the set of wheels available to us, we canonicalize the links
immediately and consider them non-existent if that operation fails.

Closes #1085.

---------

Co-authored-by: konstin <konstin@mailbox.org>
2024-01-25 10:06:38 +01:00
Charlie Marsh 036b7e5f43
Use `parse_headers` rather than parsing body (#1090)
Looking at the internals, this should make almost no difference in
performance, but anyway...
2024-01-25 09:41:21 +01:00
Zanie Blue ed1ac640b9
Consolidate `UnusableDependencies` into a generic `Unavailable` incompatibility (#1088)
Requires https://github.com/zanieb/pubgrub/pull/20

In short, `UnusableDependencies` can be generalized into `Unavailable`
which encompasses incompatibilities where a package range which is
unusable for some inherent reason as well as when its dependencies are
unusable. We can eventually use this to track more incompatibilities in
the solver. I made the reason string required because I can't see a case
where we should leave it out.

Additionally, this improves the display of conflicts in the root
requirements.
2024-01-24 22:10:44 -06:00
Zanie Blue 091f8e09ff
Use a cache directory for venv tests (#1089) 2024-01-24 22:09:37 -06:00
konsti ed6a1606b9
Use `which::which` instead of `which::which_global` (#1083)
`which::which_global` does not resolve relative paths, which we want to
support, while `which::which` does.
2024-01-24 18:35:57 -06:00
Charlie Marsh cedd2e0b3f
Use a buffered reader for wheel metadata (#1082)
## Summary

It turns out this is significantly faster when reading (e.g.) _just_ the
`METADATA` file from a zipped wheel.

I audited other `File::open` usages, and everything else seems to be
using a buffered reader already (directly, or in whatever third-party
crate it's passed to) _or_ is read immediately in full.

See the criterion benchmark:

```
file_reader/numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
                        time:   [6.9618 ms 6.9664 ms 6.9713 ms]
Found 4 outliers among 100 measurements (4.00%)
  4 (4.00%) high mild
file_reader/flask-3.0.1-py3-none-any.whl
                        time:   [237.50 µs 238.25 µs 239.13 µs]
Found 7 outliers among 100 measurements (7.00%)
  3 (3.00%) high mild
  4 (4.00%) high severe

buffered_reader/numpy-1.26.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
                        time:   [648.92 µs 653.85 µs 660.09 µs]
Found 4 outliers among 100 measurements (4.00%)
  3 (3.00%) high mild
  1 (1.00%) high severe
buffered_reader/flask-3.0.1-py3-none-any.whl
                        time:   [39.578 µs 39.712 µs 39.869 µs]
Found 8 outliers among 100 measurements (8.00%)
  3 (3.00%) high mild
  5 (5.00%) high severe
```
2024-01-24 15:22:55 -05:00
Zanie Blue 0019fe71f6
Add warning when target version does not match build version (#1072)
Follow-up to https://github.com/astral-sh/puffin/pull/1040 adding a
user-facing warning when we cannot build with their requested version.

e.g.

```
❯ cargo run -- pip compile requirements.in --python-version 3.11.4 --no-build
Resolved 8 packages in 483ms
❯ cargo run -- pip compile requirements.in --python-version 3.11.4
warning: The requested Python version 3.11.4 is not available; 3.11.7 will be used to build dependencies instead.
Resolved 8 packages in 71ms
❯ cargo run -- pip compile requirements.in --python-version 3.11
Resolved 8 packages in 71ms
```
2024-01-24 13:42:19 -06:00
Charlie Marsh 738e8341e2
Use a consistent `Timestamp` struct (#1081)
## Summary

This PR uses `ctime` consistently on Unix as a more conservative
approach to change detection. It also ensures that our timestamp
abstraction is entirely internal, so we can change the representation
and logic easily across the codebase in the future.
2024-01-24 14:21:31 -05:00
Zanie Blue bdfabfb088
Fixup doc for `find_best` (#1079) 2024-01-24 12:55:01 -06:00
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
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](https://github.com/astral-sh/puffin/assets/1309177/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](https://github.com/astral-sh/puffin/assets/1309177/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](https://github.com/astral-sh/puffin/assets/1309177/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 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 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`
https://github.com/astral-sh/puffin/actions/runs/7616995716/job/20745019216
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 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 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
Zanie Blue 33b35f7020
Add support for disabling installation from pre-built wheels (#956)
Adds support for disabling installation from pre-built wheels i.e. the
package must be built from source locally.
We will still always use pre-built wheels for metadata during
resolution.

Available via `--no-binary` and `--no-binary-package <name>` flags in
`pip install` and `pip sync`. There is no flag for `pip compile` since
no installation happens there.

```
--no-binary

    Don't install pre-built wheels.
    
    When enabled, all installed packages will be installed from a source distribution. 
    The resolver will still use pre-built wheels for metadata.


--no-binary-package <NO_BINARY_PACKAGE>

    Don't install pre-built wheels for a specific package.
    
    When enabled, the specified packages will be installed from a source distribution. 
    The resolver will still use pre-built wheels for metadata.
```

When packages are already installed, the `--no-binary` flag will have no
affect without the `--reinstall` flag. In the future, I'd like to change
this by tracking if a local distribution is from a pre-built wheel or a
locally-built wheel. However, this is significantly more complex and
different than `pip`'s behavior so deferring for now.

For reference, `pip`'s flag works as follows:

```
--no-binary <format_control>

    Do not use binary packages. Can be supplied multiple times, and each time adds to the
    existing value. Accepts either ":all:" to disable all binary packages, ":none:" to empty the
    set (notice the colons), or one or more package names with commas between them (no colons).
    Note that some packages are tricky to compile and may fail to install when this option is
    used on them.
```

Note we are not matching the exact `pip` interface here because it seems
complicated to use. I think we may want to consider adjusting our
interface for this behavior since we're not entirely compatible anyway
e.g. I think `--force-build` and `--force-build-package` are clearer
names. We could also consider matching the `pip` interface or only
allowing `--no-binary <package>` for compatibility. We can of course do
whatever we want in our _own_ install interfaces later.

Additionally, we may want to further consider the semantics of
`--no-binary`. For example, if I run `pip install pydantic --no-binary`
I expect _just_ Pydantic to be installed without binaries but by default
we will build all of Pydantic's dependencies too.

This work was prompted by #895, as it is much easier to measure
performance gains from building source distributions if we have a flag
to ensure we actually build source distributions. Additionally, this is
a flag I have used frequently in production to debug packages that ship
Cythonized wheels.
2024-01-19 11:24:27 -06:00
Zanie Blue 8b49d900bd
Refer to the user instead of "root" when mentioning direct dependencies (#982)
Closes https://github.com/astral-sh/puffin/issues/857
2024-01-19 11:17:42 -06:00
Zanie Blue ae7a2cddc2
Avoid showing negations of ranges in error messages (#981)
Closes https://github.com/astral-sh/puffin/issues/980
2024-01-19 11:07:14 -06:00
Zanie Blue 02ed195982
Improve simple no version messages using complement of range (#979)
Improves some of the "no versions of <package> are available" messages
by showing the complement or inversion of the package.

Does not address cases like

```
Because there are no versions of crow that satisfy any of:
    crow>1.0.0,<2.0.0a5
    crow>2.0.0a7,<2.0.0b1
    crow>2.0.0b1,<2.0.0b5
...
```

which are a bit more complicated; I'll focus on those cases in a
follow-up.
2024-01-19 16:48:20 +00:00
Zanie Blue 7bb4fda8af
Say "depend on" instead of "depends on" when proper in error messages (#968)
I would like to spend some additional time working on the package range
display abstractions, but maybe that is best done _after_ I've done a
good bit of fiddling with the error messages.

Addresses
https://github.com/astral-sh/puffin/pull/868#discussion_r1447593081
2024-01-19 16:08:17 +00:00
Zanie Blue 5fe3444e5a
Use more realistic names in scenario snapshots (#978)
This is helpful to make the error messages more realistic and the names
are indisputably cuter.
2024-01-19 10:01:34 -06:00
Charlie Marsh 5adb08a304
Allow relative paths and environment variables in all editable representations (#1000)
## Summary

I don't know if this is actually a good change, but it tries to make the
editable install experience more consistent. Specifically, we now
support...

```
# Use a relative path with a `file://` prefix.
# Prior to this PR, we supported `file:../foo`, but not `file://../foo`, which felt inconsistent.
-e file://../foo

# Use environment variables with paths, not just URLs.
# Prior to this PR, we supported `file://${PROJECT_ROOT}/../foo`, but not the below.
-e ${PROJECT_ROOT}/../foo
```

Importantly, `-e file://../foo` is actually not supported by pip... `-e
file:../foo` _is_ supported though. We support both, as of this PR. Open
to feedback.
2024-01-19 09:00:37 -05:00
konsti cd2fb6fd60
Box `PrioritizedDistribution` (#948)
On top of https://github.com/astral-sh/puffin/pull/947, we can also box
`PrioritizedDistribution`.

In a simple benchmark, this seems to slightly improve performance when
comparing only this commit to main, even though the benchmark is too
noisy to establish significance:

```
$ hyperfine --warmup 30 --runs 300 "target/profiling/main-dev resolve meine_stadt_transparent" "target/profiling/puffin-dev resolve meine_stadt_transparent"
  Benchmark 1: target/profiling/main-dev resolve meine_stadt_transparent
    Time (mean ± σ):      83.6 ms ±   2.0 ms    [User: 77.7 ms, System: 20.0 ms]
    Range (min … max):    81.4 ms …  98.2 ms    300 runs

    Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

  Benchmark 2: target/profiling/puffin-dev resolve meine_stadt_transparent
    Time (mean ± σ):      80.8 ms ±   2.2 ms    [User: 75.4 ms, System: 19.5 ms]
    Range (min … max):    78.6 ms …  98.6 ms    300 runs

    Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

  Summary
    target/profiling/puffin-dev resolve meine_stadt_transparent ran
      1.03 ± 0.04 times faster than target/profiling/main-dev resolve meine_stadt_transparent
```

The effect on type sizes however is considerable ([downstack
PR](https://gist.github.com/konstin/38e6c774db541db46d61f1d4ea6b498f)
vs. [this
PR](https://gist.github.com/konstin/003a77fe7d7d246b0d535e3fc843cb36)):

```patch
--- branch.txt  2024-01-17 14:26:01.826085176 +0100
+++ boxed-prioritized-dist.txt  2024-01-17 14:25:57.101900963 +0100
@@ -1,19 +1,3 @@
-9264 alloc::collections::btree::node::InternalNode<pep440_rs::version::Version, distribution_types::PrioritizedDistribution> align=8
-   9168 data
-     96 edges
-
-9264 alloc::collections::btree::node::InternalNode<pep440_rs::Version, distribution_types::PrioritizedDistribution> align=8
-   9168 data
-     96 edges
-
-9168 alloc::collections::btree::node::LeafNode<pep440_rs::version::Version, distribution_types::PrioritizedDistribution> align=8
-   9064 vals
-     88 keys
-
-9168 alloc::collections::btree::node::LeafNode<pep440_rs::Version, distribution_types::PrioritizedDistribution> align=8
-   9064 vals
-     88 keys
-
 8992 tokio::sync::mpsc::block::Block<hyper::client::dispatch::Envelope<http::request::Request<reqwest::async_impl::body::ImplStream>, http::response::Response<hyper::body::body::Body>>> align=8
    8960 values
      32 header
@@ -74,10 +58,23 @@
          40 __tracing_attr_span
      64 variant Unresumed, Returned, Panicked

+5648 {async fn body@crates/puffin-client/src/registry_client.rs:224:5: 224:30} align=8
+   5647 variant Suspend0
+       5576 __awaitee align=8
+         40 __tracing_attr_span
```
2024-01-19 10:44:41 +01:00
konsti 47fc90d1b3
Reduce stack usage by boxing `File` in `Dist`, `CachePolicy` and large futures (#1004)
This is https://github.com/astral-sh/puffin/pull/947 again but this time
merging into main instead of downstack, sorry for the noise.

---

Windows has a default stack size of 1MB, which makes puffin often fail
with stack overflows. The PR reduces stack size by three changes:

* Boxing `File` in `Dist`, reducing the size from 496 to 240.
* Boxing the largest futures.
* Boxing `CachePolicy`

## Method

Debugging happened on linux using
https://github.com/astral-sh/puffin/pull/941 to limit the stack size to
1MB. Used ran the command below.

```
RUSTFLAGS=-Zprint-type-sizes cargo +nightly build -p puffin-cli -j 1 > type-sizes.txt && top-type-sizes -w -s -h 10 < type-sizes.txt > sizes.txt
```

The main drawback is top-type-sizes not saying what the `__awaitee` is,
so it requires manually looking up with a future with matching size.

When the `brotli` features on `reqwest` is active, a lot of brotli types
show up. Toggling this feature however seems to have no effect. I assume
they are false positives since the `brotli` crate has elaborate control
about allocation. The sizes are therefore shown with the feature off.

## Results

The largest future goes from 12208B to 6416B, the largest type
(`PrioritizedDistribution`, see also #948) from 17448B to 9264B. Full
diff: https://gist.github.com/konstin/62635c0d12110a616a1b2bfcde21304f

For the second commit, i iteratively boxed the largest file until the
tests passed, then with an 800KB stack limit looked through the
backtrace of a failing test and added some more boxing.

Quick benchmarking showed no difference:

```console
$ hyperfine --warmup 2 "target/profiling/main-dev resolve meine_stadt_transparent" "target/profiling/puffin-dev resolve meine_stadt_transparent" 
Benchmark 1: target/profiling/main-dev resolve meine_stadt_transparent
  Time (mean ± σ):      49.2 ms ±   3.0 ms    [User: 39.8 ms, System: 24.0 ms]
  Range (min … max):    46.6 ms …  63.0 ms    55 runs
 
  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
 
Benchmark 2: target/profiling/puffin-dev resolve meine_stadt_transparent
  Time (mean ± σ):      47.4 ms ±   3.2 ms    [User: 41.3 ms, System: 20.6 ms]
  Range (min … max):    44.6 ms …  60.5 ms    62 runs
 
  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
 
Summary
  target/profiling/puffin-dev resolve meine_stadt_transparent ran
    1.04 ± 0.09 times faster than target/profiling/main-dev resolve meine_stadt_transparent
```
2024-01-19 09:38:36 +00:00
konsti 66e651901e
Add an env var to artificially limit the stack size (#941)
By default, windows has a stack size limit of 1MB which we run against
in debug without any explicit culprit. A new environment variable
`PUFFIN_STACK_SIZE` allows setting an artificially smaller stack size.
2024-01-19 09:34:46 +00:00
Charlie Marsh 69c72b6fa1
Validate wheel metadata against filename (#1002)
Closes #983.
2024-01-19 05:48:55 +00:00
Charlie Marsh f86d9b1c31
Add tests for missing file errors (#1001) 2024-01-19 05:47:25 +00:00
Charlie Marsh c8285cb5ef
Bump version to v0.0.3 (#999) 2024-01-18 23:39:35 -05:00
Charlie Marsh 9b24fcd306
Remove verbatim URL from path file location (#998)
## Summary

I got confused by why `VerbatimUrl` was on `Path`. Since it's directly
computed from it, I think we should just compute it as-needed. I think
it's also possibly-buggy because the URL is the URL of the _directory_,
not the artifact itself, which differs from other distributions.
2024-01-18 22:40:48 -05:00
Charlie Marsh 732ef7adb7
Bump version to v0.0.2 (#987)
Bumping the version so that I can test the release process again
(including PyPI publish).
2024-01-18 20:56:09 -05:00
Charlie Marsh fe180804b5
Avoid encoding current version in test output (#988) 2024-01-19 01:50:23 +00:00
Charlie Marsh 3a1cd44fc6
Add Puffin Docker image (#985)
Missing piece for the release.

## Test Plan

Built the image locally:

```shell
❯ docker run 99956098e1f8f04e209dcfc4a0afcee67df1fe8a726c164884e67f035b1a0f42
Usage: puffin [OPTIONS] <COMMAND>

Commands:
  pip    Resolve and install Python packages
  venv   Create a virtual environment
  clean  Clear the cache
  help   Print this message or the help of the given subcommand(s)

Options:
  -q, --quiet                  Do not print any output
  -v, --verbose                Use verbose output
  -n, --no-cache               Avoid reading from or writing to the cache
      --cache-dir <CACHE_DIR>  Path to the cache directory [env: PUFFIN_CACHE_DIR=]
  -h, --help                   Print help
  -V, --version                Print version
```
2024-01-18 20:21:31 -05:00
Charlie Marsh 5e2b715366
Rename `puffin-cli` crate to `puffin` (#976)
## Summary

Like in Ruff, this simplifies a few things.
2024-01-18 19:02:52 -05:00
Charlie Marsh 6cad0f609c
Mark `puffin-dev` as `publish = false` (#975) 2024-01-18 17:20:44 -05:00
Charlie Marsh 8eadca4f8d
Remove unused path method (#974) 2024-01-18 21:59:12 +00:00
Charlie Marsh a262936366
Allow file:-relative paths in editable installs (#970)
Supports editable install via (e.g.) `puffin pip install -e file:.`,
which pip seems to support.

Closes #964.
2024-01-18 21:15:42 +00:00
Charlie Marsh f9154e8297
Add release workflow (#961)
## Summary

This PR adds a release workflow powered by `cargo-dist`. It's similar to
the version that's PR'd in Ruff
(https://github.com/astral-sh/ruff/pull/9559), with the exception that
it doesn't include the Docker build or the "update dependents" step for
pre-commit.
2024-01-18 15:44:11 -05:00
Charlie Marsh a883de4fb0
Enforce modification freshness checks against virtual environment (#959)
## Summary

This PR is like #957, but for validating the virtual environment, rather
than the cache. So, if you have a local wheel, and you rebuild it, we'll
now correctly uninstall and reinstall it in the virtual environment.
2024-01-18 20:21:16 +00:00
Charlie Marsh 96a61fb351
Remove RFC2047 decoder (#967)
## Summary

- This was inherited from
d719988323/src/metadata.rs (LL78C2-L91C26)
- ...which introduced this code here:
9cd1d43f7c
- ...with the originating issue here:
https://github.com/PyO3/maturin/issues/612
- ...and the upstream issue here:
https://github.com/staktrace/mailparse/issues/50

It seems like the goal was to support Unicode in certain header fields,
but I don't think this is necessary for us. We only use
`get_first_value` for `Requires-Python`, which has to be ASCII, doesn't
it?

In my testing, it seems like the `charset` hack can also be removed. The
tests I copied over actually work without it, which makes me a bit
skeptical.

The main benefit here is that we get to a remove a _big_ dependency
stack, including Chumsky and Stacker and psm which have limited
cross-platform support.
2024-01-18 15:09:45 -05:00
Charlie Marsh f17bad0a75
Mark path-based cache entries as stale during install plan (#957)
## Summary

This is a small correctness improvement that ensures that we avoid using
stale cache entries for local dependencies in the install plan. We
already have some logic like this in the source distribution builder,
but it didn't apply in the install plan, and so we'd end up using stale
wheels.

Specifically, now, if you create a new local wheel, and run `pip sync`,
we'll mark the cache entries as stale and make sure we unzip it and
install it. (If the wheel is _already_ installed, we won't reinstall it
though, which will be a separate change. This is just about reading from
the cache, not the environment.)
2024-01-18 19:13:29 +00:00
konsti a11744e438
Normalize base python in venv creation (#966)
Fixes #965

We have to canonicalize the interpreter path, otherwise the home is set
to the venv dir instead of the real root. This would make
python-build-standalone fail with the encodings module not being found
because its home is wrong.
2024-01-18 15:32:30 +00:00
konsti 7acde5a9a0
Fix `pep508_rs` doc test (#963)
Since nextest does not run doctests, this did not show up on CI.
2024-01-18 14:24:30 +00:00
konsti 5ec5a3243c
Set miette hook in all of puffin-cli (#962)
Fixes #938
2024-01-18 08:37:26 -05:00
Charlie Marsh 8ae8ddc7d9
Fix 3-to-2 reference in pip sync test (#958) 2024-01-18 04:33:46 +00:00
Charlie Marsh fbe70f4218
Split install plan into builder and struct (#955)
The `InstallPlan` does a lot of work in the constructor, which I tend to
feel is an anti-pattern. With cache refresh, it's also going to need to
be made `async`, so it really feels like it should be a clearer method
rather than an async, fallible constructor that does a bunch of IO. This
PR splits into a `Planner` (with a `build` method) and a `Plan`.
2024-01-17 15:28:46 -05:00
Charlie Marsh 055fd64eb1
Add an `--update-package` setting to allow individual package upgrades (#953)
Closes #950.
2024-01-17 14:31:52 -05:00
Zanie Blue a4204d00c1
Bump to latest packse version with "extras" scenarios (#935)
Includes:

- https://github.com/zanieb/packse/pull/83 (replaces some of the
post-processing here)
- https://github.com/zanieb/packse/pull/82
- https://github.com/zanieb/packse/pull/81
2024-01-17 13:25:48 -06:00
Charlie Marsh a0420114c3
Avoid storing absolute URLs for files (#944)
## Summary

It turns out that storing an absolute URL for every file caused a
significant performance regression. This PR attempts to address the
regression with two changes.

The first is that we now store the raw string if the URL is an absolute
URL. If the URL is relative, we store the base URL alongside the raw
relative string. As such, we avoid serializing and deserializing URLs
until we need them (later on), except for the base URL.

The second is that we now use the internal `Url` crate methods for
serializing and deserializing. If you look inside `Url`, its standard
serializer and deserialization actually convert it to a string, then
parse the string. But the crate exposes some other methods for faster
serialization and deserialization (with fewer guarantees). I think this
is totally fine since the cache is entirely internal.

If we _just_ change the `Url` serialization (and no other code -- so
continue to store URLs for every file), then the regression goes down to
about 5%:

```shell
❯ python -m scripts.bench \
        --puffin-path ./target/release/main \
        --puffin-path ./target/release/relative --puffin-path ./target/release/puffin \
        scripts/requirements/home-assistant.in --benchmark resolve-warm
Benchmark 1: ./target/release/main (resolve-warm)
  Time (mean ± σ):     496.3 ms ±   4.3 ms    [User: 452.4 ms, System: 175.5 ms]
  Range (min … max):   487.3 ms … 502.4 ms    10 runs

Benchmark 2: ./target/release/relative (resolve-warm)
  Time (mean ± σ):     284.8 ms ±   2.1 ms    [User: 245.8 ms, System: 165.6 ms]
  Range (min … max):   280.3 ms … 288.0 ms    10 runs

Benchmark 3: ./target/release/puffin (resolve-warm)
  Time (mean ± σ):     300.4 ms ±   3.2 ms    [User: 255.5 ms, System: 178.1 ms]
  Range (min … max):   295.4 ms … 305.1 ms    10 runs

Summary
  './target/release/relative (resolve-warm)' ran
    1.05 ± 0.01 times faster than './target/release/puffin (resolve-warm)'
    1.74 ± 0.02 times faster than './target/release/main (resolve-warm)'
```

So I considered _just_ making that change. But 5% is kind of
borderline...

With both of these changes, the regression is down to 1-2%:

```
Benchmark 1: ./target/release/relative (resolve-warm)
  Time (mean ± σ):     282.6 ms ±   7.4 ms    [User: 244.6 ms, System: 181.3 ms]
  Range (min … max):   275.1 ms … 318.5 ms    30 runs

Benchmark 2: ./target/release/puffin (resolve-warm)
  Time (mean ± σ):     286.8 ms ±   2.2 ms    [User: 247.0 ms, System: 169.1 ms]
  Range (min … max):   282.3 ms … 290.7 ms    30 runs

Summary
  './target/release/relative (resolve-warm)' ran
    1.01 ± 0.03 times faster than './target/release/puffin (resolve-warm)'
```

It's consistently ~2%-ish, but at this point it's unclear if that's due
to the URL change or something other change between now and then.

Closes #943.
2024-01-17 09:15:21 -05:00
Charlie Marsh b8fbd529a1
Move `OnceMap` into its own crate (#946)
## Summary

This is extremely generic (like `WaitMap`), and I want to use it in the
cache.
2024-01-17 04:09:15 +00:00
konsti 5051b2c004
Use tempfile to prevent install io race crashes (#929)
On ubuntu and python 3.10,

```
cargo run -q -- pip-install --find-links https://storage.googleapis.com/jax-releases/jax_cuda_releases.html "jax[cuda12_pip]==0.4.23"
```

non-deterministically but for me consistently fails to install with
messages such as

```
error: Failed to install: nvidia_nccl_cu12-2.19.3-py3-none-manylinux1_x86_64.whl (nvidia-nccl-cu12==2.19.3)
  Caused by: failed to remove file `/home/konsti/projects/puffin/.venv/lib/python3.10/site-packages/nvidia/__init__.py`
  Caused by: No such file or directory (os error 2)
```

```
error: Failed to install: nvidia_cublas_cu12-12.3.4.1-py3-none-manylinux1_x86_64.whl (nvidia-cublas-cu12==12.3.4.1)
  Caused by: Replacing an existing file or directory failed
```

```
error: Failed to install: nvidia_cuda_nvcc_cu12-12.3.107-py3-none-manylinux1_x86_64.whl (nvidia-cuda-nvcc-cu12==12.3.107)
  Caused by: failed to hardlink file from /home/konsti/.cache/puffin/wheels-v0/pypi/nvidia-cuda-nvcc-cu12/nvidia_cuda_nvcc_cu12-12.3.107-py3-none-manylinux1_x86_64/nvidia/__init__.py to /home/konsti/projects/puffin/.venv/lib/python3.10/site-packages/nvidia/__init__.py
  Caused by: File exists (os error 17)
```

We install a lot of nvidia package, that all contain
`nvidia/__init__.py`, since they all install themselves into the
`nvidia` module:

```
nvidia-cublas-cu12==12.3.4.1
nvidia-cuda-cupti-cu12==12.3.101
nvidia-cuda-nvcc-cu12==12.3.107
nvidia-cuda-nvrtc-cu12==12.3.107
nvidia-cuda-runtime-cu12==12.3.101
nvidia-cudnn-cu12==8.9.7.29
nvidia-cufft-cu12==11.0.12.1
nvidia-cusolver-cu12==11.5.4.101
nvidia-cusparse-cu12==12.2.0.103
nvidia-nccl-cu12==2.19.3
nvidia-nvjitlink-cu12==12.3.101
```

```
$  tree -L 1 .venv/lib/python3.10/site-packages/nvidia
.venv/lib/python3.10/site-packages/nvidia
├── cublas
├── cuda_cupti
├── cuda_nvcc
├── cuda_nvrtc
├── cuda_runtime
├── cudnn
├── cufft
├── cusolver
├── cusparse
├── __init__.py
├── nccl
└── nvjitlink
```

When installing we get a race condition, each package installation is
its own thread:
* Installer Thread 1 creates `nvidia/__init__.py`
* Installer Thread 2 sees an existing  `nvidia/__init__.py`
* Installer Thread 3 sees an existing  `nvidia/__init__.py`
* Installer Thread 2 removes `nvidia/__init__.py`
* Installer Thread 3 tries to remove `nvidia/__init__.py`, it doesn't
exist anymore -> failure.

We switch to a new strategy: When the target files exists, we don't
remove it, but instead hardlink the source file to a tempfile first,
then renaming the tempfile to the target file. Renaming is considered an
atomic operation.

I've put the logging on debug level because they cases indicate a
conflict between two packages while being rare.

Closes #925

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2024-01-16 21:07:39 +00:00
Charlie Marsh b50e5fcbc5
Fetch `--find-links` indexes in parallel (#934)
## Summary

Removes a TODO.

## Test Plan

Tested manually with:

```shell
cargo run -p puffin-cli -- \
    pip compile requirements.in -n \
    --find-links 'https://download.pytorch.org/whl/torch_stable.html' \
    --find-links 'https://storage.googleapis.com/jax-releases/jax_cuda_releases.html' \
    --verbose
```

And inspecting the logs to ensure that the two requests were kicked off
concrurently.
2024-01-16 11:37:35 +01:00
Charlie Marsh 2f8f126f2f
Share a single `Index` across resolutions (#906)
## Summary

This PR uses a single `Index` that's shared between the top-level
resolver and any sub-resolutions happen in the course of that top-level
resolution (namely, to resolve build dependencies for any source
distributions).

In theory it's an optimization, since (e.g.) if we have two packages
that both need the `flit-core` build system, and we attempt to build
them both at once, we'll only fetch its metadata _once_, and share it
across the two resolutions. In practice, I haven't been able to get this
to show up in benchmarks. I suspect you'd need a _lot_ of source
distributions for it to matter... Though it may still be worth doing, it
strikes me as a cleaner design.

Closes #200.

Closes #541.
2024-01-16 05:37:15 +00:00
Charlie Marsh 0f592b67bb
Remove clone from `RegistryWheelIndex` (#937)
Doesn't need to own the package names.
2024-01-15 16:18:12 -05:00
Charlie Marsh 2a69b273ce
Use a standalone error type for `--find-links` registry (#936) 2024-01-15 19:48:48 +00:00
Charlie Marsh e71e3e8dd1
Refresh `BuildDispatch` when running pip install with `--reinstall` (#933)
## Summary

This fixes an extremely subtle bug in `pip install --reinstall`, whereby
if you depend on `setuptools` at the top level, we end up uninstalling
it after resolving, which breaks some cached state. If we have
`--reinstall`, we need to reset that cached state between resolving and
installing.

## Test Plan

Running `pip install --reinstall` with:

```txt
setuptools
devpi @ e334eb4dc9bb023329e4b610e4515b/devpi-2.2.0.tar.gz
```

Fails on `main`, but passes.
2024-01-15 18:56:18 +00:00
Charlie Marsh 116da6b7de
Share in-flight map across resolutions (#932)
## Summary

This PR fixes a subtle bug in `pip install` when using `--reinstall`. If
a package depends on a build system directly (e.g., `waitress` depends
on `setuptools`), and then you have other packages that also need the
build system to build a source distribution, right now, we don't share
the `OnceMap` between those cases.

This lifts the `InFlight` tracking up a level, so that it's initialized
once per command, then shared everywhere.

## Test Plan

I'm having trouble coming up with an identical test-case and hesitant to
add this slow test to the suite... But if you run `pip install
--reinstall` with:

```
waitress @ git+https://github.com/zanieb/waitress
devpi-server @ git+https://github.com/zanieb/devpi#subdirectory=server
```

It fails consistently on `main` and passes here.
2024-01-15 13:11:22 -05:00
Charlie Marsh 249ca10765
Move Puffin subcommands to a pip namespace (#921)
## Summary

This makes the separation clearer between the legacy `pip` API and the
API we'll add in the future for the package manager itself. It also
enables seamless `puffin pip` aliasing for those that want it.

Closes #918.
2024-01-15 16:36:45 +00:00
Charlie Marsh e54fdea93f
Continue to respect `--find-links` with `--no-index` (#931)
Like `pip`, we should allow `--find-links` with `--no-index`.
2024-01-15 16:19:27 +00:00
Charlie Marsh 42888a9609
Share flat index across resolutions (#930)
## Summary

This PR restructures the flat index fetching in a few ways:

1. It now lives in its own `FlatIndexClient`, since it felt a bit
awkward (in my opinion) for it to live in `RegistryClient`.
2. We now fetch the `FlatIndex` outside of the resolver. This has a few
benefits: (1) the resolver construct is no longer `async` and no longer
returns `Result`, which feels better for a resolver; and (2) we can
share the `FlatIndex` across resolutions rather than re-fetching it for
every source distribution build.
2024-01-15 11:02:02 -05:00
Charlie Marsh e6d7124147
Add an extra struct around the package-to-flat index map (#923)
## Summary

`FlatIndex` is now the thing that's keyed on `PackageName`, while
`FlatDistributions` is what used to be called `FlatIndex` (a map from
version to `PrioritizedDistribution`, for a single package). I find this
a bit clearer, since we can also remove the `from_files` that doesn't
return `Self`, which I had trouble following.
2024-01-15 14:48:10 +00:00
Charlie Marsh 9a3f3d385c
Remove `PubGrubVersion` (#924)
## Summary

I'm running into some annoyances converting `&Version` to
`&PubGrubVersion` (which is just a wrapper type around `Version`), and I
realized... We don't even need `PubGrubVersion`?

The reason we "need" it today is due to the orphan trait rule: `Version`
is defined in `pep440_rs`, but we want to `impl
pubgrub::version::Version for Version` in the resolver crate.

Instead of introducing a new type here, which leads to a lot of
awkwardness around conversion and API isolation, what if we instead just
implement `pubgrub::version::Version` in `pep440_rs` via a feature? That
way, we can just use `Version` everywhere without any confusion and
conversion for the wrapper type.
2024-01-15 08:51:12 -05:00
konsti 8860a9c29e
Add flat index urls to registry wheel index (#928)
Previously, we were missing flat index wheels in the cache.
2024-01-15 10:21:59 +00:00
konsti 95f3cca28d
Use fs_err in more places (#926)
Before:

```
error: Failed to download distributions
  Caused by: Failed to fetch wheel: jaxlib==0.4.23+cuda12.cudnn89
  Caused by: Directory not empty (os error 39)
```

After:

```
error: Failed to download distributions
  Caused by: Failed to fetch wheel: jaxlib==0.4.23+cuda12.cudnn89
  Caused by: failed to rename file from /home/konsti/.cache/puffin/.tmpcG7tVP/jaxlib-0.4.23+cuda12.cudnn89-cp310-cp310-manylinux2014_x86_64.whl to /home/konsti/.cache/puffin/wheels-v0/index/9ff50b883297fa9d/jaxlib/jaxlib-0.4.23+cuda12.cudnn89-cp310-cp310-manylinux2014_x86_64
  Caused by: Directory not empty (os error 39)
```
2024-01-15 09:39:33 +00:00
konsti 82ff136a74
Add find links supports to pip-sync (#914)
Closes #877
2024-01-15 03:04:55 +00:00
konsti f63776b894
Support HTML indexes in `--find-links` (#913)
The simple html format parser luckily seems to work for find links too,
at least it can parse
https://storage.googleapis.com/jax-releases/jax_cuda_releases.html.
2024-01-15 02:54:34 +00:00
konsti e9b6b6fa36
Implement `--find-links` as flat indexes (directories in pip-compile) (#912)
Add directory `--find-links` support for local paths to pip-compile.

It seems that pip joins all sources and then picks the best package. We
explicitly give find links packages precedence if the same exists on an
index and locally by prefilling the `VersionMap`, otherwise they are
added as another index and the existing rules of precedence apply.

Internally, the feature is called _flat index_, which is more meaningful
than _find links_: We're not looking for links, we're picking up local
directories, and (TBD) support another index format that's just a flat
list of files instead of a nested index.

`RegistryBuiltDist` and `RegistrySourceDist` now use `WheelFilename` and
`SourceDistFilename` respectively. The `File` inside `RegistryBuiltDist`
and `RegistrySourceDist` gained the ability to represent both a url and
a path so that `--find-links` with a url and with a path works the same,
both being locked as `<package_name>@<version>` instead of
`<package_name> @ <url>`. (This is more of a detail, this PR in general
still work if we strip that and have directory find links represented as
`<package_name> @ file:///path/to/file.ext`)

`PrioritizedDistribution` and `FlatIndex` have been moved to locations
where we can use them in the upstack PR.

I added a `scripts/wheels` directory with stripped down wheels to use
for testing.

We're lacking tests for correct tag priority precedence with flat
indexes, i only confirmed this manually since it is not covered in the
pip-compile or pip-sync output.

Closes #876
2024-01-15 02:04:10 +00:00
konsti 5ffbfadf66
Make hashes optional (#910)
There is no guarantee that indexes provide hashes at all or the sha256
we support specifically. [PEP
503](https://peps.python.org/pep-0503/#specification):

> The URL SHOULD include a hash in the form of a URL fragment with the
following syntax: #<hashname>=<hashvalue>, where <hashname> is the
lowercase name of the hash function (such as sha256) and <hashvalue> is
the hex encoded digest.

We instead use the url as input to generate a hash when caching.
2024-01-14 16:32:55 -05:00
Zanie Blue 9ad19b7e54
Bump to the latest packse version (#916) 2024-01-14 12:49:23 -06:00
konsti a53bdeba4c
Remove `base` from `RegistryBuiltDist` and `RegistrySourceDist` (#919)
Follow-up to https://github.com/astral-sh/puffin/pull/917 i found
rebasing the find-links PRs, this field became unused through the
absolute URLs.
2024-01-14 17:46:16 +00:00
Charlie Marsh 0374000ec0
Normalize extras when evaluating PEP 508 markers (#915)
## Summary

We always normalize extra names in our requirements (e.g., `cuda12_pip`
to `cuda12-pip`), but we weren't normalizing within PEP 508 markers,
which meant we ended up comparing `cuda12-pip` (normalized) against
`cuda12_pip` (unnormalized).

Closes https://github.com/astral-sh/puffin/issues/911.
2024-01-14 17:16:54 +00:00
konsti a99e5e00f2
Use absolute urls in `distribution_type::File` (#917)
Previously, the url on file could either be a relative or an absolute
url, depending on the index, and we would finalize it lazily. Now we
finalize the url when converting `pypi_types::File` to
`distribution_types::File`. This change is required to make the hashes
on `File` optional (https://github.com/astral-sh/puffin/pull/910), which
are currently the only unique field usable for caching.
2024-01-14 17:15:24 +00:00
Charlie Marsh 6e18e56789
Adjust markers to match target Python version (#909)
## Summary

This PR ensures that when the user passes in `--python-version`, we
adjust the _markers_ to match the target version, thus forcing us to
select compatible wheels for the `--python-version`, rather than the
installed version.

## Context

Let's call Python 3.10 the "installed" environment and Python 3.12 the
"target" environment. For each version, we have _both_ a Python version
(to match against `Requires-Python`) and a set of tags (to match against
wheels).

The rules for resolution are as follows...

- For each package, for each version, we try to find the "best
candidate" for resolution and installation.
- We first look for a wheel that's compatible with the _target_
environment. This requires testing against both the `Requires-Python`
and the markers. (We won't have to build or run this code, so the
_installed_ version is irrelevant.) **(This PR corrects _this_ bullet --
previously, we validated against the _installed_ markers, rather than
the target markers.)**
- If we can't find a compatible wheel, we accept any _incompatible_
wheel as long as there's a source distribution. The source distribution
_must_ be compatible with the target environment. (We won't have to
build or run this code, so the _installed_ version is irrelevant.)
- If there are no wheels, then the source distribution must be
compatible with _both_ the installed and target environments, since we
need to build it.

This is all true for the top-level resolution. When we perform a
sub-resolution (when resolving the build dependencies of a source
distribution), we should _only_ use the installed environment, and
ignore the target environment, since we assume that the dependencies
will be the same in both environments once built -- so our goal is
"just" to build the distribution, without concern for which build
dependencies it uses.

Closes https://github.com/astral-sh/puffin/issues/883.
2024-01-14 15:39:15 +00:00
Charlie Marsh 8187c05d8a
Use `DashMap` for redirects (#908)
## Summary

We don't need to wait on these, so it's simpler to use a standard
concurrent hash map.
2024-01-13 20:36:02 +00:00
Charlie Marsh f527f2add9
Remove erroneous local `Index` in resolver (#907) 2024-01-13 15:19:00 -05:00
Charlie Marsh 231686e71b
Remove `incompatibilities` from index (#905)
This isn't really part of the "index", it's part of the resolution.
2024-01-13 02:57:15 +00:00
Charlie Marsh 477186dcb3
Remove `ResolutionGraph#requirements` (#903) 2024-01-12 20:09:19 +00:00
Charlie Marsh d3f65c317d
Avoid some additional clones for `PackageName` (#896) 2024-01-12 17:54:40 +00:00
konsti aee6aed684
Make install_editable test faster (#901)
Remove a test case from the `install_editable` that slows it down from
3.6s to 6.5s while providing low test coverage. It also seems to block
other tests sometimes, `cargo nextest run -E "test(editable)"
--all-features` has more consistent and lower runtimes. Surprisingly
this seems to have bigger effect than switching from pyo3 to cffi.

Used test commands:
```
rm -rf scripts/editable-installs/maturin_editable/target/ && time cargo nextest run -E "test(=install_editable)" --all-features
rm -rf scripts/editable-installs/maturin_editable/target/ && time cargo nextest run -E "test(editable)" --all-features
 ```

Part of #878
2024-01-12 18:50:27 +01:00
konsti 878bc4bf8d
Stub out DTLSsocket test (#900)
Replace the DTLSsocket test with a dummy package that does nothing but
contain the build system specs that we need. This should speed up one of
the slowest tests.

Part of #878
2024-01-12 18:50:16 +01:00
Charlie Marsh 06039e1293
Add hashes to `pip-compile` output (#894)
## Summary

Adds hashes to `pip-compile` output, though we don't actually check
those hashes in `pip-sync` yet.

Closes https://github.com/astral-sh/puffin/issues/131.
2024-01-12 12:44:19 -05:00
konsti 0cc98c771e
Fix a tracing panic (#899) 2024-01-12 14:47:58 +00:00
Charlie Marsh 11b11d04a7
Ignore installed version when determining wheel compatibility (#890) 2024-01-12 08:57:00 -05:00
Charlie Marsh 5fd2c380a7
Add `into_cached_dist` to `LocalWheel` (#893)
Simplifies `unzip_wheel` a bit and avoids unnecessarily cloning in the
common case.
2024-01-12 09:01:30 +00:00
Charlie Marsh 35c1faa575
Move in-flight tracking to the download level (#892)
## Summary

Now that `get_or_build_wheel` will often _also_ handle the unzip step,
we need to move our per-target locking (`OnceMap`) up a level.
Previously, it was only applied to the unzip step, to prevent us from
attempting to unzip into the same target concurrently; now, it's applied
at the `get_wheel` level, which includes both downloading and unzipping.

## Test Plan

It seems like none of our existing tests catch this -- perhaps because
they're too "simple"? You need to run into a situation in which you're
doing multiple source distribution builds concurrently (since they'll
all try to download `setuptools`):

```
rm -rf foo && virtualenv --clear .venv && cargo run -p puffin-cli -- pip-compile ./scripts/requirements/pydantic.in  --verbose --cache-dir foo
```
2024-01-12 09:52:22 +01:00
Charlie Marsh 60cea0f07d
Use consistent parse terminology in pyproject error (#891)
We use `parse` for the other file types.
2024-01-11 21:25:47 -05:00
bojanserafimov 4c047f858f
Remove InMemoryWheel and dead code (#879) 2024-01-11 10:11:07 -05:00
bojanserafimov 10227a74f8
Unzip while downloading (#856) 2024-01-11 09:41:46 -05:00
konsti 0dfbddd275
Shorten resolve many dev output (#885) 2024-01-11 13:53:13 +00:00
konsti 8c2b7d55af
Cleanup deps and docs (#882)
Fix warnings from `cargo +nightly udeps` and `cargo doc`.

Removes all mentions of regex from pep440_rs.
2024-01-11 10:43:40 +00:00
Zanie Blue d6fa628e11
Fix failing test (#880) 2024-01-11 00:41:37 +00:00
Zanie Blue 811332eacc
Improve handling of "full" version ranges (#868)
Reduces the number of implementation branches handling `Range:full`,
deferring it to `PackageRange`.
Improves some user-facing messages, e.g. saying `all versions of
<package>` instead of `<package>*`.
Changes the member names of the `PackageRangeKind` enum — they were not
very clear.
2024-01-10 21:03:55 +00:00
Zanie Blue a65c55ff4a
Say "cannot be used" and "must be used" instead of "forbidden" and "mandatory" (#867)
Closes #858
2024-01-10 20:49:40 +00:00
Zanie Blue 845ba6801d
Improve formatting of incompatible terms when there are two items (#866) 2024-01-10 20:36:54 +00:00
Zanie Blue 93d3093a2a
Improve formatting of package ranges in error messages (#864)
Closes #810
Closes https://github.com/astral-sh/puffin/issues/812
Requires https://github.com/zanieb/pubgrub/pull/19 and
https://github.com/zanieb/pubgrub/pull/18

- Always pair package ranges with names e.g. `... of a matching a<1.0`
instead of `... of a matching <1.0`
- Split range segments onto multiple lines when not a singleton as
suggested in
[#850](https://github.com/astral-sh/puffin/pull/850#discussion_r1446419610)
- Improve formatting when ranges are split across multiple lines e.g. by
avoiding extra spaces and improving wording

Note review will require expanding the hidden files as there are
significant changes to the report formatter and snapshots.

Bear with me here as these are definitely not perfect still.

The following changes build on top of this independently for further
improvements:
- #868 
- #867 
- #866 
- #871
2024-01-10 14:16:23 -06:00
konsti 4d8bfd7f61
Split source dist error type into error and kind (#872)
It's a better, less redundant error type. It will come in handy when
adding a second parse function.
2024-01-10 17:42:54 +00:00
Charlie Marsh fbb57b24dd
Add `--seed` flag to `venv` to allow seed package environments (#865)
## Summary

Installs the seed packages you get with `virtualenv`, but opt-in rather
than opt-out.

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

## Test Plan

```
❯ ./scripts/benchmarks/venv.sh
+ hyperfine --runs 20 --warmup 3 --prepare 'rm -rf .venv' './target/release/puffin venv' --prepare 'rm -rf .venv' 'virtualenv --without-pip .venv' --prepare 'rm -rf .venv' 'python -m venv --without-pip .venv'
Benchmark 1: ./target/release/puffin venv
  Time (mean ± σ):       4.6 ms ±   0.2 ms    [User: 2.4 ms, System: 3.6 ms]
  Range (min … max):     4.3 ms …   4.9 ms    20 runs

  Warning: Command took less than 5 ms to complete. Note that the results might be inaccurate because hyperfine can not calibrate the shell startup time much more precise than this limit. You can try to use the `-N`/`--shell=none` option to disable the shell completely.

Benchmark 2: virtualenv --without-pip .venv
  Time (mean ± σ):      73.3 ms ±   0.3 ms    [User: 57.4 ms, System: 14.2 ms]
  Range (min … max):    72.8 ms …  74.0 ms    20 runs

Benchmark 3: python -m venv --without-pip .venv
  Time (mean ± σ):      22.5 ms ±   0.3 ms    [User: 17.0 ms, System: 4.9 ms]
  Range (min … max):    22.0 ms …  23.2 ms    20 runs

Summary
  './target/release/puffin venv' ran
    4.92 ± 0.20 times faster than 'python -m venv --without-pip .venv'
   16.00 ± 0.63 times faster than 'virtualenv --without-pip .venv'
+ hyperfine --runs 20 --warmup 3 --prepare 'rm -rf .venv' './target/release/puffin venv --seed' --prepare 'rm -rf .venv' 'virtualenv .venv' --prepare 'rm -rf .venv' 'python -m venv .venv'
Benchmark 1: ./target/release/puffin venv --seed
  Time (mean ± σ):      20.2 ms ±   0.4 ms    [User: 8.6 ms, System: 15.7 ms]
  Range (min … max):    19.7 ms …  21.2 ms    20 runs

Benchmark 2: virtualenv .venv
  Time (mean ± σ):     135.1 ms ±   2.4 ms    [User: 66.7 ms, System: 65.7 ms]
  Range (min … max):   133.2 ms … 142.8 ms    20 runs

Benchmark 3: python -m venv .venv
  Time (mean ± σ):      1.656 s ±  0.014 s    [User: 1.447 s, System: 0.186 s]
  Range (min … max):    1.641 s …  1.697 s    20 runs

Summary
  './target/release/puffin venv --seed' ran
    6.67 ± 0.17 times faster than 'virtualenv .venv'
   81.79 ± 1.70 times faster than 'python -m venv .venv'
```
2024-01-09 20:45:56 -05:00
Charlie Marsh 55f2be72e2
Default to PEP 517-based builds (#843)
## Summary

Our current setup uses the legacy `setup.py`-based builds if a
`pyproject.toml` file isn't present. This matches pip's behavior.
However, `pypa/build` uses PEP 517-based builds in such cases, and it
looks like pip plans to make that the default
(https://github.com/pypa/pip/issues/9175), with the limiting factor
being performance issues related to isolated builds.

This is now the default behavior, but the `--legacy-setup-py` flag
allows users to opt-in to using `setup.py` directly for distributions
that lack a `pyproject.toml`.
2024-01-10 01:27:06 +00:00
Charlie Marsh e26dc8e33d
Add support for `prepare_metadata_for_build_wheel` (#842)
## Summary

This PR adds support for `prepare_metadata_for_build_wheel`, which
allows us to determine source distribution metadata without building the
source distribution. This represents an optimization for the resolver,
as we can skip the expensive build phase for build backends that support
it.

For reference, `prepare_metadata_for_build_wheel` seems to be supported
by:

- `hatchling` (as of
[1.0.9](https://hatch.pypa.io/latest/history/hatchling/#hatchling-v1.9.0)).
- `flit`
- `setuptools`

In fact, it seems to work for every backend _except_ those using legacy
`setup.py`.

Closes #599.
2024-01-10 00:07:37 +00:00
konsti 858d5584cc
Use `Dist` in `VersionMap` (#851)
Refactoring split out from find links support: Find links files can be
represented as `Dist`, but not really as `File`, they don't have url nor
hashes.

`DistRequiresPython` is somewhat odd as an in between type.
2024-01-10 00:14:42 +01:00
konsti 1203f8f9e8
Gourgeist updates (#862)
* Use caching again
* Make clap feature only required for the cli/bin optional
2024-01-09 23:04:15 +00:00
Zanie Blue 34d548de21
Improve error messages when there are no versions of a singleton range (#855) 2024-01-09 15:09:52 -06:00
Charlie Marsh 33982efb25
Remove a TOCTOU read in build (#860)
We should just read and handle the not-found case, rather than checking
if the file doesn't exist first.
2024-01-09 20:33:08 +00:00
Charlie Marsh 31139aa88d
Add derive feature to `gourgeist` (#854)
Needed to build `gourgeist` directly, probably dropped during a
refactor.
2024-01-09 17:46:16 +00:00
konsti ee6d809b60
Remove unused `Result` (#849)
Remove some dead code, seems to be a refactoring oversight
2024-01-09 16:35:10 +00:00
konsti 643e5e4a49
Use pdm for black editable as PEP 621 test case (#848)
This gives us a PEP 621 test package in tree and increases the diversity
for the editable tests a bit.
2024-01-09 16:33:05 +00:00
konsti 5b0b072e3c
Allow files >4GB on 32-bit platforms (#847)
Changes `File::size` from a `usize` to a `u64`.

The motivations are that with tensorflow wheels being 475 MB
(https://pypi.org/project/tensorflow/2.15.0.post1/#files), we're already
only one order of magnitude away and to avoid target dependent failures.
2024-01-09 17:31:49 +01:00
Charlie Marsh ee3a6431c7
Show available pre-releases in error hints (#844)
## Summary

If pre-releases are available for a package that we otherwise couldn't
resolve, we now show a hint that includes one of the example versions.

Closes https://github.com/astral-sh/puffin/issues/811.
2024-01-09 09:58:38 -05:00
konsti b1edecdf1f
Filter out files with invalid requires python specifiers (#775)
Instead of trying to fixup _all_ the invalid version specifiers on pypi
and elsewhere, this filters out distributions with invalid
`requires-python` version specifiers that even
`LenientVersionSpecifiers` couldn't parse, as opposed to failing
entirely, which we currently do.

I would be nicer to model through an invalid distribution pubgrub type,
together with e.g. source dists with an unknown extension, so that the
version itself still shows up in the error trace.

At the same time, we reduce the log level for fixups from warning to
trace, as they are not actionable for the user.
2024-01-09 02:46:27 +00:00
Zanie Blue 64da1f0306
Always pair package names with ranges in error messages (#838)
Adjusts display of "no versions available" in error messages to be
consistent with other package/range pairings i.e. we usually display
"<package-name><range>".
2024-01-08 22:11:10 +00:00
Charlie Marsh 19c6d655b5
Avoid duplicated source distribution handling in url (#841)
## Summary

Right now, both the callback _and_ the "We have no compatible wheel"
paths have a lot of repeated code. This PR changes the callback to
_just_ remove all the wheels and handle the download, and the rest of
the method following the callback is responsible for finding and
building any wheels.
2024-01-08 16:19:54 -05:00
Charlie Marsh cc9140643e
Rename `metadata` to `built_wheel` in `source/mod.rs` (#840) 2024-01-08 19:20:20 +00:00
Charlie Marsh df254087d9
Break `source_dist.rs` into a module (#839)
## Summary

Finding this file hard to edit and work in since it's gotten quite
large.
2024-01-08 19:14:45 +00:00
Zanie Blue 2b0c2e294b
Fix formatting of negated singleton versions in error messages (#836)
Closes #805 
Requires https://github.com/zanieb/pubgrub/pull/17
2024-01-08 12:33:01 -06:00
Charlie Marsh aeefe65227
Fix `tracing-duration-export` compilation (#835)
## Summary

I'm unable to run `puffin-cli` on `main` as the
`tracing-durations-export` is marked as optional, but the crate actually
depends on it to compile. Further, without `tracing-durations-export`,
there are `Option` types that can't resolve to a concrete type.

This PR fixes compilation with and without the feature.
2024-01-08 18:04:23 +00:00
Charlie Marsh c06bf658bb
Remove some filesystem calls from the installer (#834)
Noticed these when working on something unrelated. Generally:

- Prefer `entry.file_type()` over `entry.path().is_file()` or similar,
as the former is almost always free on Unix.
- Call `entry.path()` once, since it allocates internally (returns a
`PathBuf`).
2024-01-08 12:59:01 -05:00
konsti 004147d441
Add tracing_durations_export feature to puffin-cli (#830)
The optional `tracing-durations-export` feature allows creating
parallelism plots from all puffin-cli commands without affecting
production builds.

Usage:

```
virtualenv --clear -p 3.10 .venv310 && TRACING_DURATIONS_FILE=target/traces/jupyter-no-cache.ndjson RUST_LOG=puffin=info VIRTUAL_ENV=.venv310 cargo run --bin puffin --profile profiling --features tracing-durations-export -- pip-install -v --no-cache jupyter
virtualenv --clear -p 3.10 .venv310 && TRACING_DURATIONS_FILE=target/traces/jupyter.ndjson RUST_LOG=puffin=info VIRTUAL_ENV=.venv310 cargo run --bin puffin --profile profiling --features tracing-durations-export -- pip-install -v jupyter
 ```

Output, plotted in collapsed mode for readability:

Cached jupyter:

![jupyter](https://github.com/astral-sh/puffin/assets/6826232/f7e03c68-0438-4cf4-bceb-9a4a146cc506)

Uncached jupyter:

![image](https://github.com/astral-sh/puffin/assets/6826232/cfdd3383-7a9d-43d6-b8d0-201f64611596)
2024-01-08 16:20:45 +01:00
konsti b6338b5e4a
Use tracing-durations-export to visualize parallelism bottlenecks (dev commands) (#816)
Example usage:

```
# Cached
TRACING_DURATIONS_FILE=target/traces/black.ndjson RUST_LOG=puffin=info cargo run --bin puffin-dev --profile profiling -- resolve black
TRACING_DURATIONS_FILE=target/traces/meine_stadt_transparent.ndjson RUST_LOG=puffin=info cargo run --bin puffin-dev --profile profiling -- resolve meine_stadt_transparent
TRACING_DURATIONS_FILE=target/traces/jupyter.ndjson RUST_LOG=puffin=info cargo run --bin puffin-dev --profile profiling -- resolve jupyter

# No cache
TRACING_DURATIONS_FILE=target/traces/black-no-cache.ndjson RUST_LOG=puffin=info cargo run --bin puffin-dev --profile profiling -- resolve --no-cache black
TRACING_DURATIONS_FILE=target/traces/meine_stadt_transparent-no-cache.ndjson RUST_LOG=puffin=info cargo run --bin puffin-dev --profile profiling -- resolve --no-cache meine_stadt_transparent
TRACING_DURATIONS_FILE=target/traces/jupyter-no-cache.ndjson RUST_LOG=puffin=info cargo run --bin puffin-dev --profile profiling -- resolve --no-cache jupyter
```

Uncached black output example:


![black-no-cache](https://github.com/astral-sh/puffin/assets/6826232/38497b89-7214-453b-9456-c9d9cbf7d2d5)
2024-01-08 16:20:38 +01:00
konsti 243392f718
`cargo run` run `puffin` by default (#831)
`cargo run` now runs `puffin` by default. `cargo run --bin puffin-dev`
remains working.
2024-01-08 12:49:06 +00:00
konsti 3f587156ec
Improve install instrumentation (#829)
Add tracing spans to different phases of the wheel installation.
2024-01-08 10:13:59 +00:00
konsti 60ba7dd14f
Use `std::io::read_to_string` (#826)
The `std::io::read_to_string` shorthand was stabilized in 1.65.
2024-01-08 09:15:38 +00:00
Charlie Marsh 54838914be
Migrate back to `owo-colors` (#824)
In the past, I moved us to `owo-colors`
(https://github.com/astral-sh/puffin/pull/121); then, we moved back,
because we ran into issues with overriding the settings to force-disable
colors. But `anstream` solved those problems, so I'm moving us _back_ to
`owo-colors`, since it's what `anstream` recommends, and it's already
used by many of our dependencies (`miette`, `configparser`).

---------

Co-authored-by: konstin <konstin@mailbox.org>
2024-01-08 08:54:57 +00:00
Charlie Marsh 17452e3e64
Simplify ranges in pre-release hints (#825)
Closes https://github.com/astral-sh/puffin/issues/807.
2024-01-07 12:40:22 -05:00
Charlie Marsh e6fcb9c4d3
Use `anstream` for all color control (#823)
## Summary

We can use `anstream` for all color control, rather than going through
`colored`. Note that we still need the `colored` crate, since `colored`
and `anstream` solve different problems. (`anstream` recommends using
`owo-colors` alongside it, but `colored` seems to work fine?)

Resolves the issue raised in
https://github.com/astral-sh/puffin/pull/742 via `anstream` rather than
`colored`.

Closes https://github.com/astral-sh/puffin/issues/782.
2024-01-06 20:44:05 -05:00
Charlie Marsh fed492831a
Inline some format placeholders (#822) 2024-01-06 23:13:44 +00:00
Charlie Marsh 77c3a67029
Remove `pub(crate)` from `RegistryClient` fields (#821) 2024-01-06 22:05:18 +00:00
Charlie Marsh 9ded337870
Remove unused `proxy` field from client (#820) 2024-01-06 17:02:35 -05:00
Zanie Blue 88adba83a0
Add scenarios with unresolvable dependencies due to excluded versions (#801)
Scenarios added in https://github.com/zanieb/packse/pull/71
2024-01-05 16:21:47 -06:00
Zanie Blue 9a75703973
Bump packse to hide `requires-python` in docstrings when not relevant (#797) 2024-01-05 20:49:09 +00:00
Zanie Blue def7f79f20
Add pre-release test scenario reproducing hint simplification bug (#796)
A reproduction of #751 

Scenarios added in https://github.com/zanieb/packse/pull/68
2024-01-05 14:41:40 -06:00
konsti 65efee1d76
Add compare_release fast path (#799)
Looking at the profile for tf-models-nightly after #789,
`compare_release` is the single biggest item. Adding a fast path, we
avoid paying the cost for padding releases with 0s when they are the
same length, resulting in a 16% for this pathological case. Note that
this mainly happens because tf-models-nightly is almost all large dev
releases that hit the slow path.

**Before**


![image](https://github.com/astral-sh/puffin/assets/6826232/0d2b4553-da69-4cdb-966b-0894a6dd5d94)

**After**


![image](https://github.com/astral-sh/puffin/assets/6826232/6d484808-9d16-408d-823e-a12d321802a5)

```
$ hyperfine --warmup 1 --runs 3 "target/profiling/main pip-compile -q scripts/requirements/tf-models-nightly.txt"
 "target/profiling/puffin pip-compile -q scripts/requirements/tf-models-nightly.txt"
Benchmark 1: target/profiling/main pip-compile -q scripts/requirements/tf-models-nightly.txt
  Time (mean ± σ):     11.963 s ±  0.225 s    [User: 11.478 s, System: 0.451 s]
  Range (min … max):   11.747 s … 12.196 s    3 runs

Benchmark 2: target/profiling/puffin pip-compile -q scripts/requirements/tf-models-nightly.txt
  Time (mean ± σ):     10.317 s ±  0.720 s    [User: 9.885 s, System: 0.404 s]
  Range (min … max):    9.501 s … 10.860 s    3 runs

Summary
  target/profiling/puffin pip-compile -q scripts/requirements/tf-models-nightly.txt ran
    1.16 ± 0.08 times faster than target/profiling/main pip-compile -q scripts/requirements/tf-models-nightly.txt
```
2024-01-05 15:14:11 -05:00
Andrew Gallant 6c98ae9d77
pep440: rewrite the parser and make version comparisons cheaper (#789)
This PR builds on #780 by making both version parsing faster, and
perhaps more importantly, making version comparisons much faster.
Overall, these changes result in a considerable improvement for the
`boto3.in` workload. Here's the status quo:

```
$ time puffin pip-compile --no-build --cache-dir ~/astral/tmp/cache/ -o /dev/null ./scripts/requirements/boto3.in
Resolved 31 packages in 34.56s

real    34.579
user    34.004
sys     0.413
maxmem  2867 MB
faults  0
```

And now with this PR:

```
$ time puffin pip-compile --no-build --cache-dir ~/astral/tmp/cache/ -o /dev/null ./scripts/requirements/boto3.in
Resolved 31 packages in 9.20s

real    9.218
user    8.919
sys     0.165
maxmem  463 MB
faults  0
```

This particular workload gets stuck in pubgrub doing resolution, and
thus benefits mightily from a faster `Version::cmp` routine. With that
said, this change does also help a fair bit with "normal" runs:

```
$ hyperfine -w10 \
    "puffin-base pip-compile --cache-dir ~/astral/tmp/cache/ -o /dev/null ./scripts/benchmarks/requirements.in" \
    "puffin-cmparc pip-compile --cache-dir ~/astral/tmp/cache/ -o /dev/null ./scripts/benchmarks/requirements.in"
Benchmark 1: puffin-base pip-compile --cache-dir ~/astral/tmp/cache/ -o /dev/null ./scripts/benchmarks/requirements.in
  Time (mean ± σ):     337.5 ms ±   3.9 ms    [User: 310.5 ms, System: 73.2 ms]
  Range (min … max):   333.6 ms … 343.4 ms    10 runs

Benchmark 2: puffin-cmparc pip-compile --cache-dir ~/astral/tmp/cache/ -o /dev/null ./scripts/benchmarks/requirements.in
  Time (mean ± σ):     189.8 ms ±   3.0 ms    [User: 168.1 ms, System: 78.4 ms]
  Range (min … max):   185.0 ms … 196.2 ms    15 runs

Summary
  puffin-cmparc pip-compile --cache-dir ~/astral/tmp/cache/ -o /dev/null ./scripts/benchmarks/requirements.in ran
    1.78 ± 0.03 times faster than puffin-base pip-compile --cache-dir ~/astral/tmp/cache/ -o /dev/null ./scripts/benchmarks/requirements.in
```

There is perhaps some future work here (detailed in the commit
messages), but I suspect it would be more fruitful to explore ways of
making resolution itself and/or deserialization faster.

Fixes #373, Closes #396
2024-01-05 11:57:32 -05:00
Zanie Blue 74777c01ea
Improve documentation for scenario tests (#795)
- Fix documentation of scenario test module
- Add instructions to scenario update script for local development
2024-01-05 16:51:25 +00:00
konsti 5820a9d937
Update dependencies (#794)
Pull in a bunch of updates so they get some testing before we announce
the project. textwrap 0.16 is blocked on miette updating, http 1.0 on
reqwest.
2024-01-05 11:40:12 -05:00
Zanie Blue 08edbc9f60
Add assertions of expected scenario results (#791)
Uses new metadata added in https://github.com/zanieb/packse/pull/61 to
assert that resolution succeeded or failed _and_ that the installed
package versions match the expected result.
2024-01-05 10:32:37 -06:00
konsti 673bece595
Allow `pip-compile` without a venv (#494)
The semantics are a bit unintuitive because `--python-version` is a
preference when looking for a python version without a venv, but if we
don't find that exact version we'll take `python3` and patch the
markers. This will make more sense once we start provisioning python
builds.

We can now resolve black with both python 3.8 and 3.12, with or without
that python version being in scope. In the example below,
`PATH=$HOME/.cargo/bin:/usr/bin` removes the pyenv builds and leaves
only `python3`, which is python 3.11.

```console
$ RUST_LOG=puffin::commands=debug cargo run --bin puffin -q -- pip-compile -v scripts/benchmarks/requirements/black.in --python-version py38
    0.004108s DEBUG puffin::commands::pip_compile Using Python 3.8 at /home/konsti/.local/bin/python3.8
Resolved 8 packages in 44ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    puffin pip-compile -v scripts/benchmarks/requirements/black.in --python-version py38
black==23.11.0
[...]
platformdirs==4.0.0
    # via black
tomli==2.0.1
    # via black
typing-extensions==4.8.0
    # via black
$ PATH=$HOME/.cargo/bin:/usr/bin RUST_LOG=puffin::commands=debug cargo run --bin puffin -q -- pip-compile -v scripts/benchmarks/requirements/black.in --python-version py38
    0.004315s DEBUG puffin::commands::pip_compile Using Python 3.11 at /usr/bin/python3
Resolved 8 packages in 43ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    puffin pip-compile -v scripts/benchmarks/requirements/black.in --python-version py38
black==23.11.0
[...]
platformdirs==4.0.0
    # via black
tomli==2.0.1
    # via black
typing-extensions==4.8.0
    # via black
```

```console
$ RUST_LOG=puffin::commands=debug cargo run --bin puffin -q -- pip-compile -v scripts/benchmarks/requirements/black.in --python-version py312
    0.004216s DEBUG puffin::commands::pip_compile Using Python 3.12 at /home/konsti/.local/bin/python3.12
Resolved 6 packages in 37ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    puffin pip-compile -v scripts/benchmarks/requirements/black.in --python-version py312
black==23.11.0
[...]
platformdirs==4.0.0
    # via black
$ PATH=$HOME/.cargo/bin:/usr/bin RUST_LOG=puffin::commands=debug cargo run --bin puffin -q -- pip-compile -v scripts/benchmarks/requirements/black.in --python-version py312
    0.004190s DEBUG puffin::commands::pip_compile Using Python 3.11 at /usr/bin/python3
Resolved 6 packages in 39ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    puffin pip-compile -v scripts/benchmarks/requirements/black.in --python-version py312
black==23.11.0
[...]
platformdirs==4.0.0
    # via black
```

Fixes #235.

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2024-01-05 15:01:06 +00:00
Charlie Marsh 76064cdec2
Document Python interpreter discovery in README (#792) 2024-01-05 09:44:06 -05:00
Zanie Blue 0cd57a6cd8
Add pre-release scenarios (#790)
Scenarios added in https://github.com/zanieb/packse/pull/58
2024-01-05 03:10:43 +00:00
Zanie Blue 3d6ea7809a
Update scenario tests to include `requires-python` coverage (#769)
Includes creating a virtual env with the relevant environment python
version.

Scenarios added in https://github.com/zanieb/packse/pull/55
2024-01-04 14:15:13 -06:00
konsti 57c96df288
Explain ld errors (#773)
One of the most common ways source dists fail to build (on linux) is
when the linker fails because the shared library of a native dependency
is not installed. These errors are hard to understand when you're not a
c programmer:

```
       In file included from /usr/include/python3.10/unicodeobject.h:1046,
                        from /usr/include/python3.10/Python.h:83,
                        from Modules/3.x/readline.c:8:
       Modules/3.x/readline.c: In function ‘on_completion’:
       /usr/include/python3.10/cpython/unicodeobject.h:744:29: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
         744 | #define _PyUnicode_AsString PyUnicode_AsUTF8
             |                             ^~~~~~~~~~~~~~~~
       Modules/3.x/readline.c:842:23: note: in expansion of macro ‘_PyUnicode_AsString’
         842 |             char *s = _PyUnicode_AsString(r);
             |                       ^~~~~~~~~~~~~~~~~~~
       Modules/3.x/readline.c: In function ‘readline_until_enter_or_signal’:
       Modules/3.x/readline.c:1044:9: warning: ‘sigrelse’ is deprecated: Use the sigprocmask function instead [-Wdeprecated-declarations]
        1044 |         sigrelse(SIGINT);
             |         ^~~~~~~~
       In file included from Modules/3.x/readline.c:10:
       /usr/include/signal.h:359:12: note: declared here
         359 | extern int sigrelse (int __sig) __THROW
             |            ^~~~~~~~
       Modules/3.x/readline.c: In function ‘PyInit_readline’:
       Modules/3.x/readline.c:1179:34: warning: assignment to ‘char * (*)(FILE *, FILE *, const char *)’ from incompatible pointer type ‘char * (*)(FILE *, FILE *, char *)’ [-Wincompatible-pointer-types]
        1179 |     PyOS_ReadlineFunctionPointer = call_readline;
             |                                  ^
       In file included from /usr/include/string.h:535,
                        from /usr/include/python3.10/Python.h:30,
                        from Modules/3.x/readline.c:8:
       In function ‘strncpy’,
           inlined from ‘call_readline’ at Modules/3.x/readline.c:1124:9:
       /usr/include/x86_64-linux-gnu/bits/string_fortified.h:95:10: warning: ‘__builtin_strncpy’ output truncated before terminating nul copying as many bytes from a string as its length [-Wstringop-truncation]
          95 |   return __builtin___strncpy_chk (__dest, __src, __len,
             |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
          96 |                                   __glibc_objsize (__dest));
             |                                   ~~~~~~~~~~~~~~~~~~~~~~~~~
       Modules/3.x/readline.c: In function ‘call_readline’:
       Modules/3.x/readline.c:1099:9: note: length computed here
        1099 |     n = strlen(p);
             |         ^~~~~~~~~
       /usr/bin/ld: cannot find -lncurses: No such file or directory
       collect2: error: ld returned 1 exit status
       error: command '/usr/bin/x86_64-linux-gnu-gcc' failed with exit code 1
       ---
```

We parse these errors out, tell the user about the missing shared
library and even the most likely debian/ubuntu package name:

```
This error likely indicates that you need to install the library that provides a shared library for ncurses for pygraphviz-1.11 (e.g. libncurses-dev)
```
2024-01-04 20:56:38 +01:00
Zanie Blue 8ac6f9a198
Wrap scenario descriptions in docstrings (#787)
Otherwise, the lines can get kind of long.
2024-01-04 19:43:50 +00:00
Zanie Blue f89c6456e3
Explicitly pin scenarios to a packse commit (#788)
Previously, we just pulled the latest commit from `main` on every
update. This causes problems when you do not intend to update the
scenarios as in #787.

This bumps to the latest `packse` commit without new scenarios.
2024-01-04 19:38:48 +00:00
Zanie Blue 5e04a95c45
Disable line wrapping during scenario tests (#784)
Adds support for a `PUFFIN_NO_WRAP` environment variable which disables
line wrapping in `miette` output.

We set this variable in the scenario tests to improve the readability of
snapshots.

I contributed the ability to disable line wrapping upstream at
https://github.com/zkat/miette/pull/328
2024-01-04 19:07:16 +00:00
Andrew Gallant d7c9b151fb
pep440: some minor refactoring, mostly around error types (#780)
This PR does a bit of refactoring to the pep440 crate, and in
particular around the erorr types. This PR is meant to be a precursor
to another PR that does some surgery (both in parsing and in `Version`
representation) that benefits somewhat from this refactoring.

As usual, please review commit-by-commit.
2024-01-04 12:28:36 -05:00
Andrew Gallant 1cc3250e76
puffin-cli: fix botched merge (#785)
This fixes a compilation error with tests on current `main`. I didn't
track down the exact provenance, but I'd guess it's the result of a
botched merge. (i.e., Two or more PRs that pass CI independently, but
when merged cause failures.)
2024-01-04 17:03:45 +00:00
Charlie Marsh c6bdc43f37
Add missing feature to `Cargo.toml` (#777) 2024-01-04 11:39:11 -05:00
Zanie Blue e75fde7bfe
Filter prefixes from scenario snapshots to improve readability (#779)
I'm a _little_ unsure since this could be confusing but the prefixes can
be pretty long and this is much easier to read.
2024-01-04 09:57:41 -06:00
konsti 9b77a8873e
Disable color output when redirecting stderr (#742)
I'm still confused about it, but this seems to do the right thing?

`HierarchicalLayer` internally has [`let ansi =
io::stderr().is_terminal();`](fcd9eed252/src/lib.rs (L74)),
so the logging itself is already correctly uncolored, but errors in the
log weren't.

Test command, ran with network deactivated:

```shell
RUST_LOG=debug cargo run --bin puffin -- pip-compile -v ./scripts/popular_packages/pypi_8k_downloads.txt 2> log.txt
```

**Before**

```
error: Request error: error sending request for url (https://pypi.org/simple/apache-airflow-providers-dbt-cloud/): error trying to connect: dns error: failed to lookup address information: Temporary failure in name resolution
  Caused by: error sending request for url (https://pypi.org/simple/apache-airflow-providers-dbt-cloud/): error trying to connect: dns error: failed to lookup address information: Temporary failure in name resolution
  Caused by: error trying to connect: dns error: failed to lookup address information: Temporary failure in name resolution
  Caused by: dns error: failed to lookup address information: Temporary failure in name resolution
  Caused by: failed to lookup address information: Temporary failure in name resolution
  ```

  **After**

  ```
  error: Request error: error sending request for url (https://pypi.org/simple/fissix/): error trying to connect: dns error: failed to lookup address information: Temporary failure in name resolution
    Caused by: error sending request for url (https://pypi.org/simple/fissix/): error trying to connect: dns error: failed to lookup address information: Temporary failure in name resolution
    Caused by: error trying to connect: dns error: failed to lookup address information: Temporary failure in name resolution
    Caused by: dns error: failed to lookup address information: Temporary failure in name resolution
    Caused by: failed to lookup address information: Temporary failure in name resolution
```
2024-01-04 16:43:44 +01:00
konsti 92c780ec2f
Run custom insta filters before generic filters (#781)
I've noticed some non-deterministic test failures when a temp dir looks
like a timestamp
(https://github.com/astral-sh/puffin/actions/runs/7410022542/job/20161416805).
Running the custom filters for e.g. the temp dirs before the generic
time filters should fix that.
2024-01-04 16:40:28 +01:00
Charlie Marsh b2230e7f4d
Make index URLs insensitive to trailing slashes (#771)
Closes https://github.com/astral-sh/puffin/issues/770.
2024-01-04 08:45:50 -05:00
konsti 7d6e6fae25
Requirement fixup for trailing comma after trailing quote (#776)
Fixup for
7349527ceadde8fc265a33e6a4e662/boto3-1.2.0-py2.py3-none-any.whl:

```
botocore>=1.3.0,<1.4.0',
```

Note that neither the quote nor the comma are right.
2024-01-04 08:45:41 -05:00
konsti 0c5ca1cdd8
Delete unused file (#772)
This is a duplicate that's not used anymore, probably a refactoring
artifact.
2024-01-04 11:32:12 +00:00
Zanie Blue e18a6a0c03
Include permalink to scenarios used to generate test cases (#767) 2024-01-03 20:41:14 -06:00
Zanie Blue 0d5252580c
Improve scenario update script (#759)
Following #757, improves the script for generating scenario test cases
with:

- A requirements file
- Support for downloading packse scenarios from GitHub dynamically
- Running rustfmt on the generated test file
- Updating snapshots / running tests
2024-01-03 20:13:11 -06:00
Charlie Marsh bf9e9daa39
Make editable installs their own test feature flag (#766)
For whatever reason these fail for me with mold, and it's not worth it
to me to disable the linker.
2024-01-03 20:33:22 -05:00
Charlie Marsh 252d53e83a
Make environment validation a `--strict` flag (#765)
I don't necessarily want users to pay this cost every time. We could
consider making this `true` by default.

Closes https://github.com/astral-sh/puffin/issues/763.
2024-01-04 01:29:06 +00:00
Charlie Marsh ae8c7d11e3
Use `create_venv_py312` in pip-uninstall tests (#764) 2024-01-04 01:16:13 +00:00
Charlie Marsh 286145bc7f
Add a dedicated error for missing RECORD files (#762)
Related to: https://github.com/astral-sh/puffin/issues/716
2024-01-04 00:28:50 +00:00
Charlie Marsh 2d1d6ac0dd
Add context and diagnostics for missing `METADATA` (#761)
Closes https://github.com/astral-sh/puffin/issues/717.
2024-01-03 19:09:23 -05:00
Zanie Blue 1f2112191f
Unpack scenario root requirements in test cases (#757)
As mentioned in #746, instead of just installing the scenario root we
will unpack the root dependencies into the install command to allow
better coverage of direct user requests with scenarios.

I added display of the package tree provided by each scenario.

Use a mustache template for iterative replacements.
2024-01-03 17:31:29 -06:00
Charlie Marsh 02b157085e
Add INSTALLER file to install-wheel-rs (#760)
See:
https://packaging.python.org/en/latest/specifications/recording-installed-packages/#the-installer-file
2024-01-03 17:30:54 -05:00
Zanie Blue c9f43e915c
Add packse scenario tests (#746)
Adds tests using packse test scenarios! Uses `test.pypi.org` as a
backing index.

Tests are generated by a simple Python script. Requires
https://github.com/zanieb/packse/pull/49.

This opens us to a slight attack surface, as we cannot force use of
`test.pypi.org` only and someone could register these package names on
the real `pypi.org` index with malicious content. I could publish these
packages there too.
2024-01-03 15:50:06 -06:00
Charlie Marsh aa75d264cd
Clean up the Puffin CLI (#755)
- Rename to `puffin pip-freeze` for consistency.
- Add a `virtualenv` alias to `venv`.
- Hide the `add` and `remove` commands.
2024-01-03 21:22:06 +00:00
Charlie Marsh cfffcbb269
Cancel waiting tasks on resolution error (#753)
## Summary

I don't understand why this works (because I don't understand why it's
erroring) but it does. See:
https://github.com/astral-sh/puffin/pull/746#issuecomment-1875722454.

## Test Plan

```
cargo run --bin puffin pip-install requires-transitive-incompatible-with-transitive-8329cfc0 --extra-index-url https://test.pypi.org/simple -n
```
2024-01-03 20:18:27 +00:00
Charlie Marsh a8e52d2899
Split `resolver.rs` into a module (#752)
This is just getting hard to navigate. No code changes, just moving
stuff around.
2024-01-03 14:02:30 -05:00
Charlie Marsh 48c7359622
Always simplify dependency sets (#748)
`simplify_set` can itself simplify to the full range, so it seems like
we should be checking if the set is `Range::full` _after_ simplifying
rather than before.
2024-01-03 13:21:03 -05:00
Charlie Marsh 607a5bee6d
Use `register_owned` in prefetch path (#750) 2024-01-03 17:31:23 +00:00
Charlie Marsh fd556ccd44
Model Python version as a PubGrub package (#745)
## Summary

This PR modifies the resolver to treat the Python version as a package,
which allows for better error messages (since we no longer treat
incompatible packages as if they "don't exist at all").

There are a few tricky pieces here...

First, we need to track both the interpreter's Python version and the
_target_ Python version, because we support resolving for other versions
via `--python 3.7`.

Second, we allow using incompatible wheels during resolution, as long as
there's a compatible source distribution. So we still need to test for
`requires-python` compatibility when selecting distributions.

This could use more testing, but it feels like an area where `packse`
would be more productive than writing PyPI tests.

Closes https://github.com/astral-sh/puffin/issues/406.
2024-01-03 15:20:45 +00:00
Charlie Marsh 5a98add54e
Always pre-fetch distribution metadata (#744)
This PR fixes our prefetching logic to ensure that we always attempt to
prefetch the "best-guess" distribution for all dependencies. This logic
already existed, but because we only attempted to prefetch when package
metadata was available, it almost never triggered. Now, we wait for the
package metadata to become available, _then_ kick off the "best-guess"
prefetch (for every package).

In my testing, this dramatically improves performance (like 2x). I'm
wondering if this regressed at some point?

Closes #743.

Co-authored-by: konsti <konstin@mailbox.org>
2024-01-03 11:37:45 +01:00
Charlie Marsh ba23115465
Remove some package clones (#749) 2024-01-02 23:21:46 -05:00
Charlie Marsh 94076d6000
Use dependency package when simplifying dependency set (#747)
This manifested itself here:
https://github.com/astral-sh/puffin/pull/745/files#r1439912440.
2024-01-02 20:26:56 -06:00
konsti 26f597a787
Add spans to all significant tasks (#740)
I've tried to investigate puffin's performance wrt to builds and
parallelism in general, but found the previous instrumentation to
granular. I've tried to add spans to every function that either needs
noticeable io or cpu resources without creating duplication. This also
fixes some wrong tracing usage on async functions
(https://docs.rs/tracing/latest/tracing/struct.Span.html#in-asynchronous-code)
and some spans that weren't actually entered.
2024-01-02 16:17:03 +00:00
konsti a3d8b3d9ca
Don't install incompatible path and url wheels (#739)
Add early tag checking for path and url wheels.

This does not check for resolve for consistency with index wheels.
2024-01-02 15:00:50 +00:00
konsti cd43708369
Flag to force latest version in resolve-many (#741)
Also fixes color when redirecting puffin-dev to a log file.
2024-01-02 11:04:26 +00:00
konsti 35f6ea204b
Remove Box::pin usages (#738)
Rust 1.75 update follow-up, simplifies the code.
2023-12-29 15:49:12 +00:00
Charlie Marsh 2cfa4a3574
Add a dedicated error message to hint users towards enabling pre-releases (#697)
This PR adds a dedicated error message for resolutions that fail, but
might've succeeded if pre-releases were allowed. Specifically, if we see
a failed resolution, and failed to find a version for a package that
included a pre-release marker, we add a hint nudging the user to
explicitly enable all pre-releases.

We'd prefer a solution like
https://github.com/astral-sh/puffin/pull/666, but believe that it will
break some assumptions in PubGrub, so this is the lighter-weight
solution.

Closes https://github.com/astral-sh/puffin/issues/659.
2023-12-28 21:44:35 -05:00
konsti 2d4cb1ebf2
Rust 1.75 (#736)
The `async fn` and return-position `impl Trait` in traits improve
`BuildContext` ergonomics. The traits use `impl Future` over `async fn`
to make the send bound explicit
(https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html).

The remaining changes are due to clippy.
2023-12-28 16:08:35 -04:00
konsti 7bf2790a25
Remove all quotes from (lenient) version specifiers (#735)
Found in
https://pypi.org/simple/algoliasearch/?format=application/vnd.pypi.simple.v1+json
and
https://pypi.org/simple/okta/?format=application/vnd.pypi.simple.v1+json
2023-12-28 14:40:42 +00:00
konsti 0ebff943e4
Finish install-many with pypi 10k most dependents (#732)
This PR combines three small changes to finish up the install-many
testing.

* Download pypi_10k_most_dependents.txt in script I'd like to have the
setup process of the large scale checks automated.
* Some install-many dev script improvements 
* Fix mkl_fft-1.3.6-58-cp310-cp310-manylinux2014_x86_64.whl:
mkl_fft-1.3.6-58-cp310-cp310-manylinux2014_x86_64.whl has multiple
Wheel-Version entries, we have to ignore that like pip

Apart from the mkl-fft fix the only other errors i've seen showing up
are
https://github.com/astral-sh/puffin/issues/520#issuecomment-1869625642.
2023-12-27 09:42:51 -05:00
Charlie Marsh 007f52bb4e
Add support for relative URLs in simple metadata responses (#721)
## Summary

This PR adds support for relative URLs in the simple JSON responses. We
already support relative URLs for HTML responses, but the handling has
been consolidated between the two. Similar to index URLs, we now store
the base alongside the metadata, and use the base when resolving the
URL.

Closes #455.

## Test Plan

`cargo test` (to test HTML indexes). Separately, I also ran `cargo run
-p puffin-cli -- pip-compile requirements.in -n
--index-url=http://localhost:3141/packages/pypi/+simple` on the
`zb/relative` branch with `packse` running, and forced both HTML and
JSON by limiting the `accept` header.
2023-12-27 08:53:21 -05:00
Charlie Marsh ae83a74309
Review feedback for HTML indexes (#733)
See: https://github.com/astral-sh/puffin/pull/719
2023-12-26 21:57:20 +00:00
Charlie Marsh bbe0246205
Change internal representation of `CacheEntry` to avoid allocations (#730)
Removes a TODO.
2023-12-26 02:10:30 +00:00
Charlie Marsh 188ab75769
Split `File` into internal and external type (#729)
## Summary

This PR makes the `pypi_types::File` a response-only type (i.e., a type
that's only used when deserializing over the wire), and adds a separate
internal `File` type. Right now, the representations are similar, but
already, we can avoid the "lenient" deserialization on our internal
`File` type, and avoid the special-casing of the property names that's
required in the JSON. Over time, we can evolve this representation
entirely separately from the representation we receive from PyPI and
other indexes.
2023-12-25 15:42:28 -05:00
Charlie Marsh 6ff21374dc
Split `puffin-cache` into Puffin-specific and generic utilities (#728)
This crate started off as generic caching utilities, but we started
adding a lot of Puffin-specific stuff (like the cache buckets
abstraction that knows about Git vs. direct URL vs. indexes and so on).
This PR moves the generic stuff into a new `cache-key` crate.
2023-12-25 14:38:56 +00:00
Charlie Marsh 187ccef4e1
Cache `Tags` on `Interpreter` (#726) 2023-12-25 13:41:10 +00:00
Charlie Marsh 5b2e381f87
Remove `platform-tags` dependency on `puffin-interpreter` (#725)
Cuts off a large internal dependency chain from what is otherwise a very
general crate.
2023-12-24 23:06:50 +00:00
Charlie Marsh ad34bb02a9
Modify some inconsistent exports (#724) 2023-12-24 22:30:03 +00:00
Charlie Marsh 343880820b
Un-escape HTML entities when decoding (#723)
I don't have a good testing strategy here (I'm manually testing against
`devpi` via `packse`), but the HTML index uses (e.g.)
`data-requires-python="&gt;=3.8"`, so we need to decode.
2023-12-24 16:35:45 -05:00
Charlie Marsh 2d721a497e
Add a `SimpleHttp` abstraction similar to `SimpleJson` (#722)
Just an internal refactor to turn some standalone functions into
associated methods (and reduce the diff in the next PR).
2023-12-24 20:55:57 +00:00
konsti e23292641f
Add pypi 10k packages with most dependents dataset (#711)
From manual inspection, this dataset generated through the [libraries.io
API](https://libraries.io/api#project-search) seems more mainstream than
the current 8k one, which is also preserved. I've added the dataset to
the repo because the API requires an API key.
2023-12-24 18:31:52 +00:00
Charlie Marsh 5bce699ee1
Add support for HTML indexes (#719)
## Summary

This PR adds support for HTML index responses (as with
`--index-url=https://download.pytorch.org/whl`).

Closes https://github.com/astral-sh/puffin/issues/412.
2023-12-24 16:04:00 +00:00
Charlie Marsh 9e6cb706a0
Update test fixtures (#720) 2023-12-24 15:50:10 +00:00
konsti b7ad97a823
Show resource and lockfile when waiting (#715)
We lock git checkout directories and the virtualenv to avoid two puffin
instances running in parallel changing files at the same time and
leading to a broken state. When one instance is blocking another, we
need to inform the user (why is the program hanging?) and also add some
information for them to debug the situation.

The new messages will print

```
Waiting to acquire lock for /home/konsti/projects/puffin/.venv (lockfile: /home/konsti/projects/puffin/.venv/.lock)
```

or

```
Waiting to acquire lock for git+https://github.com/pydantic/pydantic-extra-types@0ce9f207a1e09a862287ab77512f0060c1625223 (lockfile: /home/konsti/projects/puffin/cache-all-kinds/git-v0/locks/f157fd329a506a34)
```

The messages aren't perfect but clear enough to see what the contention
is and in the worst case to delete the lockfile.

Fixes #714
2023-12-21 00:05:49 +01:00
konsti e60f0ec732
Update pubgrub (#713)
Easier than i expected: We simply never construct the pubgrub error
variants since we have our own main loop. The `unreachable!()`s can be
removed when never is stabilized
2023-12-20 23:56:59 +01:00
Zanie Blue e705267dac
Fix fallback download when index does not support HTTP range requests (#702)
Otherwise, when a server does not support HTTP range requests we throw
an error instead of downloading without range requests.

---------

Co-authored-by: konstin <konstin@mailbox.org>
2023-12-20 10:55:23 +00:00
Zanie Blue 665a59dae6
Fix deserialization of index response when `requires_python` field is missing (#708)
Closes https://github.com/astral-sh/puffin/issues/707
2023-12-20 11:53:48 +01:00
Zanie Blue 4e437ba7e5
Allow the default index url to be configured with `PUFFIN_INDEX_URL` (#704)
This allows the default index URL to be easily overridden with a local
index e.g. a `packse` server

```
export PUFFIN_INDEX_URL="http://localhost:3141/packages/all/+simple"
```
2023-12-20 11:52:00 +01:00
Zanie Blue ab15b08cbe
Perform 3 retries by default instead of 0 on failed index requests (#710)
As a user, I'd expect retries to occur by default.

We should also expose this via a setting probably.
2023-12-20 11:51:24 +01:00
konsti 9f8b7e7e12
Refactor `DistFinder` to allow handling errors (#709)
For the install tests, i need the ability to ignore failures in the
`DistFinder`. To avoid just copy&pasting a version that collects errors
separately, i followed
https://gendignoux.com/blog/2021/04/01/rust-async-streams-futures-part1.html
and switched the custom channel over to an async stream yielding
`Result` items.

I like the async streams mirror the normal iterator api.
2023-12-20 04:07:55 +00:00
Zanie Blue 12eedb1c12
Include `Accept` header specifying that we can only parse JSON responses (#701)
Otherwise, when an index does not support the query variable we get an
HTML response and a JSON parse error.
2023-12-19 12:22:53 -06:00
Zanie Blue 52ba65aa9c
Derive `Debug` for `CachedClientError` (#703)
Discovered while debugging https://github.com/astral-sh/puffin/pull/702
2023-12-19 12:22:39 -06:00
Andrew Gallant aa9f47bbde
improve tests for version parser (#696)
The high level goal here is to improve the tests for the version parser.
Namely, we now check not just that version strings parse successfully,
but that they parse to the expected result.

We also do a few other cleanups. Most notably, `Version` is now an
opaque type so that we can more easily change its representation going
forward.

Reviewing commit-by-commit is suggested. :-)
2023-12-19 12:25:32 -05:00
Charlie Marsh 6f90edda78
Reduce visibility of `PubGrubReportFormatter` (#699) 2023-12-19 08:53:38 -06:00
konsti 9e2bbee7f0
Name the directory whose lock we're waiting on (#700) 2023-12-19 12:19:27 +00:00
konsti 114548d945
Test that cache errors are non-fatal (#685)
The test creates a cache from multiple sources and injects faults (once
using invalid data and once by making the files unreadable on the fs
level), then resolves again.

I didn't test git because it has its own locking and correctness logic.

The main drawback is that this test is slow (2.5s for me), we could
`#[ignore]` it.
2023-12-19 12:02:49 +00:00
Charlie Marsh 878bb5c035
Remove remaining snapshot files from resolver test (#698) 2023-12-19 05:41:50 +00:00
Charlie Marsh 3660d8a08e
Introduce separate traits for ahead-of-time and installed metadata (#692)
This is a pure refactor to follow-up #690, to separate the metadata that
we know upfront about distributions (like the version, for
registry-based distributions) vs. the metadata that requires building
(like the version, for URL-based distributions).
2023-12-18 22:37:45 +00:00
Charlie Marsh 31afb39a10
Show URLs and version together for installed, URL-based dependencies (#690)
The snapshot test changes will give you a sense for the impact of the
change and the output formatting.

Closes https://github.com/astral-sh/puffin/issues/686.
2023-12-18 22:21:37 +00:00
Charlie Marsh 365c860e27
Show fully-resolved URLs in non-resolution contexts (#689)
We now show the fully-resolved URL, rather than the URL as given by the
user, _everywhere_ except for the output resolution file (which should
retain relative paths, unexpanded environment variables, etc.).

Closes https://github.com/astral-sh/puffin/issues/687.
2023-12-18 22:10:24 +00:00
konsti 43c837f7bb
Show enum defaults in `--help` output (#693)
With `Option<T>` and `.unwrap_or_default()` later, the default of `T`
isn't shown in the help output.

Old:

```
      --link-mode <LINK_MODE>
          The method to use when installing packages from the global cache

          Possible values:
          - clone:    Clone (i.e., copy-on-write) packages from the wheel into the site packages
          - copy:     Copy packages from the wheel into the site packages
          - hardlink: Hard link packages from the wheel into the site packages

      -q, --quiet
      Do not print any output

      --resolution <RESOLUTION>
          Possible values:
          - highest:       Resolve the highest compatible version of each package
          - lowest:        Resolve the lowest compatible version of each package
          - lowest-direct: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies

      --prerelease <PRERELEASE>
          Possible values:
          - disallow:                 Disallow all pre-release versions
          - allow:                    Allow all pre-release versions
          - if-necessary:             Allow pre-release versions if all versions of a package are pre-release
          - explicit:                 Allow pre-release versions for first-party packages with explicit pre-release markers in their version requirements
          - if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
```

![Screenshot from 2023-12-18
21-04-16](https://github.com/astral-sh/puffin/assets/6826232/6b3cb47a-f224-408a-8d7a-186ebeb88ecd)

New:

```
      --link-mode <LINK_MODE>
          The method to use when installing packages from the global cache

          [default: hardlink]

          Possible values:
          - clone:    Clone (i.e., copy-on-write) packages from the wheel into the site packages
          - copy:     Copy packages from the wheel into the site packages
          - hardlink: Hard link packages from the wheel into the site packages

  -q, --quiet
          Do not print any output

      --resolution <RESOLUTION>
          [default: highest]

          Possible values:
          - highest:       Resolve the highest compatible version of each package
          - lowest:        Resolve the lowest compatible version of each package
          - lowest-direct: Resolve the lowest compatible version of any direct dependencies, and the highest compatible version of any transitive dependencies

      --prerelease <PRERELEASE>
          [default: if-necessary-or-explicit]

          Possible values:
          - disallow:                 Disallow all pre-release versions
          - allow:                    Allow all pre-release versions
          - if-necessary:             Allow pre-release versions if all versions of a package are pre-release
          - explicit:                 Allow pre-release versions for first-party packages with explicit pre-release markers in their version requirements
          - if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
```


![image](https://github.com/astral-sh/puffin/assets/6826232/26c2c391-d959-4769-999d-481b3f179502)
2023-12-18 21:50:47 +00:00
Charlie Marsh 98fcb76015
Lock entire virtualenv during modifying commands (#695)
These commands all assume that the `site-packages` are constant
throughout.

Closes #691.
2023-12-18 16:44:45 -05:00
Charlie Marsh 207bb83a1c
Rename puffin-warnings macros to avoid tracing collision (#694)
Also more consistent with Ruff.
2023-12-18 21:33:21 +00:00
Charlie Marsh e98804141c
Re-add tf-models-nightly filter in `resolve_many.rs` (#688)
I accidentally resolved this in a prior PR.
2023-12-18 16:56:04 +00:00
Charlie Marsh dbf055fe6f
Use borrowed data in `BuildDispatch` (#679)
This PR uses borrowed data in `BuildDispatch` which makes creating a
`BuildDispatch` extremely cheap (only one allocation, for the Python
executable). I can be talked out of this, it will have no measurable
impact.
2023-12-18 16:43:03 +00:00
Charlie Marsh c400ab7d07
Add support for `file://` URLs in editable requirements (#680) 2023-12-18 14:55:37 +00:00
Charlie Marsh 74ca9128b4
Canonicalize virtualenv path once (#678)
This avoids filesystem calls when creating a `BuildDispatch`.

Co-authored-by: konsti <konstin@mailbox.org>
2023-12-18 14:42:58 +00:00
konsti 89ca0d68b9
`exclude_newer` in puffin-dev resolve-cli (#684)
Internal dev tool change.
2023-12-18 14:06:54 +00:00
konsti 7926749296
Fixup for `>=2.7,!=3.0.*,!=3.1.*,<3.4.*` (#683)
Found in
https://pypi.org/simple/wincertstore/?format=application/vnd.pypi.simple.v1+json
2023-12-18 12:56:48 +00:00
konsti f4f67ebde0
Rebase: Uninstall existing non-editable versions when installing editable requirements bug (#682)
Separate branch for rebasing #677 onto main because i don't trust the
rebase enough to force push.

Closes #677.

---

If you install `black` from PyPI, then `-e ../black`, we need to
uninstall the existing `black`. This sounds simple, but that in turn
requires that we _know_ `-e ../black` maps to the package `black`, so
that we can mark it for uninstallation in the install plan. This, in
turn, means that we need to build editable dependencies prior to the
install plan.

This is just a bunch of reorganization to fix that specific bug
(installing multiple versions of `black` if you run through the above
workflow): we now run through the list of editables upfront, mark those
that are already installed, build those that aren't, and then ensure
that `InstallPlan` correctly removes those that need to be removed, etc.

Closes #676.

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-12-18 09:28:14 +00:00
Charlie Marsh 0bb2c92246
Add editable install support to `pip-install` (#675)
Per the title: adds support for `-e` installs to `puffin pip-install`.
There were some challenges here around threading the editable installs
to the right places. Namely, we want to build _once_, then reuse the
editable installs from the resolution. At present, we were losing the
`editable: true` flag on the `Dist` that came back through the
resolution, so it required some changes to the resolver.

Closes https://github.com/astral-sh/puffin/issues/672.
2023-12-18 09:52:32 +01:00
konsti 8c6463d220
Allow identical `VIRTUAL_ENV` and `CONDA_PREFIX` env vars (#681)
Port of https://github.com/PyO3/maturin/pull/1879 for
https://github.com/PyO3/maturin/issues/1878
2023-12-18 08:42:31 +00:00
Charlie Marsh 77c6e6fa6c
Add support for `reinstall` to editable packages (#674)
Closes https://github.com/astral-sh/puffin/issues/673.
2023-12-17 15:41:57 +00:00
Charlie Marsh 00e1c33af4
Add an editable index to the site-packages registry (#671)
This PR modifies `SitePackages` to store all distributions in a flat
vector, and maintain two indexes (hash maps) from "per-element data for
an element in the vector" to "index of that element". This enables us to
maintain a map on both package name and editable URL.
2023-12-17 03:44:36 +00:00
Charlie Marsh 08edd173db
Add support for editable packages in `pip-uninstall` (#670) 2023-12-17 02:56:37 +00:00
konsti f059c6e6a6
Support editable in pip-sync and pip-compile (#587)
Support `-e path/do/dir` in pip-sync and and pip-compile.
2023-12-16 22:37:34 +00:00
Charlie Marsh f62458f600
Add explicit error message for URLs without package names (#669)
`pip` supports installing packages without names (e.g.,
`git+https://github.com/pallets/flask.git`), but it doesn't adhere to
the PEP grammar, and we don't yet support it (and may never) (#313).

This PR adds a dedicated error path for such cases, to ensure that we
can give meaningful feedback to the user:

```
error: Couldn't parse requirement in requirements.in position 0 to 18
  Caused by: URL requirement is missing a package name; expected: `package_name @ https://google.com`
https://google.com
^^^^^^^^^^^^^^^^^^
```

Closes https://github.com/astral-sh/puffin/issues/650.
2023-12-16 21:14:34 +00:00
konsti 71964ec7a8
Switch to msgpack in the cached client (#662)
This gives a 1.23 speedup on transformers-extras. We could change to
msgpack for the entire cache if we want. I only tried this format and
postcard so far, where postcard was much slower (like 1.6s).

I don't actually want to merge it like this, i wanted to figure out the
ballpark of improvement for switching away from json.

```
hyperfine --warmup 3 --runs 10 "target/profiling/puffin pip-compile --cache-dir cache-msgpack scripts/requirements/transformers-extras.in" "target/profiling/branch pip-compile scripts/requirements/transformers-extras.in"
Benchmark 1: target/profiling/puffin pip-compile --cache-dir cache-msgpack scripts/requirements/transformers-extras.in
  Time (mean ± σ):     179.1 ms ±   4.8 ms    [User: 157.5 ms, System: 48.1 ms]
  Range (min … max):   174.9 ms … 188.1 ms    10 runs

Benchmark 2: target/profiling/branch pip-compile scripts/requirements/transformers-extras.in
  Time (mean ± σ):     221.1 ms ±   6.7 ms    [User: 208.1 ms, System: 46.5 ms]
  Range (min … max):   213.5 ms … 235.5 ms    10 runs

Summary
  target/profiling/puffin pip-compile --cache-dir cache-msgpack scripts/requirements/transformers-extras.in ran
    1.23 ± 0.05 times faster than target/profiling/branch pip-compile scripts/requirements/transformers-extras.in
```

Disadvantage: We can't manually look into the cache anymore to debug
things

- [ ] Check more formats, i currently only tested json, msgpack and
postcard, there should be other formats, too
- [x] Switch over `CachedByTimestamp` serialization (for the interpreter
caching)
- [x] Switch over error handling and make sure puffin is still resilient
to cache failure
2023-12-16 21:01:35 +00:00
Charlie Marsh e4673a0c52
Modify PEP 508 cursor to use byte offsets (#668)
This enables us to remove a number of allocations (in particular,
`peek_while` and `take_while` no longer allocate). It also makes it
trivial to move the cursor to a new location, since you can just slice
and call `.chars()`. At present, moving to a new location would require
converting the iterator to a string, then back to a character iterator.
2023-12-15 22:05:28 +00:00
Charlie Marsh 875c9a635e
Rename `CharIter` to `Cursor` (#667)
This better aligns with the analogous struct that we have in Ruff.
2023-12-15 21:57:59 +00:00
konsti 620f73b38b
Speed up version parsing for a 1.27±0.03 speedup in transformers-extras with conservative changes (#660)
Two low-hanging fruits as optimizations for version parsing: A fast path
for release only versions and removing the regex from version specifiers
(still calling into version's parsing regex if required). This enables
optimizing the serde format since we now see the serde part instead of
only PEP 440 parsing. I intentionally didn't rewrite the full PEP 440 at
this step.

```console
$ hyperfine --warmup 5 --runs 50 "target/profiling/puffin pip-compile scripts/requirements/transformers-extras.in" "target/profiling/main pip-compile scripts/requirements/transformers-extras.in"
  Benchmark 1: target/profiling/puffin pip-compile scripts/requirements/transformers-extras.in
    Time (mean ± σ):     217.1 ms ±   3.2 ms    [User: 194.0 ms, System: 55.1 ms]
    Range (min … max):   211.0 ms … 228.1 ms    50 runs

  Benchmark 2: target/profiling/main pip-compile scripts/requirements/transformers-extras.in
    Time (mean ± σ):     276.7 ms ±   5.7 ms    [User: 252.4 ms, System: 54.6 ms]
    Range (min … max):   268.9 ms … 303.5 ms    50 runs

  Summary
    target/profiling/puffin pip-compile scripts/requirements/transformers-extras.in ran
      1.27 ± 0.03 times faster than target/profiling/main pip-compile scripts/requirements/transformers-extras.in
```

---------

Co-authored-by: Andrew Gallant <andrew@astral.sh>
2023-12-15 14:03:35 -05:00
Charlie Marsh 305b9b080a
Show resolution error once on pip-install failure (#665)
Closes https://github.com/astral-sh/puffin/issues/664.
2023-12-15 18:43:23 +00:00
Charlie Marsh 47290f784e
Add fixup for invalid double quotes (#663)
Closes https://github.com/astral-sh/puffin/issues/658.
2023-12-15 18:11:22 +00:00
Charlie Marsh 9470c20e7a
Avoid double resolution during source builds (#656)
## Summary

This PR ensures that we re-use the resolution to install the build
dependencies when building a source distribution. Currently, we only
pass along the list of requirements, and then use the `Finder` to map
each requirement to a distribution. But we already determine the correct
distribution when resolving!

Closes https://github.com/astral-sh/puffin/issues/655.
2023-12-15 17:27:16 +00:00
Charlie Marsh 1129661a22
Ignore missing manifest entries in the built wheel cache (#654)
## Summary

This is more of a hypothetical problem, but the cache manifest could in
theory get out-of-sync with the contents on disk. This PR modifies the
`BuiltWheelMetadata` lookup to warn (but not fail) if the manifest
includes a wheel that no longer exists on disk. You can mimic this by
removing a wheel from the `built-wheels-v0` cache without modifying the
manifest correspondingly.
2023-12-15 17:24:09 +00:00
Charlie Marsh 84093773ef
Store source distribution sources in the cache (#653)
## Summary

This PR modifies `source_dist.rs` to store source distributions (from
remote URLs) in the cache. The cache structure for registries now looks
like:

<img width="1053" alt="Screen Shot 2023-12-14 at 10 43 43 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/3c2dbf6b-5926-41f2-b69b-74031741aba8">

(I will update the docs prior to merging, if approved.)

The benefit here is that we can reuse the source distribution (avoid
download + unzipping it) if we need to build multiple wheels. In the
future, it will be even more relevant, since we'll need to reuse the
source distribution to support
https://github.com/astral-sh/puffin/issues/599.

I also included some misc. refactors to DRY up repeated operations and
add some more abstraction to `source_dist.rs`.
2023-12-15 17:19:33 +00:00
Charlie Marsh a361ccfbb3
Remove additional metadata call in `source_dist.rs` (#652) 2023-12-14 19:45:31 +00:00
Charlie Marsh 22c7057b35
Expand environment variables in URLs (#640)
## Summary

This PR enables users to express relative dependencies via environment
variables. Like pip, PDM, Hatch, Rye, and others, we now allow users to
express dependencies like:

```text
flask @ file://${PROJECT_ROOT}/flask-3.0.0-py3-none-any.whl
```

In the compiled requirements file, we'll also preserve the unexpanded
environment variable.

Closes https://github.com/astral-sh/puffin/issues/592.
2023-12-14 15:09:12 +00:00
Charlie Marsh ed8dfbfcf7
Preserve verbatim URLs (#639)
## Summary

This PR adds a `VerbatimUrl` struct to preserve verbatim URLs throughout
the resolution and installation pipeline. In short, alongside the parsed
`Url`, we also keep the URL as written by the user. This enables us to
display the URL exactly as written by the user, rather than the
serialized path that we use internally.

This will be especially useful once we start expanding environment
variables since, at that point, we'll be able to write the version of
the URL that includes the _unexpected_ environment variable to the
output file.
2023-12-14 15:03:39 +00:00
Charlie Marsh eef9612719
Allow reporters to take `dyn Metadata` (#645) 2023-12-14 12:36:28 +01:00
Charlie Marsh 1a62ca0c62
Move source dist extraction into extract crate (#649) 2023-12-14 05:56:49 +00:00
Charlie Marsh 402b728bf7
Use `fs_err` with `AutoStream` (#648) 2023-12-14 04:56:55 +00:00
Charlie Marsh db7e2dedbb
Move archive extraction into its own crate (#647)
We have some shared utilities beyond `puffin-build` and
`puffin-distribution`, and further, I want to be able to access the
sdist archive extraction logic from `puffin-distribution`. This is
really generic, so moving into its own crate.
2023-12-14 04:49:09 +00:00
Charlie Marsh 388641643d
Remove `SourceDistDownload` struct (#646)
This is created in one place, then immediately destructed into fields.
2023-12-14 02:34:50 +00:00
Charlie Marsh e0127581b6
Use `fs_err` in more places (#644) 2023-12-14 01:11:45 +00:00
Charlie Marsh 4fd69c74b6
Use URL rather than String in direct URL types (#643) 2023-12-14 01:01:27 +00:00
Charlie Marsh 8071a23863
Add dedicated ID types to avoid opaque strings (#642)
This allows us to enforce type safety within the resolver. For example,
in the index, we can remove `String` as a key type and enforce that
callers _must_ present us with a `PackageId`. (This actually caught one
bug, where we were using the SHA rather than the package ID. That bug
shouldn't have had any effect given where it was, since those are 1:1,
but it's still problematic.)
2023-12-14 00:53:33 +00:00
Charlie Marsh 3549d9638e
Inline all snapshot files (#641)
Right now, we're inconsistent between checking in and inlining these.
The outputs are small in Puffin, so let's just inline them in all cases.
2023-12-14 00:35:38 +00:00
Charlie Marsh 2da6563a64
Use `Manifest::simple` in tests (#638) 2023-12-13 17:41:29 +00:00
Charlie Marsh eb1a630db2
Avoid hard-error for non-existent extras (#627)
## Summary

When resolving `transformers[tensorboard]`, the `[tensorboard]` extra
doesn't exist. Previously, we returned "unknown" dependencies for this
variant, which leads the resolution to try all versions, then fail. This
PR instead warns, but returns the base dependencies for the package,
which matches `pip`. (Poetry doesn't even warn, it just proceeds as
normal.)

Arguably, it would be better to return a custom incompatibility here and
then propagate... But this PR is better than the status quo, and I don't
know if we have support for that behavior yet...? (\cc @zanieb)

Closes #386.

Closes https://github.com/astral-sh/puffin/issues/423.
2023-12-13 17:36:27 +00:00
konsti 5c38825b93
Don't preserve tar mtime to work around tar-rs bug. (#634)
Don't preserve mtime to work around
https://github.com/alexcrichton/tar-rs/issues/349 to fix #579.
2023-12-13 15:11:02 +00:00
Charlie Marsh 69581c03c3
Enable package overrides in `pip-compile` (#631)
## Summary

This PR enables overrides to be passed to `pip-compile` and
`pip-install` via a new `--overrides` flag.

When overrides are provided, we effectively replace any requirements
that are overridden with the overridden versions. This is applied at all
depths of the tree.

The merge semantics are such that we replace _all_ requirements of a
package with _all_ requirements from the overrides files. So, for
example, if a package declares:

```
foo >= 1.0; python_version < '3.11'
foo < 1.0; python_version >= '3.11'
```

And the user provides an override like:
```
foo >= 2.0
```

Then _both_ of the `foo` requirements in the package will be replaced
with the override.

If instead, the user provided an override like:
```
foo >= 2.0; python_version < '3.11'
foo < 3.0; python_version >= '3.11'
```

Then we'd replace _both_ of the original `foo` requirements with both of
these overrides. (In technical terms, for each package in the
requirements file, we flat-map over its overrides.)

Closes https://github.com/astral-sh/puffin/issues/511.
2023-12-13 15:03:38 +00:00
konsti e51b397779
Typo: editable -> editables (#637)
Split out to minimize the diff in #587
2023-12-13 13:24:28 +00:00
konsti 0b20f6a25a
Proper unzip error type (#636)
Move the `Unzip` trait from anyhow to `ZipError|io::Error`.
2023-12-13 12:55:59 +00:00
konsti 0dde84dd27
Fix main (#635)
Seems to be a PR timing error
2023-12-13 13:55:06 +01:00
Charlie Marsh ea920e22d1
Validate environment after `pip-sync` (#629)
Not 100% sure that we actually want to do this, it seems reasonable
though.

Closes https://github.com/astral-sh/puffin/issues/410.
2023-12-13 09:13:43 +01:00
Charlie Marsh cbfd39093e
Clean up some function signatures (#633) 2023-12-13 06:21:47 +00:00
Charlie Marsh 920e10fc8f
Use `FxHash` consistently (#632) 2023-12-13 05:36:03 +00:00
Charlie Marsh edd741bf13
Add a diagnostic to detect invalid Python versions (#630)
Related to: https://github.com/astral-sh/puffin/issues/410.
2023-12-13 03:45:02 +00:00
Charlie Marsh a24eb57e93
Make warnings user-facing (#628)
## Summary

Now, `puffin_warnings::warn_once` and `puffin_warnings::warn` will go to
`stderr`, as long as the user isn't running under `--quiet`. Previously,
these went through `tracing`, and so were only visible when running
under `--verbose`.
2023-12-12 21:24:38 -05:00
Zanie Blue 490fb55ac5
Use available versions to simplify unsat error reports (#547)
Uses https://github.com/pubgrub-rs/pubgrub/pull/156 to consolidate
version ranges in error reports using the actual available versions for
each package.

Alternative to https://github.com/zanieb/pubgrub/pull/8 which implements
this behavior as a method in the `Reporter` — here it's implemented in
our custom report formatter (#521) instead which requires no upstream
changes.

Requires https://github.com/zanieb/pubgrub/pull/11 to only retrieve the
versions for packages that will be used in the report.

This is a work in progress. Some things to do:
- ~We may want to allow lazy retrieval of the version maps from the
formatter~
- [x] We should probably create a separate error type for no solution
instead of mixing them with other resolve errors
- ~We can probably do something smarter than creating vectors to hold
the versions~
- [x] This degrades error messages when a single version is not
available, we'll need to special case that
- [x] It seems safer to coerce the error type in `resolve` instead of
`solve` if feasible
2023-12-12 23:25:16 +00:00
Charlie Marsh a8512d7d51
Remove one string clone (#626) 2023-12-12 20:56:15 +00:00
konsti a24a681db9
Towards using `prepare_metadata_for_build_wheel` in the resolver (#616)
Make `prepare_metadata_for_build_wheel` accessible across the puffin
codebase by splitting the built call into a setup, a metadata and a
wheel call. This does not actually use the hook yet, but it's the
required refactoring for it.

Part of #599.
2023-12-12 20:45:37 +00:00
Charlie Marsh 85c37b2b9c
Add extra to debug logging (#625) 2023-12-12 20:09:09 +00:00
Charlie Marsh f459e1ee50
Use a non-async `Mutex` in `OnceMap` (#624)
I don't know why, but this seems to resolve
https://github.com/astral-sh/puffin/issues/619. The Tokio docs also say
that using Tokio's Mutex is _not_ recommended unless you need to hold
the Mutex across an `.await`, which we don't.

Since this is a non-deterministic failure, I just ran it a bunch of
times and ensured it didn't hang (whereas it did hang occasionally prior
to this PR).

Closes https://github.com/astral-sh/puffin/issues/619
2023-12-12 14:59:45 -05:00
Charlie Marsh 4fb2e0955e
Add a fast-path to skip resolution when installation is complete (#613)
For a very large resolution (a few hundred packages), I see 13ms vs.
400ms for a no-op. It's worth optimizing this case, in my opinion.
2023-12-12 17:43:12 +00:00
Charlie Marsh 3aaab32a9d
Omit extra in resolver progress (#623)
Closes #621.
2023-12-12 12:41:18 -05:00
Charlie Marsh 6c7f5cb846
Validate installed packages in virtual environment (#611)
## Summary

Now, after running `pip-install`, we validate that the set of installed
packages is consistent -- that is, that we don't have any packages that
are missing dependencies, or incompatible versions of installed
dependencies.
2023-12-12 17:33:38 +00:00
Charlie Marsh c764155988
Avoid double-resolving during `pip-install` (#610)
## Summary

At present, when performing a `pip-install`, we first do a resolution,
then take the set of requirements and basically run them through our
`pip-sync`, which itself includes re-resolving the dependencies to get a
specific `Dist` for each package. (E.g., the set of requirements might
say `flask==3.0.0`, but the installer needs a specific _wheel_ or source
distribution to install.)

This PR removes this second resolution by exposing the set of pinned
packages from the resolution. The main challenge here is that we have an
optimization in the resolver such that we let the resolver read metadata
from an incompatible wheel as long as a source distribution exists for a
given package. This lets us avoid building source distributions in the
resolver under the assumption that we'll be able to install the package
later on, if needed. As such, the resolver now needs to track the
resolution and installation filenames separately.
2023-12-12 17:29:09 +00:00
Charlie Marsh a0b3815d84
Respect existing versions when pip-installing (#608)
## Summary

When running `puffin pip-install`, we should respect versions that are
already installed in the environment. For example, if you run `puffin
pip-install flask==2.0.0` and then `puffin pip-install flask`, we should
avoid upgrading Flask. The most natural way to model this is to mark
them as "preferences".

(It's not enough to just filter those requirements out prior to
resolving, since we may not have the _dependencies_ of those packages
installed. We _could_ recursively verify this across the
`site-packages`, but that would be a larger PR.)
2023-12-12 17:22:47 +00:00
Charlie Marsh 974cb4cc15
Add a `pip-install` subcommand (#607)
## Summary

This PR adds a `pip-install` command that operates like, well, `pip
install`. In short, it resolves the provided dependency, then makes sure
they're all installed in the environment. The primary differences with
`pip-sync` are that (1) `pip-sync` ignores dependencies, and assumes
that the packages represent a complete set; and (2) `pip-sync`
uninstalls any unlisted packages.

There are a bunch of TODOs that I'll resolve in subsequent PRs.

Closes https://github.com/astral-sh/puffin/issues/129.
2023-12-12 12:16:00 -05:00
Charlie Marsh 3e837da5b8
Avoid unicode decoding in name normalization (#617) 2023-12-12 10:01:02 -05:00
konsti 5ae4023cda
Activate venv before source dist build (#567)
Fixes #552
2023-12-12 15:46:37 +01:00
konsti 7c1dd71f66
Implement editable installs in dev command (#566)
First step, sufficient to run
```shell
cargo run --bin puffin-dev -- build --editable -w target/editables/ scripts/editable-installs/poetry_editable/
```
and check the wheel to confirm its working. Tests will be added with the
pip-sync integration.
2023-12-12 15:45:55 +01:00
Charlie Marsh c25d5240f1
Remove regular expressions for package name normalization (#614)
Very random but the hand-written version is about 3-4x faster
(benchmarked in a standalone repo).
2023-12-12 05:50:48 +00:00
Charlie Marsh edcb71b1be
Remove some unused fields from `SimpleJson` (#612) 2023-12-11 23:01:37 -05:00
Charlie Marsh 1181288078
Download, build, and install in a single pipeline phase (#605)
## Summary

At present, we have two separate phases within the installation pipeline
related to populating wheels into the cache. The first phase downloads
the distribution, and then builds any source distributions into wheels;
the second phase unzips all the built wheels into the cache.

This PR merges those two phases into one, such that we seamlessly
download, build, and unzip wheels in one pass. This is more efficient,
since we can start unzipping while we build. It also ensures that if the
install _fails_ partway through, we don't end up with a bunch of
downloaded wheels that we never had a chance to unzip. The code is also
much simpler.

The main downside is that the user-facing feedback isn't as granular,
since we only have one phase and one progress bar for what was
originally three distinct phases.

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

## Test Plan

I ran the benchmark script on two separate requirements files, and saw a
7% and 31% speedup respectively:

```text
+ TARGET=./scripts/benchmarks/requirements.txt
+ hyperfine --runs 100 --warmup 10 --prepare 'virtualenv --clear .venv' './target/release/main pip-sync ./scripts/benchmarks/requirements.txt --no-cache' --prepare 'virtualenv --clear .venv' './target/release/puffin pip-sync ./scripts/benchmarks/requirements.txt --no-cache'
Benchmark 1: ./target/release/main pip-sync ./scripts/benchmarks/requirements.txt --no-cache
  Time (mean ± σ):     269.4 ms ±  33.0 ms    [User: 42.4 ms, System: 117.5 ms]
  Range (min … max):   221.7 ms … 446.7 ms    100 runs

Benchmark 2: ./target/release/puffin pip-sync ./scripts/benchmarks/requirements.txt --no-cache
  Time (mean ± σ):     250.6 ms ±  28.3 ms    [User: 41.5 ms, System: 127.4 ms]
  Range (min … max):   207.6 ms … 336.4 ms    100 runs

Summary
  './target/release/puffin pip-sync ./scripts/benchmarks/requirements.txt --no-cache' ran
    1.07 ± 0.18 times faster than './target/release/main pip-sync ./scripts/benchmarks/requirements.txt --no-cache'
```

```text
+ TARGET=./scripts/benchmarks/requirements-large.txt
+ hyperfine --runs 100 --warmup 10 --prepare 'virtualenv --clear .venv' './target/release/main pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache' --prepare 'virtualenv --clear .venv' './target/release/puffin pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache'
Benchmark 1: ./target/release/main pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache
  Time (mean ± σ):      5.053 s ±  0.354 s    [User: 1.413 s, System: 6.710 s]
  Range (min … max):    4.584 s …  6.333 s    100 runs

Benchmark 2: ./target/release/puffin pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache
  Time (mean ± σ):      3.845 s ±  0.225 s    [User: 1.364 s, System: 6.970 s]
  Range (min … max):    3.482 s …  4.715 s    100 runs

Summary
  './target/release/puffin pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache' ran
```
2023-12-11 15:42:29 +00:00
konsti b84fbb86b2
Impl Version debug as display (#606)
Currently, `dbg!` is hard to read because versions are verbose, showing
all optional fields, and we have a lot of versions. Changing debug
formatting to displaying the version number (which can be losslessly
converted to the struct and back) makes this more readable.

See e.g.
https://gist.github.com/konstin/38c0f32b109dffa73b3aa0ab86b9662b

**Before**

```text
version: Version {
    epoch: 0,
    release: [
        1,
        2,
        3,
    ],
    pre: None,
    post: None,
    dev: None,
    local: None,
},
```

**After**

```text
version: "1.2.3",
```
2023-12-11 16:38:14 +01:00
Charlie Marsh 00f1703111
Avoid storing partial wheels in the cache (#604)
Closes https://github.com/astral-sh/puffin/issues/603.
2023-12-09 19:11:30 -05:00
Charlie Marsh 32f54a5947
Use async `Command` for wheel build operations (#601)
Incredibly, this speeds up the install on a large project from 2m6s to
50s.
2023-12-09 16:20:52 +00:00
Charlie Marsh f1c05dcd66
Buffer streamed file writes (#602) 2023-12-09 16:20:31 +00:00
Charlie Marsh 0499fe0613
Fix incorrect unknown size marker in traces (#600)
It said `(unknown size)` for _all_ disk-based wheels.
2023-12-09 04:46:01 +00:00
Charlie Marsh 24d81912cf
Use consistent change event order (#598)
Closes #591.
2023-12-09 04:12:40 +00:00
Charlie Marsh 714a64549b
Use a progress bar for the build phase (#597)
I think this might've been an oversight when copying over the build
reporting during the source distribution refactor.
2023-12-09 04:05:13 +00:00
Charlie Marsh 5878f8dde7
Misc. tweaks to puffin-lib's `lib.rs` (#596) 2023-12-09 03:37:47 +00:00
Charlie Marsh 600c5d072f
Avoid walrus operator in PEP 517 scripts (#595)
I believe this unnecessarily puts a Python 3.7+ requirement on these
scripts.
2023-12-09 01:25:22 +00:00
Charlie Marsh a24534b0ce
Use `rustc-hash` instead of `fxhash` crate (#594)
`fxhash` is the old, less maintained version of this crate
(`rustc-hash`). We use the latter in Ruff.
2023-12-08 20:27:49 +00:00
konsti 6005d7a552
Keep track of in flight unzips using `OnceMap` (#544)
I saw warnings when we were e.g. unzipping wheel and setuptools in two
tasks at the same time. We now keep track of in flight unzips.

This introduces a `OnceMap` abstraction which we also use in the
resolver.
2023-12-08 20:18:11 +00:00
Charlie Marsh ffb8480087
Add `--reinstall` flag to `pip-sync` (#590)
## Summary

This PR adds two flags to `pip-sync`: `--reinstall`, and
`--reinstall-package [PACKAGE]`. The former reinstalls all packages in
the requirements, while the latter can be repeated and reinstalls all
specified packages.

For our purposes, a reinstall includes (1) purging the cache, and (2)
marking any already-installed versions as extraneous.

Closes #572.

Closes https://github.com/astral-sh/puffin/issues/271.
2023-12-08 19:58:42 +00:00
Charlie Marsh 4b8642c6f7
Enable selective cache purging in `puffin clean` (#589)
## Summary

This PR enables `puffin clean` to accept package names as command line
arguments, and selectively purge entries from the cache tied to the
given package.

Relate to #572.

## Test Plan

Modified all the caching tests to run an additional step to (1) purge
the cache, and (2) re-install the package.
2023-12-08 19:51:32 +00:00
Charlie Marsh cbe1cb4229
Avoid race when unpacking wheels (#593)
## Summary

If someone else beats us to the unzip, we should let them win.

We already have a check for this at the top of the unzip method, but
it's also possible that two source distributions get built in parallel
that both try to unpack the same build dependency.
2023-12-08 17:46:19 +00:00
Charlie Marsh 5ae3a8b1cb
Restructure Git cache to include package name (#588)
## Summary

This PR modifies the Git wheel cache to: (1) use a shorter version of
the SHA, to save space; and (2) include the package name, for
consistency with all other buckets.

I considered removing the URL hash entirely, and _just_ using the SHA,
which would be even _more_ consistent with other buckets. But if we
remove the URL, then we won't have separate directories for
subdirectories (which are part of the URL).

Before:

<img width="1035" alt="Screen Shot 2023-12-07 at 7 23 42 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/86afce67-682f-464f-9ba1-0b60d5b7f19f">

After:

<img width="1232" alt="Screen Shot 2023-12-07 at 8 09 23 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/eda42a19-974f-47fe-8c83-54a602ddfd2d">
2023-12-07 20:17:41 -05:00
Zanie Blue ef7be9103c
Parse `SimpleJson` into categorized data in the client (#522)
Extends #517 with a suggestion from @konstin to parse the `SimpleJson`
into an intermediate type `SimpleMetadata(BTreeMap<Version,
VersionFiles>)` before converting to a `VersionMap`. This reduces the
number of times we need to parse the response. Additionally, we cache
the parsed response now instead of `SimpleJson`.

`VersionFiles` stores two vectors with
`WheelFilename`/`SourceDistFilename` and `File` tuples. These can be
iterated over together or separately. A new enum `DistFilename` was
added to capture the `SourceDistFilename` and `WheelFilename` variants
allowing iteration over both vectors.
2023-12-07 11:04:47 -06:00
Charlie Marsh 5d3ce963b2
Raise an error when `pip-sync` manifest contains duplicates (#584)
Also ensures that we filter out any incompatible requirements when
building the install plan. In general, we assume that requirements were
generated by `pip-compile`, in which case all requirements should be
compatible and there should be no duplicates; but we should handle this
case gracefully.

Closes https://github.com/astral-sh/puffin/issues/582.
2023-12-07 05:26:42 +00:00
Charlie Marsh a825b2db06
Shard the registry cache by package (#583)
## Summary

This PR modifies the cache structure in a few ways. Most notably, we now
shard the set of registry wheels by package, and index them lazily when
computing the install plan.

This applies both to built wheels:

<img width="989" alt="Screen Shot 2023-12-06 at 4 42 19 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/0e8a306f-befd-4be9-a63e-2303389837bb">

And remote wheels:

<img width="836" alt="Screen Shot 2023-12-06 at 4 42 30 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/7fd908cd-dd86-475e-9779-07ed067b4a1a">

For other distributions, we now consistently cache using the package
name, which is really just for clarity and debuggability (we could
consider omitting these):

<img width="955" alt="Screen Shot 2023-12-06 at 4 58 30 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/3e8d0f99-df45-429a-9175-d57b54a72e56">

Obliquely closes https://github.com/astral-sh/puffin/issues/575.
2023-12-07 05:02:46 +00:00
Charlie Marsh aa065f5c97
Modify install plan to support all distribution types (#581)
This PR adds caching support for built wheels in the installer.
Specifically, the `RegistryWheelIndex` now indexes both downloaded and
built wheels (from registries), and we have a new `BuiltWheelIndex` that
takes a subdirectory and returns the "best-matching" compatible wheel.

Closes #570.
2023-12-07 04:43:34 +00:00
Charlie Marsh edaeb9b0e8
Add tests for repeated installs with source distributions (#580)
Adds a few more tests for re-installs with various kinds of source
distributions, and changes the tests to use packages that we can safely
import (via `check_command`) for extra validation.

Once we properly respect cached built wheels, we should expect these
snapshots to change, since we'll no longer download and re-build
unnecessarily.
2023-12-06 20:02:32 +00:00
Zanie Blue 2bb04771ce
Allow switching out the resolver's IO (#517)
I'm working off of @konstin's commit here to implement arbitrary unsat
test cases for the resolver.

The entirety of the resolver's io are two functions: Get the version map
for a package (PEP 440 version -> distribution) and get the metadata for
a distribution. A new trait `ResolverProvider` abstracts these two away and
allows replacing the real network requests e.g. with stored responses
(https://github.com/pradyunsg/pip-resolver-benchmarks/blob/main/scenarios/pyrax_198.json).

---------

Co-authored-by: konsti <konstin@mailbox.org>
2023-12-06 11:53:16 -06:00
konsti 366c389385
Parse editable installs (#564)
Parse `-e` for editable installs in `requirements.txt`.

Unlike all the other requirements, editable installs don't have the name
of the package specified.
2023-12-06 18:21:15 +01:00
konsti 3f4d7b7826
Improve path source dist caching (#578)
Path distribution cache reading errors are no longer fatal.

We now invalidate the path file source dists if its modification
timestamp changed, and invalidate path dir source dists if
`pyproject.toml` or alternatively `setup.py` changed, which seems good
choices since changing pyproject.toml should trigger a rebuild and the
user can `touch` the file as part of their workflow.

`CachedByTimestamp` is now a shared util. It doesn't have methods as i
don't think it's worth it yet for two users.

Closes #478

TODO(konstin): Write a test. This is probably twice as much work as that
fix itself, so i made that PR without one for now.
2023-12-06 11:47:01 -05:00
konsti 1bf754556f
Add test for cache source dist installing (#545)
The code changes are outdated, now it's only adding a test
2023-12-06 11:37:55 +00:00
Charlie Marsh 218894375a
Avoid removing existing directories when unzipping and building (#577)
Now that we don't store zipped and unzipped wheels at the same location,
we can avoid these safeguards that entail removing existing directories
when writing. This supersedes
https://github.com/astral-sh/puffin/pull/545.

Closes https://github.com/astral-sh/puffin/issues/554.
2023-12-06 02:36:12 +00:00
Charlie Marsh 5fec63bff5
Add caching for path source distributions (#576)
Follows the strategy that we use for other source distributions.

Closes https://github.com/astral-sh/puffin/issues/557.
2023-12-06 01:33:52 +00:00
Charlie Marsh 5370484307
Remove `.whl` extension for cached, unzipped wheels (#574)
## Summary

This PR uses the wheel stem (e.g., `foo-1.2.3-py3-none-any`) instead of
the wheel name (e.g., `foo-1.2.3-py3-none-any.whl`) when storing
unzipped wheels in the cache, which removes a class of confusing issues
around overwrites and directory-vs.-file collisions.

For now, we retain _both_ the zipped and unzipped wheels in the cache,
though we can easily change this by storing the zipped wheels in a
temporary directory.

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

## Test Plan

Some examples from my local cache:

<img width="835" alt="Screen Shot 2023-12-05 at 4 09 55 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/784146aa-b080-416e-9767-40c843fe5d6a">
<img width="847" alt="Screen Shot 2023-12-05 at 4 12 14 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/4bc7f30f-bef3-47f1-b4e8-da9cabf87f28">
<img width="637" alt="Screen Shot 2023-12-05 at 4 09 50 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/25ca4944-4a06-4a08-ac85-c6f7d8b5c8ea">
2023-12-05 22:41:22 +00:00
Charlie Marsh a15da36d74
Avoid removing local wheels when unzipping (#560)
## Summary

When installing a local wheel, we need to avoid removing the zipped
wheel (since it lives outside of the cache), _and_ need to ensure that
we unzip the wheel into the cache (rather than replacing the zipped
wheel, which may even live outside of the project).

Closes https://github.com/astral-sh/puffin/issues/553.
2023-12-05 17:50:08 +00:00
Charlie Marsh 6f055ecf3b
Remove existing built wheels when building source distributions (#559)
This PR modifies the source distribution building to replace any
existing targets after building the new wheel. In some cases, the
existence of an existing target may be indicative of a bug, so we warn.
It's partially a workaround for some (but not all) of the errors in
https://github.com/astral-sh/puffin/issues/554.
2023-12-05 12:45:24 -05:00
Charlie Marsh f99e3560e8
Avoid returning zipped wheels from registry and URL indexes (#558)
## Summary

This is hard to reproduce, but if you run a long installation process
that errors part-way through, you can end up with zipped wheels in the
`Wheels` cache, which is intended to contain only unzipped wheels. This
PR avoids returning those entries from the registry, which will then
lead to errors downstream when we treat them as directories.
2023-12-05 09:53:45 +01:00
Charlie Marsh 2d1e19e474
Allow yanked versions when specified via `==` (#561)
## Summary

This enables users to rely on yanked versions via explicit `==` markers,
which is necessary in some projects (and, in my opinion, reasonable).

Closes #551.
2023-12-05 09:44:06 +01:00
Charlie Marsh c3a917bbf6
Support granular target Python versions (#534)
## Summary

Allows, e.g., `--python-version 3.7` or `--python-version 3.7.9`. This
was also feedback I received in the original PR.

Closes https://github.com/astral-sh/puffin/issues/533.
2023-12-05 02:38:49 +00:00
Charlie Marsh 06ee321e9c
Use `u64` instead of `u32` in `Version` fields (#555)
It turns out that it's not uncommon to use timestamps as patch versions
(e.g., `20230628214621`). I believe this is the ISO 8601 "basic format".
These can't be represented by a `u32`, so I think it makes sense to just
bump to `u64` to remove this limitation.
2023-12-04 21:00:55 -05:00
Charlie Marsh af13c83177
Overwrite individual files when reflinking (#556)
Similar to #516, but for individual files.

## Test Plan

Ran:

```sh
cargo run -p puffin-cli -- pip-uninstall plaid-python
mkdir -p /Users/crmarsh/workspace/puffin/.venv/lib/python3.10/site-packages/tests
echo "x=1" > /Users/crmarsh/workspace/puffin/.venv/lib/python3.10/site-packages/tests/__init__.py
cargo run -p puffin-cli -- pip-sync requirements.txt --no-cache --verbose
```
2023-12-04 23:59:35 +00:00
Charlie Marsh 5fddcc362e
Improve error messages for 'file not found' case (#550)
Right now, if you specify a wheel that doesn't exist, you get: `no such
file or directory` with no additional context. Oops!
2023-12-04 22:01:51 +00:00
Charlie Marsh 4e05cd5dfd
Show build progress for path source distributions (#549) 2023-12-04 20:56:56 +00:00
konsti d5abd33813
Use atomic writes for the cache consistently (#546)
Ensure we're using atomic writes everywhere in our cache to avoid broken
cache records and error with parallel puffin actions
(https://github.com/astral-sh/puffin/pull/544#issuecomment-1838841581).

All json files that are written to the cache are written atomically and
the build wheels are written to temp dir and then moved atomically. I
didn't touch venv creation though, i don't think that's worth it since
python does not support atomic package installation through its design.
2023-12-04 12:02:01 -05:00
konsti e9c9e9718e
Use version in `RegistryIndex` (#543)
When building up the `RegistryIndex`, index by both package name and
version to fix #537.
2023-12-04 17:26:14 +01:00
Charlie Marsh 95b8316023
Preserve seed packages for non-Puffin-created virtualenvs (#535)
## Summary

This PR modifies the install plan to avoid removing seed packages if the
virtual environment was created by anyone other than Puffin.

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

## Test Plan

- Ran: `virtualenv .venv`.
- Ran: `cargo run -p puffin-cli -- pip-sync
scripts/benchmarks/requirements.txt --verbose --no-cache`.
- Verified that `pip` et al were not removed, and that the logging
including a message around preserving seed packages.
2023-12-04 09:31:00 -05:00
konsti 77b3921b7a
Fix cargo warning (#542)
It's odd that `dev-dependencies` don't default to `dependencies` for
workspace versions.
2023-12-04 11:10:36 +00:00
Charlie Marsh 0ac4254a7e
Enforce target and interpreter `requires-python` versions (#532)
## Summary

This PR modifies the behavior of our `--python-version` override in two
ways:

1. First, we always use the "real" interpreter in the source
distribution builder. I think this is correct. We don't need to use the
fake markers for recursive builds, because all we care about is the
top-level resolution, and we already assume that a single source
distribution will always return the same metadata regardless of its
build environment.
2. Second, we require that source distributions are compatible with
_both_ the "real" interpreter version and the marker environment. This
ensures that we don't try to build source distributions that are
compatible with our interpreter, but incompatible with the target
version.

Closes https://github.com/astral-sh/puffin/issues/407.
2023-12-04 11:27:36 +01:00
Charlie Marsh d96c18b3a8
Respect `requires` for non-`build-backend` PEP 517 builds (#530)
## Summary

This PR modifies `puffin-build` to be closer in behavior to
[pip](a15dd75d98/src/pip/_internal/pyproject.py (L53))
and
[build](de5b44b0c2/src/build/__init__.py (L94)).

Specifically, if a project contains a `[build-system]` field, but no
`build-backend`, we now perform a PEP 517 build (instead of using
`setup.py` directly) _and_ respect the `requires` of the
`[build-system]`. Without this change, we were failing to build source
distributions for packages like `ujson`.

Closes #527.

---------

Co-authored-by: konstin <konstin@mailbox.org>
2023-12-04 10:13:42 +00:00
konsti 6dc8ebcb90
Test interpreter cache invalidation (#540)
Add missing test for #529/#508.
2023-12-04 10:03:43 +00:00
konsti 811c088603
Improve wheel cache docs: Unzipping is lazy (#539)
Also sneaking `fs_err::rename(staging.into_path(), &normalized_path)?`
in here, for a better resolution of
https://github.com/astral-sh/puffin/pull/524#discussion_r1412459016
2023-12-04 10:01:35 +00:00
Charlie Marsh ee009ace86
Remove target directory prior to unzipping (#538)
## Summary

This is not a _fix_ for https://github.com/astral-sh/puffin/issues/537,
but it does ensure that we avoid hard-failing on what's really an
optimization and caching case.
2023-12-04 05:18:45 +00:00
Charlie Marsh fc20d01593
Ignore empty `VIRTUAL_ENV` variables (#536)
I'm not sure how my interpreter gets into this state, but it's certainly
wrong to respect these.
2023-12-04 04:53:26 +00:00
Charlie Marsh 3b55d0b295
Deduplicate various `.dist-info/METADATA` read implementations (#531)
Closes https://github.com/astral-sh/puffin/issues/484.
2023-12-03 21:29:00 -05:00
Charlie Marsh fa3107b173
Use full Python version when determining compatibility (#528)
## Summary

When resolving with Python 3.7.13, I was failing to find a matching
distribution that required Python 3.7.9 or later.
2023-12-04 01:02:24 +00:00
Charlie Marsh 2613382747
Invalidate interpreter marker cache (#529)
In a refactor, we lost the cache invalidation behavior for interpreter
markers, leading to stale interpreter errors for me when creating
environments with different Python versions. Specifically, the
modification timestamp used to be part of the _cache key_ when we used
`cacache`. Now it's not -- but it's stored within the cache. So we need
to validate the key after-the-fact.
2023-12-03 22:44:43 +00:00
Charlie Marsh ee2fca3a48
Add CACHEDIR and .gitignore tags to cache directories (#526)
## Summary

Even if this will typically be in the user's application folder (rather
than a local directory), it's still a good practice.

Closes https://github.com/astral-sh/puffin/issues/280.
2023-12-02 00:37:51 +00:00
konsti 9806901a16
Consolidate wheel caches (#524)
After this change, two wheel caches remain: `built-wheels-v0` and
`wheels-v0`, docs screenshots below. Each contains both the wheel
metadata, cache policy and zip or unzipped wheels under the same name.

The zipped/unzipped strategy is as follows: In `pip-compile`, when we
build a wheel, we store it zipped. When `pip-sync` or a source dist
build in `pip-compile` need to install the wheel, we unzip it, remove
the file and replace it with the unzipped wheel.

This removes `WheelCache` and `UrlIndex` in favor of `Cache` plus
`WheelCache`. The non-built wheel cache now considers index urls and the
url for url wheels.

I'm unsure if we need the `Unzipper` type, this could just be a
function.

I move `no_index` into `IndexUrls` and started using `IndexUrl` up to
the clap level.

I left a number of TODOs in the code, namely performing the actual
invalidation of unzipped wheels and making the `InstallPlan` understand
cache invalidation (i.e. uninstall wheels when their remote changed).


![image](https://github.com/astral-sh/puffin/assets/6826232/c4d45979-485b-4954-848d-fd3347ee2510)
2023-12-01 20:16:33 +00:00
konsti 4551994b7d
Clear built wheels when remote changed (#519)
Remove built wheels alongside their metadata when their index source
dist or url source dist changed. For git source dists, we currently
don't clear the previous build but use a new directory (not sure what's
right here - are there any generic cache GC approaches out there? I've
seen that e.g. spotify keeps its cache at 10GB max, but i also haven't
seen any reusable, well tested approaches for this). Path distributions
are unchanged (#478).

I like the structure of metadata alongside the wheel for cache
invalidation, i'll try to do that for `wheels-v0`/`wheel-metadata-v0`
too. (The unzipped wheels afaik currently lack cache invalidation when
the remote changed.) This should give is roughly the same structure for
wheel and built wheels and a very similar pattern of invalidation.
2023-12-01 14:56:47 -05:00
Zanie Blue 2a8544df9e
Use a custom pubgrub report formatter (#521)
Uses https://github.com/zanieb/pubgrub/pull/10 to drastically simplify
our reporter implementation. This will allow us to make use of upstream
improvements to the reporter e.g.
https://github.com/zanieb/pubgrub/pull/8 without multiple duplicative
pull requests.
2023-12-01 13:36:12 -06:00
Zanie Blue 5f1f207628
Recursively merge existing package directories on installation (#516)
Previously, when installing a package we would delete the target
directory before copying (or linking) the contents of the package.
However, this means that we do not properly support namespace packages
which can share a target directory. Instead the last package to be
installed would be override existing packages. Since we install packages
in parallel, this could result in a race condition where the target
directory already exists which is not allowed when using `clonefile`.
See example error in #515.
c7e63d2dce
provides a regression test for this — it fails on `main`.

Here, we implement a recursive merge when the target directory already
exists. Both packages will be installed into the same directory. We no
longer delete the target directory, which seems okay since we uninstall
packages before installing now.

When files conflict, we will likely throw an error still. The correct
behavior to implement in this case is unclear, as if we just take "first
write wins" or "last write wins" we could end up with some files from
one package and some from another resulting in two broken packages. A
possible solution here is to lock the target directories while copying.
2023-11-30 10:14:51 -06:00
konsti 6841c06e2d
Show error paths in install-wheel-rs (#514)
Ensure that we consistently show a path for all io errors in
install-wheel-rs either (preferred) through `fs_err`, or as fallback by
a custom error type. For zip reading errors, we rely on the caller to
add the name and/or location of the wheel.
2023-11-29 20:14:34 +01:00
konsti 2539f00952
Better tracing span (#513)
This will help us get better insight into what is happening and how long
it takes. I'm particularly interested in how long the different source
dist steps take (download, extract, build step(s)), to make better
decisions about their caching, which i want to report through tracing.

Example output:

```console
$ RUST_LOG=puffin=info cargo run --bin puffin -q -- pip-compile -v --no-cache scripts/requirements/all-kinds.in > /dev/null
  puffin_distribution::source_dist::download_source_dist filename="werkzeug-3.0.1.tar.gz", source_dist=werkzeug @ ff1904eb5e2853bf83db817a7dd53d/werkzeug-3.0.1.tar.gz
  puffin_dispatch::build_source source_dist="werkzeug @ ff1904eb5e2853bf83db817a7dd53d/werkzeug-3.0.1.tar.gz", subdirectory=None
    puffin_build::extract_archive sdist="werkzeug-3.0.1.tar.gz"
    puffin_dispatch::resolve requirements="flit-core <4"
    puffin_dispatch::install requirements="flit-core ==3.9.0", venv="/tmp/.tmpgZAEAh/.venv"
    puffin_build::get_requires_for_build_wheel name="build_wheel", python_version=3.12
    puffin_build::build package_id="werkzeug @ ff1904eb5e2853bf83db817a7dd53d/werkzeug-3.0.1.tar.gz"
      puffin_build::run_python_script name="build_wheel", python_version=3.12
  puffin_dispatch::build_source source_dist="pydantic-extra-types @ git+https://github.com/pydantic/pydantic-extra-types.git@843b753e9e8cb74e83cac55598719b39a4d5ef1f", subdirectory=None
    puffin_dispatch::resolve requirements="hatchling"
    puffin_dispatch::install requirements="hatchling ==1.18.0, trove-classifiers ==2023.11.22, editables ==0.5, pathspec ==0.11.2, pluggy ==1.3.0, packaging ==23.2", venv="/tmp/.tmpJjweUn/.venv"
    puffin_build::get_requires_for_build_wheel name="build_wheel", python_version=3.12
    puffin_build::build package_id="pydantic-extra-types @ git+https://github.com/pydantic/pydantic-extra-types.git@843b753e9e8cb74e83cac55598719b39a4d5ef1f"
      puffin_build::run_python_script name="build_wheel", python_version=3.12
  puffin_distribution::source_dist::download_source_dist filename="django-allauth-0.51.0.tar.gz", source_dist=django-allauth==0.51.0
  puffin_dispatch::build_source source_dist="django-allauth==0.51.0", subdirectory=None
    puffin_build::extract_archive sdist="django-allauth-0.51.0.tar.gz"
    puffin_dispatch::resolve requirements="wheel, setuptools, pip"
    puffin_dispatch::install requirements="setuptools ==69.0.2, pip ==23.3.1, wheel ==0.42.0", venv="/tmp/.tmplSZisu/.venv"
    puffin_build::build package_id="django-allauth==0.51.0"
 Resolved 35 packages in 11.71s
```
2023-11-29 10:34:18 +00:00
konsti 929df586fb
Skip tf-models-nightly in resolve-many dev script for now (#510)
`tf-models-nightly` has pathologic backtracking behaviour, skip it for
now so we can benchmark the rest.
2023-11-28 18:25:32 +00:00
konsti d89fbeb642
Migrate interpreter query to custom caching (#508)
This removes the last usage of cacache by replacing it with a custom,
flat json caching keyed by the digest of the executable path.


![image](https://github.com/astral-sh/puffin/assets/6826232/8f777c4c-1f1b-4656-ba7b-002175270556)

A step towards #478. I've made `CachedByTimestamp<T>` generic over `T`
but intentionally not moved it to `puffin-cache` yet.
2023-11-28 17:14:59 +00:00
konsti 5435d44756
Introduce `Cache`, `CacheBucket` and `CacheEntry` (#507)
This is mostly a mechanical refactor that moves 80% of our code to the
same cache abstraction.

It introduces cache `Cache`, which abstracts away the path of the cache
and the temp dir drop and is passed throughout the codebase. To get a
specific cache bucket, you need to requests your `CacheBucket` from
`Cache`. `CacheBucket` is the centralizes the names of all cache
buckets, moving them away from the string constants spread throughout
the crates.

Specifically for working with the `CachedClient`, there is a
`CacheEntry`. I'm not sure yet if that is a strict improvement over
`cache_dir: PathBuf, cache_file: String`, i may have to rotate that
later.

The interpreter cache moved into `interpreter-v0`.

We can use the `CacheBucket` page to document the cache structure in
each bucket:


![image](https://github.com/astral-sh/puffin/assets/6826232/b023fdfb-e34d-4c2d-8663-b5f73937a539)
2023-11-28 17:11:14 +00:00
Charlie Marsh 3d47d2b1da
Error when `ldd` is not in path (#506)
Closes https://github.com/astral-sh/puffin/issues/493.
2023-11-28 05:55:04 +00:00
konsti 8855f44b5f
Move simple index queries to `CachedClient` (#504)
Replaces the usage of `http-cache-reqwest` for simple index queries with
our custom cached client, removing `http-cache-reqwest` altogether.

The new cache paths are `<cache>/simple-v0/<index>/<package_name>.json`.
I could not test with a non-pypi index since i'm not aware of any other
json indices (jax and torch are both html indices).

In a future step, we can transform the response to be a
`HashMap<Version, {source_dists: Vec<(SourceDistFilename, File)>,
wheels: Vec<(WheeFilename, File)>}` (independent of python version, this
cache is used by all environments together). This should speed up cache
deserialization a bit, since we don't need to try source dist and wheel
anymore and drop incompatible dists, and it should make building the
`VersionMap` simpler. We can speed this up even further by splitting
into a version lists and the info for each version. I'm mentioning this
because deserialization was a major bottleneck in the rust part of the
old python prototype.

Fixes #481
2023-11-28 00:11:03 +00:00
konsti 1142a14f4d
Check compatibility for cached unzipped wheels (#501)
**Motivation** Previously, we would install any wheel with the correct
package name and version from the cache, even if it doesn't match the
current python interpreter.

**Summary** The unzipped wheel cache for registries now uses the entire
wheel filename over the name-version (`editables-0.5-py3-none-any.whl`
over `editables-0.5`).

Built wheels are not stored in the `wheels-v0` unzipped wheels cache
anymore. For each source distribution, there can be multiple built
wheels (with different compatibility tags), so i argue that we need a
different cache structure for them (follow up PR).

For `all-kinds.in` with

```bash
rm -rf cache-all-kinds
virtualenv --clear -p 3.12 .venv
cargo run --bin puffin -- pip-sync --cache-dir cache-all-kinds target/all-kinds.txt
```

we get:

**Before**
```
cache-all-kinds/wheels-v0/
├── registry
│   ├── annotated_types-0.6.0
│   ├── asgiref-3.7.2
│   ├── blinker-1.7.0
│   ├── certifi-2023.11.17
│   ├── cffi-1.16.0
│   ├── [...]
│   ├── tzdata-2023.3
│   ├── urllib3-2.1.0
│   └── wheel-0.42.0
└── url
    ├── 4b8be67c801a7ecb
    │   ├── flask
    │   └── flask-3.0.0.dist-info
    ├── 6781bd6440ae72c2
    │   ├── werkzeug
    │   └── werkzeug-3.0.1.dist-info
    └── a67db8ed076e3814
        ├── pydantic_extra_types
        └── pydantic_extra_types-2.1.0.dist-info

48 directories, 0 files
```

**After**

```
cache-all-kinds/wheels-v0/
├── registry
│   ├── annotated_types-0.6.0-py3-none-any.whl
│   ├── asgiref-3.7.2-py3-none-any.whl
│   ├── blinker-1.7.0-py3-none-any.whl
│   ├── certifi-2023.11.17-py3-none-any.whl
│   ├── cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
│   ├── [...]
│   ├── tzdata-2023.3-py2.py3-none-any.whl
│   ├── urllib3-2.1.0-py3-none-any.whl
│   └── wheel-0.42.0-py3-none-any.whl
└── url
    └── 4b8be67c801a7ecb
        └── flask-3.0.0-py3-none-any.whl

39 directories, 0 files
```

**Outlook** Part of #477 "Fix wheel caching". Further tasks:
* Replace the `CacheShard` with `WheelMetadataCache` which handles urls
properly.
* Delete unzipped wheels when their remote wheel changed
* Store built wheels next to the `metadata.json` in the source dist
directory; delete built wheels when their source dist changed (different
cache bucket, but it's the same problem of fixing wheel caching) I'll
make stacked PRs for those
2023-11-27 16:03:58 -08:00
konsti 71295702bf
Reduce pip_sync test duplication (#502)
Move venv creation and running python to check the installation into
function instead of copy&pasting them every time
2023-11-27 10:21:40 +00:00
Charlie Marsh afda835544
Avoid clone for `WheelMetadataCache` (#500)
This doesn't need to own the underlying data which allows us to remove a
number of clones.
2023-11-25 23:33:59 +00:00
Charlie Marsh 3eb0a43995
Perform a single Git fetch when building source distributions (#499)
## Summary

We need to pass in the distribution with the "precise" URL to avoid
refetching.

## Test Plan

Ran `cargo run -p puffin-cli -- pip-compile requirements.in --verbose`
with `flask @ git+https://github.com/pallets/flask.git` and verified
that we only checked out Flask once.
2023-11-25 23:29:41 +00:00
konsti d54e780843
Source dist metadata refactor (#468)
## Summary and motivation

For a given source dist, we store the metadata of each wheel built
through it in `built-wheel-metadata-v0/pypi/<source dist
filename>/metadata.json`. During resolution, we check the cache status
of the source dist. If it is fresh, we check `metadata.json` for a
matching wheel. If there is one we use that metadata, if there isn't, we
build one. If the source is stale, we build a wheel and override
`metadata.json` with that single wheel. This PR thereby ties the local
built wheel metadata cache to the freshness of the remote source dist.
This functionality is available through `SourceDistCachedBuilder`.

`puffin_installer::Builder`, `puffin_installer::Downloader` and
`Fetcher` are removed, instead there are now `FetchAndBuild` which calls
into the also new `SourceDistCachedBuilder`. `FetchAndBuild` is the new
main high-level abstraction: It spawns parallel fetching/building, for
wheel metadata it calls into the registry client, for wheel files it
fetches them, for source dists it calls `SourceDistCachedBuilder`. It
handles locks around builds, and newly added also inter-process file
locking for git operations.

Fetching and building source distributions now happens in parallel in
`pip-sync`, i.e. we don't have to wait for the largest wheel to be
downloaded to start building source distributions.

In a follow-up PR, I'll also clear built wheels when they've become
stale.

Another effect is that in a fully cached resolution, we need neither zip
reading nor email parsing.

Closes #473

## Source dist cache structure 

Entries by supported sources:
 * `<build wheel metadata cache>/pypi/foo-1.0.0.zip/metadata.json`
* `<build wheel metadata
cache>/<sha256(index-url)>/foo-1.0.0.zip/metadata.json`
* `<build wheel metadata
cache>/url/<sha256(url)>/foo-1.0.0.zip/metadata.json`
But the url filename does not need to be a valid source dist filename

(<https://github.com/search?q=path%3A**%2Frequirements.txt+master.zip&type=code>),
so it could also be the following and we have to take any string as
filename:
* `<build wheel metadata
cache>/url/<sha256(url)>/master.zip/metadata.json`

Example:
```text
# git source dist
pydantic-extra-types @ git+https://github.com/pydantic/pydantic-extra-types.git
# pypi source dist
django_allauth==0.51.0
# url source dist
werkzeug @ ff1904eb5e2853bf83db817a7dd53d/werkzeug-3.0.1.tar.gz
```
will be stored as
```text
built-wheel-metadata-v0
├── git
│   └── 5c56bc1c58c34c11
│       └── 843b753e9e8cb74e83cac55598719b39a4d5ef1f
│           └── metadata.json
├── pypi
│   └── django-allauth-0.51.0.tar.gz
│       └── metadata.json
└── url
    └── 6781bd6440ae72c2
        └── werkzeug-3.0.1.tar.gz
            └── metadata.json
```

The inside of a `metadata.json`:
```json
{
  "data": {
    "django_allauth-0.51.0-py3-none-any.whl": {
      "metadata-version": "2.1",
      "name": "django-allauth",
      "version": "0.51.0",
      ...
    }
  }
}
```
2023-11-24 17:47:58 +00:00
konsti 8d247fe95b
Add `Tags::from_interpreter` (#498)
Small refactoring
2023-11-24 11:36:01 +00:00
konsti f7976ce5cc
Write docs for distribution types (#495)
Document the type hierarchy, excluding the traits.
2023-11-23 13:39:39 +00:00
konsti 1c0e03f807
puffin_interpreter cleanup ahead of #235 (#492)
Preparing for #235, some refactoring to `puffin_interpreter`.

* Added a dedicated error type instead of anyhow
* `InterpreterInfo` -> `Interpreter`
* `detect_virtual_env` now returns an option so it can be chained for
#235
2023-11-23 08:57:33 +00:00
Charlie Marsh 9d35128840
Use Clippy lint table over Cargo config (#490)
Closes https://github.com/astral-sh/puffin/issues/482.
2023-11-22 15:10:27 +00:00
Charlie Marsh 443a0a9df2
Use a sparse Metadata 2.1 representation (#488)
This is an optimization to avoid parsing the entire Metadata 2.1 when we
only need a small subset of the fields.

Closes #175.
2023-11-22 13:25:35 +00:00
konsti a030a466e6
Error before download with no_build (#487)
This is fixes a performance regression where when `--no-build` was set,
the fetcher would still download the source dist only to error
afterwards.
2023-11-22 10:38:10 +00:00
konsti e1dafe7203
Allow applying multiple fixups for version specifiers (#486)
Allow applying multiple fixups for version specifiers, remove the
duplication from the code and add another test case.
2023-11-22 10:26:12 +00:00
konsti ff1100a1ab
Fixup for `>= '2.7'` (#485)
Fixup to allow parsing
https://pypi.org/simple/shellingham/?format=application/vnd.pypi.simple.v1+json
2023-11-22 10:00:12 +00:00
konsti 7c7daa8f83
Consistent Cargo.toml syntax (#483)
Remove the last Cargo.toml inconsistencies, see
1526b3458a (r1401083681).
Now all `[dependencies]` are workspace dependencies.
2023-11-22 08:34:08 +00:00
konsti 934e32ea98
Remove outdated todos (#476) 2023-11-21 13:57:40 +00:00
Charlie Marsh 17228ba04e
Add support for path dependencies (#471)
## Summary

This PR adds support for local path dependencies. The approach mostly
just falls out of our existing approach and infrastructure for Git and
URL dependencies.

Closes https://github.com/astral-sh/puffin/issues/436. (We'll open a
separate issue for editable installs.)

## Test Plan

Added `pip-compile` tests that pre-download a wheel or source
distribution, then install it via local path.
2023-11-21 11:49:42 +00:00
Charlie Marsh f1aa70d9d3
Refactor distribution types to return `Result` (#470)
## Summary

A variety of small refactors to the distribution types crate to (1)
return `Result` if we find an invalid wheel, rather than treating it as
a source distribution with a `.whl` suffix, and (2) DRY up some repeated
code around URLs.
2023-11-20 23:08:54 +00:00
konsti f0841cdb6e
Wheel metadata refactor (#462)
A consistent cache structure for remote wheel metadata:

 * `<wheel metadata cache>/pypi/foo-1.0.0-py3-none-any.json`
* `<wheel metadata
cache>/<digest(index-url)>/foo-1.0.0-py3-none-any.json`
* `<wheel metadata cache>/url/<digest(url)>/foo-1.0.0-py3-none-any.json`

The source dist caching will use a similar structure (#468).
2023-11-20 17:26:36 +01:00
konsti d3e9e1783f
Refactor lenient parsing (#467)
Deduplicate lenient parsing code between version specifiers and
Requirement. Use `warn_once!` since the warnings did show up multiple
times in my code. Fix the macro hygiene in `warn_once!`.
2023-11-20 15:35:38 +00:00
Charlie Marsh 60f595b469
Prefer future stream over `JoinSet` in downloader (#469)
This avoids introducing a static lifetime requirement and, in my
benchmarks, is even a little faster.
2023-11-20 13:23:30 +00:00
Charlie Marsh 8decb29bad
Use a dedicated error type for `puffin-distribution` (#466) 2023-11-20 11:38:27 +00:00
Charlie Marsh 342fc628f0
Store downloaded wheels in a local cache (#463)
This PR modifies the `Fetcher` to cache remote wheels that we _already_
store to-disk. We might read these again in the future, so we might as
well store them in the cache for consistency (rather than using a
temporary directory).
2023-11-20 11:32:22 +00:00
Charlie Marsh 35fd86631b
Unify distribution operations into a single crate (#460)
## Summary

This PR unifies the behavior that lived in the resolver's `distribution`
crates with the behaviors that were spread between the various structs
in the installer crate into a single `Fetcher` struct that is intended
to manage all interactions with distributions. Specifically, the
interface of this struct is such that it can access distribution
metadata, download distributions, return those downloads, etc., all with
a common cache.

Overall, this is mostly just DRYing up code that was repeated between
the two crates, and putting it behind a reasonable shared interface.
2023-11-20 11:22:52 +00:00
konsti 45d032dd7d
Fix wheel filename serialization (#465)
We need an underscore in the wheel filename, not a dash
2023-11-20 11:21:22 +00:00
konsti 46bb18f06e
Track file index (#452)
Track the index (or at least its url) where we got a file from across
the source code.

Fixes #448
2023-11-20 08:48:16 +00:00
Charlie Marsh 6fd582f8b9
Rename `puffin-distribution` to `distribution-types` (#458)
## Summary

This crate only contains types, and I want to introduce a new crate for
all _operations_ on distributions, so this feels like a more natural
name given we also have `pypi-types`.
2023-11-20 09:40:26 +01:00
konsti 2fed14fdc6
Optional serde feature for distribution-filename (#461)
https://github.com/astral-sh/puffin/pull/459#discussion_r1398482972
2023-11-19 19:53:32 +00:00
konsti 255edf4445
Serde support for WheelFilename through str repr (#459)
I need this later, splitting out for PR size
2023-11-19 19:43:14 +00:00
Charlie Marsh 3df3110800
Use shortened `anyhow::Result` everywhere (#457) 2023-11-19 19:26:21 +00:00
Charlie Marsh 380030bb5c
Pin all resolver tests using `--exclude-newer` (#456)
Uses yesterday's date, which should make it much less likely that our
tests become stale over time.

Closes https://github.com/astral-sh/puffin/issues/449.
2023-11-19 15:10:57 +00:00
konsti 24f00f5a33
Create cache dir before canonicalize (#454)
`fs::canonicalize` fails when the directory does not exist, which i
missed in #453
2023-11-19 13:49:13 +00:00
konsti ab60233131
Use absolute cache paths (#453)
Previously, git requirements would fail when setting `--cache-dir`:

```console
$ cargo run --bin puffin -- pip-compile --cache-dir cache-all-kinds scripts/benchmarks/requirements/all-kinds.in
error: Failed to build distribution from URL: git+https://github.com/pydantic/pydantic-extra-types.git
  Caused by: Invalid path URL: cache-all-kinds/git-v0/db/b49ffcfeb6c2e9d8
  ```

The cause is using a relative and not an absolute path, which `Url` needs, the solution is to turn the cache dir into an absolute path.

This never showed up in the tests since the tests use absolute temp dirs for everything.
2023-11-19 13:32:32 +00:00
konsti dd4347980a
Fix tests: Certifi got an update (#451) 2023-11-19 12:10:54 +00:00
Zanie Blue 5dedfeb097
Fix import of `CacheArgs` in `puffin-cli` (#447)
```
error[E0432]: unresolved imports `puffin_cache::CacheArgs`, `puffin_cache::CacheDir`
  --> crates/puffin-cli/src/main.rs:11:20
   |
11 | use puffin_cache::{CacheArgs, CacheDir};
   |                    ^^^^^^^^^  ^^^^^^^^ no `CacheDir` in the root
   |                    |
   |                    no `CacheArgs` in the root
   |
note: found an item that was configured out
  --> /Users/mz/eng/src/astral-sh/puffin/crates/puffin-cache/src/lib.rs:7:15
   |
7  | pub use cli::{CacheArgs, CacheDir};
   |               ^^^^^^^^^
   = note: the item is gated behind the `clap` feature
note: found an item that was configured out
  --> /Users/mz/eng/src/astral-sh/puffin/crates/puffin-cache/src/lib.rs:7:26
   |
7  | pub use cli::{CacheArgs, CacheDir};
   |                          ^^^^^^^^
   = note: the item is gated behind the `clap` feature

For more information about this error, try `rustc --explain E0432`.
error: could not compile `puffin-cli` (bin "puffin") due to previous error
```
2023-11-17 15:35:01 -05:00
Charlie Marsh 03599d2bb4
Split resolver inputs into manifest and options (#446)
## Summary

This is a refactor to address a TODO in the build context whereby we
aren't respecting the resolution options in recursive resolutions. Now,
the options are split out from the resolution _manifest_, and shared
across the build context tree.
2023-11-17 18:53:53 +00:00
konsti 9db6644be6
Test requirements script (#382)
This script can compare different requirements between pip(-compile) and
puffin across python versions, with debug and release builds.

Examples:
```shell
scripts/compare_with_pip/compare_with_pip.py
scripts/compare_with_pip/compare_with_pip.py -p 3.10
scripts/compare_with_pip/compare_with_pip.py --release -p 3.9 --target 'transformers[deepspeed-testing,dev-tensorflow]'
```

It found a bunch of fixed bugs, e.g. the lack of yanked package handling
and source dist handling, as well as #423, which is currently most of
the output.

Example output:
https://gist.github.com/konstin/9ccf8dc7c2dcca737bf705429ced4892

#443 should be merged first
2023-11-17 18:26:55 +00:00
konsti bf71e7adcf
Add graphviz output to puffin-dev resolve-cli (#443)
I added output in graphviz DOT format to `puffin-dev resolve-cli` to
help with debugging resolutions. This requires tracking the requested
ranges in the graph. I also fixed the direction of the graph.

 Output for `black`:

```dot
digraph {
    0 [ label="click\n8.1.7"]
    1 [ label="black\n23.11.0"]
    2 [ label="packaging\n23.2"]
    3 [ label="mypy-extensions\n1.0.0"]
    4 [ label="tomli\n2.0.1"]
    5 [ label="pathspec\n0.11.2"]
    6 [ label="typing-extensions\n4.8.0"]
    7 [ label="platformdirs\n4.0.0"]
    1 -> 0 [ label=">=8.0.0"]
    1 -> 3 [ label=">=0.4.3"]
    1 -> 5 [ label=">=0.9.0"]
    1 -> 4 [ label=">=1.1.0"]
    1 -> 6 [ label=">=4.0.1"]
    1 -> 2 [ label=">=22.0"]
    1 -> 7 [ label=">=2"]
}
```


![image](https://github.com/astral-sh/puffin/assets/6826232/4a440fcd-6248-4349-8e1a-c3e0363e42b1)

transformers:


![image](https://github.com/astral-sh/puffin/assets/6826232/a13a693c-a8c0-4a4f-95d9-3458431c678a)

jupyter:


![graphviz](https://github.com/astral-sh/puffin/assets/6826232/ef730033-6fd9-4ea9-ac93-8c874c19a101)
2023-11-17 18:16:24 +00:00
Zanie Blue d39e9b3499
Remove duplicate `cache_dir` argument from `puffin-dev resolve-cli` (#445) 2023-11-17 17:17:00 +00:00
Zanie Blue 221751487c
Use `UnusableDependencies` for URL dependency conflicts (#425)
Extends #424 with support for URL dependency incompatibilities.

Requires changes to `miette` to prevent URLs from being word wrapped;
accepted upstream in https://github.com/zkat/miette/pull/321
2023-11-17 08:28:12 -06:00
Charlie Marsh 2094680cdd
Add a `warn_user_once!` macro (#442)
Closes https://github.com/astral-sh/puffin/issues/429.
2023-11-17 02:34:06 +00:00
Charlie Marsh 25fcee0d9f
Avoid using incompatible wheels for source distribution-less packages (#441)
We're willing to use platform-incompatible wheels during resolution, to
quicken access to metadata... But we should avoid choosing an
incompatible wheel if the package lacks a source distribution since, in
that case, we definitely won't be able to install it.

Closes https://github.com/astral-sh/puffin/issues/439.
2023-11-17 02:10:54 +00:00
Charlie Marsh b1c29447df
Use `temp_dir` casing everywhere (#440) 2023-11-16 21:04:10 +00:00
konsti 1883dbdc21
Always¹ clear temporary directories (#437)
Always¹ clear the temporary directories we create.

* Clear source dist downloads: Previously, the temporary directories
would remain in the cache dir, now they are cleared properly
* Clear wheel file downloads: Delete the `.whl` file, we only need to
cache the unpacked wheel
* Consistent handling of cache arguments: Abstract the handling for CLI
cache args away, again making sure we remove the `--no-cache` temp dir.

There are no more `into_path()` calls that persist `TempDir`s that i
could find.

¹Assuming drop is run, and deleting the directory doesn't silently
error.
2023-11-16 20:49:48 +00:00
Zanie Blue 0d9d4f9fca
Add an `UnusableDependencies` incompatibility kind and use for conflicting versions (#424)
Addresses
https://github.com/astral-sh/puffin/issues/309#issuecomment-1792648969

Similar to #338 this throws an error when merging versions results in an
empty set. Instead of propagating that error, we capture it and return a
new dependency type of `Unusable`. Unusable dependencies are a new
incompatibility kind which includes an arbitrary "reason" string that we
present to the user. Adding a new incompatibility kind requires changes
to the vendored pubgrub crate.

We could use this same incompatibility kind for conflicting urls as in
#284 which should allow the solver to backtrack to another valid version
instead of failing (see #425).

Unlike #383 this does not require changes to PubGrub's package mapping
model. I think in the long run we'll want PubGrub to accept multiple
versions per package to solve this specific issue, but we're interested
in it being merged upstream first. This pull request is just using the
issue as a simple case to explore adding a new incompatibility type.

We may or may not be able convince them to add this new incompatibility
type upstream. As discussed in
https://github.com/pubgrub-rs/pubgrub/issues/152, we may want a more
general incompatibility kind instead which can be used for arbitrary
problems. An upstream pull request has been opened for discussion at
https://github.com/pubgrub-rs/pubgrub/pull/153.

Related to:
- https://github.com/pubgrub-rs/pubgrub/issues/152
- #338 
- #383

---------

Co-authored-by: konsti <konstin@mailbox.org>
2023-11-16 20:02:06 +00:00
Zanie Blue 832058dbba
Switch from vendored PubGrub to a fork (#438)
A fork will let us stay up to date with the upstream while replaying our
work on top of it.

I expect a similar workflow to the RustPython-Parser fork we maintained,
except that I wrote an automation to create tags for each commit on the
fork (https://github.com/zanieb/pubgrub/pull/2) so we do not need to
manually tag and document each commit.

To update with the upstream:

- Rebase our fork's `main` branch on top of the latest changes in
upstream's `dev` branch
- Force push, overwriting our `main` branch history
- Change the commit hash here to the last commit on `main` in our fork

Since we automatically tag each commit on the fork, we should never lose
the commits that are dropped from `main` during rebase.
2023-11-16 13:49:19 -06:00
konsti e41ec12239
Option to resolve at a fixed timestamp with `pip-compile --exclude-newer YYYY-MM-DD` (#434)
This works by filtering out files with a more recent upload time, so if
the index you use does not provide upload times, the results might be
inaccurate. pypi provides upload times for all files. This is, the field
is non-nullable in the warehouse schema, but the simple API PEP does not
know this field.

If you have only pypi dependencies, this means deterministic,
reproducible(!) resolution. We could try doing the same for git repos
but it doesn't seem worth the effort, i'd recommend pinning commits
since git histories are arbitrarily malleable and also if you care about
reproducibility and such you such not use git dependencies but a custom
index.

Timestamps are given either as RFC 3339 timestamps such as
`2006-12-02T02:07:43Z` or as UTC dates in the same format such as
`2006-12-02`. Dates are interpreted as including this day, i.e. until
midnight UTC that day. Date only is required to make this ergonomic and
midnight seems like an ergonomic choice.

In action for `pandas`:

```console
$ target/debug/puffin pip-compile --exclude-newer 2023-11-16 target/pandas.in
Resolved 6 packages in 679ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    target/debug/puffin pip-compile --exclude-newer 2023-11-16 target/pandas.in
numpy==1.26.2
    # via pandas
pandas==2.1.3
python-dateutil==2.8.2
    # via pandas
pytz==2023.3.post1
    # via pandas
six==1.16.0
    # via python-dateutil
tzdata==2023.3
    # via pandas
$ target/debug/puffin pip-compile --exclude-newer 2022-11-16 target/pandas.in
Resolved 5 packages in 655ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    target/debug/puffin pip-compile --exclude-newer 2022-11-16 target/pandas.in
numpy==1.23.4
    # via pandas
pandas==1.5.1
python-dateutil==2.8.2
    # via pandas
pytz==2022.6
    # via pandas
six==1.16.0
    # via python-dateutil
$ target/debug/puffin pip-compile --exclude-newer 2021-11-16 target/pandas.in
Resolved 5 packages in 594ms
# This file was autogenerated by Puffin v0.0.1 via the following command:
#    target/debug/puffin pip-compile --exclude-newer 2021-11-16 target/pandas.in
numpy==1.21.4
    # via pandas
pandas==1.3.4
python-dateutil==2.8.2
    # via pandas
pytz==2021.3
    # via pandas
six==1.16.0
    # via python-dateutil
```
2023-11-16 19:46:17 +00:00
konsti 0d455ebd06
Always use puffin as binary name (#435)
It doesn't matter how exactly the user called puffin, the lockfile
should look the same either way.
2023-11-16 19:05:46 +01:00
konsti 751f7fa9c6
Improve PEP 691 compatibility (#428)
[PEP 691](https://peps.python.org/pep-0691/#project-detail) has slightly
different, more relaxed rules around file metadata. These changes are
now reflected in the `File` struct. This will make it easier to support
alternative indices.

I had expected that i need to introduce a separate type for that, so i'm
happy it's two `Option`s more and an alias.

Part of #412
2023-11-16 19:03:44 +01:00
konsti 3a4988f999
Small test cleanup after #431 (#433)
Remove unused filters after #431
2023-11-16 11:22:47 +00:00
konsti c0339893e7
Use `sys.executable` as python root path (#431)
Previously, we were assuming that `which <python>` return the path to
the python executable. This is not true when using pyenv shims, which
are bash scripts. Instead, we have to use `sys.executable`. Luckily,
we're already querying the python interpreter and can do it in that
pass.

We are also not allowed to cache the execution of the python interpreter
through the shim because pyenv might change the target. As a heuristic,
we check whether `sys.executable`, the real binary, is the same our
canonicalized `which` result.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2023-11-16 12:16:49 +01:00
Charlie Marsh d3caf9ae86
Choose most-compatible wheel in resolver and installer (#422)
## Summary

This PR implements logic to sort wheels by priority, where priority is
defined as preferring more "specific" wheels over less "specific"
wheels. For example, in the case of Black, my machine now selects
`black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl`, whereas sorting by
lowest priority instead gives me `black-23.11.0-py3-none-any.whl`.

As part of this change, I've also modified the resolver to fallback to
using incompatible wheels when determining package metadata, if no
compatible wheels are available.

The `VersionMap` was also moved out of `resolver.rs` and into its own
file with a wrapper type, for clarity.

Closes https://github.com/astral-sh/puffin/issues/380.
Closes https://github.com/astral-sh/puffin/issues/421.
2023-11-15 18:22:11 +00:00
konsti 1147a4de14
Simpler and more resilient pip compile tests (#426)
The pip compile test now explicitly set their python version and `puffin
venv` resolves e.g. `python3.12` correctly now. The venv creation is
moved to a shared method
2023-11-15 18:32:33 +01:00
Charlie Marsh a20325f184
Remove unnecessary clones in resolver (#420) 2023-11-13 21:00:52 -05:00
Charlie Marsh 13ba4405aa
Update README and crates manifest (#419) 2023-11-14 01:20:07 +00:00
konsti bacf1dc911
Filter out yanked files (#413)
Implement two behaviors for yanked versions:

* During `pip-compile`, yanked versions are filtered out entirely, we
currently treat them is if they don't exist. This is leads to confusing
error messages because a version that does exist seems to have suddenly
disappeared.
* During `pip-sync`, we warn when we fetch a remote distribution and it
has been yanked. We currently don't warn on cached or installed
distributions that have been yanked.
2023-11-13 20:58:50 +00:00
Charlie Marsh 28ec4e79f0
Co-locate lenient requirement parsing (#418)
No behavior changes.
2023-11-13 15:46:21 -05:00
Charlie Marsh 437d4fb87e
Add trailing-comma fix to lenient requirements (#417)
Closes https://github.com/astral-sh/puffin/issues/408.
2023-11-13 20:20:57 +00:00
Charlie Marsh 582c94cec3
Add missing-dot fix to lenient requirements (#416)
Part of https://github.com/astral-sh/puffin/issues/408.
2023-11-13 20:17:01 +00:00
Charlie Marsh 0af2f7e39f
Use `anstream` to avoid writing colorized output (#415)
A more robust solution to avoiding colorized output by ensuring we write
to `stdout` and `stderr` via the
[`anstream`](https://docs.rs/anstream/latest/anstream/) crate.

Closes https://github.com/astral-sh/puffin/issues/393.
2023-11-13 20:00:12 +00:00
konsti 76a41066ac
Filter out incompatible dists (#398)
Filter out source dists and wheels whose `requires-python` from the
simple api is incompatible with the current python version.

This change showed an important problem: When we use a fake python
version for resolving, building source distributions breaks down because
we can only build with versions we actually have.

This change became surprisingly big. The tests now require python 3.7 to
be installed, but changing that would mean an even bigger change.

Fixes #388
2023-11-13 17:14:07 +01:00
konsti 81c9cd0d4a
Print url for bad json error (#409)
Split out from #382
2023-11-13 11:41:20 +00:00
konsti fa423b8751
Backend path is supported (#405)
The check is outdated now
2023-11-13 08:19:20 +00:00
Zanie Blue beadd3274a
Improve debug log version display (#403)
Follow-up to https://github.com/astral-sh/puffin/pull/346 for some debug
messages
2023-11-10 17:07:29 -06:00
Charlie Marsh 06b312de7e
Overwrite existing files when hardlinking (#402)
## Summary

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

## Test Plan

Installed `jupyter_core==5.5.0`, then removed the `jupyter_core` and
`jupyter_core-5.5.0.dist-info` directories from my virtualenv manually,
but left `jupyter.py`. I then re-ran `puffin pip-compile`, and verified
that it errored on `main` but succeeded here.
2023-11-10 20:24:19 +00:00
Charlie Marsh 56a4b51eb6
Refactor hardlink fallback to use an enum (#401)
Makes an invalid state unrepresentable (`first_try_hard_linking = true`,
`use_copy_fallback` = true`).
2023-11-10 15:18:51 -05:00
Andrew Gallant ff4d079dc9
pep508-rs: remove \x20 trailing whitespace hack (#400)
... we just remove the trailing whitespace from the input and that
resolves things.

Thanks @konstin for pointing this out!

Ref https://github.com/astral-sh/puffin/pull/399#discussion_r1389854823
2023-11-10 15:11:29 -05:00
Charlie Marsh e8108cb28b
Remove `__pycache__` directories when uninstalling (#397)
According to the [packaging
documentation](https://packaging.python.org/en/latest/specifications/binary-distribution-format/#binary-distribution-format),
"uninstallers should be smart enough to remove .pyc even if it is not
mentioned in RECORD". Previously, we weren't handling this case, so if
you installed via Puffin, then imported a file (to trigger bytecode
compilation), then uninstalled, we'd leave spare `__pycache__`
directories around.

Closes https://github.com/astral-sh/puffin/issues/395.
2023-11-10 14:55:33 -05:00
Andrew Gallant 63f7f65190
change global allocator to jemalloc (and mimalloc on Windows) (#399)
This copies the allocator configuration used in the Ruff project. In
particular, this gives us an instant 10% win when resolving the top 1K
PyPI packages:

    $ hyperfine \
"./target/profiling/puffin-dev-main resolve-many --cache-dir
cache-docker-no-build --no-build pypi_top_8k_flat.txt --limit 1000 2>
/dev/null" \
"./target/profiling/puffin-dev resolve-many --cache-dir
cache-docker-no-build --no-build pypi_top_8k_flat.txt --limit 1000 2>
/dev/null"
Benchmark 1: ./target/profiling/puffin-dev-main resolve-many --cache-dir
cache-docker-no-build --no-build pypi_top_8k_flat.txt --limit 1000 2>
/dev/null
Time (mean ± σ): 974.2 ms ± 26.4 ms [User: 17503.3 ms, System: 2205.3
ms]
      Range (min … max):   943.5 ms … 1015.9 ms    10 runs

Benchmark 2: ./target/profiling/puffin-dev resolve-many --cache-dir
cache-docker-no-build --no-build pypi_top_8k_flat.txt --limit 1000 2>
/dev/null
Time (mean ± σ): 883.1 ms ± 23.3 ms [User: 14626.1 ms, System: 2542.2
ms]
      Range (min … max):   849.5 ms … 916.9 ms    10 runs

    Summary
'./target/profiling/puffin-dev resolve-many --cache-dir
cache-docker-no-build --no-build pypi_top_8k_flat.txt --limit 1000 2>
/dev/null' ran
1.10 ± 0.04 times faster than './target/profiling/puffin-dev-main
resolve-many --cache-dir cache-docker-no-build --no-build
pypi_top_8k_flat.txt --limit 1000 2> /dev/null'

I was moved to do this because I noticed `malloc`/`free` taking up a
fairly sizeable percentage of time during light profiling.

As is becoming a pattern, it will be easier to review this
commit-by-commit.

Ref #396 (wouldn't call this issue fixed)

-----

I did also try adding a `smallvec` optimization to the
`Version::release` field, but it didn't bare any fruit. I still think
there is more to explore since the results I observed don't quite line
up with what I expect. (So probably either my mental model is off or my
measurement process is flawed.) You can see that attempt with a little
more explanation here:
f9528b4ecd

In the course of adding the `smallvec` optimization, I also shrunk the
`Version` fields from a `usize` to a `u32`. They should at least be a
fixed size integer since version numbers aren't used to index memory,
and I shrunk it to `u32` since it seems reasonable to assume that all
version numbers will be smaller than `2^32`.
2023-11-10 14:48:59 -05:00
konsti d8408b1783
Add source to failing metadata parsing (#387)
Before:
```
cargo run --bin puffin-dev -q -- resolve-cli "transformers[accelerate, agents, all, audio, codecarbon, deepspeed, deepspeed-testing, dev, dev-tensorflow, dev-torch, docs, docs_specific, flax, flax-speech, ftfy, integrations, ja, modelcreation, onnx, onnxruntime, optuna, quality, ray, retrieval, sagemaker, sentencepiece, serving, sigopt, sklearn, speech, testing, tf, tf-cpu, tf-speech, timm, tokenizers, torch, torch-speech, torch-vision, torchhub, video, vision]"
puffin-dev failed
  Caused by: No solution found when resolving: transformers[accelerate,agents,all,audio,codecarbon,deepspeed,deepspeed-testing,dev,dev-tensorflow,dev-torch,docs,docs-specific,flax,flax-speech,ftfy,integrations,ja,modelcreation,onnx,onnxruntime,optuna,quality,ray,retrieval,sagemaker,sentencepiece,serving,sigopt,sklearn,speech,testing,tf,tf-cpu,tf-speech,timm,tokenizers,torch,torch-speech,torch-vision,torchhub,video,vision]
  Caused by: Not a valid package or extra name: ".none". Names must start and end with a letter or digit and may only contain -, _, ., and alphanumeric characters
```
After:
```
cargo run --bin puffin-dev -q -- resolve-cli "transformers[accelerate, agents, all, audio, codecarbon, deepspeed, deepspeed-testing, dev, dev-tensorflow, dev-torch, docs, docs_specific, flax, flax-speech, ftfy, integrations, ja, modelcreation, onnx, onnxruntime, optuna, quality, ray, retrieval, sagemaker, sentencepiece, serving, sigopt, sklearn, speech, testing, tf, tf-cpu, tf-speech, timm, tokenizers, torch, torch-speech, torch-vision, torchhub, video, vision]"
puffin-dev failed
  Caused by: No solution found when resolving: transformers[accelerate,agents,all,audio,codecarbon,deepspeed,deepspeed-testing,dev,dev-tensorflow,dev-torch,docs,docs-specific,flax,flax-speech,ftfy,integrations,ja,modelcreation,onnx,onnxruntime,optuna,quality,ray,retrieval,sagemaker,sentencepiece,serving,sigopt,sklearn,speech,testing,tf,tf-cpu,tf-speech,timm,tokenizers,torch,torch-speech,torch-vision,torchhub,video,vision]
  Caused by: Couldn't parse metadata in fastapi-0.10.1-py3-none-any.whl (97ac91cb7cd2baab1a50b0c7a17d83/fastapi-0.10.1-py3-none-any.whl)
  Caused by: Not a valid package or extra name: ".none". Names must start and end with a letter or digit and may only contain -, _, ., and alphanumeric characters
```
2023-11-10 18:33:49 +00:00
Charlie Marsh b3edf7c2b2
Delete any directories listed in the RECORD file (#394)
## Summary

It looks like, when you install `pip`, it includes a bunch of
`__pycache__` directories in the RECORD file (although these directories
don't exist until you run `pip`). Our uninstaller assumed that the
RECORD file only contained _files_.

Closes https://github.com/astral-sh/puffin/issues/389.
2023-11-10 18:17:52 +00:00
Charlie Marsh 6a15950cb5
Rename `Distribution` to `Dist` in all structs and traits (#384)
We tend to avoid abbreviations, but this one is just so long and
absolutely ubiquitous.
2023-11-10 14:55:11 +00:00
konsti 5cef40d87a
Add proper caching for pypi metadata fetching kinds (#368)
I intend this to become the main form of caching for puffin: You can
make http requests, you tranform the data to what you really need, you
have control over the cache key, and the cache is always json (or
anything else much faster we want to replace it with as long as it's
serde!)
2023-11-10 11:03:40 +00:00
konsti d1b57acaa8
Implement PEP 517 backend-path (#385)
Closes #192
2023-11-10 11:54:23 +01:00
Charlie Marsh a148f9d0be
Refactor distribution types to adhere to a clear hierarchy (#369)
## Summary

This PR refactors our `RemoteDistribution` type such that it now follows
a clear hierarchy that matches the actual variants, and encodes the
differences between source and built distributions:

```rust
pub enum Distribution {
    Built(BuiltDistribution),
    Source(SourceDistribution),
}

pub enum BuiltDistribution {
    Registry(RegistryBuiltDistribution),
    DirectUrl(DirectUrlBuiltDistribution),
}

pub enum SourceDistribution {
    Registry(RegistrySourceDistribution),
    DirectUrl(DirectUrlSourceDistribution),
    Git(GitSourceDistribution),
}

/// A built distribution (wheel) that exists in a registry, like `PyPI`.
pub struct RegistryBuiltDistribution {
    pub name: PackageName,
    pub version: Version,
    pub file: File,
}

/// A built distribution (wheel) that exists at an arbitrary URL.
pub struct DirectUrlBuiltDistribution {
    pub name: PackageName,
    pub url: Url,
}

/// A source distribution that exists in a registry, like `PyPI`.
pub struct RegistrySourceDistribution {
    pub name: PackageName,
    pub version: Version,
    pub file: File,
}

/// A source distribution that exists at an arbitrary URL.
pub struct DirectUrlSourceDistribution {
    pub name: PackageName,
    pub url: Url,
}

/// A source distribution that exists in a Git repository.
pub struct GitSourceDistribution {
    pub name: PackageName,
    pub url: Url,
}
```

Most of the PR just stems downstream from this change. There are no
behavioral changes, so I'm largely relying on lint, tests, and the
compiler for correctness.
2023-11-10 02:45:41 +00:00
Andrew Gallant 33c0901a28
distribution-filename: speed up is_compatible (#367)
This PR tweaks the representation of `Tags` in order to offer a
faster implementation of `WheelFilename::is_compatible`. We now use a
nested map of tags that lets us avoid looping over every supported
platform tag. As the code comments suggest, that is the essential gain.
We still do not mind looping over the tags in each wheel name since they
tend to be quite small. And pushing our thumb on that side of things can
make things worse overall since it would likely slow down WheelFilename
construction itself.

For micro-benchmarks, we improve considerably for compatibility
checking:

    $ critcmp base test3
group base test3
----- ---- -----
build_platform_tags/burntsushi-archlinux 1.00 46.2±0.28µs ? ?/sec 2.48
114.8±0.45µs ? ?/sec
wheelname_parsing/flyte-long-compatible 1.00 624.8±3.31ns 174.0 MB/sec
1.01 629.4±4.30ns 172.7 MB/sec
wheelname_parsing/flyte-long-incompatible 1.00 743.6±4.23ns 165.4 MB/sec
1.00 746.9±4.62ns 164.7 MB/sec
wheelname_parsing/flyte-short-compatible 1.00 526.7±4.76ns 54.3 MB/sec
1.01 530.2±5.81ns 54.0 MB/sec
wheelname_parsing/flyte-short-incompatible 1.00 540.4±4.93ns 60.0 MB/sec
1.01 545.7±5.31ns 59.4 MB/sec
wheelname_parsing_failure/flyte-long-extension 1.00 13.6±0.13ns 3.2
GB/sec 1.01 13.7±0.14ns 3.2 GB/sec
wheelname_parsing_failure/flyte-short-extension 1.00 14.0±0.20ns 1160.4
MB/sec 1.01 14.1±0.14ns 1146.5 MB/sec
wheelname_tag_compatibility/flyte-long-compatible 11.33 159.8±2.79ns
680.5 MB/sec 1.00 14.1±0.23ns 7.5 GB/sec
wheelname_tag_compatibility/flyte-long-incompatible 237.60
1671.8±37.99ns 73.6 MB/sec 1.00 7.0±0.08ns 17.1 GB/sec
wheelname_tag_compatibility/flyte-short-compatible 16.07 223.5±8.60ns
128.0 MB/sec 1.00 13.9±0.30ns 2.0 GB/sec
wheelname_tag_compatibility/flyte-short-incompatible 149.83 628.3±2.13ns
51.6 MB/sec 1.00 4.2±0.10ns 7.6 GB/sec

We do regress slightly on the time it takes for `Tags::new` to run, but
this is somewhat expected. And in absolute terms, 114us is perfectly
acceptable given that it's only executed ~once for each `puffin`
invocation.

Ad hoc benchmarks indicate an overall 25% perf improvement in `puffin
pip-compile` times. This roughly corresponds with how much time
`is_compatible` was taking. Indeed, profiling confirms that it has
virtually disappeared from the profile.

Fixes #157
2023-11-09 09:01:03 -05:00
konsti bdb89b4072
Allow setting num tasks in puffin-dev parallel resolve (#374) 2023-11-09 13:12:03 +00:00
Charlie Marsh 6144de0a7e
Implement some minor optimizations to version match (#371)
`Range::intersection` goes from 74.2% to 64.9%, and `sortable_tuple`
goes from 2.3% to 1.5%.
2023-11-09 02:11:40 +00:00
Charlie Marsh cfd84d6365
Support resolving for an alternate Python distribution (#364)
## Summary

Low-priority but fun thing to end the day. You can now pass
`--target-version py37`, and we'll generate a resolution for Python 3.7.

See: https://github.com/astral-sh/puffin/issues/183.
2023-11-08 23:19:16 +00:00
konsti d407bbbee6
Special case missing header build errors (on linux) (#354)
One of the most common errors i observed are build failures due to
missing header files. On ubuntu, this generally means that you need to
install some `<...>-dev` package that the documentation tells you about,
e.g. [mysqlclient](https://github.com/PyMySQL/mysqlclient#linux) needs
`default-libmysqlclient-dev`, [some psycopg
versions](https://www.psycopg.org/psycopg3/docs/basic/install.html#local-installation)
(i remember that this was always required at some earlier point) require
`libpq-dev` and pygraphviz wants `graphviz-dev`. This is quite common
for many scientific packages (where conda has an advantage because they
can provide those package as a dependency).

The error message can be completely inscrutable if you're just a python
programmer (or user) and not a c programmer (example: pygraphviz):

```
warning: no files found matching '*.png' under directory 'doc'
warning: no files found matching '*.txt' under directory 'doc'
warning: no files found matching '*.css' under directory 'doc'
warning: no previously-included files matching '*~' found anywhere in distribution
warning: no previously-included files matching '*.pyc' found anywhere in distribution
warning: no previously-included files matching '.svn' found anywhere in distribution
no previously-included directories found matching 'doc/build'
pygraphviz/graphviz_wrap.c:3020:10: fatal error: graphviz/cgraph.h: No such file or directory
 3020 | #include "graphviz/cgraph.h"
      |          ^~~~~~~~~~~~~~~~~~~
compilation terminated.
error: command '/usr/bin/gcc' failed with exit code 1
```

The only relevant part is `Fatal error: graphviz/cgraph.h: No such file
or directory`. Why is this file not there and how do i get it to be
there?

This is even harder to spot in pip's output, where it's 11 lines above
the last line:


![image](https://github.com/astral-sh/puffin/assets/6826232/7a3d7279-e7b1-4511-ab22-d0a35be5e672)

I've special cased missing headers and made sure that the last line
tells you the important information: We're missing some header, please
check the documentation of {package} {version} for what to install:


![image](https://github.com/astral-sh/puffin/assets/6826232/4bbb8923-5a82-472f-ab1f-9e1471aa2896)

Scrolling up:


![image](https://github.com/astral-sh/puffin/assets/6826232/89a2495a-e188-4288-b534-ad885ee08763)

The difference gets even clearer with a default ubuntu terminal with its
80 columns:


![image](https://github.com/astral-sh/puffin/assets/6826232/49fb27bc-07c6-4b10-a1a1-30ec8e112438)

---

Note that the situation is better for a missing compiler, there i get:

```
[...]
warning: no previously-included files matching '*~' found anywhere in distribution
warning: no previously-included files matching '*.pyc' found anywhere in distribution
warning: no previously-included files matching '.svn' found anywhere in distribution
no previously-included directories found matching 'doc/build'
error: command 'gcc' failed: No such file or directory
---
```
Putting the last line into google, the first two results tell me to
`sudo apt-get install gcc`, the third even tells me about `sudo apt
install build-essential`
2023-11-08 15:26:39 +00:00
konsti 2ebe40b986
Add `--no-build` (#358)
By default, we will build source distributions for both resolving and
installing, running arbitrary code. `--no-build` adds an option to ban
this and only install from wheels, no source distributions or git builds
allowed. We also don't fetch these and instead report immediately.

I've heard from users for whom this is a requirement, i'm implementing
it now because it's helpful for testing.

I'm thinking about adding a shared `PuffinSharedArgs` struct so we don't
have to repeat each option everywhere.
2023-11-08 10:05:15 -05:00
Charlie Marsh 4fe583257e
Use a custom PubGrub error type to always show resolution report (#365)
Closes https://github.com/astral-sh/puffin/issues/356.

The example from the issue now renders as:

```
❯ cargo run --bin puffin-dev -q -- resolve-cli tensorflow-cpu-aws
puffin-dev failed
  Caused by: No solution found when resolving build dependencies for source distribution:
  Caused by: Because there is no available version for tensorflow-cpu-aws and root depends on tensorflow-cpu-aws, version solving failed.
```
2023-11-08 09:57:26 -05:00
Charlie Marsh 3c24301193
Avoid removing progress bars (#362)
This was dumb of me. We pass out indexes when adding progress bars, but
were then removing entries on completion, so any outstanding indexes
were now _invalid_. We just shouldn't remove them. The `MultiProgress`
retains a reference anyway, IIUC.

Closes https://github.com/astral-sh/puffin/issues/360.
2023-11-07 18:58:17 +00:00
Charlie Marsh 7abe141d3f
Add SSL to possible spurious errors (#361)
\cc @konstin
2023-11-07 18:53:39 +00:00
Andrew Gallant 294955ecff
fix platform detection on Linux (#359)
Rejigger Linux platform detection

This change makes some very small improvements to the Linux platform
detection logic. In particular, the existing logic did not work on my
Archlinux machine since /lib64/ld-linux-x86-64.so.2 isn't a symlink. In
that case, the detection logic should have fallen back to the slower
`ldd --version` technique, but `read_link` fails outright when its
argument isn't a symbolic link. So we tweak the logic to allow it to
fail, and if it does, we still try the `ldd --version` approach instead
of giving up completely.

I also made some cosmetic improvements to the regex matching, as well as
ensuring that the regexes are only compiled exactly once.
2023-11-07 11:39:35 -05:00
konsti 692d2eb26f
puffin-dev resolve many improvements (#357)
Print the current step, the time for and also respect the cache dir arg.
2023-11-07 14:56:35 +00:00
Charlie Marsh b0286a8939
Add user feedback when building source distributions in the resolver (#347)
It looks like Cargo, notice the bold green lines at the top (which
appear during the resolution, to indicate Git fetches and source
distribution builds):

<img width="868" alt="Screen Shot 2023-11-06 at 11 28 47 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/9647a480-7be7-41e9-b1d3-69faefd054ae">

<img width="868" alt="Screen Shot 2023-11-06 at 11 28 51 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/6bc491aa-5b51-4b37-9ee1-257f1bc1c049">

Closes https://github.com/astral-sh/puffin/issues/287 although we can do
a lot more here.
2023-11-07 14:17:31 +00:00
Charlie Marsh 2c32bc5a86
Respect direct URLs in puffin installer (#345)
We now write the `direct_url.json` when installing, and _skip_
installing if we find a package installed via the direct URL that the
user is requesting.

A lot of TODOs, especially around cleaning up the `Source` abstraction
and its relationship to `DirectUrl`. I'm gonna keep working on these
today, but this works and makes the requirements clear.

Closes #332.
2023-11-07 09:11:27 -05:00
konsti c11586f2f0
Fix index out of bounds in SourceDistributionFilename::parse (#353)
Found this one in the top 8k pypi tests too
2023-11-07 11:44:40 +00:00
konsti c883b123ac
Allow greater than star (`torch (>=1.9.*)`) in lenient requirement (#351)
This appeared in the pypi top 8k testing.
2023-11-07 11:37:23 +00:00
konsti fbe28d3b7c
Fix mastodon-py dist-info handling (#336)
mastodon-py 1.5.1 uses a dot in its dist-info dir name, which we
previously didn't handle, causing home-assistant to fail. The new
implementation is based on
2f83540272/src/packaging/utils.py (L146-L172).

Part of #199

```
unzip -l  Mastodon.py-1.5.1-py2.py3-none-any.whl
Archive:  Mastodon.py-1.5.1-py2.py3-none-any.whl
  Length      Date    Time    Name
---------  ---------- -----   ----
   153929  2020-02-29 17:39   mastodon/Mastodon.py
     1029  2019-10-11 19:15   mastodon/__init__.py
     7357  2019-10-11 20:24   mastodon/streaming.py
       10  2020-03-14 18:14   Mastodon.py-1.5.1.dist-info/DESCRIPTION.rst
     1398  2020-03-14 18:14   Mastodon.py-1.5.1.dist-info/metadata.json
        9  2020-03-14 18:14   Mastodon.py-1.5.1.dist-info/top_level.txt
      110  2020-03-14 18:14   Mastodon.py-1.5.1.dist-info/WHEEL
     1543  2020-03-14 18:14   Mastodon.py-1.5.1.dist-info/METADATA
      753  2020-03-14 18:14   Mastodon.py-1.5.1.dist-info/RECORD
---------                     -------
   166138                     9 files
```
2023-11-07 12:36:11 +01:00
konsti aac8ae997f
Rename source distribution build to source build (#334)
This is less verbose and better reflects that we're building both source
distributions and source trees passed into the function.
2023-11-07 03:55:23 +00:00
Charlie Marsh 620afc3caf
Avoid refreshing Git repo twice (#350)
This was a bug in the Git code (that I wrote, not from Cargo) -- when we
`precise` the reference, we should store the resolved commit.
2023-11-07 02:52:15 +00:00
Charlie Marsh 243549876c
Upgrade PubGrub (#349)
Upgrades to `fe309ffb63b2f3ce9b35eb7746b2350cd704515e`, with our changes
layered on top.
2023-11-07 02:00:57 +00:00
Charlie Marsh 2c114592bd
Only store small wheels in-memory (#348)
Closes https://github.com/astral-sh/puffin/issues/246.
2023-11-07 00:50:00 +00:00
Zanie Blue e952557bf1
Improve root message when version solving fails (#344)
Matching description at
https://github.com/dart-lang/pub/blob/master/doc/solver.md#linear-error-reporting
2023-11-06 20:07:50 +00:00
Zanie Blue b0720ea5b2
Improve error message for dependencies with no versions available (#342)
Partially addresses https://github.com/astral-sh/puffin/issues/310
Addresses case at
https://github.com/astral-sh/puffin/issues/309#issuecomment-1793541558
Follow-up to #300 ensuring `PuffinExternal` is used consistently when
formatting messages

Example at
https://github.com/astral-sh/puffin/pull/342/files#diff-5c74a74ef34ef1d6e7453de8d2d19134813156e8b6a657e6b5ed71fda5a3a870
2023-11-06 14:04:29 -06:00
Zanie Blue 1748cfb522
Display dependency versions in pip-like format during solve failure (#346)
- Display `==` for exact version ranges
- Remove space between dependency and version range
2023-11-06 13:53:15 -06:00
Charlie Marsh a5e535f6fb
Remove `virtualenv` setup from gourgeist (#339)
We now only support building bare environments.
2023-11-06 18:32:45 +00:00
Charlie Marsh b013ea9c93
Move `DirectUrl` into `pypi-types` (#343)
This needs to be reused elsewhere, and there's nothing specific to wheel
installation about it.
2023-11-06 18:26:33 +00:00
Charlie Marsh 24e30e6557
Split `puffin-package` into requirements.txt parser and `pypi-types` (#341)
There are only two things left in this crate and they don't really have
anything to do with one another.
2023-11-06 18:19:49 +00:00
Charlie Marsh 1f447892f3
Rename `PartitionedRequirements` to `InstallPlan` (#340)
@konstin named this file at some point and I like it, it feels
appropriate for the struct itself too.
2023-11-06 12:44:35 -05:00
Charlie Marsh d9bcfafa16
Write `direct_url.json` in wheel installer (#337)
## Summary

This PR just adds the logic in `install-wheel-rs` to write
`direct_url.json`. We're not actually taking advantage of it yet (or
wiring it through) in Puffin.

Part of https://github.com/astral-sh/puffin/issues/332.
2023-11-06 17:09:28 +00:00
konsti 9b077f3d0f
`cargo upgrade --incompatible` (#330)
Ran `cargo upgrade --incompatible`, seems there are no changes required.

From cacache 0.12.0:
> BREAKING CHANGE: some signatures for copy have changed, and copy no
longer automatically reflinks

`which` 5.0.0 seems to have only error message changes.
2023-11-06 14:14:47 +00:00
konsti d99ca3159b
Cache the setup.py resolution (#327)
Cache the resolution for the setup.py requirements (`pip`, `setuptools`,
`wheels`) across builds.
2023-11-06 14:14:24 +00:00
konsti b2439b24a1
Fetch wheel metadata by async range requests on the remote wheel (#301)
Use range requests and async zip to extract the METADATA file from a
remote wheel.

We currently only cache when the remote says the remote declares the
resource as immutable, see
https://github.com/06chaynes/http-cache/issues/57 and
https://github.com/baszalmstra/async_http_range_reader/pull/1 . The
cache is stored as json with the description omitted, this improve cache
deserialization performance.
2023-11-06 15:06:49 +01:00
konsti 6f83a44fea
Improve error messages and make cache failures non fatal (#333) 2023-11-06 15:06:27 +01:00
konsti 3defe233e6
Use dist info name in cache again (#331)
Fixup for the `PackageName`/`DistInfoName` refactor that would lead to
invalid cache entries
2023-11-06 13:47:38 +00:00
Charlie Marsh 6d672b8951
Add source distribution support to `pip-compile` (#323)
## Summary

This is a first-pass at adding source distribution support to the
installer.

The previous installation flow was:

1. Come up with a plan.
1. Find a distribution (specific file) for every package that we'll need
to download.
1. Download those distributions.
1. Unzip them (since we assumed they were all wheels).
1. Install them into the virtual environment.

Now, Step (3) downloads both wheels and source distributions, and we
insert a step between Steps (3) and (4) to build any source
distributions into zipped wheels.

There are a bunch of TODOs, the most important (IMO) is that we
basically have two implementations of downloading and building, between
the stuff in `puffin_installer` and `puffin_resolver` (namely in
`crates/puffin-resolver/src/distribution`). I didn't attempt to clean
that up here -- it's already a problem, and it's related to the overall
problem we need to solve around unified caching and resource management.

Closes #243.
2023-11-06 08:22:36 -05:00
konsti b79a15b458
Update pyproject-toml to 0.8.0 (#329) 2023-11-06 13:16:36 +00:00
konsti 81f380b10e
Validate package and extra name (#290)
`PackageName` and `ExtraName` can now only be constructed from valid
names. They share the same rules, so i gave them the same
implementation. Constructors are split between `new` (owned) and
`from_str` (borrowed), with the owned version avoiding allocations.

Closes #279

---------

Co-authored-by: Zanie <contact@zanie.dev>
2023-11-06 10:04:31 +00:00
Charlie Marsh ea28b3d0d3
Add a git feature to tests (#325) 2023-11-06 05:32:43 +00:00
Charlie Marsh 8463e92121
Fix bad Flask reference in tests (#324) 2023-11-06 05:20:43 +00:00
Charlie Marsh 1637f1c216
Add source distribution support to the `DistributionFinder` (#322)
## Summary

This just enables the `DistributionFinder` (previously known as the
`WheelFinder`) to select source distributions when there are no matching
wheels for a given platform. As a reminder, the `DistributionFinder` is
a simple resolver that doesn't look at any dependencies: it just takes a
set of pinned packages, and finds a distribution to install to satisfy
each requirement.
2023-11-06 00:16:04 -05:00
Charlie Marsh d785ffdbff
Move `Source` abstraction into `puffin-distribution` (#321)
No code changes, but this will allow it to be shared between the
installer and the resolver.
2023-11-06 02:31:15 +00:00
Charlie Marsh 4b83d8e949
Require URL dependencies to be declared upfront (#319)
In the resolver, our current model for solving URL dependencies requires
that we visit the URL dependency _before_ the registry-based dependency.
This PR encodes a strict requirement that all URL dependencies be
declared upfront, either as requirements or constraints.

I wrote more about how it works and why it's necessary in documentation
[here](https://github.com/astral-sh/puffin/pull/319/files#diff-2b1c4f36af0c62a2b7bebeae9473ae083588f2a6b18a3ec52393a24266adecbbR20).
I think we could relax this constraint over time, but it requires a more
sophisticated model -- and for now, I just want something that's (1)
correct, (2) easy for us to reason about, and (3) easy for users to
reason about.

As additional motivation... allowing arbitrary URL dependencies anywhere
in the tree creates some really confusing situations in which I'm not
even sure what the right answers are. For example, assume you declare a
direct dependency on `Werkzeug==2.0.0`. You then depend on a version of
Flask that depends on a version of `Werkzeug` from some arbitrary URL.
You build the source distribution at that arbitrary URL, and it turns
out it _does_ build to a declared version of 2.0.0. What should happen?
(And if it resolves to a version that _isn't_ 2.0.0, what should happen
_then_?) I suspect different tools handle this differently, but it must
lead to a lot of "silent" failures. In my testing of Poetry, it seems
like Poetry just ignores the URL dependency, which seems wrong, but is
also a behavior we could implement in the future.

Closes https://github.com/astral-sh/puffin/issues/303.
Closes https://github.com/astral-sh/puffin/issues/284.
2023-11-05 17:09:58 +00:00
Charlie Marsh c03b4da3a2
Properly remove `.git ` extension even for URLs with `@` commit markers (#320) 2023-11-04 19:45:30 +00:00
Charlie Marsh a53188cac7
Avoid unnecessarily fetching non-marker-required first-party dependencies (#318)
E.g., given:

```
flask; python_version < '3.7'
requests
```

We shouldn't request the metadata for Flask when on Python versions 3.7
or later.
2023-11-04 17:03:43 +00:00
Charlie Marsh 051188dce0
Use separate representations for canonical repository vs. commit (#317)
Given `https://github.com/pypa/package.git#subdirectory=pkg_a` and
`https://github.com/pypa/package.git#subdirectory=pkg_b`, we want these
to map to the same shared _resource_ (for locking and cloning), but
different _packages_ (for determining whether the wheel already exists
in the cache). As such, we need two distinct concepts for "canonical
equality".

Closes #316.
2023-11-04 11:46:42 -04:00
Charlie Marsh b589813e59
Enforce that built package name matches declared package name (#315)
Closes https://github.com/astral-sh/puffin/issues/306.
2023-11-03 22:58:12 +00:00
Charlie Marsh 643cf3b3aa
Unify subdirectory handling in `source.rs` (#314)
Avoids having to encode all the `git+` and `subdirectory=` logic in
multiple places.
2023-11-03 19:33:38 +00:00
Charlie Marsh edce4ccb24
Add support for subdirectories in URL dependencies (#312)
Closes https://github.com/astral-sh/puffin/issues/307.
2023-11-03 15:28:38 -04:00
Zanie Blue cbfd6af125
Error if `--all-extras` is used without a `pyproject.toml` source (#292)
Closes https://github.com/astral-sh/puffin/issues/260
2023-11-03 12:07:32 -05:00
Charlie Marsh aa9882eee8
Use locks to prevent concurrent accesses to the same Git repo (#304)
Ensures that if we need to access the same Git repo twice in a
resolution, we only have one handler to that repo at a time. (Otherwise,
`git2` panics.)
2023-11-03 16:33:14 +00:00
Charlie Marsh fa1bbbbe08
Write fully-precise Git SHAs to `pip-compile` output (#299)
This PR adds a mechanism by which we can ensure that we _always_ try to
refresh Git dependencies when resolving; further, we now write the fully
resolved SHA to the "lockfile". However, nothing in the code _assumes_
we do this, so the installer will remain agnostic to this behavior.

The specific approach taken here is minimally invasive. Specifically,
when we try to fetch a source distribution, we check if it's a Git
dependency; if it is, we fetch, and return the exact SHA, which we then
map back to a new URL. In the resolver, we keep track of URL
"redirects", and then we use the redirect (1) for the actual source
distribution building, and (2) when writing back out to the lockfile. As
such, none of the types outside of the resolver change at all, since
we're just mapping `RemoteDistribution` to `RemoteDistribution`, but
swapping out the internal URLs.

There are some inefficiencies here since, e.g., we do the Git fetch,
send back the "precise" URL, then a moment later, do a Git checkout of
that URL (which will be _mostly_ a no-op -- since we have a full SHA, we
don't have to fetch anything, but we _do_ check back on disk to see if
the SHA is still checked out). A more efficient approach would be to
return the path to the checked-out revision when we do this conversion
to a "precise" URL, since we'd then only interact with the Git repo
exactly once. But this runs the risk that the checked-out SHA changes
between the time we make the "precise" URL and the time we build the
source distribution.

Closes #286.
2023-11-03 16:26:57 +00:00
Zanie Blue addcfe533a
Implement custom resolution failure reporter to hide root package versions (#300)
Extends #295 
Closes #214 

Copies some of the implementations from `pubgrub::report` so we can
implement Puffin `PubGrubPackage` specific display when explaining
failed resolutions.

Here, we just drop the dummy version number if it's a
`PubGrubPackage::Root` package. In the future, we can further customize
reporting.
2023-11-03 10:47:01 -05:00
Zanie Blue e1382cc747
Report project name instead of `root` when using `pyproject.toml` files (#295)
Part of https://github.com/astral-sh/puffin/issues/214

Adds a `project: Option<PackageName>` to the `Manifest`, `Resolver`, and
`RequirementsSpecification`.
To populate an optional `name` for `PubGubPackage::Root`.

I'll work on removing the version number next.

Should we consider using the parent directory name when a
`pyproject.toml` file is not present?
2023-11-03 10:22:10 -05:00
konsti e008c43f29
Add PackageName::as_dist_info_name (#305)
From
https://packaging.python.org/en/latest/specifications/recording-installed-packages/#recording-installed-packages

> This directory is named as {name}-{version}.dist-info, with name and
version fields corresponding to Core metadata specifications. Both
fields must be normalized (see Package name normalization and PEP 440
for the definition of normalization for each field respectively), and
replace dash (-) characters with underscore (_) characters, so the
.dist-info directory always has exactly one dash (-) character in its
stem, separating the name and version fields.

Follow up to #278
2023-11-03 08:16:44 +00:00
Charlie Marsh e47d3f1f66
Respect pip-like Git branch, tag, and commit references (#297)
We need to parse revisions out from URLs like `MyProject @
git+https://git.example.com/MyProject.git@v1.0`, per [VCS
Support](https://pip.pypa.io/en/stable/topics/vcs-support/). Cargo has
the advantage that it uses a TOML table in its configuration, so the
user has to specify whether they're fetching a commit, a tag, a branch,
etc. We have to instead assume that anything that isn't clearly a commit
is _either_ a branch or a tag.

Closes https://github.com/astral-sh/puffin/issues/296.
2023-11-02 15:10:02 -04:00
Charlie Marsh a4002fe132
Make cache non-optional in most crates (#293)
This PR makes the cache non-optional in most of Puffin, which simplifies
the code, allows us to reuse the cache within a single command (even
with `--no-cache`), and also allows us to use the cache for disk storage
across an invocation.

I left the cache as optional for the `Virtualenv` and `InterpreterInfo`
abstractions, since those are generic enough that it seems nice to have
a non-cached version, but it's kind of arbitrary.
2023-11-02 13:40:20 -04:00
Charlie Marsh a02bf2e415
Split `source_distribution.rs` into separate wheel and sdist fetchers (#291) 2023-11-02 16:04:51 +00:00
konsti c6f2dfd727
Use shared insta filters (#270)
Internal refactoring for consistency between tests
2023-11-02 16:42:59 +01:00
Charlie Marsh 62c474d880
Add support for Git dependencies (#283)
## Summary

This PR adds support for Git dependencies, like:

```
flask @ git+https://github.com/pallets/flask.git
```

Right now, they're only supported in the resolver (and not the
installer), since the installer doesn't yet support source distributions
at all.

The general approach here is based on Cargo's Git implementation.
Specifically, I adapted Cargo's
[`git`](23eb492cf9/src/cargo/sources/git/mod.rs)
module to perform the cloning, which is based on `libgit2`.

As compared to Cargo's implementation, I made the following changes:

- Removed any unnecessary code.
- Fixed any Clippy errors for our stricter ruleset.
- Removed the dependency on `curl`, in favor of `reqwest` which we use
elsewhere.
- Removed the ability to use `gix`. Cargo allows the use of `gix` as an
experimental flag, but it only supports a small subset of the
operations. When Cargo fully adopts `gix`, we should plan to do the
same.
- Removed Cargo's host key checking. We need to re-add this! I'll do it
shortly.
- Removed Cargo's progress bars. We should re-add this too, but we use
`indicatif` and Cargo had their own thing.

There are a few follow-ups to consider:

- Adding support in the installer.
- When we lock, we should write out the Git URL that includes the exact
SHA. This lets us cache in perpetuity and avoids dependencies changing
without re-locking.
- When we resolve, we should _always_ try to refresh Git dependencies.
(Right now, we skip if the wheel was already built.)

I'll work on the latter two in follow-up PRs.

Closes #202.
2023-11-02 15:14:55 +00:00
konsti 4adaa9a700
Wheel filename distribution package name (#278)
The normalized name abstractions were not consistently, this PR uses
them where they were previously missing:
* `WheelFilename::distribution`
* `Requirement::name`
* `Requirement::extras`
* `Metadata21::name`
* `Metadata21::provides_dist`

With `puffin-package` depending on `pep508_rs` this would be cyclical
crate dependency, so `puffin-normalize` gets split out from
`puffin-package`.

`DistInfoName` has the same task and semantics as `PackageName`, so it's
merged into the latter.

`PackageName` and `ExtraName` documentation is moved onto the type and
their constructors are called `new` instead of `normalize`. We now use
these constructors rarely enough the implicit allocation by
`to_string()` shouldn't matter anymore, while more actual cloning
becomes visible.
2023-11-02 11:15:27 +00:00
konsti 9488804024
Add docker builder (#238)
This docker container provides isolation of source distribution builds,
whether [intended to be
helpful](https://pypi.org/project/nvidia-pyindex/) or other more or less
malicious forms of host system modification.

Fixes #194

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2023-11-02 12:03:56 +01:00
Charlie Marsh 2ee555df7b
Use `puffin_cache::digest` in another site (#289) 2023-11-02 04:48:14 +00:00
Charlie Marsh 0c9e975f75
Rename `distribution.rs` to `file.rs` in `puffin-resolver` (#288) 2023-11-01 23:52:53 -04:00
Zanie Blue b8ff32f6be
Respect markers on constraints (#282)
Closes #252
2023-11-01 20:20:32 -05:00
Charlie Marsh 8123e1a8f6
Add stable hash crate (#281)
This PR adds a `puffin-cache` crate that we can share across a variety of
other crates to generate stable hashes.
2023-11-01 23:41:45 +00:00
Zanie Blue 67e3e45839
Add support for `--all-extras` to `pip-compile` (#259)
Closes #244

Notable decision to error if `--all-extra` and `--extra <name>` are both
provided.
2023-11-01 13:39:49 -05:00
konsti c6aa1cd7a3
Only fall back to copy when the first hard linking failed (#268)
Hard linking might not be supported but we (afaik) can't detect this
ahead of time, so we'll try hard linking the first file, if this
succeeds we'll know later hard linking errors are not due to lack of
os/fs support, if it fails we'll switch to copying for the rest of the
install. Follow up to
https://github.com/astral-sh/puffin/pull/237#discussion_r1376705137
2023-11-01 18:35:52 +01:00
konsti b0678aa6fc
Show dev resolve output (#277)
Show the resolution in a concise format for puffin-dev. Note that this
doesn't affect the main puffin output, it's just more convenient for me
when developing.
2023-11-01 15:54:47 +00:00
konsti d1af90163b
Improve client reqwest errors (#276)
I had to debug a failure involving these errors and had to improve their
output.
2023-11-01 16:52:58 +01:00
konsti 997228f4be
Add resolve from cli dev command (#272)
I don't want to create a new file for every requirement i test
2023-11-01 15:46:37 +00:00
Zanie Blue 3d5f8249ef
Add validation of extra names (#257)
Extends #254 

Adds validation of extra names provided by users in `pip-compile` e.g. 

```
error: invalid value 'foo!' for '--extra <EXTRA>': Extra names must start and end with a
letter or digit and may only contain -, _, ., and alphanumeric characters
```

We'll want to add something similar to `PackageName`. I'd be curious to
improve the AP, making the unvalidated nature of `::normalize` clear?
Perhaps worth pursuing later though as I don't have a better idea.
2023-11-01 10:40:43 -05:00
Charlie Marsh 2652caa3e3
Add support for URL dependencies (#251)
## Summary

This PR adds support for resolving and installing dependencies via
direct URLs, like:

```
werkzeug @ 960bb4017c4aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl
```

These are fairly common (e.g., with `torch`), but you most often see
them as Git dependencies.

Broadly, structs like `RemoteDistribution` and friends are now enums
that can represent either registry-based dependencies or URL-based
dependencies:

```rust
/// A built distribution (wheel) that exists as a remote file (e.g., on `PyPI`).
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum RemoteDistribution {
    /// The distribution exists in a registry, like `PyPI`.
    Registry(PackageName, Version, File),
    /// The distribution exists at an arbitrary URL.
    Url(PackageName, Url),
}
```

In the resolver, we now allow packages to take on an extra, optional
`Url` field:

```rust
#[derive(Debug, Clone, Eq, Derivative)]
#[derivative(PartialEq, Hash)]
pub enum PubGrubPackage {
    Root,
    Package(
        PackageName,
        Option<DistInfoName>,
        #[derivative(PartialEq = "ignore")]
        #[derivative(PartialOrd = "ignore")]
        #[derivative(Hash = "ignore")]
        Option<Url>,
    ),
}
```

However, for the purpose of version satisfaction, we ignore the URL.
This allows for the URL dependency to satisfy the transitive request in
cases like:

```
flask==3.0.0
werkzeug @ 254c3e9b5f5941e900b71206e6313b/werkzeug-3.0.1-py3-none-any.whl
```

There are a couple limitations in the current approach:

- The caching for remote URLs is done separately in the resolver vs. the
installer. I decided not to sweat this too much... We need to figure out
caching holistically.
- We don't support any sort of time-based cache for remote URLs -- they
just exist forever. This will be a problem for URL dependencies, where
we need some way to evict and refresh them. But I've deferred it for
now.
- I think I need to redo how this is modeled in the resolver, because
right now, we don't detect a variety of invalid cases, e.g., providing
two different URLs for a dependency, asking for a URL dependency and a
_different version_ of the same dependency in the list of first-party
dependencies, etc.
- (We don't yet support VCS dependencies.)
2023-11-01 09:21:44 -04:00
Zanie Blue fa9f8df396
Fix test snapshot filter when runtime is greater than 1s (#267)
Tests would sometimes flake with this locally e.g. "1.50s" was not
filtered correctly.

Verified with

```diff
diff --git a/crates/puffin-cli/src/commands/pip_compile.rs b/crates/puffin-cli/src/commands/pip_compile.rs
index 0193216..2d6f8af 100644
--- a/crates/puffin-cli/src/commands/pip_compile.rs
+++ b/crates/puffin-cli/src/commands/pip_compile.rs
@@ -150,6 +150,8 @@ pub(crate) async fn pip_compile(
         result => result,
     }?;
 
+    std:🧵:sleep(std::time::Duration::from_secs(1));
+
     let s = if resolution.len() == 1 { "" } else { "s" };
     writeln!(
         printer,
```
2023-11-01 13:15:06 +00:00
Charlie Marsh 079b685c8c
Use distributions for `Reporter` signatures (#266) 2023-11-01 03:19:13 +00:00
Charlie Marsh bee1b0f5ad
Avoid re-parsing wheel filename in source distribution tree (#265) 2023-10-31 21:02:09 +00:00
Charlie Marsh aff26f2301
Reuse distribution structs in Resolver's `source_distribution.rs` (#264) 2023-10-31 20:50:34 +00:00
Zanie Blue 4be9ba483f
Remove implicit clone from `ExtraName` and document requirement in `PackageName` (#262)
per discussion in #137


https://discord.com/channels/1039017663004942429/1148719284013510676/1169000261746962473
2023-10-31 15:24:27 -05:00
Zanie Blue 0dc7e6335e
Default to `puffin venv` path to `.venv` (#261)
Closes https://github.com/astral-sh/puffin/issues/236
2023-10-31 15:24:19 -05:00
Zanie Blue e00d208318
Add documentation to `PackageName::normalize` (#263) 2023-10-31 15:24:08 -05:00
Charlie Marsh 89dad0c9ad
Move distribution abstraction in shared crate (#258)
This also allows us to get rid of `PinnedPackage` _and_ to remove some
`Result<...>` types due to needless conversions between
otherwise-identical types.
2023-10-31 15:30:06 -04:00
Zanie Blue 1ddb7d2827
Add error when user requests extras that do not exist (#254)
Extends #253 
Closes #241 

Adds `extras` to `RequirementsSpecification` to track extras used to
construct the requirements so we can throw an error when not all of the
requested extras are used.
2023-10-31 19:17:36 +00:00
Zanie Blue 322532d6f9
Normalize optional dependency group names in pyproject files (#253)
Going to add some tests.

Extends #239 
Closes #245 

Normalizes optional dependency group names found in pyproject files
before comparing them to the normalized user-requested extras.
2023-10-31 14:15:00 -05:00
Charlie Marsh 3312ce30f5
Upgrade crates and remove unused dependencies (#256) 2023-10-31 13:16:58 -04:00
Charlie Marsh 16aac834ee
Move PyPI-oriented types out of `puffin-client` crate (#255)
Just an internal change to avoid a dependency on `puffin-client` for
those crates that need access to PyPI-metadata types.
2023-10-31 17:10:23 +00:00
Zanie Blue 08f09e4743
Add support for `pip-compile --extra <name>` (#239)
Adds support for `pip-compile --extra <name> ...` which includes
optional dependencies in the specified group in the resolution.

Following precedent in `pip-compile`, if a given extra is not found,
there is no error. ~We could consider warning in this case.~ We should
probably add an error but it expands scope and will be considered
separately in #241
2023-10-31 11:59:40 -05:00
Charlie Marsh 9244404102
Resolve interpreter symlinks when creating virtual environments (#250)
Closes https://github.com/astral-sh/puffin/issues/249.
2023-10-31 08:22:52 -04:00
Charlie Marsh 2f38701008
Remove unused wheel cache argument from downloader (#248) 2023-10-31 02:23:50 +00:00
Charlie Marsh ae203f998a
Rename `Unzipper#download` to `Unzipper#unzip` (#247) 2023-10-31 01:19:27 +00:00
Charlie Marsh 1f059b30dd
Remove `Box<Pin<...>>` from `process_request` (#242) 2023-10-30 16:34:57 -04:00
konsti 35d6bd761b
Fallback to copy if hardlinking failed (#237) 2023-10-30 19:10:01 +00:00
konstin 1529def563 Implement mixed PEP 517 and setup.py build
There are packages such as DTLSSocket 0.1.16 that say
```toml
[build-system]
requires = ["Cython<3", "setuptools", "wheel"]
```
In this case we need to install requires PEP 517 style but then call setup.py in the
legacy way

Part of making home-assistant work
2023-10-30 19:11:52 +01:00
konsti 29bd0a4ed8
Fix musl compilation (#234)
musl (which we already use in ruff) allows statically linked binaries on
linux. This PR switches to rustls and vendors and fixes the glibc
detection. Using static musl builds makes it easier to avoid glibc
errors in docker and we'll need it later for alpine users anyway.

An alternative is using vendored openssl.
2023-10-30 18:10:17 +01:00
konsti d47dc64974
Ignore self requirements (#233)
gps3 0.33.3 depends on itself, which we can ignore. I've also added the
home assistant requirements since it occurred when testing with this.
2023-10-30 17:13:52 +01:00
Charlie Marsh 0be20a41a4
Make version selection wheel-vs.-sdist-agnostic (#232)
Closes https://github.com/astral-sh/puffin/issues/231.
2023-10-30 11:21:10 -04:00
Charlie Marsh 8d992dca3f
Fail gracefully when invalid markers are stored (#230) 2023-10-30 04:02:51 +00:00
Charlie Marsh e73d3f0ff8
Use bounded ranges rather than constructing manual ranges (#228)
I didn't realize this, but they made a bunch of improvements to how
PubGrub represents versions which lets us greatly simplify our own
PubGrub version wrapper
(https://github.com/pubgrub-rs/guide/pull/6/files).
2023-10-30 03:58:43 +00:00
Charlie Marsh fb2d4fc421
Set style before message (#229)
Prevents flickering in the resolver case.
2023-10-30 03:57:03 +00:00
Charlie Marsh ffbf6b6c16
Avoid symlinking `RECORD` file (#227)
This is the one file that gets modified during installation. Hardlinking
it is bad!
2023-10-30 03:10:38 +00:00
Charlie Marsh 1d3ea242d4
Re-export from PubGrub module (#226) 2023-10-30 02:03:52 +00:00
Charlie Marsh f2dd0d90be
Add a resolver reporter (#225)
Closes https://github.com/astral-sh/puffin/issues/223.
2023-10-30 02:00:09 +00:00
Charlie Marsh 6da9c2f534
Tweak response buffering (#224)
In my testing, we can both increase the number of concurrent requests
and remove the `ready_chunks`.
2023-10-29 21:07:46 -04:00
Charlie Marsh 1c5cdcd70a
Prioritize packages in visited order (#222) 2023-10-30 00:48:36 +00:00
Charlie Marsh 2ba85bf80e
Add PubGrub's priority queue (#221)
Pulls in https://github.com/pubgrub-rs/pubgrub/pull/104.
2023-10-29 21:16:02 +00:00
Charlie Marsh 4209e77c95
Upgrade `pubgrub-rs` version (#220)
Upgrades our PubGrub to 8951e37fe923a7edd5a78ed5f49f165b0fdc48de.
2023-10-29 20:25:55 +00:00
Charlie Marsh 1e4259a608
Make the resolver deterministic (#218)
At a minor performance cost...

Closes https://github.com/astral-sh/puffin/issues/204.
2023-10-29 18:42:25 +00:00
Charlie Marsh bae3c89ab1
Add a `--prerelease` flag to the CLI (#217) 2023-10-29 18:39:30 +00:00
Charlie Marsh 7e7e9f8a0c
Add support for pre-release versions (#216)
We now accept a pre-release if (1) all versions are pre-releases, or (2)
there was a pre-release marker in the dependency specifiers for a direct
dependency.

The code is written such that we can support a variety of pre-release
strategies.

Closes https://github.com/astral-sh/puffin/issues/191.
2023-10-29 14:31:55 -04:00
konsti 6cd4650c1f
Support missing `build_system` key (#213)
Reduces the number of failing projects out of the top 1000 pypi projects
from 5 to 2.
2023-10-27 12:12:12 +02:00
Charlie Marsh 8b83385763
Support constraints in `requirements.in` files (#212)
Closes #172.
2023-10-27 00:41:02 +00:00
Charlie Marsh 58011f98b6
Revert "Add TODO around preferring local wheels" (#211)
Reverts astral-sh/puffin#208. Unclear if we actually want to do this.
2023-10-26 23:03:00 +00:00
Charlie Marsh d5c3ff789a
Sort wheels by size when downloading and zipping (#210)
I just learned about this from PackagingCon, and locally, it shows a
nice speedup:

```
❯ hyperfine --warmup 3 --prepare "rm -rf .venv && ./target/release/puffin venv .venv" "./target/release/puffin pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache" "./target/release/main pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache"
Benchmark 1: ./target/release/puffin pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache
  Time (mean ± σ):      3.958 s ±  0.250 s    [User: 1.323 s, System: 5.840 s]
  Range (min … max):    3.652 s …  4.402 s    10 runs

Benchmark 2: ./target/release/main pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache
  Time (mean ± σ):      4.214 s ±  0.451 s    [User: 1.322 s, System: 5.976 s]
  Range (min … max):    3.708 s …  5.268 s    10 runs

Summary
  './target/release/puffin pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache' ran
    1.06 ± 0.13 times faster than './target/release/main pip-sync ./scripts/benchmarks/requirements-large.txt --no-cache'
```
2023-10-26 20:50:56 +00:00
Charlie Marsh 12e6b46ae8
Add TODO around preferring local wheels (#208) 2023-10-26 19:09:03 +00:00
Charlie Marsh 7bce41498e
Improve debug logging in dispatcher (#206)
Also makes the order of operations more similar to that of the
`pip-compile` command.
2023-10-26 18:54:47 +00:00
konsti 5ad58474ca
Add script to check the top 8k pypi packages (#198)
To check to top 1k (current state):

```bash
scripts/resolve/get_pypi_top_8k.sh
cargo run --bin puffin-dev -- resolve-many scripts/resolve/pypi_top_8k_flat.txt --limit 1000
```

Results:
```
Errors: pywin32, geoip2, maxminddb, pypika, dirac
Success: 995, Error: 5
```
pywin32 has no solution for the build environment, 3 have no
`[build-system]` entry in pyproject.toml, `dirac` is missing cmake
2023-10-26 12:03:59 +00:00
konsti 216b6c41c2
Start puffin-dev (#193)
Currently, this is only the source distribution building feature moved.
It's intended that we can add development and test commands there
without affecting the main cli surface
2023-10-26 09:17:22 +00:00
konsti 862c1654a0
Select most recent wheel, most recent sdist (#190)
Select a compatible wheel for a version, even we already found a source
distribution previously.

If no wheel is found, select the most recent source distribution, not
the oldest compatible one.

This fixes the resolution of `mst.in`, which i added
2023-10-26 08:15:26 +00:00
Charlie Marsh 13e4171916
Inline manifest creations in resolver tests (#188) 2023-10-26 04:36:03 +00:00
Charlie Marsh 6faaf4bc24
Respect existing versions in "lockfile" (#187)
Like `pip-compile`, we now respect existing versions from the
`requirements.txt` provided via `--output-file`, unless you pass a
`--upgrade` flag.

Closes #166.
2023-10-26 04:28:58 +00:00
Charlie Marsh 9f894213e0
Omit colors when writing to output file (#186)
We were writing color escape codes to the file specified by `-o`.
2023-10-26 04:12:25 +00:00
Charlie Marsh 61a61db154
Filter and store all distributions upfront (#185)
Modifies the resolver to remove any incompatible distributions upfront,
and store them in an index by version. This will be necessary to support
`--upgrade` semantics.

This actually does cause a meaningful slowdown right now (since we now
iterate over all files, even if we otherwise never would've needed to
touch them), but we should be able to optimize it out later.
2023-10-26 01:06:44 +00:00
Charlie Marsh 5ed913af50
Rename `SolverCache` (#184)
Everywhere else, we use cache to refer to a filesystem cache, so this is
kind of confusing. It's really an in-memory index that we build up over
the course of the solve.
2023-10-25 23:53:31 +00:00
konsti 889f6173cc
Unify python interpreter abstractions (#178)
Previously, we had two python interpreter metadata structs, one in
gourgeist and one in puffin. Both would spawn a subprocess to query
overlapping metadata and both would appear in the cli crate, if you
weren't careful you could even have to different base interpreters at
once. This change unifies this to one set of metadata, queried and
cached once.

Another effect of this crate is proper separation of python interpreter
and venv. A base interpreter (such as `/usr/bin/python/`, but also pyenv
and conda installed python) has a set of metadata. A venv has a root and
inherits the base python metadata except for `sys.prefix`, which unlike
`sys.base_prefix`, gets set to the venv root. From the root and the
interpreter info we can compute the paths inside the venv. We can reuse
the interpreter info of the base interpreter when creating a venv
without having to query the newly created `python`.
2023-10-25 20:11:36 +00:00
konsti 1fbe328257
Build source distributions in the resolver (#138)
This is isn't ready, but it can resolve
`meine_stadt_transparent==0.2.14`.

The source distributions are currently being built serially one after
the other, i don't know if that is incidentally due to the resolution
order, because sdist building is blocking or because of something in the
resolver that could be improved.

It's a bit annoying that the thing that was supposed to do http requests
now suddenly also has to a whole download/unpack/resolve/install/build
routine, it messes up the type hierarchy. The much bigger problem though
is avoid recursive crate dependencies, it's the reason for the callback
and for splitting the builder into two crates (badly named atm)
2023-10-25 20:05:13 +00:00
konsti b5c57ee6fe
Fix rustdoc warnings (#182)
Changes to make `cargo doc --all --all-features` pass without warnings.
2023-10-25 11:48:24 +00:00
Charlie Marsh d0aeb2ac80
Remove vector allocation in `WheelFilename` (#177) 2023-10-24 01:23:14 +00:00
Charlie Marsh 21bb9c29cc
Add an additional requirements fixup (#174)
Also checking in a variety of different requirements inputs.
2023-10-23 19:50:39 -04:00
konstin 815c2117c8 Clippy 2023-10-23 13:54:31 +02:00
Charlie Marsh 0e097874f8
Add support for alternate index URLs (#169)
As elsewhere, we just use the `pip` and `pip-compile` APIs. So we
support `--index-url` to override PyPI, then `--extra-index-url` to add
_additional_ indexes, and `--no-index` to avoid hitting the index at
all.

Closes #156.
2023-10-23 03:18:30 +00:00
Charlie Marsh 49a27ff33c
Add support for parameterized link modes (#164)
Allows the user to select between clone, hardlink, and copy semantics
for installs. (The pnpm documentation has a decent description of what
these mean: https://pnpm.io/npmrc#package-import-method.)

Closes #159.
2023-10-22 04:35:50 +00:00
Charlie Marsh 9bcc7fe77a
Move venv command to miette (#162) 2023-10-22 04:17:16 +00:00
Charlie Marsh 370771b28c
Make `clean` non-async (#163) 2023-10-22 03:54:13 +00:00
Charlie Marsh b665f1489a
Add tests for `puffin sync` (#161)
Closes #158.
2023-10-22 03:25:00 +00:00
Charlie Marsh 3072c3265e
Add support for lowest and lowest-direct resolution modes (#160)
Borrows terminology from pnpm by introducing three resolution modes:

- "Highest": always choose the highest compliant version (default).
- "Lowest": always choose the lowest compliant version.
- "LowestDirect": choose the lowest compliant version of direct
dependencies, and the highest compliant version of any transitive
dependencies. (This makes a bit more sense than "lowest".)

Closes https://github.com/astral-sh/puffin/issues/142.
2023-10-21 22:58:06 -04:00
konsti ae9d1f7572
Add source distribution filename abstraction (#154)
The need for this became clear when working on the source distribution
integration into the resolver.

While at it i also switch the `WheelFilename` version to the parsed
`pep440_rs` version now that we have this crate.
2023-10-20 17:45:57 +02:00
Charlie Marsh 6f52b5ca4d
Use index instead of current selection (#155)
We can also use `swap_remove` because we're discarding the vector.
2023-10-20 14:02:24 +00:00
Charlie Marsh 4645f79237
Use `FxHash` (#151) 2023-10-20 05:26:06 +00:00
Charlie Marsh 8001c792e7
Show requirement sources in `pip-compile` output (#149)
Builds up a complete resolved graph from PubGrub, and shows the sources
that led to each package being included in the resolution, like
`pip-compile`.

Closes https://github.com/astral-sh/puffin/issues/60.
2023-10-20 05:14:59 +00:00
Charlie Marsh e662fe341b
Short-circuit when a dependency has no matching versions (#148)
Kind of an oversight in my initial implementation. If we find that any
package has _no_ matching versions, we should select it! This lets us
short-circuit _immediately_ when top-level dependencies aren't
satisfiable.
2023-10-20 03:49:20 +00:00
Charlie Marsh 9b3405bf0e
Upgrade PubGrub to dev branch (#147)
Updates to `29c48fb9f3daa11bd02794edd55060d0b01ee705` from the
`pubgrub-rs` dev branch. This lets us reduce the number of changes we've
made to PubGrub itself (now, only changing visibility to export a few
things from the `solver.rs` module).
2023-10-20 03:23:26 +00:00
Charlie Marsh bcd281eb1f
Remove `async` from some filesystem-only APIs (#146) 2023-10-20 01:08:51 +00:00
Charlie Marsh 03101c6a5c
Add an autogeneration header to pip-compile (#145)
Closes https://github.com/astral-sh/puffin/issues/132.
2023-10-19 20:57:27 -04:00
Charlie Marsh 0b60804db6
Add support for constraints during pip-compile resolution (#144)
Closes https://github.com/astral-sh/puffin/issues/130.
2023-10-20 00:24:05 +00:00
Charlie Marsh d5105a76c5
Improve and test diagnostics for requirements-reading CLI commands (#143)
Also removes `owo_colors` because it was really painful to get it to
avoid printing colors during tests.
2023-10-19 18:13:40 -04:00
Charlie Marsh ba181eacdd
Accept dependencies from `pyproject.toml` (#141)
Doesn't support extras yet. It's also supported for `pip uninstall`,
which `pip` itself doesn't support, but whatever.

Closes #127.
2023-10-19 18:42:05 +00:00
Charlie Marsh 385345807c
Accept multiple input files in pip-sync and pip-compile (#140)
Closes https://github.com/astral-sh/puffin/issues/126.
2023-10-19 18:17:27 +00:00
Charlie Marsh 7ef6c0315c
Unify site-packages into distribution enum (#136)
Gets rid of the custom `DistInfo` struct in the site-packages
abstraction in favor of a new kind of distribution
(`InstalledDistribution`). No change in behavior.
2023-10-19 04:37:52 +00:00
Charlie Marsh bd01fb490e
Remove packages when syncing (#135)
`pip-sync` will now uninstall any packages that aren't necessary.

Closes https://github.com/astral-sh/puffin/issues/128.
2023-10-19 00:14:20 -04:00
Charlie Marsh 41ece4184b
Print to stderr by default (#134) 2023-10-18 23:30:07 -04:00
Charlie Marsh 20bb4c5c61
Avoid showing resolver progress bar when no resolution is required (#133) 2023-10-19 03:23:22 +00:00
Charlie Marsh 573f5832a3
Allow uninstall to take multiple packages and files (#125)
Moves the command to `puffin pip-uninstall` for now to separate from the
managed interface, and redoes the command output.
2023-10-18 22:30:11 -04:00
Charlie Marsh 4b91ae4769
Add CLI tests for add and remove commands (#124) 2023-10-19 01:06:48 +00:00
Charlie Marsh e15b99b911
Rename commands to `pip-sync` and `pip-compile` (#123)
To free up the rest of the interface.
2023-10-18 21:15:20 +00:00
konsti 8cc4fe0d44
Install source distribution requirements with puffin itself instead of pip (#122)
This is also a lot faster. Unfortunately it copies a lot of code from
the sync cli since the `Printer` is private.

The first commit are some refactorings i made when i thought about how i
could reuse the existing code.
2023-10-18 19:11:17 +00:00
Charlie Marsh 7bc42ca2ce
Use `owo_colors` instead of `colored` (#121)
This is what `miette` uses so seems better to avoid two coloring crates.
2023-10-18 18:57:07 +00:00
Charlie Marsh 2d14c0647e
Add a `puffin remove` command (#120) 2023-10-18 18:50:08 +00:00
Charlie Marsh 1fc03780f9
Use `miette` for `puffin add` diagnostics (#119)
Experiment in using `miette` for better user-facing diagnostics in the
CLI crate:

<img width="710" alt="Screen Shot 2023-10-18 at 2 11 54 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/30299da0-da65-4972-944f-cb8cc5f72a77">

For now, only the `add` command has been migrated, and all the library
crates continue to use `anyhow`.
2023-10-18 14:24:09 -04:00
konsti fec4ee2848
Support prepare_metadata_for_build_wheel (#106)
Support calling `prepare_metadata_for_build_wheel`, which can give you
the metadata without executing the actual build if the backend supports
it.

This makes the code a lot uglier since we effectively have a state
machine:

* Setup: Either venv plus requires (PEP 517) or just a venv (setup.py)
* Get metadata (optional step): None (setup.py) or
`prepare_metadata_for_build_wheel` and saving that result
* Build: `setup.py`, `build_wheel()` or
`build_wheel(metadata_directory=metadata_directory)`, but i think i got
general flow right.

@charliermarsh This is a "barely works but unblocks building on top"
implementation, say if you want more polishing (i'll look at this again
tomorrow)
2023-10-18 14:48:30 +02:00
Charlie Marsh 4c87a1d42c
Add a `puffin add` command (#117)
This needs far better error handling and user-facing feedback, but it
does the basic operation (and includes discovery of the `pyproject.toml`
file, etc.).
2023-10-18 00:51:20 -04:00
Charlie Marsh 339553e228
Mark `--no-cache` as global (#116) 2023-10-17 23:15:36 -04:00
Charlie Marsh 89db5d79bc
Add support for lenient parsing (#115)
This PR enables us to make "fixups" to bad metadata. I copied over the
one fixup that @konstin made in `monotrail-resolve`, and added a few
common ones for `Requires-Python`.
2023-10-17 22:03:16 -04:00
Charlie Marsh 0d90256151
Store all distributions rather than compatible wheels (#114)
This PR reverts #109 which is actually a performance _regression_ since
we need to iterate over a bunch of wheels that we could otherwise
entirely ignore.
2023-10-17 17:09:31 -04:00
Charlie Marsh 5b046a8102
Use `select!` instead of `tokio::spawn` for network thread (#110) 2023-10-16 15:41:25 -04:00
Charlie Marsh 1b433fdcee
Only store compatible wheels in the resolver (#109)
Rather than constantly iterating over all files and testing their
compatibility with the current platform, just store wheels we can
actually consider in the solver cache.
2023-10-16 19:21:07 +00:00
Charlie Marsh 5f5788e866
Surface PubGrub derivation trees (#108)
I think the derivation trees could be stronger but this exposes
PubGrub's proof-like error messages.

Closes #102.
2023-10-16 14:14:36 -04:00
Charlie Marsh bae52d5edd
Surface request stream errors in the resolver (#107)
Closes https://github.com/astral-sh/puffin/issues/105.
2023-10-16 17:26:46 +00:00
Charlie Marsh 7e8ffeb2df
Use `fs-err` in more crates (#100)
Closes https://github.com/astral-sh/puffin/issues/88.
2023-10-16 13:37:58 +00:00
konsti fa2fd14587
Add basic sdist builder (#104)
This adds a basic sdist builder that has been tested with two source
distributions, one with a PEP 517 backend and one with setup.py.

It uses pip for requirements installation atm, lacks testing in all
directions, lacks checks for recursive requirements, can't pass in
already resolved versions, doesn't support prepare metadata for build to
allow resolution to continue without doing the actual (native) build,
error messages are mediocre, etc.

```console
$ RUST_LOG=puffin_build=debug puffin-build --wheels wheels downloads/tqdm-4.66.1.tar.gz
2023-10-16T12:28:35.503182Z DEBUG build_sdist{path="downloads/tqdm-4.66.1.tar.gz" base_python="/usr/bin/python3"}: puffin_build: Building downloads/tqdm-4.66.1.tar.gz
2023-10-16T12:28:35.521780Z  INFO build_sdist{path="downloads/tqdm-4.66.1.tar.gz" base_python="/usr/bin/python3"}:extract_archive: puffin_build: close time.busy=18.4ms time.idle=16.7µs
2023-10-16T12:28:35.845096Z DEBUG build_sdist{path="downloads/tqdm-4.66.1.tar.gz" base_python="/usr/bin/python3"}:resolve_and_install: puffin_build: Calling pip to install build dependencies
2023-10-16T12:28:37.668660Z  INFO build_sdist{path="downloads/tqdm-4.66.1.tar.gz" base_python="/usr/bin/python3"}:resolve_and_install: puffin_build: close time.busy=1.82s time.idle=13.2µs
2023-10-16T12:28:37.668744Z DEBUG build_sdist{path="downloads/tqdm-4.66.1.tar.gz" base_python="/usr/bin/python3"}: puffin_build: Calling `setuptools.build_meta.get_requires_for_build_wheel()`
2023-10-16T12:28:38.159205Z  INFO build_sdist{path="downloads/tqdm-4.66.1.tar.gz" base_python="/usr/bin/python3"}:run_python_script{python_interpreter="/tmp/.tmpm4cTra/venv/bin/python"}: puffin_build: close time.busy=490ms time.idle=13.0µs
2023-10-16T12:28:38.159304Z DEBUG build_sdist{path="downloads/tqdm-4.66.1.tar.gz" base_python="/usr/bin/python3"}: puffin_build: Calling `setuptools.build_meta.build_wheel()`
2023-10-16T12:28:38.501732Z  INFO build_sdist{path="downloads/tqdm-4.66.1.tar.gz" base_python="/usr/bin/python3"}:run_python_script{python_interpreter="/tmp/.tmpm4cTra/venv/bin/python"}: puffin_build: close time.busy=342ms time.idle=15.2µs
2023-10-16T12:28:38.522700Z  INFO build_sdist{path="downloads/tqdm-4.66.1.tar.gz" base_python="/usr/bin/python3"}: puffin_build: close time.busy=3.02s time.idle=16.2µs
Wheel built to /home/konsti/projects/puffin/crates/puffin-build/wheels/tqdm-4.66.1-py3-none-any.whl
2023-10-16T12:28:38.522772Z DEBUG puffin_build: Took 3020ms
$ puffin-build --wheels wheels downloads/geoextract-0.3.1.tar.gz
2023-10-16T12:28:40.884622Z DEBUG build_sdist{path="downloads/geoextract-0.3.1.tar.gz" base_python="/usr/bin/python3"}: puffin_build: Building downloads/geoextract-0.3.1.tar.gz
2023-10-16T12:28:40.887743Z  INFO build_sdist{path="downloads/geoextract-0.3.1.tar.gz" base_python="/usr/bin/python3"}:extract_archive: puffin_build: close time.busy=2.97ms time.idle=12.6µs
2023-10-16T12:28:41.469738Z  INFO build_sdist{path="downloads/geoextract-0.3.1.tar.gz" base_python="/usr/bin/python3"}: puffin_build: close time.busy=585ms time.idle=15.3µs
Wheel built to /home/konsti/projects/puffin/crates/puffin-build/wheels/geoextract-0.3.1-py3-none-any.whl
2023-10-16T12:28:41.469814Z DEBUG puffin_build: Took 585ms
```
2023-10-16 12:43:31 +00:00
konsti cb29c89424
Better error reporting (#95)
The main change is to print the whole error chain. We can combine this
with adding `.context` to distinct phases to be able to locate crashes
without having to use a debugger.
2023-10-16 02:15:10 +00:00
Charlie Marsh 471a1d657d
Migrate resolver proof-of-concept to PubGrub (#97)
## Summary

This PR enables the proof-of-concept resolver to backtrack by way of
using the `pubgrub-rs` crate.

Rather than using PubGrub as a _framework_ (implementing the
`DependencyProvider` trait, letting PubGrub call us), I've instead
copied over PubGrub's primary solver hook (which is only ~100 lines or
so) and modified it for our purposes (e.g., made it async).

There's a lot to improve here, but it's a start that will let us
understand PubGrub's appropriateness for this problem space. A few
observations:

- In simple cases, the resolver is slower than our current (naive)
resolver. I think it's just that the pipelining isn't as efficient as in
the naive case, where we can just stream package and version fetches
concurrently without any bottlenecks.
- A lot of the code here relates to bridging PubGrub with our own
abstractions -- so we need a `PubGrubPackage`, a `PubGrubVersion`, etc.
2023-10-15 22:05:44 -04:00
konsti de9e85978b
Fix tempdir rename (#94)
This fixes two bugs on linux:

`/tmp` and `$HOME` are technically on two different partitions on my
machine, which means that rename-as-atomic-dir-write doesn't work. The
solution is to create the temp dir in the target directory.

zip files may contain directory entries, we can't create files for them
but need to create directories. We could skip them though because iirc
they are not in the RECORD so they won't be uninstalled.
2023-10-12 18:47:38 +00:00
konsti 530edb6e39
Add output file option to compile (#93)
`pip-compile` has the same option. I need this esp. since piping doesn't
work as we write to stdout.
2023-10-12 20:42:06 +02:00
konsti 6a7954cdd0
Add `-p` base python option to venv command (#92)
This is the same option that `virtualenv` offers, except that we only
support absolute paths atm and not e.g. `-p 3.10` (which we need to
eventually).
2023-10-12 20:41:52 +02:00
Charlie Marsh a622345fbc
Replace mocked server with 'real' integration tests (#91)
We can always restore these from history, but right now, it feels a lot
more productive to just hit PyPI directly for our integration tests,
since we don't have to spend time figuring out mocks.
2023-10-12 17:34:48 +00:00
Charlie Marsh 496cb7b2ef
Migrate to `requirements_txt.rs` (#90)
Remove the parser I wrote in favor of Konsti's which is much more
complete. The only change vs. the version in `poc-monotrail` is that I
changed the tests to use insta rather than manually storing and
comparing against JSON snapshots.

Closes https://github.com/astral-sh/puffin/issues/89.
2023-10-12 17:09:00 +00:00
Charlie Marsh 906a482499
Separate unzip into its own install phase (#87) 2023-10-11 15:18:23 +00:00
Charlie Marsh 85162d1111
Parallelize wheel installations with Rayon (#84)
It looks like using _either_ async Rust with a `JoinSet` _or_
parallelizing a fixed threadpool with Rayon provide about a ~5% speed-up
over our current serial approach:

```console
❯ hyperfine --runs 30 --warmup 5 --prepare "./target/release/puffin venv .venv" \
  "./target/release/rayon sync ./scripts/benchmarks/requirements-large.txt" \
  "./target/release/async sync ./scripts/benchmarks/requirements-large.txt" \
  "./target/release/main sync ./scripts/benchmarks/requirements-large.txt"
Benchmark 1: ./target/release/rayon sync ./scripts/benchmarks/requirements-large.txt
  Time (mean ± σ):     295.7 ms ±  16.9 ms    [User: 28.6 ms, System: 263.3 ms]
  Range (min … max):   249.2 ms … 315.9 ms    30 runs

Benchmark 2: ./target/release/async sync ./scripts/benchmarks/requirements-large.txt
  Time (mean ± σ):     296.2 ms ±  20.2 ms    [User: 36.1 ms, System: 340.1 ms]
  Range (min … max):   258.0 ms … 359.4 ms    30 runs

Benchmark 3: ./target/release/main sync ./scripts/benchmarks/requirements-large.txt
  Time (mean ± σ):     306.6 ms ±  19.5 ms    [User: 25.3 ms, System: 220.5 ms]
  Range (min … max):   269.6 ms … 332.2 ms    30 runs

Summary
  './target/release/rayon sync ./scripts/benchmarks/requirements-large.txt' ran
    1.00 ± 0.09 times faster than './target/release/async sync ./scripts/benchmarks/requirements-large.txt'
    1.04 ± 0.09 times faster than './target/release/main sync ./scripts/benchmarks/requirements-large.txt'
```

It's much easier to just parallelize with Rayon and avoid async in the
underlying wheel code, so this PR takes that approach for now.
2023-10-10 23:46:30 -04:00
Charlie Marsh ed68d31e03
Add a basic test for the resolver (#86)
Mocks out the PyPI client using some checked-in fixtures. The test is
very basic, and I'm not very happy with all the ceremony around the
mocks and such, but it's an interesting experiment at least.
2023-10-11 03:30:53 +00:00
Charlie Marsh c1fb698eae
Add a separate dist-info name struct (#85) 2023-10-10 23:21:18 +00:00
Charlie Marsh d0764bdc23
Add `puffin venv` command to create virtual environments (#83)
Closes https://github.com/astral-sh/puffin/issues/58.
2023-10-10 13:46:25 -04:00
Charlie Marsh a0294a510c
Rework `puffin sync` output to summarize (#81)
This also moves away from using `tracing` for user-facing logging,
instead introducing a new `Printer` abstraction.

Closes #66.
2023-10-10 03:29:09 +00:00
Charlie Marsh 2d4a8c361b
Change puffin-cli binary to puffin (#80) 2023-10-09 17:19:33 -04:00
Charlie Marsh ba2b200fce
Enable release builds via `cargo-dist` (#79) 2023-10-09 20:48:55 +00:00
Charlie Marsh b90140e1bc
Add support for wheel uninstalls (#77)
Closes #36.
2023-10-09 14:14:33 -04:00
Charlie Marsh 239b5893d8
Fix version satisfier for unpinned dependencies (#74) 2023-10-09 11:48:39 -04:00
Charlie Marsh 485b1dceb6
Use a single requirements iterator in `sync` (#71) 2023-10-09 03:29:38 +00:00
Charlie Marsh ba72950546
Avoid passing cached wheels to the resolver step (#70)
When we go to install a locked `requirements.txt`, if a wheel is already
available in the local cache, and matches the version specifiers, we can
just use it directly without fetching the package metadata. This speeds
up the no-op case by about 33%.

Closes https://github.com/astral-sh/puffin/issues/48.
2023-10-08 22:17:19 -04:00
Charlie Marsh 5b71cfdd0b
Remove Monotrail-specific code from `install-wheel-rs` (#68)
I think this isn't necessary to support in this generic crate. If we
choose to adopt Monotrail-style concepts, we'll likely need to rework
them anyway.
2023-10-08 18:28:57 -04:00
Charlie Marsh adbee4fb32
Use recursive `clonefile` calls on macOS (#67)
It turns out that on macOS, you can pass `clonefile` a directory to
recursively copy an entire directory. This speeds up wheel installation
dramatically, by about 3x.
2023-10-08 21:44:02 +00:00
Charlie Marsh 1c942ab8fe
Tweak tracing output for sync command (#64) 2023-10-08 20:09:15 +00:00
Charlie Marsh a53f697f62
Use `tracing` for user-facing output (#63)
The setup is now as follows:

- All user-facing logging goes through `tracing` at an `info` leve.
(This excludes messages that go to `stdout`, like the compiled
`requirements.txt` file.)
- We have `--quiet` and `--verbose` command-line flags to set the
tracing filter and format defaults. So if you use `--verbose`, we
include timestamps and targets, and filter at `puffin=debug` level.
- However, we always respect `RUST_LOG`. So you can override the
_filter_ via `RUST_LOG`.

For example: the standard setup filters to `puffin=info`, and doesn't
show timestamps or targets:

<img width="1235" alt="Screen Shot 2023-10-08 at 3 41 22 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/54ca4db6-c66a-439e-bfa3-b86dee136e45">

If you run with `--verbose`, you get debug logging, but confined to our
crates:

<img width="1235" alt="Screen Shot 2023-10-08 at 3 41 57 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/c5c1af11-7f7a-4038-a173-d9eca4c3630b">

If you want verbose logging with _all_ crates, you can add
`RUST_LOG=debug`:

<img width="1235" alt="Screen Shot 2023-10-08 at 3 42 39 PM"
src="https://github.com/astral-sh/puffin/assets/1309177/0b5191f4-4db0-4db9-86ba-6f9fa521bcb6">

I think this is a reasonable setup, though we can see how it feels and
refine over time.

Closes https://github.com/astral-sh/puffin/issues/57.
2023-10-08 15:46:06 -04:00
Charlie Marsh 0ca17a1cf2
Use local copy of `gourgeist` (#62)
This PR gets `gourgeist` passing our local CI and integrated into the
broader workspace.

There's some duplicate between concepts in `gourgeist` (like the
`InterpreterInfo`) and structs we have elsewhere, but we can tackle
those later.
2023-10-08 18:45:08 +00:00
Charlie Marsh 7caf5f42b8
Copy over `gourgeist` crate (#61)
This PR copies over the `gourgeist` crate at commit
`e64c17a263dac6933702dc8d155425c053fe885a` with no modifications.

It won't pass CI, but modifications will intentionally be confined to
later PRs.
2023-10-08 14:37:09 -04:00
Charlie Marsh d1ed41170b
Cache environment marker lookups (#55)
Closes https://github.com/astral-sh/puffin/issues/53.
2023-10-08 05:31:19 +00:00
Charlie Marsh 5eef6e9636
Store cached wheels by dist-info-like name (#52)
Closes https://github.com/astral-sh/puffin/issues/50.
2023-10-08 04:28:04 +00:00
Charlie Marsh fd5aef2c75
Avoid error when repeatedly clearing cache (#51)
Also avoid failing to clear the cache when it contains non-directories
(e.g., I had a `.DS_Store` after looking at it in Finder).
2023-10-08 04:16:48 +00:00
Charlie Marsh 2a846e76b7
Store unzipped wheels in a cache (#49)
This PR massively speeds up the case in which you need to install wheels
that already exist in the global cache.

The new strategy is as follows:

- Download the wheel into the content-addressed cache.
- Unzip the wheel into the cache, but ignore content-addressing. It
turns out that writing to `cacache` for every file in the zip added a
ton of overhead, and I don't see any actual advantages to doing so.
Instead, we just unzip the contents into a directory at, e.g.,
`~/.cache/puffin/django-4.1.5`.
- (The unzip itself is now parallelized with Rayon.)
- When installing the wheel, we now support unzipping from a directory
instead of a zip archive. This required duplicating and tweaking a few
functions.
- When installing the wheel, we now use reflinks (or copy-on-write
links). These have a few fantastic properties: (1) they're extremely
cheap to create (on macOS, they are allegedly faster than hard links);
(2) they minimize disk space, since we avoid copying files entirely in
the vast majority of cases; and (3) if the user then edits a file
locally, the cache doesn't get polluted. Orogene, Bun, and soon pnpm all
use reflinks.

Puffin is now ~15x faster than `pip` for the common case of installing
cached data into a fresh environment.

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

Closes https://github.com/astral-sh/puffin/issues/39.
2023-10-08 04:04:48 +00:00
Charlie Marsh 92160e37df
Surface error when unable to find package (#45) 2023-10-07 19:43:12 +00:00
Charlie Marsh 9be02d1590
Skip already-installed dependencies during `sync` command (#43)
Closes https://github.com/astral-sh/puffin/issues/35.
2023-10-07 19:26:45 +00:00
Charlie Marsh bc1736feff
Add a `freeze` command to list installed dependencies (#42)
A pre-requisite for https://github.com/astral-sh/puffin/issues/35.
2023-10-07 18:46:09 +00:00
Charlie Marsh f3015ffc1f
Add a `clean` command to clear the cache (#41) 2023-10-07 15:19:03 +00:00
Charlie Marsh 162952bf64
Add a content-addressed cache for wheels (#38)
Closes https://github.com/astral-sh/puffin/issues/4.
2023-10-07 14:24:52 +00:00
Charlie Marsh 6c31631913
Fetch from `data-dist-info-metadata` when available (#37)
As specified in https://peps.python.org/pep-0658/#specification.
2023-10-07 13:05:29 +00:00
Charlie Marsh ae28552b3a
Use local copy of `install-wheel-rs` (#34)
This PR modifies the `install-wheel-rs` (and a few other crates) to get
everything playing nicely. Specifically, CI should pass, and all these
crates now use workspace dependencies between one another.

As part of this change, I split out the wheel name parsing into its own
`wheel-filename` crate, and the compatibility tag parsing into its own
`platform-tags` crate.
2023-10-07 01:43:55 +00:00
Charlie Marsh e824fe6d2b
Copy over `install-wheel-rs` crate (#33)
This PR copies over the `install-wheel-rs` crate at commit
`10730ea1a84c58af6b35fb74c89ed0578ab042b6` with no modifications.

It won't pass CI, but modifications will intentionally be confined to
later PRs.
2023-10-06 21:38:38 -04:00
Charlie Marsh c8477991a9
Use local versions of PEP 440 and PEP 508 crates (#32)
This PR modifies the PEP 440 and PEP 508 crates to pass CI, primarily by
fixing all lint violations.

We're also now using these crates in the workspace via `path`.
(Previously, we were still fetching them from Cargo.)
2023-10-07 00:16:44 +00:00
Charlie Marsh 4fcdb3c045
Copy over `pep508-rs` crate (#31)
This PR copies over the `pep440-rs` crate at commit
`82aa5d4dcbe676b121dc931b0afa09a82de8e3d7` with no modifications.

It won't pass CI, but modifications will intentionally be confined to
later PRs.
2023-10-06 20:12:19 -04:00
Charlie Marsh f03398bee3
Copy over `pep440-rs` crate (#30)
This PR copies over the `pep440-rs` crate at commit
`a8303b01ffef6fccfdce562a887f6b110d482ef3` with no modifications.

It won't pass CI, but modifications will intentionally be confined to
later PRs.
2023-10-06 20:11:52 -04:00
Charlie Marsh 36d0124e60
Do wheel downloads concurrently (#28) 2023-10-06 20:51:31 +00:00
Charlie Marsh dd26cfa0cc
Migrate to `tokio` (#27)
Closes https://github.com/astral-sh/puffin/issues/26.
2023-10-06 20:31:03 +00:00
Charlie Marsh ca6aa207ff
Move to workspace dependencies (#25) 2023-10-06 19:49:41 +00:00
Charlie Marsh dab70a661a
Change `install` to `sync` (with sync semantics) (#24)
For better separate at this stage (and following `pip-tools`), it's now
`puffin sync`, and it assumes `--no-deps`.
2023-10-06 19:42:58 +00:00
Charlie Marsh ff8e24a621
Move `puffin-installer` to its own crate (#23) 2023-10-06 19:31:21 +00:00
Charlie Marsh f395c9c98c Update README 2023-10-06 01:03:07 -04:00
Charlie Marsh 28721cf5fc Avoid caching wheel fetches 2023-10-06 00:50:30 -04:00
Charlie Marsh a43328d914
Support wheel installation (#19)
Closes https://github.com/astral-sh/puffin/issues/8.
2023-10-06 00:47:45 -04:00
Charlie Marsh 47bbb7a78e
Separate platform tags (#18) 2023-10-05 23:24:38 -04:00
Charlie Marsh 9ea6eaeb10
Add separate compile and install commands (#17)
Closes #9.
2023-10-05 21:44:31 -04:00
Charlie Marsh 4c30cb146a Add crate README 2023-10-05 21:09:58 -04:00
Charlie Marsh 8b151a64d5
Rename `puffin-requirements` to `puffin-package` (#16)
Closes https://github.com/astral-sh/puffin/issues/7.
2023-10-05 21:03:20 -04:00
Charlie Marsh 94895de46d
Add support for wheel tag parsing (#15)
Closes https://github.com/astral-sh/puffin/issues/12.
2023-10-05 20:59:58 -04:00
Charlie Marsh 2d6266b167
Add an HTTP cache (and `--no-cache` argument) (#14)
Closes https://github.com/astral-sh/puffin/issues/3.
2023-10-05 19:14:05 -04:00
Charlie Marsh 1063d8c150
Add Python interpreter detection (#11)
Closes https://github.com/astral-sh/puffin/issues/2.
2023-10-05 15:09:22 -04:00
Charlie Marsh b059c590c4
Add basic CI via GitHub Actions (#10)
Closes https://github.com/astral-sh/puffin/issues/1.
2023-10-05 13:42:58 -04:00
Charlie Marsh b4828fb3f2 Remove progress bar 2023-10-05 12:45:38 -04:00
Charlie Marsh 7f497fa43f Add progress bar 2023-10-05 12:45:38 -04:00
Charlie Marsh 8032d4606e Misc. changes 2023-10-05 12:45:38 -04:00
Charlie Marsh f51432382a Do basic resolution 2023-10-05 12:45:38 -04:00
Charlie Marsh 0f10595ac3 Add version selection 2023-10-05 12:45:38 -04:00
Charlie Marsh 44b444494e Fetch package metadata in parallel 2023-10-05 12:45:38 -04:00
Charlie Marsh b08e8c78b5 Remove normalized representation of SimpleJson 2023-10-05 12:45:38 -04:00
Charlie Marsh 610fd9994f Add client networking stack 2023-10-05 12:45:38 -04:00
Charlie Marsh 1a2f35801b Add client networking stack 2023-10-05 12:45:38 -04:00
Charlie Marsh 53607df7c6 Add a requirements.txt parser 2023-10-05 12:45:38 -04:00
Charlie Marsh 8b9ac30507 Add license, Cargo.toml, etc. 2023-10-05 12:45:38 -04:00