From 3f1980e43aae2f5bf64983612574cb0843038a13 Mon Sep 17 00:00:00 2001 From: konstin Date: Sun, 1 Dec 2024 12:25:42 +0100 Subject: [PATCH 1/2] Build backend: Default excludes 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" ```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, i'm considering rolling back the include-exclude anchored difference to always using anchored globs. 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.4.15,<5"] build-backend = "uv" ``` --- crates/uv-build-backend/src/lib.rs | 23 +++++++-- crates/uv-build-backend/src/metadata.rs | 53 ++++++++++----------- scripts/packages/built-by-uv/pyproject.toml | 4 +- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index 597533a90403..f62668a08e2b 100644 --- a/crates/uv-build-backend/src/lib.rs +++ b/crates/uv-build-backend/src/lib.rs @@ -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; @@ -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 = settings.wheel_exclude; + let mut excludes: Vec = 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 { @@ -693,7 +702,15 @@ pub fn build_source_dist( })?; let mut excludes: Vec = 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") { diff --git a/crates/uv-build-backend/src/metadata.rs b/crates/uv-build-backend/src/metadata.rs index afbc08077c59..d6fede06bed9 100644 --- a/crates/uv-build-backend/src/metadata.rs +++ b/crates/uv-build-backend/src/metadata.rs @@ -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 +/// `/pyproject.toml`. Use for example `assets/**/sample.csv` to include for all +/// `sample.csv` files in `/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 `/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 - /// `/pyproject.toml`. Use for example `assets/**/sample.csv` to include for all - /// `sample.csv` files in `/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, - /// 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 `/dist`. + /// If set to `false`, the default excludes aren't applied. /// - /// The glob syntax is the reduced portable glob from - /// [PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key). + /// Default excludes: `__pycache__`, `*.pyc`, and `*.pyo`. + pub(crate) default_excludes: bool, + + /// Glob expressions which files and directories to exclude from the source distribution. pub(crate) source_exclude: Vec, /// 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 `/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, /// 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(), } } diff --git a/scripts/packages/built-by-uv/pyproject.toml b/scripts/packages/built-by-uv/pyproject.toml index b8403b968ac3..543c5a49721e 100644 --- a/scripts/packages/built-by-uv/pyproject.toml +++ b/scripts/packages/built-by-uv/pyproject.toml @@ -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" From bd0a253337e1cb2c86ae2d71213cc95b2cb22fb1 Mon Sep 17 00:00:00 2001 From: konstin Date: Sun, 1 Dec 2024 12:32:03 +0100 Subject: [PATCH 2/2] Fix uv version check --- crates/uv-build-backend/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index f62668a08e2b..5af696e21656 100644 --- a/crates/uv-build-backend/src/lib.rs +++ b/crates/uv-build-backend/src/lib.rs @@ -304,7 +304,7 @@ pub fn build_wheel( ) -> Result { 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() @@ -465,7 +465,7 @@ pub fn build_editable( ) -> Result { 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()