Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions crates/uv-build-backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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 }
Expand All @@ -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"] }
Expand Down
2 changes: 2 additions & 0 deletions crates/uv-build-backend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down
147 changes: 2 additions & 145 deletions crates/uv-build-backend/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
Expand Down Expand Up @@ -796,149 +796,6 @@ pub(crate) struct ToolUv {
build_backend: Option<BuildBackendSettings>,
}

/// 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/<module-name or project_name_with_underscores>/**`.
/// * `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/<module-name or project_name_with_underscores>/**`.
/// * `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
/// `<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 {
/// 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<Identifier>,

/// 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<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.
pub(crate) source_exclude: Vec<String>,

/// Glob expressions which files and directories to exclude from the wheel.
pub(crate) wheel_exclude: Vec<String>,

/// 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
/// `<name>-<version>.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this data
/// is moved to its target location, as defined by
/// <https://docs.python.org/3.12/library/sysconfig.html#installation-paths>:
/// - `data`: Installed over the virtualenv environment root. Warning: This may override existing
/// files!
/// - `scripts`: Installed to the directory for executables, `<venv>/bin` on Unix or
/// `<venv>\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<String>,
platlib: Option<String>,
headers: Option<String>,
scripts: Option<String>,
data: Option<String>,
}

impl WheelDataIncludes {
/// Yield all data directories name and corresponding paths.
pub(crate) fn iter(&self) -> impl Iterator<Item = (&'static str, &str)> {
[
("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::*;
Expand Down
159 changes: 159 additions & 0 deletions crates/uv-build-backend/src/settings.rs
Original file line number Diff line number Diff line change
@@ -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<Identifier>,

/// 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<String>,

/// 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<String>,

/// 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<String>,

/// Data includes for wheels.
///
/// Each entry is a directory, whose contents are copied to the matching directory in the wheel
/// in `<name>-<version>.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this
/// data is moved to its target location, as defined by
/// <https://docs.python.org/3.12/library/sysconfig.html#installation-paths>. 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, `<venv>/bin` on Unix or
/// `<venv>\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<String>,
platlib: Option<String>,
headers: Option<String>,
scripts: Option<String>,
data: Option<String>,
}

impl WheelDataIncludes {
/// Yield all data directories name and corresponding paths.
pub fn iter(&self) -> impl Iterator<Item = (&'static str, &str)> {
[
("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?)))
}
}
Loading
Loading