diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index 597533a90..5af696e21 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; @@ -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() @@ -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 { @@ -456,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() @@ -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 afbc08077..d6fede06b 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, + /// 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 `/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, /// 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 b8403b968..543c5a497 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"