From da09ece8a94b22e40a1f5bd6414ea3277894141b Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 21 Apr 2025 12:27:49 +0200 Subject: [PATCH] Build backend: Add reference docs and schema (#12803) Add reference documentation and schema integration for the uv build backend. The reference documentation comes with a preview note upfront. --- Cargo.lock | 4 + crates/uv-build-backend/Cargo.toml | 10 ++ crates/uv-build-backend/src/lib.rs | 2 + crates/uv-build-backend/src/metadata.rs | 147 +---------------- crates/uv-build-backend/src/settings.rs | 159 +++++++++++++++++++ crates/uv-build-backend/src/source_dist.rs | 6 +- crates/uv-build-backend/src/wheel.rs | 6 +- crates/uv-globfilter/README.md | 2 +- crates/uv-globfilter/src/portable_glob.rs | 4 +- crates/uv-macros/src/options_metadata.rs | 4 +- crates/uv-pypi-types/src/identifier.rs | 34 ++++ crates/uv-settings/src/settings.rs | 7 +- crates/uv-workspace/Cargo.toml | 1 + crates/uv-workspace/src/pyproject.rs | 11 +- crates/uv-workspace/src/workspace.rs | 34 ++-- docs/reference/settings.md | 173 +++++++++++++++++++++ pyproject.toml | 1 + uv.schema.json | 125 +++++++++++++++ 18 files changed, 559 insertions(+), 171 deletions(-) create mode 100644 crates/uv-build-backend/src/settings.rs diff --git a/Cargo.lock b/Cargo.lock index 75d4ae566..80f0e621a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4743,6 +4743,7 @@ dependencies = [ "indoc", "insta", "itertools 0.14.0", + "schemars", "serde", "sha2", "spdx", @@ -4754,7 +4755,9 @@ dependencies = [ "uv-distribution-filename", "uv-fs", "uv-globfilter", + "uv-macros", "uv-normalize", + "uv-options-metadata", "uv-pep440", "uv-pep508", "uv-platform-tags", @@ -5916,6 +5919,7 @@ dependencies = [ "toml_edit", "tracing", "url", + "uv-build-backend", "uv-cache-key", "uv-distribution-types", "uv-fs", diff --git a/crates/uv-build-backend/Cargo.toml b/crates/uv-build-backend/Cargo.toml index 23240561c..f23581662 100644 --- a/crates/uv-build-backend/Cargo.toml +++ b/crates/uv-build-backend/Cargo.toml @@ -16,7 +16,9 @@ doctest = false uv-distribution-filename = { workspace = true } uv-fs = { workspace = true } uv-globfilter = { workspace = true } +uv-macros = { workspace = true } uv-normalize = { workspace = true } +uv-options-metadata = { workspace = true } uv-pep440 = { workspace = true } uv-pep508 = { workspace = true } uv-platform-tags = { workspace = true } @@ -29,6 +31,7 @@ flate2 = { workspace = true, default-features = false } fs-err = { workspace = true } globset = { workspace = true } itertools = { workspace = true } +schemars = { workspace = true, optional = true } serde = { workspace = true } sha2 = { workspace = true } spdx = { workspace = true } @@ -43,6 +46,13 @@ zip = { workspace = true } [lints] workspace = true +[package.metadata.cargo-shear] +# Imported by the `OptionsMetadata` derive macro +ignored = ["uv-options-metadata"] + +[features] +schemars = ["dep:schemars", "uv-pypi-types/schemars"] + [dev-dependencies] indoc = { workspace = true } insta = { version = "1.40.0", features = ["filters"] } diff --git a/crates/uv-build-backend/src/lib.rs b/crates/uv-build-backend/src/lib.rs index c2c12ee4a..00f24aa5c 100644 --- a/crates/uv-build-backend/src/lib.rs +++ b/crates/uv-build-backend/src/lib.rs @@ -1,9 +1,11 @@ mod metadata; mod serde_verbatim; +mod settings; mod source_dist; mod wheel; pub use metadata::{check_direct_build, PyProjectToml}; +pub use settings::{BuildBackendSettings, WheelDataIncludes}; pub use source_dist::{build_source_dist, list_source_dist}; pub use wheel::{build_editable, build_wheel, list_wheel, metadata}; diff --git a/crates/uv-build-backend/src/metadata.rs b/crates/uv-build-backend/src/metadata.rs index c5acb35bd..9436c5c19 100644 --- a/crates/uv-build-backend/src/metadata.rs +++ b/crates/uv-build-backend/src/metadata.rs @@ -18,10 +18,10 @@ use uv_pep440::{Version, VersionSpecifiers}; use uv_pep508::{ ExtraOperator, MarkerExpression, MarkerTree, MarkerValueExtra, Requirement, VersionOrUrl, }; -use uv_pypi_types::{Identifier, Metadata23, VerbatimParsedUrl}; +use uv_pypi_types::{Metadata23, VerbatimParsedUrl}; use crate::serde_verbatim::SerdeVerbatim; -use crate::Error; +use crate::{BuildBackendSettings, Error}; /// By default, we ignore generated python files. pub(crate) const DEFAULT_EXCLUDES: &[&str] = &["__pycache__", "*.pyc", "*.pyo"]; @@ -796,149 +796,6 @@ pub(crate) struct ToolUv { build_backend: Option, } -/// 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 -/// `src//**`. -/// * `project.license-files` and `project.readme`. -/// * All directories under `tool.uv.build-backend.data`. -/// * All patterns from `tool.uv.build-backend.source-include`. -/// -/// From these, we remove the `tool.uv.build-backend.source-exclude` matches. -/// -/// When building the wheel, the following files and directories are included: -/// * The module under `tool.uv.build-backend.module-root`, by default -/// `src//**`. -/// * `project.license-files` and `project.readme`, as part of the project metadata. -/// * Each directory under `tool.uv.build-backend.data`, as data directories. -/// -/// From these, we remove the `tool.uv.build-backend.source-exclude` and -/// `tool.uv.build-backend.wheel-exclude` matches. The source dist excludes are applied to avoid -/// source tree -> wheel source including more files than -/// source tree -> source distribution -> wheel. -/// -/// 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 { - /// The directory that contains the module directory, usually `src`, or an empty path when - /// using the flat layout over the src layout. - pub(crate) module_root: PathBuf, - - /// The name of the module directory inside `module-root`. - /// - /// The default module name is the package name with dots and dashes replaced by underscores. - /// - /// Note that using this option runs the risk of creating two packages with different names but - /// the same module names. Installing such packages together leads to unspecified behavior, - /// often with corrupted files or directory trees. - pub(crate) module_name: Option, - - /// Glob expressions which files and directories to additionally include in the source - /// distribution. - /// - /// `pyproject.toml` and the contents of the module directory are always included. - /// - /// 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. - pub(crate) source_exclude: Vec, - - /// Glob expressions which files and directories to exclude from the wheel. - pub(crate) wheel_exclude: Vec, - - /// Data includes for wheels. - /// - /// The directories included here are also included in the source distribution. They are copied - /// to the right wheel subdirectory on build. - pub(crate) data: WheelDataIncludes, -} - -impl Default for BuildBackendSettings { - fn default() -> Self { - Self { - module_root: PathBuf::from("src"), - module_name: None, - source_include: Vec::new(), - default_excludes: true, - source_exclude: Vec::new(), - wheel_exclude: Vec::new(), - data: WheelDataIncludes::default(), - } - } -} - -/// Data includes for wheels. -/// -/// Each entry is a directory, whose contents are copied to the matching directory in the wheel in -/// `-.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this data -/// is moved to its target location, as defined by -/// : -/// - `data`: Installed over the virtualenv environment root. Warning: This may override existing -/// files! -/// - `scripts`: Installed to the directory for executables, `/bin` on Unix or -/// `\Scripts` on Windows. This directory is added to PATH when the virtual environment is -/// activated or when using `uv run`, so this data type can be used to install additional -/// binaries. Consider using `project.scripts` instead for starting Python code. -/// - `headers`: Installed to the include directory, where compilers building Python packages with -/// this package as built requirement will search for header files. -/// - `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended to -/// uses these two options. -#[derive(Default, Deserialize, Debug, Clone)] -// `deny_unknown_fields` to catch typos such as `header` vs `headers`. -#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] -pub(crate) struct WheelDataIncludes { - purelib: Option, - platlib: Option, - headers: Option, - scripts: Option, - data: Option, -} - -impl WheelDataIncludes { - /// Yield all data directories name and corresponding paths. - pub(crate) fn iter(&self) -> impl Iterator { - [ - ("purelib", self.purelib.as_deref()), - ("platlib", self.platlib.as_deref()), - ("headers", self.headers.as_deref()), - ("scripts", self.scripts.as_deref()), - ("data", self.data.as_deref()), - ] - .into_iter() - .filter_map(|(name, value)| Some((name, value?))) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/uv-build-backend/src/settings.rs b/crates/uv-build-backend/src/settings.rs new file mode 100644 index 000000000..fb9803799 --- /dev/null +++ b/crates/uv-build-backend/src/settings.rs @@ -0,0 +1,159 @@ +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use uv_macros::OptionsMetadata; +use uv_pypi_types::Identifier; + +/// Settings for the uv build backend (`uv_build`). +/// +/// !!! note +/// +/// The uv build backend is currently in preview and may change in any future release. +/// +/// Note that those settings only apply when using the `uv_build` backend, other build backends +/// (such as hatchling) have their own configuration. +/// +/// All options that accept globs use the portable glob patterns from +/// [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/). +#[derive(Deserialize, Serialize, OptionsMetadata, Debug, Clone, PartialEq, Eq)] +#[serde(default, rename_all = "kebab-case")] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct BuildBackendSettings { + /// The directory that contains the module directory. + /// + /// Common values are `src` (src layout, the default) or an empty path (flat layout). + #[option( + default = r#""src""#, + value_type = "str", + example = r#"module-root = """# + )] + pub module_root: PathBuf, + + /// The name of the module directory inside `module-root`. + /// + /// The default module name is the package name with dots and dashes replaced by underscores. + /// + /// Note that using this option runs the risk of creating two packages with different names but + /// the same module names. Installing such packages together leads to unspecified behavior, + /// often with corrupted files or directory trees. + #[option( + default = r#"None"#, + value_type = "str", + example = r#"module-name = "sklearn""# + )] + pub module_name: Option, + + /// Glob expressions which files and directories to additionally include in the source + /// distribution. + /// + /// `pyproject.toml` and the contents of the module directory are always included. + #[option( + default = r#"[]"#, + value_type = "list[str]", + example = r#"source-include = ["tests/**"]"# + )] + pub source_include: Vec, + + /// If set to `false`, the default excludes aren't applied. + /// + /// Default excludes: `__pycache__`, `*.pyc`, and `*.pyo`. + #[option( + default = r#"true"#, + value_type = "bool", + example = r#"default-excludes = false"# + )] + pub default_excludes: bool, + + /// Glob expressions which files and directories to exclude from the source distribution. + #[option( + default = r#"[]"#, + value_type = "list[str]", + example = r#"source-exclude = ["*.bin"]"# + )] + pub source_exclude: Vec, + + /// Glob expressions which files and directories to exclude from the wheel. + #[option( + default = r#"[]"#, + value_type = "list[str]", + example = r#"wheel-exclude = ["*.bin"]"# + )] + pub wheel_exclude: Vec, + + /// Data includes for wheels. + /// + /// Each entry is a directory, whose contents are copied to the matching directory in the wheel + /// in `-.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this + /// data is moved to its target location, as defined by + /// . Usually, small + /// data files are included by placing them in the Python module instead of using data includes. + /// + /// - `scripts`: Installed to the directory for executables, `/bin` on Unix or + /// `\Scripts` on Windows. This directory is added to `PATH` when the virtual + /// environment is activated or when using `uv run`, so this data type can be used to install + /// additional binaries. Consider using `project.scripts` instead for Python entrypoints. + /// - `data`: Installed over the virtualenv environment root. + /// + /// Warning: This may override existing files! + /// + /// - `headers`: Installed to the include directory. Compilers building Python packages + /// with this package as build requirement use the include directory to find additional header + /// files. + /// - `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended + /// to uses these two options. + // TODO(konsti): We should show a flat example instead. + // ```toml + // [tool.uv.build-backend.data] + // headers = "include/headers", + // scripts = "bin" + // ``` + #[option( + default = r#"{}"#, + value_type = "dict[str, str]", + example = r#"data = { "headers": "include/headers", "scripts": "bin" }"# + )] + pub data: WheelDataIncludes, +} + +impl Default for BuildBackendSettings { + fn default() -> Self { + Self { + module_root: PathBuf::from("src"), + module_name: None, + source_include: Vec::new(), + default_excludes: true, + source_exclude: Vec::new(), + wheel_exclude: Vec::new(), + data: WheelDataIncludes::default(), + } + } +} + +/// Data includes for wheels. +/// +/// See `BuildBackendSettings::data`. +#[derive(Default, Deserialize, Serialize, OptionsMetadata, Debug, Clone, PartialEq, Eq)] +// `deny_unknown_fields` to catch typos such as `header` vs `headers`. +#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct WheelDataIncludes { + purelib: Option, + platlib: Option, + headers: Option, + scripts: Option, + data: Option, +} + +impl WheelDataIncludes { + /// Yield all data directories name and corresponding paths. + pub fn iter(&self) -> impl Iterator { + [ + ("purelib", self.purelib.as_deref()), + ("platlib", self.platlib.as_deref()), + ("headers", self.headers.as_deref()), + ("scripts", self.scripts.as_deref()), + ("data", self.data.as_deref()), + ] + .into_iter() + .filter_map(|(name, value)| Some((name, value?))) + } +} diff --git a/crates/uv-build-backend/src/source_dist.rs b/crates/uv-build-backend/src/source_dist.rs index 9cfd7f5f0..ba519e89b 100644 --- a/crates/uv-build-backend/src/source_dist.rs +++ b/crates/uv-build-backend/src/source_dist.rs @@ -1,6 +1,8 @@ -use crate::metadata::{BuildBackendSettings, DEFAULT_EXCLUDES}; +use crate::metadata::DEFAULT_EXCLUDES; use crate::wheel::build_exclude_matcher; -use crate::{find_roots, DirectoryWriter, Error, FileList, ListWriter, PyProjectToml}; +use crate::{ + find_roots, BuildBackendSettings, DirectoryWriter, Error, FileList, ListWriter, PyProjectToml, +}; use flate2::write::GzEncoder; use flate2::Compression; use fs_err::File; diff --git a/crates/uv-build-backend/src/wheel.rs b/crates/uv-build-backend/src/wheel.rs index 4c606f2dc..4fa0ca73c 100644 --- a/crates/uv-build-backend/src/wheel.rs +++ b/crates/uv-build-backend/src/wheel.rs @@ -17,8 +17,10 @@ use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag}; use uv_pypi_types::Identifier; use uv_warnings::warn_user_once; -use crate::metadata::{BuildBackendSettings, DEFAULT_EXCLUDES}; -use crate::{find_roots, DirectoryWriter, Error, FileList, ListWriter, PyProjectToml}; +use crate::metadata::DEFAULT_EXCLUDES; +use crate::{ + find_roots, BuildBackendSettings, DirectoryWriter, Error, FileList, ListWriter, PyProjectToml, +}; /// Build a wheel from the source tree and place it in the output directory. pub fn build_wheel( diff --git a/crates/uv-globfilter/README.md b/crates/uv-globfilter/README.md index a9a7242f1..66bbff1e7 100644 --- a/crates/uv-globfilter/README.md +++ b/crates/uv-globfilter/README.md @@ -16,7 +16,7 @@ When traversing the directory, you can use ## Syntax This crate supports the cross-language, restricted glob syntax from -[PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key): +[PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/): - Alphanumeric characters, underscores (`_`), hyphens (`-`) and dots (`.`) are matched verbatim. - The special glob characters are: diff --git a/crates/uv-globfilter/src/portable_glob.rs b/crates/uv-globfilter/src/portable_glob.rs index d98802f37..20c62c68d 100644 --- a/crates/uv-globfilter/src/portable_glob.rs +++ b/crates/uv-globfilter/src/portable_glob.rs @@ -1,4 +1,4 @@ -//! Cross-language glob syntax from [PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key). +//! Cross-language glob syntax from [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/). use globset::{Glob, GlobBuilder}; use thiserror::Error; @@ -28,7 +28,7 @@ pub enum PortableGlobError { TooManyStars { glob: String, pos: usize }, } -/// Parse cross-language glob syntax from [PEP 639](https://peps.python.org/pep-0639/#add-license-FILES-key): +/// Parse cross-language glob syntax from [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/): /// /// - Alphanumeric characters, underscores (`_`), hyphens (`-`) and dots (`.`) are matched verbatim. /// - The special glob characters are: diff --git a/crates/uv-macros/src/options_metadata.rs b/crates/uv-macros/src/options_metadata.rs index 6865ba397..afb15f161 100644 --- a/crates/uv-macros/src/options_metadata.rs +++ b/crates/uv-macros/src/options_metadata.rs @@ -291,7 +291,7 @@ fn parse_field_attributes(attribute: &Attribute) -> syn::Result return Err(syn::Error::new( meta.path.span(), format!( - "Deprecated meta {:?} is not supported by ruff's option macro.", + "Deprecated meta {:?} is not supported by uv's option macro.", meta.path.get_ident() ), )); @@ -332,7 +332,7 @@ fn parse_deprecated_attribute(attribute: &Attribute) -> syn::Result serde::de::Deserialize<'de> for Identifier { } } +impl Serialize for Identifier { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + Serialize::serialize(&self.0, serializer) + } +} + +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for Identifier { + fn schema_name() -> String { + "Identifier".to_string() + } + + fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + instance_type: Some(schemars::schema::InstanceType::String.into()), + string: Some(Box::new(schemars::schema::StringValidation { + // Best-effort Unicode support (https://stackoverflow.com/a/68844380/3549270) + pattern: Some(r"^[_\p{Alphabetic}][_0-9\p{Alphabetic}]*$".to_string()), + ..schemars::schema::StringValidation::default() + })), + metadata: Some(Box::new(schemars::schema::Metadata { + description: Some("An identifier in Python".to_string()), + ..schemars::schema::Metadata::default() + })), + ..schemars::schema::SchemaObject::default() + } + .into() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index f3d5ea2fe..769699287 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -141,6 +141,9 @@ pub struct Options { #[cfg_attr(feature = "schemars", schemars(skip))] pub r#package: Option, + + #[cfg_attr(feature = "schemars", schemars(skip))] + pub build_backend: Option, } impl Options { @@ -1842,7 +1845,6 @@ pub struct OptionsWire { dev_dependencies: Option, // Build backend - #[allow(dead_code)] build_backend: Option, } @@ -1907,7 +1909,7 @@ impl From for Options { managed, package, // Used by the build backend - build_backend: _, + build_backend, } = value; Self { @@ -1956,6 +1958,7 @@ impl From for Options { }, pip, cache_keys, + build_backend, override_dependencies, constraint_dependencies, build_constraint_dependencies, diff --git a/crates/uv-workspace/Cargo.toml b/crates/uv-workspace/Cargo.toml index 0ba386acc..59bc02f29 100644 --- a/crates/uv-workspace/Cargo.toml +++ b/crates/uv-workspace/Cargo.toml @@ -16,6 +16,7 @@ doctest = false workspace = true [dependencies] +uv-build-backend = { workspace = true, features = ["schemars"] } uv-cache-key = { workspace = true } uv-distribution-types = { workspace = true } uv-fs = { workspace = true, features = ["tokio", "schemars"] } diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 85c2f5e71..fab891cd7 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -18,7 +18,7 @@ use rustc_hash::{FxBuildHasher, FxHashSet}; use serde::{de::IntoDeserializer, de::SeqAccess, Deserialize, Deserializer, Serialize}; use thiserror::Error; use url::Url; - +use uv_build_backend::BuildBackendSettings; use uv_distribution_types::{Index, IndexName, RequirementSource}; use uv_fs::{relative_to, PortablePathBuf}; use uv_git_types::GitReference; @@ -583,6 +583,15 @@ pub struct ToolUv { "# )] pub conflicts: Option, + + // Only exists on this type for schema and docs generation, the build backend settings are + // never merged in a workspace and read separately by the backend code. + /// Configuration for the uv build backend. + /// + /// Note that those settings only apply when using the `uv_build` backend, other build backends + /// (such as hatchling) have their own configuration. + #[option_group] + pub build_backend: Option, } #[derive(Default, Debug, Clone, PartialEq, Eq)] diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 3428741ec..374b9e762 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -1824,7 +1824,8 @@ mod tests { "build-constraint-dependencies": null, "environments": null, "required-environments": null, - "conflicts": null + "conflicts": null, + "build-backend": null } }, "dependency-groups": null @@ -1918,7 +1919,8 @@ mod tests { "build-constraint-dependencies": null, "environments": null, "required-environments": null, - "conflicts": null + "conflicts": null, + "build-backend": null } }, "dependency-groups": null @@ -2060,7 +2062,7 @@ mod tests { { ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" }, - @r###" + @r#" { "project_root": "[ROOT]", "project_name": "albatross", @@ -2127,14 +2129,15 @@ mod tests { "build-constraint-dependencies": null, "environments": null, "required-environments": null, - "conflicts": null + "conflicts": null, + "build-backend": null } }, "dependency-groups": null } } } - "###); + "#); }); // Rewrite the members to both include and exclude `bird-feeder` by name. @@ -2165,7 +2168,7 @@ mod tests { { ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" }, - @r###" + @r#" { "project_root": "[ROOT]", "project_name": "albatross", @@ -2233,14 +2236,15 @@ mod tests { "build-constraint-dependencies": null, "environments": null, "required-environments": null, - "conflicts": null + "conflicts": null, + "build-backend": null } }, "dependency-groups": null } } } - "###); + "#); }); // Rewrite the exclusion to use the top-level directory (`packages`). @@ -2271,7 +2275,7 @@ mod tests { { ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" }, - @r###" + @r#" { "project_root": "[ROOT]", "project_name": "albatross", @@ -2352,14 +2356,15 @@ mod tests { "build-constraint-dependencies": null, "environments": null, "required-environments": null, - "conflicts": null + "conflicts": null, + "build-backend": null } }, "dependency-groups": null } } } - "###); + "#); }); // Rewrite the exclusion to use the top-level directory with a glob (`packages/*`). @@ -2390,7 +2395,7 @@ mod tests { { ".workspace.packages.*.pyproject_toml" => "[PYPROJECT_TOML]" }, - @r###" + @r#" { "project_root": "[ROOT]", "project_name": "albatross", @@ -2445,14 +2450,15 @@ mod tests { "build-constraint-dependencies": null, "environments": null, "required-environments": null, - "conflicts": null + "conflicts": null, + "build-backend": null } }, "dependency-groups": null } } } - "###); + "#); }); Ok(()) diff --git a/docs/reference/settings.md b/docs/reference/settings.md index 4f47f998f..2d83a96d6 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -367,6 +367,179 @@ pydantic = { path = "/path/to/pydantic", editable = true } --- +### `build-backend` + +Settings for the uv build backend (`uv_build`). + +!!! note + + The uv build backend is currently in preview and may change in any future release. + +Note that those settings only apply when using the `uv_build` backend, other build backends +(such as hatchling) have their own configuration. + +All options that accept globs use the portable glob patterns from +[PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/). + +#### [`data`](#build-backend_data) {: #build-backend_data } + + +Data includes for wheels. + +Each entry is a directory, whose contents are copied to the matching directory in the wheel +in `-.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this +data is moved to its target location, as defined by +. Usually, small +data files are included by placing them in the Python module instead of using data includes. + +- `scripts`: Installed to the directory for executables, `/bin` on Unix or + `\Scripts` on Windows. This directory is added to `PATH` when the virtual + environment is activated or when using `uv run`, so this data type can be used to install + additional binaries. Consider using `project.scripts` instead for Python entrypoints. +- `data`: Installed over the virtualenv environment root. + + Warning: This may override existing files! + +- `headers`: Installed to the include directory. Compilers building Python packages + with this package as build requirement use the include directory to find additional header + files. +- `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended + to uses these two options. + +**Default value**: `{}` + +**Type**: `dict[str, str]` + +**Example usage**: + +```toml title="pyproject.toml" +[tool.uv.build-backend] +data = { "headers": "include/headers", "scripts": "bin" } +``` + +--- + +#### [`default-excludes`](#build-backend_default-excludes) {: #build-backend_default-excludes } + + +If set to `false`, the default excludes aren't applied. + +Default excludes: `__pycache__`, `*.pyc`, and `*.pyo`. + +**Default value**: `true` + +**Type**: `bool` + +**Example usage**: + +```toml title="pyproject.toml" +[tool.uv.build-backend] +default-excludes = false +``` + +--- + +#### [`module-name`](#build-backend_module-name) {: #build-backend_module-name } + + +The name of the module directory inside `module-root`. + +The default module name is the package name with dots and dashes replaced by underscores. + +Note that using this option runs the risk of creating two packages with different names but +the same module names. Installing such packages together leads to unspecified behavior, +often with corrupted files or directory trees. + +**Default value**: `None` + +**Type**: `str` + +**Example usage**: + +```toml title="pyproject.toml" +[tool.uv.build-backend] +module-name = "sklearn" +``` + +--- + +#### [`module-root`](#build-backend_module-root) {: #build-backend_module-root } + + +The directory that contains the module directory. + +Common values are `src` (src layout, the default) or an empty path (flat layout). + +**Default value**: `"src"` + +**Type**: `str` + +**Example usage**: + +```toml title="pyproject.toml" +[tool.uv.build-backend] +module-root = "" +``` + +--- + +#### [`source-exclude`](#build-backend_source-exclude) {: #build-backend_source-exclude } + + +Glob expressions which files and directories to exclude from the source distribution. + +**Default value**: `[]` + +**Type**: `list[str]` + +**Example usage**: + +```toml title="pyproject.toml" +[tool.uv.build-backend] +source-exclude = ["*.bin"] +``` + +--- + +#### [`source-include`](#build-backend_source-include) {: #build-backend_source-include } + + +Glob expressions which files and directories to additionally include in the source +distribution. + +`pyproject.toml` and the contents of the module directory are always included. + +**Default value**: `[]` + +**Type**: `list[str]` + +**Example usage**: + +```toml title="pyproject.toml" +[tool.uv.build-backend] +source-include = ["tests/**"] +``` + +--- + +#### [`wheel-exclude`](#build-backend_wheel-exclude) {: #build-backend_wheel-exclude } + + +Glob expressions which files and directories to exclude from the wheel. + +**Default value**: `[]` + +**Type**: `list[str]` + +**Example usage**: + +```toml title="pyproject.toml" +[tool.uv.build-backend] +wheel-exclude = ["*.bin"] +``` + +--- + ### `workspace` #### [`exclude`](#workspace_exclude) {: #workspace_exclude } diff --git a/pyproject.toml b/pyproject.toml index 663c00119..4a1be869b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,7 @@ version_files = [ "crates/uv-version/Cargo.toml", "crates/uv-build/Cargo.toml", "crates/uv-build/pyproject.toml", + "docs/configuration/build_backend.md", "docs/getting-started/installation.md", "docs/guides/integration/docker.md", "docs/guides/integration/pre-commit.md", diff --git a/uv.schema.json b/uv.schema.json index cc69c5909..edaf3c9c4 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -14,6 +14,17 @@ "$ref": "#/definitions/TrustedHost" } }, + "build-backend": { + "description": "Configuration for the uv build backend.\n\nNote that those settings only apply when using the `uv_build` backend, other build backends (such as hatchling) have their own configuration.", + "anyOf": [ + { + "$ref": "#/definitions/BuildBackendSettings" + }, + { + "type": "null" + } + ] + }, "build-constraint-dependencies": { "description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.", "type": [ @@ -584,6 +595,73 @@ } ] }, + "BuildBackendSettings": { + "description": "Settings for the uv build backend (`uv_build`).\n\n!!! note\n\nThe uv build backend is currently in preview and may change in any future release.\n\nNote that those settings only apply when using the `uv_build` backend, other build backends (such as hatchling) have their own configuration.\n\nAll options that accept globs use the portable glob patterns from [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).", + "type": "object", + "properties": { + "data": { + "description": "Data includes for wheels.\n\nEach entry is a directory, whose contents are copied to the matching directory in the wheel in `-.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this data is moved to its target location, as defined by . Usually, small data files are included by placing them in the Python module instead of using data includes.\n\n- `scripts`: Installed to the directory for executables, `/bin` on Unix or `\\Scripts` on Windows. This directory is added to `PATH` when the virtual environment is activated or when using `uv run`, so this data type can be used to install additional binaries. Consider using `project.scripts` instead for Python entrypoints. - `data`: Installed over the virtualenv environment root.\n\nWarning: This may override existing files!\n\n- `headers`: Installed to the include directory. Compilers building Python packages with this package as build requirement use the include directory to find additional header files. - `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended to uses these two options.", + "default": { + "data": null, + "headers": null, + "platlib": null, + "purelib": null, + "scripts": null + }, + "allOf": [ + { + "$ref": "#/definitions/WheelDataIncludes" + } + ] + }, + "default-excludes": { + "description": "If set to `false`, the default excludes aren't applied.\n\nDefault excludes: `__pycache__`, `*.pyc`, and `*.pyo`.", + "default": true, + "type": "boolean" + }, + "module-name": { + "description": "The name of the module directory inside `module-root`.\n\nThe default module name is the package name with dots and dashes replaced by underscores.\n\nNote that using this option runs the risk of creating two packages with different names but the same module names. Installing such packages together leads to unspecified behavior, often with corrupted files or directory trees.", + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/Identifier" + }, + { + "type": "null" + } + ] + }, + "module-root": { + "description": "The directory that contains the module directory.\n\nCommon values are `src` (src layout, the default) or an empty path (flat layout).", + "default": "src", + "type": "string" + }, + "source-exclude": { + "description": "Glob expressions which files and directories to exclude from the source distribution.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "source-include": { + "description": "Glob expressions which files and directories to additionally include in the source distribution.\n\n`pyproject.toml` and the contents of the module directory are always included.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "wheel-exclude": { + "description": "Glob expressions which files and directories to exclude from the wheel.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, "CacheKey": { "anyOf": [ { @@ -753,6 +831,11 @@ "description": "The normalized name of a dependency group.\n\nSee: - - ", "type": "string" }, + "Identifier": { + "description": "An identifier in Python", + "type": "string", + "pattern": "^[_\\p{Alphabetic}][_0-9\\p{Alphabetic}]*$" + }, "Index": { "type": "object", "required": [ @@ -2422,6 +2505,48 @@ ] } ] + }, + "WheelDataIncludes": { + "description": "Data includes for wheels.\n\nSee `BuildBackendSettings::data`.", + "type": "object", + "properties": { + "data": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "headers": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "platlib": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "purelib": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "scripts": { + "default": null, + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false } } } \ No newline at end of file