Skip to content

Commit 699dbf1

Browse files
author
Release Manager
committed
gh-36393: `sage --package list`: Sort output, add switches `--{in,ex}clude-dependencies` We enhance the command `sage --package list`: - New switches `--{in,ex}clude-dependencies` walk the tree of dependencies declared in `dependencies` files - Output is always (uniquified and) sorted, eliminating the use of `sort` in various scripts - Handling of `--exclude` is moved to the end, which improves its applicability. The new parsing of the `dependencies*` files in `sage_bootstrap` is generic preparation for handling more of the bootstrapping phase in Python, as sketched in #33860 (part of #29146). This PR is immediate preparation for #35593, where it is desired to create "minimized" lists of system packages that do not include all standard packages that are really only needed as dependencies of "top- level" packages. <!-- ^^^^^ Please provide a concise, informative and self-explanatory title. Don't put issue numbers in there, do this in the PR body below. For example, instead of "Fixes #1234" use "Introduce new method to calculate 1+1" --> <!-- Describe your changes here in detail --> <!-- Why is this change required? What problem does it solve? --> <!-- If this PR resolves an open issue, please link to it here. For example "Fixes #12345". --> <!-- If your change requires a documentation PR, please link it appropriately. --> ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> <!-- If your change requires a documentation PR, please link it appropriately --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> <!-- Feel free to remove irrelevant items. --> - [x] The title is concise, informative, and self-explanatory. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [ ] I have created tests covering the changes. - [ ] I have updated the documentation accordingly. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on - #12345: short description why this is a dependency - #34567: ... --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> URL: #36393 Reported by: Matthias Köppe Reviewer(s): Michael Orlitzky
2 parents 384493c + bc7d33e commit 699dbf1

File tree

7 files changed

+126
-29
lines changed

7 files changed

+126
-29
lines changed

