0
# Package Builders
1
2
Poetry Core provides a modern package building system supporting wheels, source distributions (sdists), and editable installs. The builders handle file collection, metadata generation, and package creation according to Python packaging standards.
3
4
## Core Imports
5
6
```python
7
from poetry.core.masonry.builders.builder import Builder
8
from poetry.core.masonry.builders.wheel import WheelBuilder
9
from poetry.core.masonry.builders.sdist import SdistBuilder
10
from poetry.core.masonry.metadata import Metadata
11
``` { .api }
12
13
## Base Builder Class
14
15
### Builder
16
17
Abstract base class for all package builders providing common functionality.
18
19
```python
20
class Builder:
21
"""
22
Abstract base class for package builders.
23
24
Provides common functionality for file discovery, exclusion handling,
25
and metadata generation across different package formats.
26
"""
27
28
format: str | None = None # Format identifier ("wheel", "sdist", etc.)
29
30
def __init__(
31
self,
32
poetry: Poetry,
33
executable: Path | None = None,
34
config_settings: dict[str, Any] | None = None,
35
) -> None:
36
"""
37
Initialize builder.
38
39
Args:
40
poetry: Poetry instance representing the project
41
executable: Python executable to use (defaults to current)
42
config_settings: Build configuration settings
43
44
Raises:
45
RuntimeError: If project is not in package mode
46
47
Example:
48
>>> from poetry.core.factory import Factory
49
>>> poetry = Factory().create_poetry()
50
>>> builder = SdistBuilder(poetry)
51
"""
52
53
@property
54
def executable(self) -> Path:
55
"""Python executable used for building."""
56
57
@property
58
def default_target_dir(self) -> Path:
59
"""Default directory for build outputs (typically ./dist)."""
60
61
def build(self, target_dir: Path | None) -> Path:
62
"""
63
Build package to target directory.
64
65
Args:
66
target_dir: Directory to write package file
67
68
Returns:
69
Path to built package file
70
71
Note:
72
Must be implemented by subclasses
73
"""
74
raise NotImplementedError
75
76
def find_files_to_add(self) -> list[BuildIncludeFile]:
77
"""
78
Find all files to include in the package.
79
80
Returns:
81
List of files to add to package with their metadata
82
83
Note:
84
Handles package discovery, file includes/excludes,
85
and VCS integration for determining files.
86
"""
87
88
def find_excluded_files(self, fmt: str | None = None) -> set[str]:
89
"""
90
Find files excluded from packaging.
91
92
Args:
93
fmt: Format to check exclusions for
94
95
Returns:
96
Set of excluded file paths
97
98
Note:
99
Uses VCS ignore files, Poetry configuration excludes,
100
and format-specific exclusion rules.
101
"""
102
``` { .api }
103
104
## Wheel Builder
105
106
### WheelBuilder
107
108
Builds wheel packages according to PEP 427.
109
110
```python
111
class WheelBuilder(Builder):
112
"""
113
Wheel package builder implementing PEP 427.
114
115
Creates .whl files containing compiled Python packages
116
with proper metadata and installation support.
117
"""
118
119
format = "wheel"
120
121
def __init__(
122
self,
123
poetry: Poetry,
124
original: Path | None = None,
125
executable: Path | None = None,
126
editable: bool = False,
127
metadata_directory: Path | None = None,
128
config_settings: dict[str, Any] | None = None,
129
) -> None:
130
"""
131
Initialize wheel builder.
132
133
Args:
134
poetry: Poetry project instance
135
original: Original project path (for advanced scenarios)
136
executable: Python executable for building
137
editable: Whether to build editable wheel
138
metadata_directory: Pre-built metadata directory
139
config_settings: Build configuration settings
140
141
Example:
142
>>> builder = WheelBuilder(poetry, editable=True)
143
>>> builder = WheelBuilder(poetry, config_settings={"--build-option": ["--plat-name", "linux_x86_64"]})
144
"""
145
``` { .api }
146
147
### Class Methods
148
149
#### make_in
150
151
```python
152
@classmethod
153
def make_in(
154
cls,
155
poetry: Poetry,
156
directory: Path | None = None,
157
original: Path | None = None,
158
executable: Path | None = None,
159
editable: bool = False,
160
metadata_directory: Path | None = None,
161
config_settings: dict[str, Any] | None = None,
162
) -> str:
163
"""
164
Build wheel in specified directory (class method).
165
166
Args:
167
poetry: Poetry project instance
168
directory: Target directory for wheel file
169
original: Original project path
170
executable: Python executable for building
171
editable: Whether to build editable wheel
172
metadata_directory: Pre-built metadata directory
173
config_settings: Build configuration settings
174
175
Returns:
176
Filename of built wheel
177
178
Example:
179
>>> filename = WheelBuilder.make_in(poetry, Path("./dist"))
180
>>> print(f"Built: {filename}")
181
Built: mypackage-1.0.0-py3-none-any.whl
182
183
>>> # Editable wheel
184
>>> filename = WheelBuilder.make_in(poetry, Path("./dist"), editable=True)
185
186
>>> # With configuration
187
>>> filename = WheelBuilder.make_in(
188
... poetry,
189
... Path("./dist"),
190
... config_settings={"--build-option": ["--verbose"]}
191
... )
192
"""
193
``` { .api }
194
195
#### make
196
197
```python
198
@classmethod
199
def make(cls, poetry: Poetry, executable: Path | None = None) -> None:
200
"""
201
Build wheel in default location (./dist).
202
203
Args:
204
poetry: Poetry project instance
205
executable: Python executable for building
206
207
Example:
208
>>> WheelBuilder.make(poetry)
209
# Creates wheel in ./dist/ directory
210
"""
211
``` { .api }
212
213
### Properties
214
215
```python
216
@property
217
def wheel_filename(self) -> str:
218
"""
219
Generated wheel filename.
220
221
Returns:
222
Wheel filename following PEP 427 naming convention
223
Format: {name}-{version}-{python tag}-{abi tag}-{platform tag}.whl
224
225
Example:
226
>>> builder.wheel_filename
227
'mypackage-1.0.0-py3-none-any.whl'
228
"""
229
230
@property
231
def tag(self) -> packaging.tags.Tag:
232
"""
233
Wheel compatibility tag.
234
235
Returns:
236
Tag object describing Python/ABI/platform compatibility
237
"""
238
239
@property
240
def supports_tags(self) -> list[packaging.tags.Tag]:
241
"""List of all compatible tags for this wheel."""
242
``` { .api }
243
244
### Methods
245
246
```python
247
def build(self, target_dir: Path | None = None) -> Path:
248
"""
249
Build wheel package.
250
251
Args:
252
target_dir: Directory to write wheel file
253
254
Returns:
255
Path to built wheel file
256
257
Example:
258
>>> wheel_path = builder.build(Path("./dist"))
259
>>> print(f"Wheel created: {wheel_path}")
260
"""
261
262
def prepare_metadata(self, metadata_dir: Path) -> Path:
263
"""
264
Prepare wheel metadata without building full wheel.
265
266
Args:
267
metadata_dir: Directory to write metadata
268
269
Returns:
270
Path to .dist-info directory containing metadata
271
272
Note:
273
Used by PEP 517 prepare_metadata_for_build_wheel hook
274
for faster installation planning.
275
"""
276
``` { .api }
277
278
## Source Distribution Builder
279
280
### SdistBuilder
281
282
Builds source distribution packages according to PEP 518.
283
284
```python
285
class SdistBuilder(Builder):
286
"""
287
Source distribution builder implementing PEP 518.
288
289
Creates .tar.gz files containing source code and metadata
290
for distribution and installation from source.
291
"""
292
293
format = "sdist"
294
295
def __init__(
296
self,
297
poetry: Poetry,
298
executable: Path | None = None,
299
config_settings: dict[str, Any] | None = None,
300
) -> None:
301
"""
302
Initialize sdist builder.
303
304
Args:
305
poetry: Poetry project instance
306
executable: Python executable for building
307
config_settings: Build configuration settings
308
309
Example:
310
>>> builder = SdistBuilder(poetry)
311
>>> builder = SdistBuilder(poetry, config_settings={"--formats": ["gztar"]})
312
"""
313
``` { .api }
314
315
### Methods
316
317
```python
318
def build(self, target_dir: Path | None = None) -> Path:
319
"""
320
Build source distribution package.
321
322
Args:
323
target_dir: Directory to write sdist file
324
325
Returns:
326
Path to built sdist file (.tar.gz)
327
328
Example:
329
>>> sdist_path = builder.build(Path("./dist"))
330
>>> print(f"Sdist created: {sdist_path}")
331
Sdist created: ./dist/mypackage-1.0.0.tar.gz
332
"""
333
334
@property
335
def sdist_filename(self) -> str:
336
"""
337
Generated sdist filename.
338
339
Returns:
340
Sdist filename in format: {name}-{version}.tar.gz
341
342
Example:
343
>>> builder.sdist_filename
344
'mypackage-1.0.0.tar.gz'
345
"""
346
``` { .api }
347
348
## Metadata Handling
349
350
### Metadata
351
352
Handles package metadata generation for distributions.
353
354
```python
355
class Metadata:
356
"""
357
Package metadata generator for distributions.
358
359
Creates standard metadata files (METADATA, WHEEL, entry_points.txt)
360
according to packaging specifications.
361
"""
362
363
@classmethod
364
def from_package(cls, package: ProjectPackage) -> Metadata:
365
"""
366
Create metadata from package information.
367
368
Args:
369
package: Project package instance
370
371
Returns:
372
Metadata instance ready for distribution generation
373
"""
374
375
def write_to_directory(
376
self,
377
directory: Path,
378
fmt: str | None = None
379
) -> None:
380
"""
381
Write metadata files to directory.
382
383
Args:
384
directory: Target directory (typically .dist-info or .egg-info)
385
fmt: Format context ("wheel" or "sdist")
386
387
Note:
388
Creates standard metadata files:
389
- METADATA (core metadata)
390
- WHEEL (wheel-specific, for wheels only)
391
- entry_points.txt (if entry points defined)
392
- RECORD (for wheels, file hashes and sizes)
393
"""
394
``` { .api }
395
396
## Build Configuration
397
398
### Configuration Options
399
400
```python
401
# Common config_settings for builders
402
config_settings = {
403
# Build options
404
"--build-option": ["--verbose", "--plat-name", "linux_x86_64"],
405
406
# Global options
407
"--global-option": ["--quiet"],
408
409
# Format-specific options
410
"--formats": ["gztar"], # For sdist
411
412
# Local version label
413
"local-version": "dev123",
414
415
# Environment variables
416
"env": {
417
"POETRY_CORE_DEBUG": "1",
418
"MAKEFLAGS": "-j4"
419
}
420
}
421
``` { .api }
422
423
## Usage Examples
424
425
### Basic Package Building
426
427
```python
428
from pathlib import Path
429
from poetry.core.factory import Factory
430
from poetry.core.masonry.builders.wheel import WheelBuilder
431
from poetry.core.masonry.builders.sdist import SdistBuilder
432
433
def build_project(project_path: Path, output_dir: Path):
434
"""Build both wheel and sdist for a project."""
435
436
# Create Poetry instance
437
factory = Factory()
438
poetry = factory.create_poetry(project_path)
439
440
# Create output directory
441
output_dir.mkdir(exist_ok=True, parents=True)
442
443
print(f"Building {poetry.package.name} v{poetry.package.version}")
444
445
try:
446
# Build wheel
447
print("Building wheel...")
448
wheel_builder = WheelBuilder(poetry)
449
wheel_path = wheel_builder.build(output_dir)
450
print(f"β Wheel: {wheel_path.name}")
451
452
# Build source distribution
453
print("Building sdist...")
454
sdist_builder = SdistBuilder(poetry)
455
sdist_path = sdist_builder.build(output_dir)
456
print(f"β Sdist: {sdist_path.name}")
457
458
return wheel_path, sdist_path
459
460
except Exception as e:
461
print(f"β Build failed: {e}")
462
return None, None
463
464
# Usage
465
wheel, sdist = build_project(Path("./my-project"), Path("./dist"))
466
``` { .api }
467
468
### Advanced Wheel Building
469
470
```python
471
from poetry.core.masonry.builders.wheel import WheelBuilder
472
473
def build_specialized_wheels(poetry):
474
"""Build wheels with different configurations."""
475
476
dist_dir = Path("./dist")
477
dist_dir.mkdir(exist_ok=True)
478
479
# Regular wheel
480
print("Building regular wheel...")
481
regular_wheel = WheelBuilder.make_in(poetry, dist_dir)
482
print(f"Regular wheel: {regular_wheel}")
483
484
# Editable wheel (for development)
485
print("Building editable wheel...")
486
editable_wheel = WheelBuilder.make_in(
487
poetry,
488
dist_dir,
489
editable=True
490
)
491
print(f"Editable wheel: {editable_wheel}")
492
493
# Platform-specific wheel
494
print("Building platform-specific wheel...")
495
platform_config = {
496
"--build-option": ["--plat-name", "linux_x86_64"]
497
}
498
platform_wheel = WheelBuilder.make_in(
499
poetry,
500
dist_dir,
501
config_settings=platform_config
502
)
503
print(f"Platform wheel: {platform_wheel}")
504
505
# Development version wheel
506
print("Building development wheel...")
507
dev_config = {
508
"local-version": f"dev{datetime.now().strftime('%Y%m%d')}"
509
}
510
dev_wheel = WheelBuilder.make_in(
511
poetry,
512
dist_dir,
513
config_settings=dev_config
514
)
515
print(f"Dev wheel: {dev_wheel}")
516
517
# Usage
518
build_specialized_wheels(poetry)
519
``` { .api }
520
521
### Metadata Preparation
522
523
```python
524
from pathlib import Path
525
from poetry.core.masonry.builders.wheel import WheelBuilder
526
527
def prepare_metadata_only(poetry, metadata_dir: Path):
528
"""Prepare wheel metadata without building full wheel."""
529
530
metadata_dir.mkdir(exist_ok=True, parents=True)
531
532
# Create wheel builder
533
builder = WheelBuilder(poetry)
534
535
# Prepare metadata
536
dist_info_dir = builder.prepare_metadata(metadata_dir)
537
538
print(f"Metadata prepared in: {dist_info_dir}")
539
540
# List metadata files
541
print("Metadata files:")
542
for file in dist_info_dir.iterdir():
543
print(f" {file.name}")
544
545
# Read core metadata
546
metadata_file = dist_info_dir / "METADATA"
547
if metadata_file.exists():
548
print(f"\nCore metadata preview:")
549
print(metadata_file.read_text()[:500] + "...")
550
551
return dist_info_dir
552
553
# Usage
554
metadata_dir = prepare_metadata_only(poetry, Path("./metadata"))
555
``` { .api }
556
557
### Custom Build Process
558
559
```python
560
from poetry.core.masonry.builders.wheel import WheelBuilder
561
from poetry.core.masonry.builders.sdist import SdistBuilder
562
563
class CustomBuilder:
564
"""Custom builder with additional processing."""
565
566
def __init__(self, poetry):
567
self.poetry = poetry
568
569
def build_with_checks(self, target_dir: Path):
570
"""Build packages with pre/post checks."""
571
572
# Pre-build validation
573
print("π Pre-build validation...")
574
if not self._validate_project():
575
print("β Validation failed")
576
return
577
578
# Build packages
579
print("ποΈ Building packages...")
580
results = {}
581
582
try:
583
# Build wheel
584
wheel_builder = WheelBuilder(self.poetry)
585
wheel_path = wheel_builder.build(target_dir)
586
results['wheel'] = wheel_path
587
print(f"β Wheel: {wheel_path.name}")
588
589
# Build sdist
590
sdist_builder = SdistBuilder(self.poetry)
591
sdist_path = sdist_builder.build(target_dir)
592
results['sdist'] = sdist_path
593
print(f"β Sdist: {sdist_path.name}")
594
595
# Post-build verification
596
print("π Post-build verification...")
597
if self._verify_builds(results):
598
print("β Build verification passed")
599
else:
600
print("β οΈ Build verification issues found")
601
602
return results
603
604
except Exception as e:
605
print(f"β Build failed: {e}")
606
return None
607
608
def _validate_project(self) -> bool:
609
"""Validate project before building."""
610
package = self.poetry.package
611
612
# Check required metadata
613
if not package.name or not package.version:
614
print("Missing name or version")
615
return False
616
617
# Check for README
618
if not package.readmes:
619
print("β οΈ No README file found")
620
621
# Check for license
622
if not package.license:
623
print("β οΈ No license specified")
624
625
return True
626
627
def _verify_builds(self, results: dict) -> bool:
628
"""Verify built packages."""
629
issues = []
630
631
for build_type, path in results.items():
632
if not path.exists():
633
issues.append(f"{build_type} file not found: {path}")
634
continue
635
636
# Check file size
637
size = path.stat().st_size
638
if size < 1024: # Less than 1KB seems suspicious
639
issues.append(f"{build_type} seems too small: {size} bytes")
640
641
if issues:
642
for issue in issues:
643
print(f"β οΈ {issue}")
644
return False
645
646
return True
647
648
# Usage
649
custom_builder = CustomBuilder(poetry)
650
results = custom_builder.build_with_checks(Path("./dist"))
651
``` { .api }
652
653
### Build Monitoring and Progress
654
655
```python
656
import time
657
from pathlib import Path
658
from poetry.core.masonry.builders.wheel import WheelBuilder
659
660
def build_with_progress(poetry, target_dir: Path):
661
"""Build with progress monitoring."""
662
663
print(f"π― Target directory: {target_dir}")
664
target_dir.mkdir(exist_ok=True, parents=True)
665
666
# Track build time
667
start_time = time.time()
668
669
try:
670
print("π¦ Initializing wheel builder...")
671
builder = WheelBuilder(poetry)
672
673
print("π Package information:")
674
print(f" Name: {poetry.package.name}")
675
print(f" Version: {poetry.package.version}")
676
print(f" Dependencies: {len(poetry.package.requires)}")
677
678
print("π Discovering files...")
679
files = builder.find_files_to_add()
680
print(f" Found {len(files)} files to include")
681
682
print("π« Finding excluded files...")
683
excluded = builder.find_excluded_files()
684
print(f" Excluding {len(excluded)} files")
685
686
print("ποΈ Building wheel...")
687
wheel_path = builder.build(target_dir)
688
689
# Build statistics
690
build_time = time.time() - start_time
691
wheel_size = wheel_path.stat().st_size
692
693
print("β Build completed!")
694
print(f" Wheel: {wheel_path.name}")
695
print(f" Size: {wheel_size:,} bytes ({wheel_size/1024:.1f} KB)")
696
print(f" Time: {build_time:.2f} seconds")
697
698
return wheel_path
699
700
except Exception as e:
701
build_time = time.time() - start_time
702
print(f"β Build failed after {build_time:.2f} seconds: {e}")
703
raise
704
705
# Usage
706
wheel_path = build_with_progress(poetry, Path("./dist"))
707
``` { .api }
708
709
## Error Handling
710
711
### Build Error Handling
712
713
```python
714
from poetry.core.masonry.builders.wheel import WheelBuilder
715
from poetry.core.masonry.builders.sdist import SdistBuilder
716
717
def safe_build_packages(poetry, target_dir: Path):
718
"""Build packages with comprehensive error handling."""
719
720
results = {"success": [], "failed": []}
721
722
builders = [
723
("wheel", WheelBuilder),
724
("sdist", SdistBuilder)
725
]
726
727
for build_type, builder_class in builders:
728
try:
729
print(f"Building {build_type}...")
730
731
builder = builder_class(poetry)
732
package_path = builder.build(target_dir)
733
734
results["success"].append({
735
"type": build_type,
736
"path": package_path,
737
"size": package_path.stat().st_size
738
})
739
740
print(f"β {build_type.title()}: {package_path.name}")
741
742
except RuntimeError as e:
743
if "non-package mode" in str(e):
744
print(f"β Cannot build {build_type}: Project not in package mode")
745
results["failed"].append({"type": build_type, "error": "non-package mode"})
746
else:
747
print(f"β {build_type.title()} build failed: {e}")
748
results["failed"].append({"type": build_type, "error": str(e)})
749
750
except PermissionError as e:
751
print(f"β Permission error building {build_type}: {e}")
752
results["failed"].append({"type": build_type, "error": f"permission: {e}"})
753
754
except OSError as e:
755
print(f"β OS error building {build_type}: {e}")
756
results["failed"].append({"type": build_type, "error": f"os: {e}"})
757
758
except Exception as e:
759
print(f"β Unexpected error building {build_type}: {e}")
760
results["failed"].append({"type": build_type, "error": f"unexpected: {e}"})
761
762
# Summary
763
print(f"\nπ Build Summary:")
764
print(f" Successful: {len(results['success'])}")
765
print(f" Failed: {len(results['failed'])}")
766
767
if results["success"]:
768
print(f" Built packages:")
769
for pkg in results["success"]:
770
print(f" {pkg['type']}: {pkg['path'].name} ({pkg['size']:,} bytes)")
771
772
return results
773
774
# Usage
775
results = safe_build_packages(poetry, Path("./dist"))
776
``` { .api }
777
778
## Type Definitions
779
780
```python
781
from typing import Any, Dict, List, Tuple
782
from pathlib import Path
783
784
# Build configuration types
785
BuildConfig = Dict[str, Any]
786
ConfigSettings = Dict[str, Any] | None
787
788
# File handling types
789
BuildIncludeFile = Any # Internal build file representation
790
ExcludedFiles = set[str]
791
792
# Builder result types
793
BuildResult = Path
794
WheelTag = str
795
MetadataPath = Path
796
``` { .api }