forked from pyrocko/pyrocko
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinstall.py
374 lines (303 loc) · 11.7 KB
/
install.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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
import sys
import os
import re
import platform
import argparse
import shlex
import subprocess
import textwrap
import sysconfig
def is_virtual_environment():
return sys.base_prefix != sys.prefix or hasattr(sys, "real_prefix")
def externally_managed_path():
# https://peps.python.org/pep-0668/
try:
scheme = sysconfig.get_default_scheme()
except AttributeError:
scheme = sysconfig._get_default_scheme()
return os.path.join(
sysconfig.get_path('stdlib', scheme),
'EXTERNALLY-MANAGED')
def is_externally_managed():
try:
return not is_virtual_environment() \
and os.path.exists(externally_managed_path())
except Exception:
return False
def wrap(s):
lines = []
parts = re.split(r'\n{2,}', s)
for part in parts:
if part.startswith('usage:'):
lines.extend(part.splitlines())
else:
for line in part.splitlines():
if not line:
lines.append(line)
if not line.startswith(' '):
lines.extend(
textwrap.wrap(line, 79,))
else:
lines.extend(
textwrap.wrap(line, 79, subsequent_indent=' '*24))
lines.append('')
return '\n'.join(lines)
def yes(parser):
parser.add_argument(
'-y', '--yes',
help='do not ask any questions (batch mode)',
action='store_true',
default=False)
def quiet(parser):
parser.add_argument(
'-q', '--quiet',
help='do not print executed commands',
action='store_true',
default=False)
commands = {
'description': wrap(
'This is Pyrocko\'s "from source" installation helper.\n\nIt provides '
'shortcut commands to circumvent pip\'s default automatic dependency '
'resolution which may be problematic, e.g. when prerequisites should '
'be supplied either by the system\'s native package manager, or by '
'other non-pip package managers, like conda. '
'\n\n'
'Examples:\n\n'
'For a system-wide installation of Pyrocko "from source", run:'
'\n\n'
' /usr/bin/python install.py deps system\n'
' /usr/bin/python install.py system\n'
'\n'
'For installation "from source" into the currently activated conda '
'environment:\n\n'
' python install.py deps conda\n'
' python install.py user\n'
'\n'
'For installation "from source" into a fresh venv with prerequisites '
'supplied by the systems native package manager:\n\n'
' /usr/bin/python -m venv --system-site-packages myenv\n'
' source myenv/bin/activate\n'
' python install.py deps system\n'
' python install.py user\n'
'\n'
'For installation "from source" into a fresh venv with prerequisites '
'supplied by pip (result is in this case similar to a standard '
'"pip install .").:\n\n'
' /usr/bin/python -m venv myenv\n'
' source myenv/bin/activate\n'
' python install.py deps pip\n'
' python install.py user\n'
'\n'
'For batch installations with no questions add --yes --quiet to the '
'selected subcommands.'
),
'subcommands': {
'deps': {
'help': 'install prerequisites',
'description': wrap(
'Install Pyrocko\'s prerequisites for a subsequent build and '
'install "from source", using the selected installation type. '
'Please consult the --help message of the available '
'subcommands for further information.'),
'subcommands': {
'pip': {
'help': 'install prerequisites using pip',
'description': wrap(
'Install Pyrocko\'s prerequisites using pip into user '
'environment. This command invokes `pip install` with '
'the appropriate list of prerequisites to prepare the '
'user\'s environment for a subsequent build and '
'install of Pyrocko "from source".'),
'arguments': [yes, quiet],
},
'conda': {
'help': 'install prerequisites using conda',
'description': wrap(
'Install Pyrocko\'s prerequisites using conda into '
'into the user\'s environment. This command invokes '
'`conda install` with the appropriate list of '
'prerequisites to prepare the currently selected '
'conda environment for a subsequent build and install '
'of Pyrocko "from source".'),
'arguments': [yes, quiet],
},
'system': {
'help': 'install prerequisites using the system\'s '
'package manager',
'description': wrap(
'Install prerequisites using the system\'s '
'package manager. On supported platforms, this '
'command invokes the system\'s package manager '
'with the appropriate list of system packages to '
'prepare for a subsequent build and install of '
'Pyrocko "from source".'),
'arguments': [yes, quiet],
},
},
},
'user': {
'help': 'install into user or conda environment',
'description': wrap(
'Build Pyrocko "from source" and install it into user or '
'conda environment. Use this installation method if you do '
'not have "sudo" access on your system, or if you want to '
'install into a virtual, or a conda environment. The selected '
'options will prevent pip from automatically installing '
'dependencies. Use one of the `deps` subcommands to satisfy '
'Pyrocko\'s requirements before running this installer.'),
'arguments': [yes, quiet],
},
'system': {
'help': 'install system-wide',
'description': wrap(
'Build Pyrocko "from source" and install it system-wide for '
'all users. Requires "sudo" access. The selected options will '
'prevent pip from automatically installing dependencies. Use '
'the "deps system" subcommand to satisfy ' 'Pyrocko\'s '
'requirements with system packages before running this '
'installer.'),
'arguments': [yes, quiet],
},
}
}
def die(message):
sys.exit('Error: %s' % message)
def confirm(s, force, quiet):
if not force:
try:
return input(
'Execute:\n\n%s\n\nProceed? [y/n] ' % s).lower() == 'y'
except KeyboardInterrupt:
print()
return False
elif not quiet:
print('Running:\n\n%s\n\n' % s)
return True
else:
return True
def do_command(cmd, force=False, quiet=False):
qcmd = indent(' '.join(shlex.quote(s) for s in cmd))
if confirm(qcmd, force, quiet):
try:
subprocess.run(cmd, check=True)
except subprocess.CalledProcessError:
die('Error: Called process exited with error.')
except OSError as e:
die('Could not run the requested command: %s' % e)
else:
sys.exit('Aborted.')
def indent(s):
return '\n'.join(' ' + line for line in s.splitlines())
def do_shell_script(fn, force=False, quiet=False):
qscript = indent(open(fn, 'r').read())
if confirm(qscript, force, quiet):
os.execl('/bin/sh', 'sh', fn)
else:
sys.exit('Aborted.')
def cmd_deps_conda(parser, args):
if platform.system().lower() == 'windows':
requirements = 'requirements-conda-windows.txt'
else:
requirements = 'requirements-conda.txt'
do_command(
['conda', 'install', '--file', requirements],
force=args.yes, quiet=args.quiet)
def cmd_deps_pip(parser, args):
python = sys.executable or 'python3'
do_command(
[python, '-m', 'pip', 'install', '-r', 'requirements-all.txt'],
force=args.yes, quiet=args.quiet)
def cmd_deps_system(parser, args):
distribution = ''
try:
distribution = platform.linux_distribution()[0].lower().rstrip()
except Exception:
pass
if not distribution:
try:
uname = platform.uname()
if uname[2].find('arch') != -1:
distribution = 'arch'
elif uname[3].lower().find('ubuntu') != -1:
distribution = 'ubuntu'
elif uname[3].lower().find('debian') != -1:
distribution = 'debian'
except Exception:
pass
if not distribution:
try:
with open('/etc/redhat-release', 'r') as f:
x = f.read()
if x:
distribution = 'rpm'
except Exception:
pass
if not distribution:
sys.exit(
'Cannot determine platform for automatic prerequisite '
'installation.')
if distribution == 'ubuntu':
distribution = 'debian'
if distribution.startswith('centos'):
distribution = 'centos'
fn = 'prerequisites/prerequisites_%s_python%i.sh' % (
distribution, sys.version_info.major)
do_shell_script(fn, force=args.yes, quiet=args.quiet)
def cmd_user(parser, args):
python = sys.executable or 'python3'
do_command([
python, '-m', 'pip', 'install',
'--no-deps',
'--no-build-isolation',
'--force-reinstall',
'--upgrade',
'.'],
force=args.yes, quiet=args.quiet)
def cmd_system(parser, args):
python = sys.executable or 'python3'
pip_cmd = [
'sudo', python, '-m', 'pip', 'install',
'--no-deps',
'--no-build-isolation',
'--force-reinstall',
'--upgrade']
if is_externally_managed():
pip_cmd.append('--break-system-packages')
pip_cmd.append('.')
do_command(pip_cmd, force=args.yes, quiet=args.quiet)
def print_help(parser, args):
parser.print_help()
def kwargs(d, keys):
return dict((k, d[k]) for k in keys if k in d)
class HelpFormatter(argparse.RawDescriptionHelpFormatter):
def __init__(self, *args, **kwargs):
kwargs['width'] = 79
argparse.RawDescriptionHelpFormatter.__init__(
self, *args, **kwargs)
def make_parser(d, name=None, parent=None, path=()):
if parent is None:
parser = argparse.ArgumentParser(
formatter_class=HelpFormatter,
**kwargs(d, ['description']))
parser.set_defaults(func=print_help, parser=parser)
else:
parser = parent.add_parser(
name,
formatter_class=HelpFormatter,
**kwargs(d, ['help', 'description']))
if 'arguments' in d:
for func in d['arguments']:
func(parser)
if 'subcommands' in d:
subparsers = parser.add_subparsers(title='subcommands')
for name, d in d['subcommands'].items():
subparser = make_parser(d, name, subparsers, path + (name,))
subparser.set_defaults(
func=globals().get(
'cmd_%s' % '_'.join(path + (name,)),
print_help),
parser=subparser)
return parser
parser = make_parser(commands)
args = parser.parse_args()
args.func(args.parser, args)