bootstrap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ bootstrap () {
4747
done
4848
spkg_configures=""
4949
# initialize SAGE_ENABLE... options for standard packages
50-
for pkgname in $(sage-package list :standard: | sort); do
50+
for pkgname in $(sage-package list :standard:); do
5151
spkg_configures="$spkg_configures
5252
AS_VAR_SET_IF([SAGE_ENABLE_$pkgname], [], [AS_VAR_SET([SAGE_ENABLE_$pkgname], [yes])])"
5353
done
5454
# --enable-SPKG options
55-
for pkgname in $(sage-package list :optional: :experimental: | sort); do
55+
for pkgname in $(sage-package list :optional: :experimental:); do
5656
# Issue #29629: Temporary solution for Sage 9.1: Do not provide
5757
# --enable-SPKG options for installing pip packages
5858
if [ ! -f build/pkgs/$pkgname/requirements.txt ]; then
@@ -71,7 +71,7 @@ SAGE_SPKG_ENABLE([$pkgname], [$pkgtype], [$(grep -v ^= build/pkgs/$pkgname/SPKG.
7171
esac
7272
fi
7373
done
74-
for pkgname in $(sage-package list --has-file spkg-configure.m4 | sort); do
74+
for pkgname in $(sage-package list --has-file spkg-configure.m4); do
7575
echo "m4_sinclude([build/pkgs/$pkgname/spkg-configure.m4])"
7676
config="SAGE_SPKG_CONFIGURE_$(echo ${pkgname} | tr '[a-z]' '[A-Z]')"
7777
if grep -q SAGE_PYTHON_PACKAGE_CHECK build/pkgs/$pkgname/spkg-configure.m4; then
@@ -86,7 +86,7 @@ $config"
8686
$spkg_configures
8787
$spkg_configures_python
8888
EOF
89-
for pkgname in $(sage-package list | sort); do
89+
for pkgname in $(sage-package list); do
9090
DIR=build/pkgs/$pkgname
9191
pkgtype="$(cat $DIR/type)"
9292
if test -f "$DIR/requirements.txt"; then

build/bin/sage-package

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#
1414
# * Print a list of all available packages
1515
#
16-
# $ sage-package list | sort
16+
# $ sage-package list
1717
# 4ti2
1818
# arb
1919
# autotools

build/sage_bootstrap/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def list_cls(self, *package_classes, **filters):
5353
"""
5454
Print a list of all available packages
5555
56-
$ sage --package list | sort
56+
$ sage --package list
5757
4ti2
5858
arb
5959
autotools
@@ -63,7 +63,7 @@ def list_cls(self, *package_classes, **filters):
6363
$ sage -package list --has-file=spkg-configure.m4 :experimental:
6464
perl_term_readline_gnu
6565
66-
$ sage -package list --has-file=spkg-configure.m4 --has-file=distros/debian.txt | sort
66+
$ sage -package list --has-file=spkg-configure.m4 --has-file=distros/debian.txt
6767
arb
6868
boost_cropped
6969
brial

build/sage_bootstrap/cmdline.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,18 @@
6464

6565
epilog_list = \
6666
"""
67-
Print a list of packages known to Sage
67+
Print a list of packages known to Sage (sorted alphabetically)
6868
6969
EXAMPLE:
7070
71-
$ sage --package list | sort
71+
$ sage --package list
7272
4ti2
7373
arb
7474
autotools
7575
[...]
7676
zlib
7777
78-
$ sage --package list :standard: | sort
78+
$ sage --package list :standard:
7979
arb
8080
backports_ssl_match_hostname
8181
[...]
@@ -214,10 +214,10 @@ def make_parser():
214214
help='Print a list of packages known to Sage')
215215
parser_list.add_argument(
216216
'package_class', metavar='[package_name|:package_type:]',
217-
type=str, default=[':all:'], nargs='*',
217+
type=str, default=[':all-or-nothing:'], nargs='*',
218218
help=('package name or designator for all packages of a given type '
219219
'(one of :all:, :standard:, :optional:, and :experimental:); '
220-
'default: :all:'))
220+
'default: :all: (or nothing when --include-dependencies or --exclude-dependencies is given'))
221221
parser_list.add_argument(
222222
'--has-file', action='append', default=[], metavar='FILENAME', dest='has_files',
223223
help=('only include packages that have this file in their metadata directory '
@@ -227,8 +227,15 @@ def make_parser():
227227
help=('only include packages that do not have this file in their metadata directory '
228228
'(examples: huge, patches, huge|has_nonfree_dependencies)'))
229229
parser_list.add_argument(
230-
'--exclude', action='append', default=[], metavar='PACKAGE_NAME',
230+
'--exclude', nargs='*', action='append', default=[], metavar='PACKAGE_NAME',
231231
help='exclude package from list')
232+
parser_list.add_argument(
233+
'--include-dependencies', action='store_true',
234+
help='include (ordinary) dependencies of the packages recursively')
235+
parser_list.add_argument(
236+
'--exclude-dependencies', action='store_true',
237+
help='exclude (ordinary) dependencies of the packages recursively')
238+
232239
parser_name = subparsers.add_parser(
233240
'name', epilog=epilog_name,
234241
formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -353,7 +360,16 @@ def run():
353360
if args.subcommand == 'config':
354361
app.config()
355362
elif args.subcommand == 'list':
356-
app.list_cls(*args.package_class, has_files=args.has_files, no_files=args.no_files, exclude=args.exclude)
363+
if args.package_class == [':all-or-nothing:']:
364+
if args.include_dependencies or args.exclude_dependencies:
365+
args.package_class = []
366+
else:
367+
args.package_class = [':all:']
368+
app.list_cls(*args.package_class,
369+
has_files=args.has_files, no_files=args.no_files,
370+
exclude=args.exclude,
371+
include_dependencies=args.include_dependencies,
372+
exclude_dependencies=args.exclude_dependencies)
357373
elif args.subcommand == 'name':
358374
app.name(args.tarball_filename)
359375
elif args.subcommand == 'tarball':

build/sage_bootstrap/expand_class.py

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,19 @@
2121

2222
class PackageClass(object):
2323

24-
def __init__(self, *package_names_or_classes, **filters):
25-
self.names = []
24+
def __init__(self, *package_names_or_classes, exclude=(),
25+
include_dependencies=False, exclude_dependencies=False,
26+
**filters):
27+
self.__names = set()
2628
filenames = filters.pop('has_files', [])
2729
no_filenames = filters.pop('no_files', [])
28-
excluded = filters.pop('exclude', [])
30+
excluded = []
31+
for package_names in exclude:
32+
excluded.extend(package_names)
2933
if filters:
3034
raise ValueError('filter not supported')
3135

3236
def included_in_filter(pkg):
33-
if pkg.name in excluded:
34-
return False
3537
if not all(any(pkg.has_file(filename)
3638
for filename in filename_disjunction.split('|'))
3739
for filename_disjunction in filenames):
@@ -55,19 +57,58 @@ def included_in_filter(pkg):
5557
'which must be one of '
5658
':all:, :standard:, :optional:, or :experimental:'
5759
'got {}'.format(package_name_or_class))
58-
self.names.append(package_name_or_class)
60+
self.__names.add(package_name_or_class)
61+
62+
def include_recursive_dependencies(names, package_name):
63+
if package_name in names:
64+
return
65+
try:
66+
pkg = Package(package_name)
67+
except FileNotFoundError:
68+
# Silently ignore unknown packages,
69+
# substitutions such as $(BLAS) $(PYTHON),
70+
# and optional dependencies of the form $(find-string ...).
71+
return
72+
names.add(package_name)
73+
for dependency in pkg.dependencies:
74+
include_recursive_dependencies(names, dependency)
75+
76+
if include_dependencies:
77+
package_names = set()
78+
for name in self.__names:
79+
include_recursive_dependencies(package_names, name)
80+
self.__names = package_names
81+
82+
def exclude_recursive_dependencies(names, package_name):
83+
try:
84+
pkg = Package(package_name)
85+
except FileNotFoundError:
86+
return
87+
for dependency in pkg.dependencies:
88+
names.discard(dependency)
89+
exclude_recursive_dependencies(names, dependency)
90+
91+
if exclude_dependencies:
92+
for name in list(self.__names):
93+
exclude_recursive_dependencies(self.__names, name)
94+
95+
self.__names.difference_update(excluded)
96+
97+
@property
98+
def names(self):
99+
return sorted(self.__names)
59100

60101
def _init_all(self, predicate):
61-
self.names.extend(pkg.name for pkg in Package.all() if predicate(pkg))
102+
self.__names.update(pkg.name for pkg in Package.all() if predicate(pkg))
62103

63104
def _init_standard(self, predicate):
64-
self.names.extend(pkg.name for pkg in Package.all() if pkg.type == 'standard' and predicate(pkg))
105+
self.__names.update(pkg.name for pkg in Package.all() if pkg.type == 'standard' and predicate(pkg))
65106

66107
def _init_optional(self, predicate):
67-
self.names.extend(pkg.name for pkg in Package.all() if pkg.type == 'optional' and predicate(pkg))
108+
self.__names.update(pkg.name for pkg in Package.all() if pkg.type == 'optional' and predicate(pkg))
68109

69110
def _init_experimental(self, predicate):
70-
self.names.extend(pkg.name for pkg in Package.all() if pkg.type == 'experimental' and predicate(pkg))
111+
self.__names.update(pkg.name for pkg in Package.all() if pkg.type == 'experimental' and predicate(pkg))
71112

72113
def apply(self, function, *args, **kwds):
73114
for package_name in self.names:

build/sage_bootstrap/package.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def __init__(self, package_name):
4747
self._init_version()
4848
self._init_type()
4949
self._init_install_requires()
50+
self._init_dependencies()
5051

5152
def __repr__(self):
5253
return 'Package {0}'.format(self.name)
@@ -245,6 +246,28 @@ def distribution_name(self):
245246
return part
246247
return None
247248

249+
@property
250+
def dependencies(self):
251+
"""
252+
Return a list of strings, the package names of the (ordinary) dependencies
253+
"""
254+
# after a '|', we have order-only dependencies
255+
return self.__dependencies.partition('|')[0].strip().split()
256+
257+
@property
258+
def dependencies_order_only(self):
259+
"""
260+
Return a list of strings, the package names of the order-only dependencies
261+
"""
262+
return self.__dependencies.partition('|')[2].strip().split() + self.__dependencies_order_only.strip().split()
263+
264+
@property
265+
def dependencies_check(self):
266+
"""
267+
Return a list of strings, the package names of the check dependencies
268+
"""
269+
return self.__dependencies_order_only.strip().split()
270+
248271
def __eq__(self, other):
249272
return self.tarball == other.tarball
250273

@@ -335,3 +358,20 @@ def _init_install_requires(self):
335358
self.__install_requires = f.read().strip()
336359
except IOError:
337360
self.__install_requires = None
361+
362+
def _init_dependencies(self):
363+
try:
364+
with open(os.path.join(self.path, 'dependencies')) as f:
365+
self.__dependencies = f.readline().strip()
366+
except IOError:
367+
self.__dependencies = ''
368+
try:
369+
with open(os.path.join(self.path, 'dependencies_check')) as f:
370+
self.__dependencies_check = f.readline().strip()
371+
except IOError:
372+
self.__dependencies_check = ''
373+
try:
374+
with open(os.path.join(self.path, 'dependencies_order_only')) as f:
375+
self.__dependencies_order_only = f.readline()
376+
except IOError:
377+
self.__dependencies_order_only = ''

src/doc/bootstrap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ Sage depends. It installs them automatically if it does not find
100100
equivalent system packages.
101101
102102
EOF
103-
for PKG_BASE in $(sage-package list --has-file SPKG.rst :standard: | grep -v '^sagemath_' | sort); do
103+
for PKG_BASE in $(sage-package list --has-file SPKG.rst :standard: | grep -v '^sagemath_'); do
104104
echo "* :ref:\`spkg_$PKG_BASE\`"
105105
done >> "$OUTPUT_INDEX"
106106
cat >> "$OUTPUT_INDEX" <<EOF
@@ -112,7 +112,7 @@ For additional functionality, you can install some of the following
112112
optional packages.
113113
114114
EOF
115-
for PKG_BASE in $(sage-package list --has-file SPKG.rst :optional: | grep -v '^sagemath_' | sort); do
115+
for PKG_BASE in $(sage-package list --has-file SPKG.rst :optional: | grep -v '^sagemath_'); do
116116
echo "* :ref:\`spkg_$PKG_BASE\`"
117117
done >> "$OUTPUT_INDEX"
118118
cat >> "$OUTPUT_INDEX" <<EOF
@@ -160,7 +160,7 @@ Distribution Packages of the Sage Library
160160
-----------------------------------------
161161
162162
EOF
163-
for PKG_BASE in $(sage-package list --has-file SPKG.rst | grep '^sagemath_' | sort); do
163+
for PKG_BASE in $(sage-package list --has-file SPKG.rst | grep '^sagemath_'); do
164164
echo "* :ref:\`spkg_$PKG_BASE\`"
165165
done >> "$OUTPUT_INDEX"
166166
cat >> "$OUTPUT_INDEX" <<EOF
@@ -173,7 +173,7 @@ Some packages that provide additional functionality are marked as
173173
integration of these packages into the Sage distribution.
174174
175175
EOF
176-
for PKG_BASE in $(sage-package list --has-file SPKG.rst :experimental: | grep -v '^sagemath_' | sort); do
176+
for PKG_BASE in $(sage-package list --has-file SPKG.rst :experimental: | grep -v '^sagemath_'); do
177177
echo "* :ref:\`spkg_$PKG_BASE\`"
178178
done >> "$OUTPUT_INDEX"
179179

@@ -203,7 +203,7 @@ Packages are in alphabetical order.
203203
:maxdepth: 1
204204
205205
EOF
206-
for PKG_BASE in $(sage-package list --has-file SPKG.rst | sort); do
206+
for PKG_BASE in $(sage-package list --has-file SPKG.rst); do
207207
PKG_SCRIPTS=build/pkgs/$PKG_BASE
208208
# Instead of just copying, we may want to call
209209
# a version of sage-spkg-info to format extra information.

0 commit comments

Comments
 (0)