0
# Build Integration
1
2
Integration with build systems including setuptools command classes, onbuild hooks, and file providers for inserting version information into build artifacts. This system allows versioningit to modify files during the build process.
3
4
## Capabilities
5
6
### Setuptools Command Classes
7
8
Custom setuptools command classes that run the onbuild step during sdist and wheel building.
9
10
```python { .api }
11
def get_cmdclasses(
12
bases: Optional[dict[str, type[Command]]] = None
13
) -> dict[str, type[Command]]:
14
"""
15
Return a dict of custom setuptools Command classes that run the onbuild
16
step when building an sdist or wheel.
17
18
Parameters:
19
- bases: Optional dict of alternative base classes for customization
20
21
Returns:
22
dict[str, type[Command]]: Dict containing "sdist" and "build_py" command classes
23
"""
24
```
25
26
### Onbuild Execution
27
28
Run the onbuild step for inserting version information into build artifacts.
29
30
```python { .api }
31
def run_onbuild(
32
*,
33
build_dir: str | Path,
34
is_source: bool,
35
template_fields: dict[str, Any],
36
project_dir: str | Path = os.curdir,
37
config: Optional[dict] = None,
38
) -> None:
39
"""
40
Run the onbuild step for the given setuptools project.
41
42
Parameters:
43
- build_dir: Directory containing the in-progress build
44
- is_source: True for sdist (preserves source paths), False for wheel (uses install paths)
45
- template_fields: Dict of fields for template substitution
46
- project_dir: Path to project root (default: current directory)
47
- config: Configuration dict or None to read from configuration file
48
49
Raises:
50
- NoConfigFileError: if config is None and no configuration file exists
51
- NoConfigSectionError: if configuration file has no versioningit section
52
- ConfigError: if configuration values are invalid
53
- MethodError: if a method returns wrong type
54
"""
55
56
def get_template_fields_from_distribution(
57
dist: Distribution,
58
) -> Optional[dict[str, Any]]:
59
"""
60
Extract template fields stashed on setuptools Distribution by versioningit's
61
setuptools hook, for passing to the onbuild step.
62
63
Parameters:
64
- dist: setuptools Distribution object
65
66
Returns:
67
Optional[dict[str, Any]]: Template fields or None if building from sdist
68
"""
69
```
70
71
### File Provider System
72
73
Abstract interfaces for accessing and modifying files during the build process.
74
75
```python { .api }
76
class OnbuildFileProvider(ABC):
77
"""
78
Abstract base class for accessing files that are about to be included
79
in an sdist or wheel currently being built.
80
"""
81
82
@abstractmethod
83
def get_file(
84
self, source_path: str | PurePath, install_path: str | PurePath, is_source: bool
85
) -> OnbuildFile:
86
"""
87
Get an object for reading & writing a file in the project being built.
88
89
Parameters:
90
- source_path: Path relative to project root
91
- install_path: Path when installed (for wheels)
92
- is_source: True for sdist, False for wheel
93
94
Returns:
95
OnbuildFile: File access object
96
"""
97
98
class OnbuildFile(ABC):
99
"""
100
Abstract base class for opening a file in a project currently being built.
101
"""
102
103
@abstractmethod
104
def open(
105
self,
106
mode: str = "r",
107
encoding: str | None = None,
108
errors: str | None = None,
109
newline: str | None = None,
110
) -> IO:
111
"""
112
Open the associated file. Mode must be "r", "w", "a", "rb", "br",
113
"wb", "bw", "ab", or "ba".
114
"""
115
```
116
117
### Setuptools File Provider
118
119
Implementation for setuptools builds that operates on temporary build directories.
120
121
```python { .api }
122
@dataclass
123
class SetuptoolsFileProvider(OnbuildFileProvider):
124
"""
125
OnbuildFileProvider implementation for setuptools builds.
126
Operates directly on the temporary directory containing build files.
127
"""
128
129
build_dir: Path
130
"""The setuptools-managed temporary directory containing build files."""
131
132
modified: set[PurePath]
133
"""Set of file paths that have been opened for writing or appending."""
134
135
def get_file(
136
self, source_path: str | PurePath, install_path: str | PurePath, is_source: bool
137
) -> SetuptoolsOnbuildFile: ...
138
139
@dataclass
140
class SetuptoolsOnbuildFile(OnbuildFile):
141
"""File access implementation for setuptools builds."""
142
143
provider: SetuptoolsFileProvider
144
source_path: PurePath
145
install_path: PurePath
146
is_source: bool
147
```
148
149
### Hatch File Provider
150
151
Implementation for Hatch builds that uses temporary directories and forced inclusion.
152
153
```python { .api }
154
@dataclass
155
class HatchFileProvider(OnbuildFileProvider):
156
"""
157
OnbuildFileProvider implementation for Hatch builds.
158
Creates modified files in temporary directory for forced inclusion.
159
"""
160
161
src_dir: Path
162
"""The root of the project directory."""
163
164
tmp_dir: Path
165
"""Temporary directory for creating modified files."""
166
167
modified: set[PurePath]
168
"""Set of file paths created under the temporary directory."""
169
170
def get_file(
171
self, source_path: str | PurePath, install_path: str | PurePath, is_source: bool
172
) -> HatchOnbuildFile: ...
173
174
def get_force_include(self) -> dict[str, str]:
175
"""
176
Get mapping of temporary files to their target paths for forced inclusion.
177
"""
178
179
@dataclass
180
class HatchOnbuildFile(OnbuildFile):
181
"""File access implementation for Hatch builds."""
182
183
provider: HatchFileProvider
184
source_path: PurePath
185
install_path: PurePath
186
is_source: bool
187
```
188
189
## Usage Examples
190
191
### Setuptools Integration
192
193
```python
194
# setup.py
195
from setuptools import setup
196
from versioningit import get_cmdclasses
197
198
setup(
199
name="mypackage",
200
cmdclass=get_cmdclasses(),
201
# ... other setup parameters
202
)
203
```
204
205
```python
206
# pyproject.toml
207
[build-system]
208
requires = ["setuptools", "versioningit"]
209
build-backend = "setuptools.build_meta"
210
211
[project]
212
dynamic = ["version"]
213
214
[tool.setuptools.dynamic]
215
version = {attr = "versioningit.get_version"}
216
217
[tool.versioningit]
218
# ... configuration
219
220
[tool.versioningit.onbuild]
221
method = "replace-version"
222
source-file = "src/mypackage/__init__.py"
223
build-file = "mypackage/__init__.py"
224
```
225
226
### Custom Command Classes
227
228
```python
229
from setuptools import setup
230
from setuptools.command.build_py import build_py
231
from versioningit import get_cmdclasses
232
233
class CustomBuildPy(build_py):
234
def run(self):
235
# Custom build logic
236
super().run()
237
238
# Use custom base classes
239
cmdclasses = get_cmdclasses({"build_py": CustomBuildPy})
240
241
setup(
242
name="mypackage",
243
cmdclass=cmdclasses,
244
)
245
```
246
247
### Manual Onbuild Execution
248
249
```python
250
from pathlib import Path
251
from versioningit import run_onbuild, get_template_fields_from_distribution
252
253
def custom_build_command(self):
254
# Get template fields from distribution
255
template_fields = get_template_fields_from_distribution(self.distribution)
256
257
if template_fields is not None:
258
# Run onbuild step
259
run_onbuild(
260
build_dir=self.build_lib,
261
is_source=False, # Building wheel
262
template_fields=template_fields,
263
project_dir=Path().resolve()
264
)
265
```
266
267
### Custom File Provider
268
269
```python
270
from versioningit.onbuild import OnbuildFileProvider, OnbuildFile
271
from pathlib import Path
272
273
class CustomFileProvider(OnbuildFileProvider):
274
def __init__(self, build_dir: Path):
275
self.build_dir = build_dir
276
277
def get_file(self, source_path, install_path, is_source):
278
return CustomOnbuildFile(
279
self, source_path, install_path, is_source
280
)
281
282
class CustomOnbuildFile(OnbuildFile):
283
def __init__(self, provider, source_path, install_path, is_source):
284
self.provider = provider
285
self.source_path = source_path
286
self.install_path = install_path
287
self.is_source = is_source
288
289
def open(self, mode="r", encoding=None, errors=None, newline=None):
290
path = self.source_path if self.is_source else self.install_path
291
full_path = self.provider.build_dir / path
292
return full_path.open(mode=mode, encoding=encoding, errors=errors, newline=newline)
293
```
294
295
### Onbuild Method Implementation
296
297
```python
298
def custom_onbuild_method(
299
*,
300
file_provider: OnbuildFileProvider,
301
is_source: bool,
302
template_fields: dict[str, Any],
303
params: dict[str, Any],
304
) -> None:
305
"""Custom onbuild method example."""
306
307
source_file = params["source-file"]
308
build_file = params["build-file"]
309
310
# Get file handle
311
file = file_provider.get_file(source_file, build_file, is_source)
312
313
# Read current content
314
with file.open("r", encoding="utf-8") as fp:
315
content = fp.read()
316
317
# Modify content with template fields
318
new_content = content.format(**template_fields)
319
320
# Write back
321
with file.open("w", encoding="utf-8") as fp:
322
fp.write(new_content)
323
```
324
325
### Template Fields Usage
326
327
```python
328
from versioningit import Versioningit
329
330
vgit = Versioningit.from_project_dir()
331
report = vgit.run()
332
333
# Template fields available for onbuild
334
fields = report.template_fields
335
336
# Common substitutions in onbuild
337
version_line = '__version__ = "{version}"'.format(**fields)
338
build_info = """
339
__version__ = "{version}"
340
__version_tuple__ = {version_tuple}
341
__build_date__ = "{build_date}"
342
__git_revision__ = "{rev}"
343
""".format(**fields)
344
```
345
346
### Error Handling in Build Integration
347
348
```python
349
from versioningit import get_cmdclasses, run_onbuild
350
from versioningit.errors import ConfigError, MethodError
351
352
try:
353
cmdclasses = get_cmdclasses()
354
except Exception as e:
355
print(f"Failed to get command classes: {e}")
356
cmdclasses = {}
357
358
# In build command
359
try:
360
run_onbuild(
361
build_dir=build_dir,
362
is_source=False,
363
template_fields=template_fields
364
)
365
except ConfigError as e:
366
print(f"Configuration error in onbuild: {e}")
367
except MethodError as e:
368
print(f"Method error in onbuild: {e}")
369
```