Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/build/sage_bootstrap/cmdline.py
4052 views
1
# -*- coding: utf-8 -*-
2
"""
3
View for the Commandline UI
4
5
This module handles the main "sage-package" commandline utility, which
6
is also exposed as "sage --package".
7
8
AUTHORS:
9
10
- Volker Braun (2016): initial version
11
- Thierry Monteil (2022): clean option to remove outdated source tarballs
12
"""
13
14
# ****************************************************************************
15
# Copyright (C) 2015-2016 Volker Braun <[email protected]>
16
# 2020-2024 Matthias Koeppe
17
# 2022 Thierry Monteil
18
#
19
# This program is free software: you can redistribute it and/or modify
20
# it under the terms of the GNU General Public License as published by
21
# the Free Software Foundation, either version 2 of the License, or
22
# (at your option) any later version.
23
# https://www.gnu.org/licenses/
24
# ****************************************************************************
25
26
import sys
27
import logging
28
log = logging.getLogger()
29
30
31
import argparse
32
33
from sage_bootstrap.app import Application
34
35
36
description = \
37
"""
38
SageMath Bootstrap Library
39
40
Provides scripts to manage the packages of Sage-the-distribution,
41
including SageMath's database of equivalent system packages,
42
and to download and upload tarballs from/to SageMath servers.
43
"""
44
45
46
epilog = \
47
"""
48
The individual subcommands have their own detailed help, for example
49
run "sage --package config -h" to see the help on the config option.
50
"""
51
52
53
epilog_config = \
54
"""
55
Print the configuration
56
57
EXAMPLE:
58
59
$ sage --package config
60
Configuration:
61
* log = info
62
* interactive = True
63
"""
64
65
66
epilog_list = \
67
"""
68
Print a list of packages known to Sage (sorted alphabetically)
69
70
EXAMPLE:
71
72
$ sage --package list
73
4ti2
74
[...]
75
zipp
76
77
$ sage --package list :standard:
78
_prereq
79
[...]
80
zipp
81
"""
82
83
84
epilog_properties = \
85
"""
86
Print properties of given package.
87
88
EXAMPLE:
89
90
$ sage --package properties maxima
91
maxima:
92
path: /.../build/pkgs/maxima
93
version_with_patchlevel: 5.46.0
94
type: standard
95
source: normal
96
trees: SAGE_LOCAL
97
"""
98
99
100
epilog_dependencies = \
101
"""
102
Print the list of packages that are dependencies of given package.
103
By default, list a summary of the build, order-only, and runtime
104
dependencies.
105
106
EXAMPLE:
107
108
$ sage --package dependencies maxima openblas
109
maxima:
110
- ecl
111
- info
112
openblas:
113
- gfortran
114
$ sage --package dependencies maxima --runtime
115
- ecl
116
117
$ sage --package dependencies maxima openblas --runtime --order-only
118
maxima:
119
order_only:
120
- info
121
runtime:
122
- ecl
123
openblas:
124
order_only:
125
runtime:
126
- gfortran
127
"""
128
129
130
epilog_name = \
131
"""
132
Find the package name given a tarball filename
133
134
EXAMPLE:
135
136
$ sage --package name pari-2.8-1564-gdeac36e.tar.gz
137
pari
138
"""
139
140
141
epilog_tarball = \
142
"""
143
Find the tarball filename given a package name
144
145
EXAMPLE:
146
147
$ sage --package tarball pari
148
pari-2.8-1564-gdeac36e.tar.gz
149
"""
150
151
152
epilog_apropos = \
153
"""
154
Find up to 5 package names that are close to the given name
155
156
EXAMPLE:
157
158
$ sage --package apropos python
159
Did you mean: cython, ipython, python2, python3, patch?
160
"""
161
162
163
epilog_update = \
164
"""
165
Update a package. This modifies the Sage sources.
166
167
EXAMPLE:
168
169
$ sage --package update pari 2015 --url=http://localhost/pari/tarball.tgz
170
"""
171
172
173
epilog_update_latest = \
174
"""
175
Update a package to the latest version. This modifies the Sage sources.
176
177
EXAMPLE:
178
179
$ sage --package update-latest ipython
180
"""
181
182
183
epilog_download = \
184
"""
185
Download the tarball for a package and print the filename to stdout
186
187
EXAMPLE:
188
189
$ sage --package download pari
190
Using cached file /home/vbraun/Code/sage.git/upstream/pari-2.8-2044-g89b0f1e.tar.gz
191
/home/vbraun/Code/sage.git/upstream/pari-2.8-2044-g89b0f1e.tar.gz
192
"""
193
194
195
epilog_upload = \
196
"""
197
Upload the tarball to the Sage mirror network (requires ssh key authentication)
198
199
EXAMPLE:
200
201
$ sage --package upload pari
202
Uploading /home/vbraun/Code/sage.git/upstream/pari-2.8-2044-g89b0f1e.tar.gz
203
"""
204
205
206
epilog_fix_checksum = \
207
"""
208
Fix the checksum of a package
209
210
EXAMPLE:
211
212
$ sage --package fix-checksum pari
213
Updating checksum of pari (tarball pari-2.8-2044-g89b0f1e.tar.gz)
214
"""
215
216
epilog_create = \
217
"""
218
Create new package, or overwrite existing package
219
220
EXAMPLE:
221
222
$ sage --package create foo --version=3.14 --tarball=Foo-VERSION.tar.bz2 --type=standard
223
Creating new package "foo"
224
"""
225
226
epilog_clean = \
227
"""
228
Remove outdated source tarballs from the upstream/ directory
229
230
EXAMPLE:
231
232
$ sage --package clean
233
42 files were removed from the .../upstream directory
234
"""
235
236
epilog_metrics = \
237
"""
238
Print metrics of given package.
239
240
EXAMPLE:
241
242
$ sage --package metrics :standard:
243
has_file_distros_arch_txt=131
244
...
245
has_file_distros_void_txt=184
246
has_file_patches=35
247
has_file_spkg_check=59
248
has_file_spkg_configure_m4=222
249
has_file_spkg_install=198
250
has_tarball_upstream_url=231
251
line_count_file_patches=22561
252
...
253
packages=272
254
type_standard=272
255
"""
256
257
258
def make_parser():
259
"""
260
The main commandline argument parser
261
"""
262
parser = argparse.ArgumentParser(
263
description=description, epilog=epilog,
264
prog='sage --package',
265
formatter_class=argparse.RawDescriptionHelpFormatter,
266
)
267
parser.add_argument('--log', dest='log', default=None,
268
help='one of [DEBUG, INFO, ERROR, WARNING, CRITICAL]')
269
subparsers = parser.add_subparsers(dest='subcommand')
270
271
parser_config = subparsers.add_parser(
272
'config', epilog=epilog_config,
273
formatter_class=argparse.RawDescriptionHelpFormatter,
274
help='print the configuration')
275
276
parser_list = subparsers.add_parser(
277
'list', epilog=epilog_list,
278
formatter_class=argparse.RawDescriptionHelpFormatter,
279
help='print a list of packages known to Sage')
280
parser_list.add_argument(
281
'package_class', metavar='[PACKAGE_NAME|pkg:pypi/DISTRIBUTION-NAME|:PACKAGE_TYPE:]',
282
type=str, default=[':all-or-nothing:'], nargs='*',
283
help=('package name, pkg:pypi/ followed by a distribution name, '
284
'or designator for all packages of a given type '
285
'(one of :all:, :standard:, :optional:, and :experimental:); '
286
'default: :all: (or nothing when --include-dependencies or --exclude-dependencies is given)'))
287
parser_list.add_argument(
288
'--has-file', action='append', default=[], metavar='FILENAME', dest='has_files',
289
help=('only include packages that have this file in their metadata directory '
290
'(examples: SPKG.rst, spkg-configure.m4, distros/debian.txt, spkg-install|spkg-install.in)'))
291
parser_list.add_argument(
292
'--no-file', action='append', default=[], metavar='FILENAME', dest='no_files',
293
help=('only include packages that do not have this file in their metadata directory '
294
'(examples: huge, patches, huge|has_nonfree_dependencies)'))
295
parser_list.add_argument(
296
'--exclude', nargs='*', action='append', default=[], metavar='PACKAGE_NAME',
297
help='exclude package from list')
298
parser_list.add_argument(
299
'--include-dependencies', action='store_true',
300
help='include (ordinary) dependencies of the packages recursively')
301
parser_list.add_argument(
302
'--exclude-dependencies', action='store_true',
303
help='exclude (ordinary) dependencies of the packages recursively')
304
305
parser_properties = subparsers.add_parser(
306
'properties', epilog=epilog_properties,
307
formatter_class=argparse.RawDescriptionHelpFormatter,
308
help='print properties of given packages')
309
parser_properties.add_argument(
310
'package_class', metavar='[PACKAGE_NAME|pkg:pypi/DISTRIBUTION-NAME|:PACKAGE_TYPE:]',
311
type=str, nargs='+',
312
help=('package name, pkg:pypi/ followed by a distribution name, '
313
'or designator for all packages of a given type '
314
'(one of :all:, :standard:, :optional:, and :experimental:)'))
315
parser_properties.add_argument(
316
'--format', type=str, default='plain',
317
help='output format (one of plain and shell; default: plain)')
318
319
parser_dependencies = subparsers.add_parser(
320
'dependencies', epilog=epilog_dependencies,
321
formatter_class=argparse.RawDescriptionHelpFormatter,
322
help='print the list of packages that are dependencies of given packages')
323
parser_dependencies.add_argument(
324
'package_class', metavar='[package_name|:package_type:]',
325
type=str, nargs='+',
326
help=('package name or designator for all packages of a given type '
327
'(one of :all:, :standard:, :optional:, and :experimental:)'))
328
parser_dependencies.add_argument(
329
'--order-only', action='store_true',
330
help='list the order-only build dependencies')
331
parser_dependencies.add_argument(
332
'--optional', action='store_true',
333
help='list the optional build dependencies')
334
parser_dependencies.add_argument(
335
'--runtime', action='store_true',
336
help='list the runtime dependencies')
337
parser_dependencies.add_argument(
338
'--check', action='store_true',
339
help='list the check dependencies')
340
parser_dependencies.add_argument(
341
'--format', type=str, default='plain',
342
help='output format (one of plain, rst, and shell; default: plain)')
343
344
parser_name = subparsers.add_parser(
345
'name', epilog=epilog_name,
346
formatter_class=argparse.RawDescriptionHelpFormatter,
347
help='find the package name given a tarball filename')
348
parser_name.add_argument('tarball_filename', type=str, help='tarball filename')
349
350
parser_tarball = subparsers.add_parser(
351
'tarball', epilog=epilog_tarball,
352
formatter_class=argparse.RawDescriptionHelpFormatter,
353
help='find the tarball filename given a package name')
354
parser_tarball.add_argument('package_name', type=str, help='package name')
355
356
parser_apropos = subparsers.add_parser(
357
'apropos', epilog=epilog_apropos,
358
formatter_class=argparse.RawDescriptionHelpFormatter,
359
help='find up to 5 package names that are close to the given name')
360
parser_apropos.add_argument(
361
'incorrect_name', type=str,
362
help='fuzzy name to search for')
363
364
parser_update = subparsers.add_parser(
365
'update', epilog=epilog_update,
366
formatter_class=argparse.RawDescriptionHelpFormatter,
367
help='update a package, modifying the Sage sources')
368
parser_update.add_argument(
369
'package_name', type=str, help='package name')
370
parser_update.add_argument(
371
'new_version', type=str, help='new version')
372
parser_update.add_argument(
373
'--url', type=str, default=None, help='download URL')
374
parser_update.add_argument(
375
'--commit', action="store_true",
376
help='whether to run "git commit"')
377
378
parser_update_latest = subparsers.add_parser(
379
'update-latest', epilog=epilog_update_latest,
380
formatter_class=argparse.RawDescriptionHelpFormatter,
381
help='update a package to the latest version, modifying the Sage sources')
382
parser_update_latest.add_argument(
383
'package_name', type=str, help='package name (:all: for all packages)')
384
parser_update_latest.add_argument(
385
'--commit', action="store_true",
386
help='whether to run "git commit"')
387
388
parser_download = subparsers.add_parser(
389
'download', epilog=epilog_download,
390
formatter_class=argparse.RawDescriptionHelpFormatter,
391
help='download tarball')
392
parser_download.add_argument(
393
'package_class', metavar='[package_name|:package_type:]',
394
type=str, nargs='+',
395
help=('package name or designator for all packages of a given type '
396
'(one of :all:, :standard:, :optional:, and :experimental:)'))
397
parser_download.add_argument(
398
'--has-file', action='append', default=[], metavar='FILENAME', dest='has_files',
399
help=('only include packages that have this file in their metadata directory '
400
'(examples: SPKG.rst, spkg-configure.m4, distros/debian.txt, spkg-install|spkg-install.in)'))
401
parser_download.add_argument(
402
'--no-file', action='append', default=[], metavar='FILENAME', dest='no_files',
403
help=('only include packages that do not have this file in their metadata directory '
404
'(examples: huge, patches, huge|has_nonfree_dependencies)'))
405
parser_download.add_argument(
406
'--exclude', nargs='*', action='append', default=[], metavar='PACKAGE_NAME',
407
help='exclude package from list')
408
parser_download.add_argument(
409
'--allow-upstream', action="store_true",
410
help='whether to fall back to downloading from the upstream URL')
411
parser_download.add_argument(
412
'--on-error', choices=['stop', 'warn'], default='stop',
413
help='what to do if the tarball cannot be downloaded')
414
parser_download.add_argument(
415
'--no-check-certificate', action='store_true',
416
help='do not check SSL certificates for https connections')
417
418
parser_upload = subparsers.add_parser(
419
'upload', epilog=epilog_upload,
420
formatter_class=argparse.RawDescriptionHelpFormatter,
421
help='upload tarball to Sage mirrors')
422
parser_upload.add_argument(
423
'package_name', type=str, help='package name or :type:')
424
425
parser_fix_checksum = subparsers.add_parser(
426
'fix-checksum', epilog=epilog_fix_checksum,
427
formatter_class=argparse.RawDescriptionHelpFormatter,
428
help='fix the checksum of normal packages')
429
parser_fix_checksum.add_argument(
430
'package_class', metavar='[PACKAGE_NAME|pkg:pypi/DISTRIBUTION-NAME|:PACKAGE_TYPE:]',
431
type=str, default=[':all:'], nargs='*',
432
help=('package name, pkg:pypi/ followed by a distribution name, '
433
'or designator for all packages of a given type '
434
'(one of :all:, :standard:, :optional:, and :experimental:; default: :all:)'))
435
436
parser_create = subparsers.add_parser(
437
'create', epilog=epilog_create,
438
formatter_class=argparse.RawDescriptionHelpFormatter,
439
help='create or overwrite a package')
440
parser_create.add_argument(
441
'package_name', default=None, type=str,
442
help='package name')
443
parser_create.add_argument(
444
'--source', type=str, default=None, help='package source (one of normal, wheel, script, pip); default depends on provided arguments')
445
parser_create.add_argument(
446
'--version', type=str, default=None, help='package version')
447
parser_create.add_argument(
448
'--tarball', type=str, default=None, help='tarball filename pattern, e.g. Foo-VERSION.tar.bz2')
449
parser_create.add_argument(
450
'--type', type=str, default=None, help='package type')
451
parser_create.add_argument(
452
'--url', type=str, default=None, help='download URL pattern, e.g. http://example.org/Foo-VERSION.tar.bz2')
453
parser_create.add_argument(
454
'--description', type=str, default=None, help='short description of the package (for SPKG.rst)')
455
parser_create.add_argument(
456
'--license', type=str, default=None, help='license of the package (for SPKG.rst)')
457
parser_create.add_argument(
458
'--upstream-contact', type=str, default=None, help='upstream contact (for SPKG.rst)')
459
parser_create.add_argument(
460
'--pypi', action="store_true",
461
help='create a package for a Python package available on PyPI')
462
463
parser_clean = subparsers.add_parser(
464
'clean', epilog=epilog_clean,
465
formatter_class=argparse.RawDescriptionHelpFormatter,
466
help='remove outdated source tarballs from the upstream/ directory')
467
468
parser_metrics = subparsers.add_parser(
469
'metrics', epilog=epilog_metrics,
470
formatter_class=argparse.RawDescriptionHelpFormatter,
471
help='print metrics of given packages')
472
parser_metrics.add_argument(
473
'package_class', metavar='[PACKAGE_NAME|pkg:pypi/DISTRIBUTION-NAME|:PACKAGE_TYPE:]',
474
type=str, nargs='*', default=[':all:'],
475
help=('package name, pkg:pypi/ followed by a distribution name, '
476
'or designator for all packages of a given type '
477
'(one of :all:, :standard:, :optional:, and :experimental:; default: :all:)'))
478
479
return parser
480
481
482
def run():
483
parser = make_parser()
484
if len(sys.argv) == 1:
485
parser.print_help()
486
return
487
args = parser.parse_args(sys.argv[1:])
488
if args.log is not None:
489
level = getattr(logging, args.log.upper())
490
log.setLevel(level=level)
491
log.debug('Commandline arguments: %s', args)
492
app = Application()
493
if args.subcommand == 'config':
494
app.config()
495
elif args.subcommand == 'list':
496
if args.package_class == [':all-or-nothing:']:
497
if args.include_dependencies or args.exclude_dependencies:
498
args.package_class = []
499
else:
500
args.package_class = [':all:']
501
app.list_cls(*args.package_class,
502
has_files=args.has_files, no_files=args.no_files,
503
exclude=args.exclude,
504
include_dependencies=args.include_dependencies,
505
exclude_dependencies=args.exclude_dependencies)
506
elif args.subcommand == 'properties':
507
app.properties(*args.package_class, format=args.format)
508
elif args.subcommand == 'dependencies':
509
types = []
510
if args.order_only:
511
types.append('order_only')
512
if args.optional:
513
types.append('optional')
514
if args.runtime:
515
types.append('runtime')
516
if args.check:
517
types.append('check')
518
if not types:
519
types = None
520
app.dependencies(*args.package_class, types=types, format=args.format)
521
elif args.subcommand == 'name':
522
app.name(args.tarball_filename)
523
elif args.subcommand == 'tarball':
524
app.tarball(args.package_name)
525
elif args.subcommand == 'apropos':
526
app.apropos(args.incorrect_name)
527
elif args.subcommand == 'update':
528
app.update(args.package_name, args.new_version, url=args.url, commit=args.commit)
529
elif args.subcommand == 'update-latest':
530
app.update_latest_cls(args.package_name, commit=args.commit)
531
elif args.subcommand == 'download':
532
if args.no_check_certificate:
533
try:
534
import ssl
535
ssl._create_default_https_context = ssl._create_unverified_context
536
except ImportError:
537
pass
538
app.download_cls(*args.package_class,
539
has_files=args.has_files, no_files=args.no_files,
540
exclude=args.exclude,
541
allow_upstream=args.allow_upstream,
542
on_error=args.on_error)
543
elif args.subcommand == 'create':
544
app.create(args.package_name, args.version, args.tarball, args.type, args.url,
545
args.description, args.license, args.upstream_contact,
546
pypi=args.pypi, source=args.source)
547
elif args.subcommand == 'upload':
548
app.upload_cls(args.package_name)
549
elif args.subcommand == 'fix-checksum':
550
app.fix_checksum_cls(*args.package_class)
551
elif args.subcommand == 'clean':
552
app.clean()
553
elif args.subcommand == 'metrics':
554
app.metrics_cls(*args.package_class)
555
else:
556
raise RuntimeError('unknown subcommand: {0}'.format(args))
557
558
559
if __name__ == '__main__':
560
run()
561
562