commands: add --update option to spack list
- Add a `--update FILE` option to `spack list` - Output is written to the file only if any package is newer than the file - Simplify the code in docs/conf.py using this new option
This commit is contained in:
parent
6380f1917a
commit
3340d586c4
5 changed files with 171 additions and 96 deletions
|
@ -54,8 +54,8 @@
|
|||
os.environ['COLUMNS'] = '120'
|
||||
|
||||
# Generate full package list if needed
|
||||
subprocess.Popen(
|
||||
['spack', 'list', '--format=html', '--update=package_list.html'])
|
||||
subprocess.call([
|
||||
'spack', 'list', '--format=html', '--update=package_list.html'])
|
||||
|
||||
# Generate a command index if an update is needed
|
||||
subprocess.call([
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import argparse
|
||||
import cgi
|
||||
import fnmatch
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import math
|
||||
|
@ -46,6 +47,9 @@ def setup_parser(subparser):
|
|||
subparser.add_argument(
|
||||
'--format', default='name_only', choices=formatters,
|
||||
help='format to be used to print the output [default: name_only]')
|
||||
subparser.add_argument(
|
||||
'--update', metavar='FILE', default=None, action='store',
|
||||
help='write output to the specified file, if any package is newer')
|
||||
|
||||
arguments.add_common_arguments(subparser, ['tags'])
|
||||
|
||||
|
@ -90,11 +94,11 @@ def match(p, f):
|
|||
|
||||
|
||||
@formatter
|
||||
def name_only(pkgs):
|
||||
def name_only(pkgs, out):
|
||||
indent = 0
|
||||
if sys.stdout.isatty():
|
||||
if out.isatty():
|
||||
tty.msg("%d packages." % len(pkgs))
|
||||
colify(pkgs, indent=indent)
|
||||
colify(pkgs, indent=indent, output=out)
|
||||
|
||||
|
||||
def github_url(pkg):
|
||||
|
@ -123,64 +127,69 @@ def rows_for_ncols(elts, ncols):
|
|||
|
||||
|
||||
@formatter
|
||||
def rst(pkg_names):
|
||||
def rst(pkg_names, out):
|
||||
"""Print out information on all packages in restructured text."""
|
||||
|
||||
pkgs = [spack.repo.get(name) for name in pkg_names]
|
||||
|
||||
print('.. _package-list:')
|
||||
print()
|
||||
print('============')
|
||||
print('Package List')
|
||||
print('============')
|
||||
print()
|
||||
print('This is a list of things you can install using Spack. It is')
|
||||
print('automatically generated based on the packages in the latest Spack')
|
||||
print('release.')
|
||||
print()
|
||||
print('Spack currently has %d mainline packages:' % len(pkgs))
|
||||
print()
|
||||
print(rst_table('`%s`_' % p for p in pkg_names))
|
||||
print()
|
||||
out.write('.. _package-list:\n')
|
||||
out.write('\n')
|
||||
out.write('============\n')
|
||||
out.write('Package List\n')
|
||||
out.write('============\n')
|
||||
out.write('\n')
|
||||
out.write('This is a list of things you can install using Spack. It is\n')
|
||||
out.write(
|
||||
'automatically generated based on the packages in the latest Spack\n')
|
||||
out.write('release.\n')
|
||||
out.write('\n')
|
||||
out.write('Spack currently has %d mainline packages:\n' % len(pkgs))
|
||||
out.write('\n')
|
||||
out.write(rst_table('`%s`_' % p for p in pkg_names))
|
||||
out.write('\n')
|
||||
out.write('\n')
|
||||
|
||||
# Output some text for each package.
|
||||
for pkg in pkgs:
|
||||
print('-----')
|
||||
print()
|
||||
print('.. _%s:' % pkg.name)
|
||||
print()
|
||||
out.write('-----\n')
|
||||
out.write('\n')
|
||||
out.write('.. _%s:\n' % pkg.name)
|
||||
out.write('\n')
|
||||
# Must be at least 2 long, breaks for single letter packages like R.
|
||||
print('-' * max(len(pkg.name), 2))
|
||||
print(pkg.name)
|
||||
print('-' * max(len(pkg.name), 2))
|
||||
print()
|
||||
print('Homepage:')
|
||||
print(' * `%s <%s>`__' % (cgi.escape(pkg.homepage), pkg.homepage))
|
||||
print()
|
||||
print('Spack package:')
|
||||
print(' * `%s/package.py <%s>`__' % (pkg.name, github_url(pkg)))
|
||||
print()
|
||||
out.write('-' * max(len(pkg.name), 2))
|
||||
out.write('\n')
|
||||
out.write(pkg.name)
|
||||
out.write('\n')
|
||||
out.write('-' * max(len(pkg.name), 2))
|
||||
out.write('\n\n')
|
||||
out.write('Homepage:\n')
|
||||
out.write(
|
||||
' * `%s <%s>`__\n' % (cgi.escape(pkg.homepage), pkg.homepage))
|
||||
out.write('\n')
|
||||
out.write('Spack package:\n')
|
||||
out.write(' * `%s/package.py <%s>`__\n' % (pkg.name, github_url(pkg)))
|
||||
out.write('\n')
|
||||
if pkg.versions:
|
||||
print('Versions:')
|
||||
print(' ' + ', '.join(str(v) for v in
|
||||
reversed(sorted(pkg.versions))))
|
||||
print()
|
||||
out.write('Versions:\n')
|
||||
out.write(' ' + ', '.join(str(v) for v in
|
||||
reversed(sorted(pkg.versions))))
|
||||
out.write('\n\n')
|
||||
|
||||
for deptype in spack.dependency.all_deptypes:
|
||||
deps = pkg.dependencies_of_type(deptype)
|
||||
if deps:
|
||||
print('%s Dependencies' % deptype.capitalize())
|
||||
print(' ' + ', '.join('%s_' % d if d in pkg_names
|
||||
else d for d in deps))
|
||||
print()
|
||||
out.write('%s Dependencies\n' % deptype.capitalize())
|
||||
out.write(' ' + ', '.join('%s_' % d if d in pkg_names
|
||||
else d for d in deps))
|
||||
out.write('\n\n')
|
||||
|
||||
print('Description:')
|
||||
print(pkg.format_doc(indent=2))
|
||||
print()
|
||||
out.write('Description:\n')
|
||||
out.write(pkg.format_doc(indent=2))
|
||||
out.write('\n\n')
|
||||
|
||||
|
||||
@formatter
|
||||
def html(pkg_names):
|
||||
def html(pkg_names, out):
|
||||
"""Print out information on all packages in Sphinx HTML.
|
||||
|
||||
This is intended to be inlined directly into Sphinx documentation.
|
||||
|
@ -199,83 +208,90 @@ def html(pkg_names):
|
|||
def head(n, span_id, title, anchor=None):
|
||||
if anchor is None:
|
||||
anchor = title
|
||||
print(('<span id="id%d"></span>'
|
||||
'<h1>%s<a class="headerlink" href="#%s" '
|
||||
'title="Permalink to this headline">¶</a>'
|
||||
'</h1>') % (span_id, title, anchor))
|
||||
out.write(('<span id="id%d"></span>'
|
||||
'<h1>%s<a class="headerlink" href="#%s" '
|
||||
'title="Permalink to this headline">¶</a>'
|
||||
'</h1>\n') % (span_id, title, anchor))
|
||||
|
||||
# Start with the number of packages, skipping the title and intro
|
||||
# blurb, which we maintain in the RST file.
|
||||
print('<p>')
|
||||
print('Spack currently has %d mainline packages:' % len(pkgs))
|
||||
print('</p>')
|
||||
out.write('<p>\n')
|
||||
out.write('Spack currently has %d mainline packages:\n' % len(pkgs))
|
||||
out.write('</p>\n')
|
||||
|
||||
# Table of links to all packages
|
||||
print('<table border="1" class="docutils">')
|
||||
print('<tbody valign="top">')
|
||||
out.write('<table border="1" class="docutils">\n')
|
||||
out.write('<tbody valign="top">\n')
|
||||
for i, row in enumerate(rows_for_ncols(pkg_names, 3)):
|
||||
print('<tr class="row-odd">' if i % 2 == 0 else
|
||||
'<tr class="row-even">')
|
||||
out.write('<tr class="row-odd">\n' if i % 2 == 0 else
|
||||
'<tr class="row-even">\n')
|
||||
for name in row:
|
||||
print('<td>')
|
||||
print('<a class="reference internal" href="#%s">%s</a></td>'
|
||||
% (name, name))
|
||||
print('</td>')
|
||||
print('</tr>')
|
||||
print('</tbody>')
|
||||
print('</table>')
|
||||
print('<hr class="docutils"/>')
|
||||
out.write('<td>\n')
|
||||
out.write('<a class="reference internal" href="#%s">%s</a></td>\n'
|
||||
% (name, name))
|
||||
out.write('</td>\n')
|
||||
out.write('</tr>\n')
|
||||
out.write('</tbody>\n')
|
||||
out.write('</table>\n')
|
||||
out.write('<hr class="docutils"/>\n')
|
||||
|
||||
# Output some text for each package.
|
||||
for pkg in pkgs:
|
||||
print('<div class="section" id="%s">' % pkg.name)
|
||||
out.write('<div class="section" id="%s">\n' % pkg.name)
|
||||
head(2, span_id, pkg.name)
|
||||
span_id += 1
|
||||
|
||||
print('<dl class="docutils">')
|
||||
out.write('<dl class="docutils">\n')
|
||||
|
||||
print('<dt>Homepage:</dt>')
|
||||
print('<dd><ul class="first last simple">')
|
||||
print(('<li>'
|
||||
'<a class="reference external" href="%s">%s</a>'
|
||||
'</li>') % (pkg.homepage, cgi.escape(pkg.homepage)))
|
||||
print('</ul></dd>')
|
||||
out.write('<dt>Homepage:</dt>\n')
|
||||
out.write('<dd><ul class="first last simple">\n')
|
||||
out.write(('<li>'
|
||||
'<a class="reference external" href="%s">%s</a>'
|
||||
'</li>\n') % (pkg.homepage, cgi.escape(pkg.homepage)))
|
||||
out.write('</ul></dd>\n')
|
||||
|
||||
print('<dt>Spack package:</dt>')
|
||||
print('<dd><ul class="first last simple">')
|
||||
print(('<li>'
|
||||
'<a class="reference external" href="%s">%s/package.py</a>'
|
||||
'</li>') % (github_url(pkg), pkg.name))
|
||||
print('</ul></dd>')
|
||||
out.write('<dt>Spack package:</dt>\n')
|
||||
out.write('<dd><ul class="first last simple">\n')
|
||||
out.write(('<li>'
|
||||
'<a class="reference external" href="%s">%s/package.py</a>'
|
||||
'</li>\n') % (github_url(pkg), pkg.name))
|
||||
out.write('</ul></dd>\n')
|
||||
|
||||
if pkg.versions:
|
||||
print('<dt>Versions:</dt>')
|
||||
print('<dd>')
|
||||
print(', '.join(str(v) for v in reversed(sorted(pkg.versions))))
|
||||
print('</dd>')
|
||||
out.write('<dt>Versions:</dt>\n')
|
||||
out.write('<dd>\n')
|
||||
out.write(', '.join(
|
||||
str(v) for v in reversed(sorted(pkg.versions))))
|
||||
out.write('\n')
|
||||
out.write('</dd>\n')
|
||||
|
||||
for deptype in spack.dependency.all_deptypes:
|
||||
deps = pkg.dependencies_of_type(deptype)
|
||||
if deps:
|
||||
print('<dt>%s Dependencies:</dt>' % deptype.capitalize())
|
||||
print('<dd>')
|
||||
print(', '.join(
|
||||
out.write('<dt>%s Dependencies:</dt>\n' % deptype.capitalize())
|
||||
out.write('<dd>\n')
|
||||
out.write(', '.join(
|
||||
d if d not in pkg_names else
|
||||
'<a class="reference internal" href="#%s">%s</a>' % (d, d)
|
||||
for d in deps))
|
||||
print('</dd>')
|
||||
out.write('\n')
|
||||
out.write('</dd>\n')
|
||||
|
||||
print('<dt>Description:</dt>')
|
||||
print('<dd>')
|
||||
print(cgi.escape(pkg.format_doc(indent=2)))
|
||||
print('</dd>')
|
||||
print('</dl>')
|
||||
out.write('<dt>Description:</dt>\n')
|
||||
out.write('<dd>\n')
|
||||
out.write(cgi.escape(pkg.format_doc(indent=2)))
|
||||
out.write('\n')
|
||||
out.write('</dd>\n')
|
||||
out.write('</dl>\n')
|
||||
|
||||
print('<hr class="docutils"/>')
|
||||
print('</div>')
|
||||
out.write('<hr class="docutils"/>\n')
|
||||
out.write('</div>\n')
|
||||
|
||||
|
||||
def list(parser, args):
|
||||
# retrieve the formatter to use from args
|
||||
formatter = formatters[args.format]
|
||||
|
||||
# Retrieve the names of all the packages
|
||||
pkgs = set(spack.repo.all_package_names())
|
||||
# Filter the set appropriately
|
||||
|
@ -288,5 +304,17 @@ def list(parser, args):
|
|||
sorted_packages = set(sorted_packages) & packages_with_tags
|
||||
sorted_packages = sorted(sorted_packages)
|
||||
|
||||
# Print to stdout
|
||||
formatters[args.format](sorted_packages)
|
||||
if args.update:
|
||||
# change output stream if user asked for update
|
||||
if os.path.exists(args.update):
|
||||
if os.path.getmtime(args.update) > spack.repo.path.last_mtime():
|
||||
tty.msg('File is up to date: %s' % args.update)
|
||||
return
|
||||
|
||||
tty.msg('Updating file: %s' % args.update)
|
||||
with open(args.update, 'w') as f:
|
||||
formatter(sorted_packages, f)
|
||||
|
||||
else:
|
||||
# Print to stdout
|
||||
formatter(sorted_packages, sys.stdout)
|
||||
|
|
|
@ -183,6 +183,10 @@ def _create_new_cache(self):
|
|||
|
||||
return cache
|
||||
|
||||
def last_mtime(self):
|
||||
return max(
|
||||
sinfo.st_mtime for sinfo in self._packages_to_stats.values())
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self._packages_to_stats[item]
|
||||
|
||||
|
@ -607,6 +611,10 @@ def load_module(self, fullname):
|
|||
sys.modules[fullname] = module
|
||||
return module
|
||||
|
||||
def last_mtime(self):
|
||||
"""Time a package file in this repo was last updated."""
|
||||
return max(repo.last_mtime() for repo in self.repos)
|
||||
|
||||
def repo_for_pkg(self, spec):
|
||||
"""Given a spec, get the repository for its package."""
|
||||
# We don't @_autospec this function b/c it's called very frequently
|
||||
|
@ -1018,6 +1026,10 @@ def exists(self, pkg_name):
|
|||
"""Whether a package with the supplied name exists."""
|
||||
return pkg_name in self._pkg_checker
|
||||
|
||||
def last_mtime(self):
|
||||
"""Time a package file in this repo was last updated."""
|
||||
return self._pkg_checker.last_mtime()
|
||||
|
||||
def is_virtual(self, pkg_name):
|
||||
"""True if the package with this name is virtual, False otherwise."""
|
||||
return self.provider_index.contains(pkg_name)
|
||||
|
|
|
@ -59,3 +59,30 @@ def test_list_format_html():
|
|||
|
||||
assert '<div class="section" id="hdf5">' in output
|
||||
assert '<h1>hdf5' in output
|
||||
|
||||
|
||||
def test_list_update(tmpdir):
|
||||
update_file = tmpdir.join('output')
|
||||
|
||||
# not yet created when list is run
|
||||
list('--update', str(update_file))
|
||||
assert update_file.exists()
|
||||
with update_file.open() as f:
|
||||
assert f.read()
|
||||
|
||||
# created but older than any package
|
||||
with update_file.open('w') as f:
|
||||
f.write('empty\n')
|
||||
update_file.setmtime(0)
|
||||
list('--update', str(update_file))
|
||||
assert update_file.exists()
|
||||
with update_file.open() as f:
|
||||
assert f.read() != 'empty\n'
|
||||
|
||||
# newer than any packages
|
||||
with update_file.open('w') as f:
|
||||
f.write('empty\n')
|
||||
list('--update', str(update_file))
|
||||
assert update_file.exists()
|
||||
with update_file.open() as f:
|
||||
assert f.read() == 'empty\n'
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
import spack.repo
|
||||
|
@ -56,3 +57,10 @@ def test_repo_pkg_with_unknown_namespace(repo_for_test):
|
|||
def test_repo_unknown_pkg(repo_for_test):
|
||||
with pytest.raises(spack.repo.UnknownPackageError):
|
||||
repo_for_test.get('builtin.mock.nonexistentpackage')
|
||||
|
||||
|
||||
@pytest.mark.maybeslow
|
||||
def test_repo_last_mtime():
|
||||
latest_mtime = max(os.path.getmtime(p.module.__file__)
|
||||
for p in spack.repo.path.all_packages())
|
||||
assert spack.repo.path.last_mtime() == latest_mtime
|
||||
|
|
Loading…
Reference in a new issue