-
Notifications
You must be signed in to change notification settings - Fork 195
/
make_dist.py
executable file
·289 lines (225 loc) · 8.8 KB
/
make_dist.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#!/usr/bin/env python3
"""Script to create a new distribution.
The following options are supported:
pywikibot The pywikibot repository to build (default)
scripts The pywikibot-scripts repository to build
-help Print documentation of this file and of setup.py
-local Install the distribution as a local site-package. If a
Pywikibot package is already there, it will be uninstalled
first. Clears old dist folders first.
-remote Upload the package to pypi. This cannot be done if the
Pywikibot version is a development release. Clears old dist
folders first.
-clear Clear old dist folders and leave. Does not create a
distribution.
-upgrade Upgrade pip first; upgrade or install distribution packages
build and twine first.
Usage::
[pwb] make_dist [repo] [options]
.. versionadded:: 7.3
.. versionchanged:: 7.4
- updates pip, setuptools, wheel and twine packages first
- installs pre-releases over stable versions
- also creates built distribution together with source distribution
- *-upgrade* option was added
.. versionchanged:: 7.5
- *clear* option was added
- *nodist* option was added
.. versionchanged:: 8.1
*nodist* option was removed, *clear* option does not create a
distribution. *local* and *remote* option clears old distributions
first.
.. versionchanged:: 8.2
Build frontend was changed from setuptools to build. ``-upgrade``
option also installs packages if necessary.
.. versionchanged:: 9.4
The pywikibot-scripts distribution can be created.
"""
#
# (C) Pywikibot team, 2022-2024
#
# Distributed under the terms of the MIT license.
#
from __future__ import annotations
import abc
import shutil
import sys
from contextlib import suppress
from dataclasses import dataclass, field
from importlib import import_module
from pathlib import Path
from subprocess import check_call, run
from pywikibot import __version__, error, info, input_yn, warning
@dataclass
class SetupBase(abc.ABC):
"""Setup distribution base class.
.. versionadded:: 8.0
.. versionchanged:: 8.1
*dataclass* is used.
"""
local: bool
remote: bool
clear: bool
upgrade: bool
build_opt: str = field(init=False)
folder: Path = field(init=False)
def __post_init__(self) -> None:
"""Post-init initializer."""
self.folder = Path.cwd()
def clear_old_dist(self) -> None: # pragma: no cover
"""Delete old dist folders.
.. versionadded:: 7.5
"""
info('<<lightyellow>>Removing old dist folders... ', newline=False)
shutil.rmtree(self.folder / 'build', ignore_errors=True)
shutil.rmtree(self.folder / 'dist', ignore_errors=True)
shutil.rmtree(self.folder / 'pywikibot.egg-info', ignore_errors=True)
shutil.rmtree(self.folder / 'pywikibot_scripts.egg-info',
ignore_errors=True)
info('<<lightyellow>>done')
@abc.abstractmethod
def copy_files(self) -> None:
"""Copy files."""
@abc.abstractmethod
def cleanup(self) -> None:
"""Cleanup copied files."""
def run(self) -> bool:
"""Run the installer script.
:return: True if no error occurs, else False
"""
if self.local or self.remote or self.clear:
self.clear_old_dist()
if self.clear:
return True
if self.upgrade: # pragma: no cover
check_call('python -m pip install --upgrade pip', shell=True)
for module in ('build', 'twine'):
info(f'<<lightyellow>>Install or upgrade {module}')
try:
import_module(module)
except ModuleNotFoundError:
check_call(f'pip install {module}', shell=True)
else:
check_call(f'pip install --upgrade {module}', shell=True)
else:
for module in ('build', 'twine'):
try:
import_module(module)
except ModuleNotFoundError as e:
error(f'<<lightred>>{e}')
info('<<lightblue>>You may use -upgrade option to install')
return False
return self.build() # pragma: no cover
def build(self) -> bool: # pragma: no cover
"""Build the packages.
.. versionadded:: 9.3
"""
self.copy_files()
info('<<lightyellow>>Build package')
try:
check_call(f'python -m build {self.build_opt}')
except Exception as e:
error(e)
return False
finally:
self.cleanup()
info('<<lightyellow>>Check package and description')
if run('twine check dist/*', shell=True).returncode:
return False
if self.local:
info('<<lightyellow>>Install locally')
check_call(f'pip uninstall {self.package} -y', shell=True)
check_call(f'pip install --no-cache-dir --no-index --pre '
f'--find-links=dist {self.package}', shell=True)
if self.remote and input_yn(
'<<lightblue>>Upload dist to pypi', automatic_quit=False):
check_call('twine upload dist/*', shell=True)
return True
class SetupPywikibot(SetupBase):
"""Setup for Pywikibot distribution.
.. versionadded:: 8.0
"""
build_opt = '' # defaults to current directory
package = 'pywikibot'
def __init__(self, *args) -> None:
"""Set source and target directories."""
super().__init__(*args)
source = self.folder / 'scripts' / 'i18n' / 'pywikibot'
target = self.folder / 'pywikibot' / 'scripts' / 'i18n' / 'pywikibot'
self.target = target
self.source = source
def copy_files(self) -> None: # pragma: no cover
"""Copy i18n files to pywikibot.scripts folder.
Pywikibot i18n files are used for some translations. They are copied
to the pywikibot scripts folder.
"""
info('<<lightyellow>>Copy files')
info(f'directory is {self.folder}')
info(f'clear {self.target} directory')
shutil.rmtree(self.target, ignore_errors=True)
info('copy i18n files ... ', newline=False)
shutil.copytree(self.source, self.target)
info('done')
def cleanup(self) -> None: # pragma: no cover
"""Remove all copied files from pywikibot scripts folder."""
info('<<lightyellow>>Remove copied files... ', newline=False)
shutil.rmtree(self.target)
# restore pywikibot en.json file
filename = 'en.json'
self.target.mkdir()
shutil.copy(self.source / filename, self.target / filename)
info('<<lightyellow>>done')
class SetupScripts(SetupBase):
"""Setup pywikibot-scripts distribution.
.. versionadded:: 9.4
"""
build_opt = '-w' # only wheel (yet)
package = 'pywikibot_scripts'
replace = 'MANIFEST.in', 'pyproject.toml', 'setup.py'
def copy_files(self) -> None:
"""Ignore copy files yet."""
info('<<lightyellow>>Copy files ...', newline=False)
for filename in self.replace:
file = self.folder / filename
file.rename(self.folder / (filename + '.saved'))
with suppress(FileNotFoundError):
shutil.copy(self.folder / 'scripts' / filename, self.folder)
info('<<lightyellow>>done')
def cleanup(self) -> None:
"""Ignore cleanup yet."""
info('<<lightyellow>>Copy files ...', newline=False)
for filename in self.replace:
file = self.folder / (filename + '.saved')
file.replace(self.folder / filename)
info('<<lightyellow>>done')
def handle_args() -> tuple[bool, bool, bool, bool]:
"""Handle arguments and print documentation if requested.
:return: Return whether dist is to be installed locally or to be
uploaded
"""
if '-help' in sys.argv:
import re
import setup
help_text = re.sub(r'^\.\. version(added|changed)::.+', '',
__doc__, flags=re.MULTILINE | re.DOTALL)
info(help_text)
info(setup.__doc__)
sys.exit()
local = '-local' in sys.argv
remote = '-remote' in sys.argv
clear = '-clear' in sys.argv
upgrade = '-upgrade' in sys.argv
scripts = 'scripts' in sys.argv
if not scripts and remote and 'dev' in __version__: # pragma: no cover
warning('Distribution must not be a developmental release to upload.')
remote = False
sys.argv = [sys.argv[0]]
return local, remote, clear, upgrade, scripts
def main() -> None:
"""Script entry point."""
*args, scripts = handle_args()
installer = SetupScripts if scripts else SetupPywikibot
return installer(*args).run()
if __name__ == '__main__':
if not main():
sys.exit(1) # pragma: no cover