Build backend: Default excludes (#9552)

When adding excludes, we usually don't want to include python cache
files. On the contrary, I haven't seen any project in my ecosystem
research that would want any of `__pycache__`, `*.pyc`, `*.pyo` to be
included. By moving them behind a `default-excludes` toggle, they are
always active even when defining custom excludes, but can be deactivated
if the user so chooses.

With includes and excludes being this small again, we can roll back the
include-exclude anchored difference to always using anchored globs (i.e.
you would need to use `**/build-*.h` below).

A pyproject.toml with custom settings with the change applied:

```toml
[project]
name = "foo"
version = "0.1.0"
readme = "README.md"
license-files = ["LICENSE*", "third-party-licenses/*"]

[tool.uv.build-backend]
# A file we need for the source dist -> wheel step, but not in the wheel itself (currently unused)
source-include = ["data/build-script.py"]
# A temporary or generated file we want to ignore
source-exclude = ["/src/foo/not-packaged.txt"]
# Headers are build-only
wheel-exclude = ["build-*.h"]

[tool.uv.build-backend.data]
scripts = "scripts"
data = "assets"
headers = "header"

[build-system]
requires = ["uv>=0.5.5,<0.6"]
build-backend = "uv"
```
This commit is contained in:
konsti 2024-12-01 14:09:55 +01:00 committed by GitHub
parent dfcceb6a1d
commit bb70382dac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 50 additions and 34 deletions

View file

@ -1,6 +1,6 @@
mod metadata;
use crate::metadata::{BuildBackendSettings, PyProjectToml, ValidationError};
use crate::metadata::{BuildBackendSettings, PyProjectToml, ValidationError, DEFAULT_EXCLUDES};
use flate2::write::GzEncoder;
use flate2::Compression;
use fs_err::File;
@ -304,7 +304,7 @@ pub fn build_wheel(
) -> Result<WheelFilename, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
pyproject_toml.check_build_system("1.0.0+test");
pyproject_toml.check_build_system(uv_version);
let settings = pyproject_toml
.settings()
.cloned()
@ -326,7 +326,16 @@ pub fn build_wheel(
let mut wheel_writer = ZipDirectoryWriter::new_wheel(File::create(&wheel_path)?);
// Wheel excludes
let mut excludes: Vec<String> = settings.wheel_exclude;
let mut excludes: Vec<String> = Vec::new();
if settings.default_excludes {
excludes.extend(DEFAULT_EXCLUDES.iter().map(ToString::to_string));
}
for exclude in settings.wheel_exclude {
// Avoid duplicate entries.
if !excludes.contains(&exclude) {
excludes.push(exclude);
}
}
// The wheel must not include any files excluded by the source distribution (at least until we
// have files generated in the source dist -> wheel build step).
for exclude in settings.source_exclude {
@ -456,7 +465,7 @@ pub fn build_editable(
) -> Result<WheelFilename, Error> {
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
let pyproject_toml = PyProjectToml::parse(&contents)?;
pyproject_toml.check_build_system("1.0.0+test");
pyproject_toml.check_build_system(uv_version);
let settings = pyproject_toml
.settings()
.cloned()
@ -693,7 +702,15 @@ pub fn build_source_dist(
})?;
let mut excludes: Vec<String> = Vec::new();
excludes.extend(settings.source_exclude);
if settings.default_excludes {
excludes.extend(DEFAULT_EXCLUDES.iter().map(ToString::to_string));
}
for exclude in settings.source_exclude {
// Avoid duplicate entries.
if !excludes.contains(&exclude) {
excludes.push(exclude);
}
}
debug!("Source dist excludes: {:?}", excludes);
let exclude_matcher = build_exclude_matcher(excludes)?;
if exclude_matcher.is_match("pyproject.toml") {

View file

@ -17,7 +17,7 @@ use version_ranges::Ranges;
use walkdir::WalkDir;
/// By default, we ignore generated python files.
const DEFAULT_EXCLUDES: &[&str] = &["__pycache__", "*.pyc", "*.pyo"];
pub(crate) const DEFAULT_EXCLUDES: &[&str] = &["__pycache__", "*.pyc", "*.pyo"];
#[derive(Debug, Error)]
pub enum ValidationError {
@ -708,6 +708,8 @@ pub(crate) struct ToolUv {
/// To select which files to include in the source distribution, we first add the includes, then
/// remove the excludes from that.
///
/// ## Include and exclude configuration
///
/// When building the source distribution, the following files and directories are included:
/// * `pyproject.toml`
/// * The module under `tool.uv.build-backend.module-root`, by default
@ -732,6 +734,21 @@ pub(crate) struct ToolUv {
/// There are no specific wheel includes. There must only be one top level module, and all data
/// files must either be under the module root or in a data directory. Most packages store small
/// data in the module root alongside the source code.
///
/// ## Include and exclude syntax
///
/// Includes are anchored, which means that `pyproject.toml` includes only
/// `<project root>/pyproject.toml`. Use for example `assets/**/sample.csv` to include for all
/// `sample.csv` files in `<project root>/assets` or any child directory. To recursively include
/// all files under a directory, use a `/**` suffix, e.g. `src/**`. For performance and
/// reproducibility, avoid unanchored matches such as `**/sample.csv`.
///
/// Excludes are not anchored, which means that `__pycache__` excludes all directories named
/// `__pycache__` and it's children anywhere. To anchor a directory, use a `/` prefix, e.g.,
/// `/dist` will exclude only `<project root>/dist`.
///
/// The glob syntax is the reduced portable glob from
/// [PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key).
#[derive(Deserialize, Debug, Clone)]
#[serde(default, rename_all = "kebab-case")]
pub(crate) struct BuildBackendSettings {
@ -744,38 +761,19 @@ pub(crate) struct BuildBackendSettings {
///
/// `pyproject.toml` and the contents of the module directory are always included.
///
/// Includes are anchored, which means that `pyproject.toml` includes only
/// `<project root>/pyproject.toml`. Use for example `assets/**/sample.csv` to include for all
/// `sample.csv` files in `<project root>/assets` or any child directory. To recursively include
/// all files under a directory, use a `/**` suffix, e.g. `src/**`. For performance and
/// reproducibility, avoid unanchored matches such as `**/sample.csv`.
///
/// The glob syntax is the reduced portable glob from
/// [PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key).
pub(crate) source_include: Vec<String>,
/// If set to `false`, the default excludes aren't applied.
///
/// Default excludes: `__pycache__`, `*.pyc`, and `*.pyo`.
pub(crate) default_excludes: bool,
/// Glob expressions which files and directories to exclude from the source distribution.
///
/// Default: `__pycache__`, `*.pyc`, and `*.pyo`.
///
/// Excludes are not anchored, which means that `__pycache__` excludes all directories named
/// `__pycache__` and it's children anywhere. To anchor a directory, use a `/` prefix, e.g.,
/// `/dist` will exclude only `<project root>/dist`.
///
/// The glob syntax is the reduced portable glob from
/// [PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key).
pub(crate) source_exclude: Vec<String>,
/// Glob expressions which files and directories to exclude from the wheel.
///
/// Default: `__pycache__`, `*.pyc`, and `*.pyo`.
///
/// Excludes are not anchored, which means that `__pycache__` excludes all directories named
/// `__pycache__` and it's children anywhere. To anchor a directory, use a `/` prefix, e.g.,
/// `/dist` will exclude only `<project root>/dist`.
///
/// The glob syntax is the reduced portable glob from
/// [PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key).
pub(crate) wheel_exclude: Vec<String>,
/// Data includes for wheels.
@ -790,8 +788,9 @@ impl Default for BuildBackendSettings {
Self {
module_root: PathBuf::from("src"),
source_include: Vec::new(),
source_exclude: DEFAULT_EXCLUDES.iter().map(ToString::to_string).collect(),
wheel_exclude: DEFAULT_EXCLUDES.iter().map(ToString::to_string).collect(),
default_excludes: true,
source_exclude: Vec::new(),
wheel_exclude: Vec::new(),
data: WheelDataIncludes::default(),
}
}

View file

@ -11,7 +11,7 @@ license-files = ["LICENSE*", "third-party-licenses/*"]
# A file we need for the source dist -> wheel step, but not in the wheel itself (currently unused)
source-include = ["data/build-script.py"]
# A temporary or generated file we want to ignore
source-exclude = ["/src/built_by_uv/not-packaged.txt", "__pycache__", "*.pyc", "*.pyo"]
source-exclude = ["/src/built_by_uv/not-packaged.txt"]
# Headers are build-only
wheel-exclude = ["build-*.h"]
@ -21,5 +21,5 @@ data = "assets"
headers = "header"
[build-system]
requires = ["uv>=0.4.15,<5"]
requires = ["uv>=0.5,<0.6"]
build-backend = "uv"