<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:
- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->
## Summary
<!-- What's the purpose of the change? What does it do, and why? -->
Currently record hashes are the hex encoded sha-256 sum. However,
they're supposed to be urlsafe-base64-nopad.
https://packaging.python.org/en/latest/specifications/recording-installed-packages/#the-record-fileFixes#15398
## Test Plan
<!-- How was it tested? -->
Build any wheel
```
uv build --wheel
```
Unpack the wheel
```
uvx wheel unpack dist/*.whl
```
Before this change, it will fail with a hash mismatch. I could confirm
with a local build that now the wheel can be unpacked with the `wheel`
command. While I don't enable hash checking when syncing, presumably it
would also currently fail.
## Summary
Make the use of `Self` consistent. Mostly done by running `cargo clippy
--fix -- -A clippy::all -W clippy::use_self`.
## Test Plan
<!-- How was it tested? -->
No need.
Following a CI failure in https://github.com/astral-sh/uv/pull/15028,
ensure that all workspace crates are inheriting the MSRV and other
workspace configuration from the workspace root.
Fixes#14920
## Summary
Problem: When building wheel packages, metadata files (such as RECORD,
METADATA, WHEEL, and
license files) were being created with incorrect Unix permissions
(--w--wx---), lacking
read permissions and having unexpected executable permissions.
Solution: The fix ensures that all metadata files in wheel packages are
created with proper
644 (rw-r--r--) permissions by:
- Adding explicit unix_permissions(0o644) setting in the write_bytes
method for metadata
files
- Updating permission constants to use octal notation for clarity
- Improving code comments to document the permission settings
Impact: This change ensures wheel packages created by uv have standard
file permissions
consistent with other Python build tools like setuptools, improving
compatibility and
following Python packaging best practices.
It seems that non-standard entrypoints are still widely used,
downgrading the error to a tracing warning.
Fixes#14442
---------
Co-authored-by: Ed Morley <501702+edmorley@users.noreply.github.com>
Support multiple root modules in namespace packages by enumerating them:
```toml
[tool.uv.build-backend]
module-name = ["foo", "bar"]
```
This allows applications with multiple root packages without migrating
to workspaces. Since those are regular module names (we iterate over
them an process each one like a single module names), it allows
combining dotted (namespace) names and regular names. It also
technically allows combining regular and stub modules, even though this
is even less recommends.
We don't recommend this structure (please use a workspace instead, or
structure everything in one root module), but it reduces the number of
cases that need `namespace = true`.
Fixes#14435Fixes#14438
---------
Co-authored-by: Zanie Blue <contact@zanie.dev>
The uv build backend has gone through some feedback cycles, we expect no
more major configuration changes, and we're ready to take the next step:
The uv build backend in stable.
This PR stabilizes:
* Using `uv_build` as build backend
* The documentation of the uv build backend
* The direct build fast path, where uv doesn't use PEP 517 if you're
using `uv_build` in a compatible version.
* `uv build --list`, which is limited to `uv_build`.
It does not:
* Make `uv_build` the default on `uv init`
* Make `--package` the default on `uv init`
In workspaces with multiple packages, you usually don't want to include
shared files such as the license repeatedly. Instead, we reading from
symlinked files. This would be supported if we had used std's `is_file`
and read methods, but walkdir's `is_file` does not consider symlinked
files as files.
See https://github.com/astral-sh/uv/issues/3957#issuecomment-2994675003
Unlike regular packages, specifying all `__init__.py` directories for a
namespace package would be very verbose There is e.g.
https://github.com/python-poetry/poetry/tree/main/src/poetry, which has
18 modules, or https://github.com/googleapis/api-common-protos which is
inconsistently nested. For both the Google Cloud SDK, there are both
packages with a single module and those with complex structures, with
many having multiple modules due to versioning through `<module>_v1`
versioning. The Azure SDK seems to use one module per package (it's not
explicitly documented but seems to follow from the process in
https://azure.github.io/azure-sdk/python_design.html#azure-sdk-distribution-packages
and
ccb0e03a3d/doc/dev/packaging.md).
For simplicity with complex projects, we add a `namespace = true` switch
which disabled checking for an `__init__.py`. We only check that there's
no `<module_root>/<module_name>/__init__.py` and otherwise add the whole
`<module_root>/<module_name>` folder. This comes at the cost of
`namespace = true` effectively creating an opt-out from our usual checks
that allows creating an almost entirely arbitrary package.
For simple projects with only a single module, the module name can be
dotted to point to the target module, so the build still gets checked:
```toml
[tool.uv.build-backend]
module-name = "poetry.core"
```
## Alternatives
### Declare all packages
We could make `module-name` a list and allow or require declaring all
packages:
```toml
[tool.uv.build-backend]
module-name = ["cloud_sdk.service.storage", "cloud_sdk.service.storage_v1", "cloud_sdk.billing.storage"]
```
Or for Poetry:
```toml
[tool.uv.build-backend]
module-name = [
"poetry.config",
"poetry.console",
"poetry.inspection",
"poetry.installation",
"poetry.json",
"poetry.layouts",
"poetry.masonry",
"poetry.mixology",
"poetry.packages",
"poetry.plugins",
"poetry.publishing",
"poetry.puzzle",
"poetry.pyproject",
"poetry.repositories",
"poetry.toml",
"poetry.utils",
"poetry.vcs",
"poetry.version"
]
```
### Support multiple namespaces
We could also allow namespace packages with multiple root level module:
```toml
[tool.uv.build-backend]
module-name = ["cloud_sdk.my_ext", "local_sdk.my_ext"]
```
For lack of use cases, we delegate this to creating a workspace with one
package per module.
## Implementation
Due to the more complex options for the module name, I'm moving
verification on deserialization later, dropping the source span we'd get
from serde. We also don't show similarly named directories anymore.
---------
Co-authored-by: Andrew Gallant <andrew@astral.sh>
Stubs packages are different in that their name ends with `-stubs`,
their module is `<module name>-stubs` (with a dash, not the generally
legal underscore) and their modules contain a `__init__.pyi` instead of
an `__init__.py`
(https://typing.python.org/en/latest/spec/distributing.html#stub-only-packages).
We add support in the uv build backend by detecting the `-stubs` suffix.
Fixes#13546
---------
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Rustfmt introduces a lot of formatting changes in the 2024 edition. To
not break everything all at once, we split out the set of formatting
changes compatible with both the 2021 and 2024 edition by first
formatting with the 2024 style, and then again with the currently used
2021 style.
Notable changes are the formatting of derive macro attributes and lines
with overly long strings and adding trailing semicolons after statements
consistently.
We may run on case-sensitive file systems (Linux, generally) or on
case-insensitive file systems (Windows, generally), while modules in
Python may be lower or upper case. For robustness over filesystem
casing, we require an explicit module name for modules with upper cases.
Fixes#13419
Unlike OS APIs, glob inclusion checks don't work when there are relative
path elements such as `./`. We normalize the path before using it for
the glob.
Fixes#13407
PEP 639 does not allow any characters that aren't in either their
limited glob syntax or the alphanumeric Unicode characters. This means
there's no way to express a glob such as `**/@test` for the excludes.
We extend the glob syntax from PEP 639 by introducing backslash escapes,
which can escape all characters but path separators (forward and
backwards slashes) to be parsed verbatim.
This means we have two glob parsers: The strict PEP 639 parser for
`project.license-files`, and our extended parser for `tool.uv`, with a
slight difference if you need to use special characters, to both adhere
to PEP 639 and to support cases such as #13280.
Fixes#13280
The goal of this PR is to support reproducible builds and best-effort
platform-independent builds. Previously, while the build backend would
build the same source dist and wheel on the same machine, they would
look different across different operating systems. This PR fixes the
platform-dependent walk dir order by sorting and removes
platform-specific permissions from the source dist that had caused those
differences.
The reproducibility goal does not extend to platform-dependent
filesystem features, such as permissions and links, especially in
interaction with Git. Since most users share code across platforms
through Git, we're focusing on cross-platform behavior under Git. One of
those caveats is intentional: If a file, such as a bash script, has an
executable bit, we preserve it. This means that E.g. builds of Git
checkout of a repository with an executable shell script in the sources
will have different archives on Unix and Windows. Another relevant case
are symlinks: By default, Git on Windows replaces symlinks with a file
that contains the path to the target file
(https://stackoverflow.com/q/5917249/3549270). (This example comes from
Cargo, where it means that the package archive is different on Windows
when symlinking license from the repository root to a workspace package)
Best reviewed commit-by-commit
Check that the source and module directory exist when build a source
distribution, instead of delaying the check to building the wheel. This
prevents building source distributions that can never be built into
wheels.
I removed the `set_cksum` as the value of it is replaced inside of
`append_data`.
## Summary
This should fix#12762 but I don't know how to test it.
---------
Co-authored-by: konstin <konstin@mailbox.org>
Match the module name to its module directory with potentially different
casing.
For example, a package may have the dist-info-normalized package name
`pil_util`, but the importable module is named `PIL_util`.
We get the module name either as dist-info-normalized package name, or
explicitly from the user. For dist-info-normalizing a package name, the
rules are lowercasing, replacing `.` with `_` and replace `-` with `_`.
Since `.` and `-` are not allowed in module names, we can check whether
a directory name matches our expected module name by lowercasing it.
Fixes#12187
---------
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
## Summary
This PR enables module name overrides for editable installs.
Builds upon https://github.com/astral-sh/uv/pull/11884. The
`tool.uv.build-backend.module-name` option is now respected during
editable build processes.
## Test Plan
Added a test.
---------
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
Thank you for uv, it has game-changer capabilities in the field of
Python package and environment maangement!
## Summary
This is a small PR adding the option `module-name`
(`tool.uv.build-backend.module-name`) to the uv build backend (
https://github.com/astral-sh/uv/issues/8779 ).
Currently, the uv build backend will assume that the module name matches
the (dash to underdash-transformed) package name. In some packaging
scenarios this is not the case, and currently there exists no
possibility to override it, which this PR addresses.
From the main issue ( https://github.com/astral-sh/uv/issues/8779 ) I
could not tell if there is any extensive roadmap or plans how to
implement more complex scenarios, hence this PR as a suggestion for a
small feature with a big impact for certain scenarios.
I am new to Rust, I hope the borrow/reference usage is correct.
## Test Plan
So far I tested this at an example, if desired I can look into extending
the tests.
Fixes#11428
---------
Co-authored-by: konstin <konstin@mailbox.org>
uv itself is a large package with many dependencies and lots of
features. To build a package using the uv build backend, you shouldn't
have to download and install the entirety of uv. For platform where we
don't provide wheels, it should be possible and fast to compile the uv
build backend. To that end, we're introducing a python package that
contains a trimmed down version of uv that only contains the build
backend, with a minimal dependency tree in rust.
The `uv_build` package is publish from CI just like uv itself. It is
part of the workspace, but has much less dependencies for its own
binary. We're using cargo deny to enforce that the network stack is not
part of the dependencies. A new build profile ensure we're getting the
minimum possible binary size for a rust binary.
---------
Co-authored-by: Zanie Blue <contact@zanie.dev>
## Summary
This PR extends the thinking in #10525 to platform tags, and then uses
the structured tag enums everywhere, rather than passing around strings.
I think this is a big improvement! It means we're no longer doing ad hoc
tag parsing all over the place.
From PEP 517:
```python
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
...
```
> Must create a .dist-info directory containing wheel metadata inside
the specified metadata_directory (i.e., creates a directory like
{metadata_directory}/{package}-{version}.dist-info/).
```python
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
...
```
> If the build frontend has previously called
prepare_metadata_for_build_wheel and depends on the wheel resulting from
this call to have metadata matching this earlier call, then it should
provide the path to the created .dist-info directory as the
metadata_directory argument.
Notice that the `metadata_directory` is different for the both hooks:
For `prepare_metadata_for_build_wheel` is doesn't contain the
`.dist-info` directory as final segment, for `build_wheel` it does.
Previously, the code assumed that both directories didn't contain the
`.dist-info` for both cases.
Checked with:
```
maturin build
uv init test-uv-build-backend --build-backend uv
cd test-uv-build-backend
uv build --sdist --preview
cd ..
UV_PREVIEW=1 pip install test-uv-build-backend/dist/test_uv_build_backend-0.1.0.tar.gz --no-index --find-links target/wheels/ -v --no-cache-dir
```
Fixes#9969
This is like #9556, but at the level of all other builds, including the
resolver and installer. Going through PEP 517 to build a package is
slow, so when building a package with the uv build backend, we can call
into the uv build backend directly instead: No temporary virtual env, no
temp venv sync, no python subprocess calls, no uv subprocess calls.
This fast path is gated through preview. Since the uv wheel is not
available at test time, I've manually confirmed the feature by comparing
`uv venv && cargo run pip install . -v --preview --reinstall .` and `uv
venv && cargo run pip install . -v --reinstall .`. When hacking the
preview so that the python uv build backend works without the setting
the direct build also (wheel built with `maturin build --profile
profiling`), we can see the perfomance difference:
```
$ hyperfine --prepare "uv venv" --warmup 3 \
"UV_PREVIEW=1 target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --preview" \
"target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --find-links target/wheels/"
Benchmark 1: UV_PREVIEW=1 target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --preview
Time (mean ± σ): 33.1 ms ± 2.5 ms [User: 25.7 ms, System: 13.0 ms]
Range (min … max): 29.8 ms … 47.3 ms 73 runs
Benchmark 2: target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --find-links target/wheels/
Time (mean ± σ): 115.1 ms ± 4.3 ms [User: 54.0 ms, System: 27.0 ms]
Range (min … max): 109.2 ms … 123.8 ms 25 runs
Summary
UV_PREVIEW=1 target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --preview ran
3.48 ± 0.29 times faster than target/profiling/uv pip install --no-deps --reinstall scripts/packages/built-by-uv --find-links target/wheels/
```
Do we need a global option to disable the fast path? There is one for
`uv build` because `--force-pep517` moves `uv build` much closer to a
`pip install` from source that a user of a library would experience (See
discussion at #9610), but uv overall doesn't really make guarantees
around the build env of dependencies, so I consider the direct build a
valid option.
Best reviewed commit-by-commit, only the last commit is the actual
implementation, while the preview mode introduction is just a
refactoring touching too many files.
Using the directory writer trait, we can collect the files instead of
writing them to a real sink. This builds up to a `uv build --list`
similar to `cargo package --list`. It is not connected to the cli yet.
For listing files, we first use a directory writer for source dists,
which we will use for collecting the filenames instead of writing the
archive in the future. I've split breaking `lib.rs` of uv-build-backend
into modules into the next PR.
No logic changes, only restructuring.
Best reviewed commit-by-commit
Going through PEP 517 to build a package is slow, so when building a
package with the uv build backend, we can call into the uv build backend
directly. This is the basis for the `uv build --list`.
This does not enable the fast path for general source dependencies.
There is a possible difference in execution if the latest uv version is
newer than the one currently running: The PEP 517 path would use the
latest version, while the fast path uses the current version.
Please review commit-by-commit
### Benchmark
`built_with_uv`, using the fast path:
```
$ hyperfine "~/projects/uv/target/profiling/uv build"
Time (mean ± σ): 9.2 ms ± 1.1 ms [User: 4.6 ms, System: 4.6 ms]
Range (min … max): 6.4 ms … 12.7 ms 290 runs
```
`hatcling_editable`, with hatchling being optimized for fast startup
times:
```
$ hyperfine "~/projects/uv/target/profiling/uv build"
Time (mean ± σ): 270.5 ms ± 18.4 ms [User: 230.8 ms, System: 44.5 ms]
Range (min … max): 250.7 ms … 298.4 ms 10 runs
```