Skip to content

Commit

Permalink
gh-37350: sage -package dependencies
Browse files Browse the repository at this point in the history
    
<!-- ^^^^^
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 -->
We add a new command `sage --package dependencies`.
Like the command `sage --package properties` introduced in #37018, it
can be used to eliminate direct references to `build/pkgs/` from various
scripts and reduce code duplication in reading SPKG metadata.

Here we only make such changes in `sage-spkg-info`.
The only user-visible change is that the listed dependencies are now
sorted alphabetically; this hides [subtle nuances that may be expressed
in the sort order of some `dependencies` files](https://github.com/sagem
ath/sage/pull/36878#issuecomment-1876426258).

- Closes #33860

Split out from:
- #36740

<!-- 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: #37350
Reported by: Matthias Köppe
Reviewer(s): Kwankyu Lee, Matthias Köppe
  • Loading branch information
Release Manager committed Feb 21, 2024
2 parents fba0587 + cce885f commit 0df697a
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 31 deletions.
34 changes: 9 additions & 25 deletions build/bin/sage-spkg-info
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,21 @@ if [ -n "$OUTPUT_RST" ]; then
issue () { echo ":issue:\`$1\`"; }
code () { echo "\`\`$*\`\`"; }
tab () { echo ".. tab:: $1"; }
FORMAT=rst
else
ref () { echo "$1"; }
spkg () { echo "$1"; }
issue () { echo "https://github.com/sagemath/sage/issues/$1"; }
code () { echo "$1"; }
tab () { echo "$1:"; }
FORMAT=plain
fi
PKG_SCRIPTS="$SAGE_ROOT/build/pkgs/$PKG_BASE"
if ! props=$(sage-package properties --format=shell $PKG_BASE 2> /dev/null); then
echo >&2 "sage-spkg-info: unknown package $PKG_BASE"
exit 1
fi
eval "$props"
eval PKG_SCRIPTS=\$path_$PKG_BASE
for ext in rst txt; do
SPKG_FILE="$PKG_SCRIPTS/SPKG.$ext"
if [ -f "$SPKG_FILE" ]; then
Expand All @@ -44,30 +51,7 @@ echo
echo "Dependencies"
echo "------------"
echo
dep=
for dep_file in dependencies dependencies_order_only; do
if [ -r "$PKG_SCRIPTS/$dep_file" ] ; then
for dep in $(sed 's/^ *//; s/ *#.*//; q' "$PKG_SCRIPTS/$dep_file"); do
case "$dep" in
# Do not use order-only syntax, too much information
\|) ;;
# Suppress dependencies on source file of the form $(SAGE_ROOT)/..., $(SAGE_SRC)/...
\$\(SAGE_*) ;;
# Suppress FORCE
FORCE) ;;
# Dependencies like $(BLAS)
\$\(*) echo "- $dep";;
# Looks like a package
*) if [ -r "$SAGE_ROOT/build/pkgs/$dep/SPKG.rst" ]; then
# This RST label is set in src/doc/bootstrap
echo "- $(spkg $dep)"
else
echo "- $dep"
fi;;
esac
done
fi
done
sage-package dependencies --format=$FORMAT $PKG_BASE
echo
echo "Version Information"
echo "-------------------"
Expand Down
2 changes: 1 addition & 1 deletion build/pkgs/giac/dependencies
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
readline libpng $(MP_LIBRARY) mpfr mpfi ntl gsl pari glpk curl cliquer ecm $(findstring libnauty,$(OPTIONAL_INSTALLED_PACKAGES))
readline libpng $(MP_LIBRARY) mpfr mpfi ntl gsl pari glpk curl cliquer ecm

----------
All lines of this file are ignored except the first.
1 change: 1 addition & 0 deletions build/pkgs/giac/dependencies_optional
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libnauty
78 changes: 76 additions & 2 deletions build/sage_bootstrap/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ def properties(self, *package_classes, **kwds):
pc = PackageClass(*package_classes)
for package_name in pc.names:
package = Package(package_name)
if format == 'plain':
print("{0}:".format(package_name))
if len(pc.names) > 1:
if format == 'plain':
print("{0}:".format(package_name))
for p in props:
value = getattr(package, p)
if value is None:
Expand All @@ -108,6 +109,79 @@ def properties(self, *package_classes, **kwds):
else:
print("{0}_{1}='{2}'".format(p, package_name, value))

def dependencies(self, *package_classes, **kwds):
"""
Find the dependencies given package names
$ sage --package dependencies maxima --runtime --order-only --format=shell
order_only_deps_maxima='info'
runtime_deps_maxima='ecl'
"""
types = kwds.pop('types', None)
format = kwds.pop('format', 'plain')
log.debug('Looking up dependencies')
pc = PackageClass(*package_classes)
if format in ['plain', 'rst']:
if types is None:
typesets = [['order_only', 'runtime']]
else:
typesets = [[t] for t in types]
elif format == 'shell':
if types is None:
types = ['order_only', 'optional', 'runtime', 'check']
typesets = [[t] for t in types]
else:
raise ValueError('format must be one of "plain", "rst", and "shell"')

for package_name in pc.names:
package = Package(package_name)
if len(pc.names) > 1:
if format == 'plain':
print("{0}:".format(package_name))
indent1 = " "
elif format == 'rst':
print("\n{0}\n{1}\n".format(package_name, "~" * len(package_name)))
indent1 = ""
else:
indent1 = ""

