uv_build_backend/settings.rs
1use serde::{Deserialize, Serialize};
2use std::path::{Path, PathBuf};
3use uv_macros::OptionsMetadata;
4
5/// Settings for the uv build backend (`uv_build`).
6///
7/// Note that those settings only apply when using the `uv_build` backend, other build backends
8/// (such as hatchling) have their own configuration.
9///
10/// All options that accept globs use the portable glob patterns from
11/// [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).
12#[derive(Deserialize, Serialize, OptionsMetadata, Debug, Clone, PartialEq, Eq)]
13#[serde(default, rename_all = "kebab-case")]
14#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
15pub struct BuildBackendSettings {
16 /// The directory that contains the module directory.
17 ///
18 /// Common values are `src` (src layout, the default) or an empty path (flat layout).
19 #[option(
20 default = r#""src""#,
21 value_type = "str",
22 example = r#"module-root = """#
23 )]
24 pub module_root: PathBuf,
25
26 /// The name of the module directory inside `module-root`.
27 ///
28 /// The default module name is the package name with dots and dashes replaced by underscores.
29 ///
30 /// Package names need to be valid Python identifiers, and the directory needs to contain a
31 /// `__init__.py`. An exception are stubs packages, whose name ends with `-stubs`, with the stem
32 /// being the module name, and which contain a `__init__.pyi` file.
33 ///
34 /// For namespace packages with a single module, the path can be dotted, e.g., `foo.bar` or
35 /// `foo-stubs.bar`.
36 ///
37 /// For namespace packages with multiple modules, the path can be a list, e.g.,
38 /// `["foo", "bar"]`. We recommend using a single module per package, splitting multiple
39 /// packages into a workspace.
40 ///
41 /// Note that using this option runs the risk of creating two packages with different names but
42 /// the same module names. Installing such packages together leads to unspecified behavior,
43 /// often with corrupted files or directory trees.
44 #[option(
45 default = r#"None"#,
46 value_type = "str | list[str]",
47 example = r#"module-name = "sklearn""#
48 )]
49 pub module_name: Option<ModuleName>,
50
51 /// Glob expressions which files and directories to additionally include in the source
52 /// distribution.
53 ///
54 /// `pyproject.toml` and the contents of the module directory are always included.
55 #[option(
56 default = r#"[]"#,
57 value_type = "list[str]",
58 example = r#"source-include = ["tests/**"]"#
59 )]
60 pub source_include: Vec<String>,
61
62 /// If set to `false`, the default excludes aren't applied.
63 ///
64 /// Default excludes: `__pycache__`, `*.pyc`, and `*.pyo`.
65 #[option(
66 default = r#"true"#,
67 value_type = "bool",
68 example = r#"default-excludes = false"#
69 )]
70 pub default_excludes: bool,
71
72 /// Glob expressions which files and directories to exclude from the source distribution.
73 ///
74 /// These exclusions are also applied to wheels to ensure that a wheel built from a source tree
75 /// is consistent with a wheel built from a source distribution.
76 #[option(
77 default = r#"[]"#,
78 value_type = "list[str]",
79 example = r#"source-exclude = ["*.bin"]"#
80 )]
81 pub source_exclude: Vec<String>,
82
83 /// Glob expressions which files and directories to exclude from the wheel.
84 #[option(
85 default = r#"[]"#,
86 value_type = "list[str]",
87 example = r#"wheel-exclude = ["*.bin"]"#
88 )]
89 pub wheel_exclude: Vec<String>,
90
91 /// Build a namespace package.
92 ///
93 /// Build a PEP 420 implicit namespace package, allowing more than one root `__init__.py`.
94 ///
95 /// Use this option when the namespace package contains multiple root `__init__.py`, for
96 /// namespace packages with a single root `__init__.py` use a dotted `module-name` instead.
97 ///
98 /// To compare dotted `module-name` and `namespace = true`, the first example below can be
99 /// expressed with `module-name = "cloud.database"`: There is one root `__init__.py` `database`.
100 /// In the second example, we have three roots (`cloud.database`, `cloud.database_pro`,
101 /// `billing.modules.database_pro`), so `namespace = true` is required.
102 ///
103 /// ```text
104 /// src
105 /// └── cloud
106 /// └── database
107 /// ├── __init__.py
108 /// ├── query_builder
109 /// │ └── __init__.py
110 /// └── sql
111 /// ├── parser.py
112 /// └── __init__.py
113 /// ```
114 ///
115 /// ```text
116 /// src
117 /// ├── cloud
118 /// │ ├── database
119 /// │ │ ├── __init__.py
120 /// │ │ ├── query_builder
121 /// │ │ │ └── __init__.py
122 /// │ │ └── sql
123 /// │ │ ├── __init__.py
124 /// │ │ └── parser.py
125 /// │ └── database_pro
126 /// │ ├── __init__.py
127 /// │ └── query_builder.py
128 /// └── billing
129 /// └── modules
130 /// └── database_pro
131 /// ├── __init__.py
132 /// └── sql.py
133 /// ```
134 #[option(
135 default = r#"false"#,
136 value_type = "bool",
137 example = r#"namespace = true"#
138 )]
139 pub namespace: bool,
140
141 /// Data includes for wheels.
142 ///
143 /// Each entry is a directory, whose contents are copied to the matching directory in the wheel
144 /// in `<name>-<version>.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this
145 /// data is moved to its target location, as defined by
146 /// <https://docs.python.org/3.12/library/sysconfig.html#installation-paths>. Usually, small
147 /// data files are included by placing them in the Python module instead of using data includes.
148 ///
149 /// - `scripts`: Installed to the directory for executables, `<venv>/bin` on Unix or
150 /// `<venv>\Scripts` on Windows. This directory is added to `PATH` when the virtual
151 /// environment is activated or when using `uv run`, so this data type can be used to install
152 /// additional binaries. Consider using `project.scripts` instead for Python entrypoints.
153 /// - `data`: Installed over the virtualenv environment root.
154 ///
155 /// Warning: This may override existing files!
156 ///
157 /// - `headers`: Installed to the include directory. Compilers building Python packages
158 /// with this package as build requirement use the include directory to find additional header
159 /// files.
160 /// - `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended
161 /// to use these two options.
162 // TODO(konsti): We should show a flat example instead.
163 // ```toml
164 // [tool.uv.build-backend.data]
165 // headers = "include/headers",
166 // scripts = "bin"
167 // ```
168 #[option(
169 default = r#"{}"#,
170 value_type = "dict[str, str]",
171 example = r#"data = { headers = "include/headers", scripts = "bin" }"#
172 )]
173 pub data: WheelDataIncludes,
174}
175
176impl Default for BuildBackendSettings {
177 fn default() -> Self {
178 Self {
179 module_root: PathBuf::from("src"),
180 module_name: None,
181 source_include: Vec::new(),
182 default_excludes: true,
183 source_exclude: Vec::new(),
184 wheel_exclude: Vec::new(),
185 namespace: false,
186 data: WheelDataIncludes::default(),
187 }
188 }
189}
190
191/// Whether to include a single module or multiple modules.
192#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
193#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
194#[serde(untagged)]
195pub enum ModuleName {
196 /// A single module name.
197 Name(String),
198 /// Multiple module names, which are all included.
199 Names(Vec<String>),
200}
201
202/// Data includes for wheels.
203///
204/// See `BuildBackendSettings::data`.
205#[derive(Default, Deserialize, Serialize, OptionsMetadata, Debug, Clone, PartialEq, Eq)]
206// `deny_unknown_fields` to catch typos such as `header` vs `headers`.
207#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
208#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
209pub struct WheelDataIncludes {
210 purelib: Option<PathBuf>,
211 platlib: Option<PathBuf>,
212 headers: Option<PathBuf>,
213 scripts: Option<PathBuf>,
214 data: Option<PathBuf>,
215}
216
217impl WheelDataIncludes {
218 /// Yield all data directories name and corresponding paths.
219 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Path)> {
220 [
221 ("purelib", self.purelib.as_deref()),
222 ("platlib", self.platlib.as_deref()),
223 ("headers", self.headers.as_deref()),
224 ("scripts", self.scripts.as_deref()),
225 ("data", self.data.as_deref()),
226 ]
227 .into_iter()
228 .filter_map(|(name, value)| Some((name, value?)))
229 }
230}