Files
uv/crates/uv-build-backend/src/settings.rs
T
konsti 39fe2d9eac Error early for parent path in build backend (#15733)
Paths referencing above the directory of the `pyproject.toml`, such as
`module-root = ".."`, are not supported by the build backend. The check
that should catch was not working properly, so the source distribution
built successfully and only the wheel build failed. We now error early.
The same fix is applied to data includes.

Fix #15702
2025-09-08 13:53:16 +00:00

228 lines
8.6 KiB
Rust

use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use uv_macros::OptionsMetadata;
/// Settings for the uv build backend (`uv_build`).
///
/// 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.
///
/// Package names need to be valid Python identifiers, and the directory needs to contain a
/// `__init__.py`. An exception are stubs packages, whose name ends with `-stubs`, with the stem
/// being the module name, and which contain a `__init__.pyi` file.
///
/// For namespace packages with a single module, the path can be dotted, e.g., `foo.bar` or
/// `foo-stubs.bar`.
///
/// For namespace packages with multiple modules, the path can be a list, e.g.,
/// `["foo", "bar"]`. We recommend using a single module per package, splitting multiple
/// packages into a workspace.
///
/// 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 | list[str]",
example = r#"module-name = "sklearn""#
)]
pub module_name: Option<ModuleName>,
/// 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>,
/// Build a namespace package.
///
/// Build a PEP 420 implicit namespace package, allowing more than one root `__init__.py`.
///
/// Use this option when the namespace package contains multiple root `__init__.py`, for
/// namespace packages with a single root `__init__.py` use a dotted `module-name` instead.
///
/// To compare dotted `module-name` and `namespace = true`, the first example below can be
/// expressed with `module-name = "cloud.database"`: There is one root `__init__.py` `database`.
/// In the second example, we have three roots (`cloud.database`, `cloud.database_pro`,
/// `billing.modules.database_pro`), so `namespace = true` is required.
///
/// ```text
/// src
/// └── cloud
/// └── database
/// ├── __init__.py
/// ├── query_builder
/// │ └── __init__.py
/// └── sql
/// ├── parser.py
/// └── __init__.py
/// ```
///
/// ```text
/// src
/// ├── cloud
/// │ ├── database
/// │ │ ├── __init__.py
/// │ │ ├── query_builder
/// │ │ │ └── __init__.py
/// │ │ └── sql
/// │ │ ├── __init__.py
/// │ │ └── parser.py
/// │ └── database_pro
/// │ ├── __init__.py
/// │ └── query_builder.py
/// └── billing
/// └── modules
/// └── database_pro
/// ├── __init__.py
/// └── sql.py
/// ```
#[option(
default = r#"false"#,
value_type = "bool",
example = r#"namespace = true"#
)]
pub namespace: bool,
/// 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 use 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(),
namespace: false,
data: WheelDataIncludes::default(),
}
}
}
/// Whether to include a single module or multiple modules.
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum ModuleName {
/// A single module name.
Name(String),
/// Multiple module names, which are all included.
Names(Vec<String>),
}
/// 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<PathBuf>,
platlib: Option<PathBuf>,
headers: Option<PathBuf>,
scripts: Option<PathBuf>,
data: Option<PathBuf>,
}
impl WheelDataIncludes {
/// Yield all data directories name and corresponding paths.
pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Path)> {
[
("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?)))
}
}