for typeset in typesets:
if len(typesets) > 1:
if format == 'plain':
print(indent1 + "{0}: ".format('/'.join(typeset)))
indent2 = indent1 + " "
elif format == 'rst':
print("\n" + indent1 + ".. tab:: {0}\n".format('/'.join(typeset)))
indent2 = indent1 + " "
else:
indent2 = indent1

deps = []
for t in typeset:
deps.extend(getattr(package, 'dependencies_' + t))
deps = sorted(set(deps))

if format in ['plain', 'rst']:
for dep in deps:
if '/' in dep:
# Suppress dependencies on source files, e.g. of the form $(SAGE_ROOT)/..., $(SAGE_SRC)/...
continue
if dep == 'FORCE':
# Suppress FORCE
continue
if dep.startswith('$('):
# Dependencies like $(BLAS)
print(indent2 + "- {0}".format(dep))
elif format == 'rst' and Package(dep).has_file('SPKG.rst'):
# This RST label is set in src/doc/bootstrap
print(indent2 + "- :ref:`spkg_{0}`".format(dep))
else:
print(indent2 + "- {0}".format(dep))
elif format == 'shell':
# We single-quote the values because dependencies
# may contain Makefile variable substitutions
print("{0}_deps_{1}='{2}'".format(t, package_name, ' '.join(deps)))

def name(self, tarball_filename):
"""
Find the package name given a tarball filename
Expand Down
68 changes: 68 additions & 0 deletions build/sage_bootstrap/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,36 @@
"""


epilog_dependencies = \
"""
Print the list of packages that are dependencies of given package.
By default, list a summary of the build, order-only, and runtime
dependencies.
EXAMPLE:
$ sage --package dependencies maxima openblas
maxima:
- ecl
- info
openblas:
- gfortran
$ sage --package dependencies maxima --runtime
- ecl
$ sage --package dependencies maxima openblas --runtime --order-only
maxima:
order_only:
- info
runtime:
- ecl
openblas:
order_only:
runtime:
- gfortran
"""


epilog_name = \
"""
Find the package name given a tarball filename
Expand Down Expand Up @@ -286,6 +316,31 @@ def make_parser():
'--format', type=str, default='plain',
help='output format (one of plain and shell; default: plain)')

parser_dependencies = subparsers.add_parser(
'dependencies', epilog=epilog_dependencies,
formatter_class=argparse.RawDescriptionHelpFormatter,
help='Print the list of packages that are dependencies of given packages')
parser_dependencies.add_argument(
'package_class', metavar='[package_name|:package_type:]',
type=str, nargs='+',
help=('package name or designator for all packages of a given type '
'(one of :all:, :standard:, :optional:, and :experimental:)'))
parser_dependencies.add_argument(
'--order-only', action='store_true',
help='list the order-only build dependencies')
parser_dependencies.add_argument(
'--optional', action='store_true',
help='list the optional build dependencies')
parser_dependencies.add_argument(
'--runtime', action='store_true',
help='list the runtime dependencies')
parser_dependencies.add_argument(
'--check', action='store_true',
help='list the check dependencies')
parser_dependencies.add_argument(
'--format', type=str, default='plain',
help='output format (one of plain, rst, and shell; default: plain)')

parser_name = subparsers.add_parser(
'name', epilog=epilog_name,
formatter_class=argparse.RawDescriptionHelpFormatter,
Expand Down Expand Up @@ -435,6 +490,19 @@ def run():
exclude_dependencies=args.exclude_dependencies)
elif args.subcommand == 'properties':
app.properties(*args.package_class, format=args.format)
elif args.subcommand == 'dependencies':
types = []
if args.order_only:
types.append('order_only')
if args.optional:
types.append('optional')
if args.runtime:
types.append('runtime')
if args.check:
types.append('check')
if not types:
types = None
app.dependencies(*args.package_class, types=types, format=args.format)
elif args.subcommand == 'name':
app.name(args.tarball_filename)
elif args.subcommand == 'tarball':
Expand Down
28 changes: 25 additions & 3 deletions build/sage_bootstrap/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,23 @@ def dependencies_order_only(self):
"""
return self.__dependencies.partition('|')[2].strip().split() + self.__dependencies_order_only.strip().split()

@property
def dependencies_optional(self):
"""
Return a list of strings, the package names of the optional build dependencies
"""
return self.__dependencies_optional.strip().split()

@property
def dependencies_runtime(self):
"""
Return a list of strings, the package names of the runtime dependencies
"""
# after a '|', we have order-only build dependencies
return self.__dependencies.partition('|')[0].strip().split()

dependencies = dependencies_runtime

@property
def dependencies_check(self):
"""
Expand Down Expand Up @@ -503,17 +520,22 @@ def _init_install_requires(self):
def _init_dependencies(self):
try:
with open(os.path.join(self.path, 'dependencies')) as f:
self.__dependencies = f.readline().strip()
self.__dependencies = f.readline().partition('#')[0].strip()
except IOError:
self.__dependencies = ''
try:
with open(os.path.join(self.path, 'dependencies_check')) as f:
self.__dependencies_check = f.readline().strip()
self.__dependencies_check = f.readline().partition('#')[0].strip()
except IOError:
self.__dependencies_check = ''
try:
with open(os.path.join(self.path, 'dependencies_optional')) as f:
self.__dependencies_optional = f.readline().partition('#')[0].strip()
except IOError:
self.__dependencies_optional = ''
try:
with open(os.path.join(self.path, 'dependencies_order_only')) as f:
self.__dependencies_order_only = f.readline()
self.__dependencies_order_only = f.readline().partition('#')[0].strip()
except IOError:
self.__dependencies_order_only = ''

Expand Down

0 comments on commit 0df697a

Please sign in to comment.