1
1
from __future__ import annotations
2
2
3
+ import dataclasses
4
+
3
5
from pathlib import Path
4
6
from typing import TYPE_CHECKING
5
7
from typing import Any
6
8
from typing import ClassVar
9
+ from typing import Literal
7
10
8
11
from cleo .helpers import option
9
12
from poetry .core .constraints .version import Version
18
21
if TYPE_CHECKING :
19
22
from collections .abc import Callable
20
23
21
- from build import DistributionType
22
24
from cleo .io .inputs .option import Option
25
+ from cleo .io .io import IO
23
26
27
+ from poetry .poetry import Poetry
28
+ from poetry .utils .env import Env
24
29
25
- class BuildCommand (EnvCommand ):
26
- name = "build"
27
- description = "Builds a package, as a tarball and a wheel by default."
30
+ DistributionType = Literal ["sdist" , "wheel" ]
28
31
29
- options : ClassVar [list [Option ]] = [
30
- option ("format" , "f" , "Limit the format to either sdist or wheel." , flag = False ),
31
- option (
32
- "clean" ,
33
- description = "Clean output directory before building." ,
34
- flag = True ,
35
- ),
36
- option (
37
- "local-version" ,
38
- "l" ,
39
- "Add or replace a local version label to the build. (<warning>Deprecated</warning>)" ,
40
- flag = False ,
41
- ),
42
- option (
43
- "output" ,
44
- "o" ,
45
- "Set output directory for build artifacts. Default is `dist`." ,
46
- default = "dist" ,
47
- flag = False ,
48
- ),
49
- option (
50
- "config-settings" ,
51
- description = "Provide config settings that should be passed to backend in <key>=<value> format." ,
52
- flag = False ,
53
- multiple = True ,
54
- ),
55
- ]
56
32
57
- loggers : ClassVar [list [str ]] = [
58
- "poetry.core.masonry.builders.builder" ,
59
- "poetry.core.masonry.builders.sdist" ,
60
- "poetry.core.masonry.builders.wheel" ,
61
- ]
33
+ @dataclasses .dataclass (frozen = True )
34
+ class BuildOptions :
35
+ clean : bool
36
+ formats : list [DistributionType ]
37
+ output : str
38
+ config_settings : dict [str , Any ] = dataclasses .field (default_factory = dict )
62
39
63
- def _requested_formats (self ) -> list [DistributionType ]:
64
- fmt = self .option ("format" ) or "all"
65
- formats : list [DistributionType ]
40
+ def __post_init__ (self ) -> None :
41
+ for fmt in self .formats :
42
+ if fmt not in BUILD_FORMATS :
43
+ raise ValueError (f"Invalid format: { fmt } " )
66
44
67
- if fmt in BUILD_FORMATS :
68
- formats = [fmt ] # type: ignore[list-item]
69
- elif fmt == "all" :
70
- formats = list (BUILD_FORMATS .keys ()) # type: ignore[arg-type]
71
- else :
72
- raise ValueError (f"Invalid format: { fmt } " )
73
45
74
- return formats
75
-
76
- def _config_settings (self ) -> dict [str , str ]:
77
- config_settings = {}
78
-
79
- if local_version_label := self .option ("local-version" ):
80
- self .line_error (
81
- f"<warning>`<fg=yellow;options=bold>--local-version</>` is deprecated."
82
- f" Use `<fg=yellow;options=bold>--config-settings local-version={ local_version_label } </>`"
83
- f" instead.</warning>"
84
- )
85
- config_settings ["local-version" ] = local_version_label
86
-
87
- for config_setting in self .option ("config-settings" ):
88
- if "=" not in config_setting :
89
- raise ValueError (
90
- f"Invalid config setting format: { config_setting } . "
91
- "Config settings must be in the format 'key=value'"
92
- )
93
-
94
- key , _ , value = config_setting .partition ("=" )
95
- config_settings [key ] = value
96
-
97
- return config_settings
46
+ class BuildHandler :
47
+ def __init__ (self , poetry : Poetry , env : Env , io : IO ) -> None :
48
+ self .poetry = poetry
49
+ self .env = env
50
+ self .io = io
98
51
99
52
def _build (
100
53
self ,
@@ -103,9 +56,6 @@ def _build(
103
56
target_dir : Path ,
104
57
config_settings : dict [str , Any ],
105
58
) -> None :
106
- if fmt not in BUILD_FORMATS :
107
- raise ValueError (f"Invalid format: { fmt } " )
108
-
109
59
builder = BUILD_FORMATS [fmt ]
110
60
111
61
builder (
@@ -121,9 +71,6 @@ def _isolated_build(
121
71
target_dir : Path ,
122
72
config_settings : dict [str , Any ],
123
73
) -> None :
124
- if fmt not in BUILD_FORMATS :
125
- raise ValueError (f"Invalid format: { fmt } " )
126
-
127
74
with isolated_builder (
128
75
source = self .poetry .file .path .parent ,
129
76
distribution = fmt ,
@@ -167,32 +114,133 @@ def _get_builder(self) -> Callable[..., None]:
167
114
168
115
return self ._build
169
116
170
- def handle (self ) -> int :
117
+ def build (self , options : BuildOptions ) -> int :
171
118
if not self .poetry .is_package_mode :
172
- self .line_error ("Building a package is not possible in non-package mode." )
119
+ self .io .write_error_line (
120
+ "Building a package is not possible in non-package mode."
121
+ )
173
122
return 1
174
123
175
- dist_dir = Path (self . option ( " output" ) )
124
+ dist_dir = Path (options . output )
176
125
package = self .poetry .package
177
- self .line (
126
+ self .io . write_line (
178
127
f"Building <c1>{ package .pretty_name } </c1> (<c2>{ package .version } </c2>)"
179
128
)
180
129
181
130
if not dist_dir .is_absolute ():
182
131
dist_dir = self .poetry .pyproject_path .parent / dist_dir
183
132
184
- if self . option ( " clean" ) :
133
+ if options . clean :
185
134
remove_directory (path = dist_dir , force = True )
186
135
187
136
build = self ._get_builder ()
188
137
189
- for fmt in self . _requested_formats () :
190
- self .line (f"Building <info>{ fmt } </info>" )
138
+ for fmt in options . formats :
139
+ self .io . write_line (f"Building <info>{ fmt } </info>" )
191
140
build (
192
141
fmt ,
193
142
executable = self .env .python ,
194
143
target_dir = dist_dir ,
195
- config_settings = self . _config_settings () ,
144
+ config_settings = options . config_settings ,
196
145
)
197
146
198
147
return 0
148
+
149
+
150
+ class BuildCommand (EnvCommand ):
151
+ name = "build"
152
+ description = "Builds a package, as a tarball and a wheel by default."
153
+
154
+ options : ClassVar [list [Option ]] = [
155
+ option ("format" , "f" , "Limit the format to either sdist or wheel." , flag = False ),
156
+ option (
157
+ "clean" ,
158
+ description = "Clean output directory before building." ,
159
+ flag = True ,
160
+ ),
161
+ option (
162
+ "local-version" ,
163
+ "l" ,
164
+ "Add or replace a local version label to the build. (<warning>Deprecated</warning>)" ,
165
+ flag = False ,
166
+ ),
167
+ option (
168
+ "output" ,
169
+ "o" ,
170
+ "Set output directory for build artifacts. Default is `dist`." ,
171
+ default = "dist" ,
172
+ flag = False ,
173
+ ),
174
+ option (
175
+ "config-settings" ,
176
+ description = "Provide config settings that should be passed to backend in <key>=<value> format." ,
177
+ flag = False ,
178
+ multiple = True ,
179
+ ),
180
+ ]
181
+
182
+ loggers : ClassVar [list [str ]] = [
183
+ "poetry.core.masonry.builders.builder" ,
184
+ "poetry.core.masonry.builders.sdist" ,
185
+ "poetry.core.masonry.builders.wheel" ,
186
+ ]
187
+
188
+ @staticmethod
189
+ def _prepare_config_settings (
190
+ local_version : str | None , config_settings : list [str ] | None , io : IO
191
+ ) -> dict [str , str ]:
192
+ config_settings = config_settings or []
193
+ result = {}
194
+
195
+ if local_version :
196
+ io .write_error_line (
197
+ f"<warning>`<fg=yellow;options=bold>--local-version</>` is deprecated."
198
+ f" Use `<fg=yellow;options=bold>--config-settings local-version={ local_version } </>`"
199
+ f" instead.</warning>"
200
+ )
201
+ result ["local-version" ] = local_version
202
+
203
+ for config_setting in config_settings :
204
+ if "=" not in config_setting :
205
+ raise ValueError (
206
+ f"Invalid config setting format: { config_setting } . "
207
+ "Config settings must be in the format 'key=value'"
208
+ )
209
+
210
+ key , _ , value = config_setting .partition ("=" )
211
+ result [key ] = value
212
+
213
+ return result
214
+
215
+ @staticmethod
216
+ def _prepare_formats (fmt : str | None ) -> list [DistributionType ]:
217
+ fmt = fmt or "all"
218
+ formats : list [DistributionType ]
219
+
220
+ if fmt in BUILD_FORMATS :
221
+ formats = [fmt ] # type: ignore[list-item]
222
+ elif fmt == "all" :
223
+ formats = list (BUILD_FORMATS .keys ()) # type: ignore[arg-type]
224
+ else :
225
+ raise ValueError (f"Invalid format: { fmt } " )
226
+
227
+ return formats
228
+
229
+ def handle (self ) -> int :
230
+ build_handler = BuildHandler (
231
+ poetry = self .poetry ,
232
+ env = self .env ,
233
+ io = self .io ,
234
+ )
235
+ build_options = BuildOptions (
236
+ clean = self .option ("clean" ),
237
+ formats = self ._prepare_formats (self .option ("format" )),
238
+ output = self .option ("output" ),
239
+ config_settings = self ._prepare_config_settings (
240
+ local_version = self .option ("local-version" ),
241
+ config_settings = self .option ("config-settings" ),
242
+ io = self .io ,
243
+ ),
244
+ )
245
+
246
+ return build_handler .build (options = build_options )
0 commit comments