diff options
author | Todd Gamblin <tgamblin@llnl.gov> | 2019-05-20 14:25:03 -0700 |
---|---|---|
committer | Todd Gamblin <tgamblin@llnl.gov> | 2019-05-26 18:23:44 -0700 |
commit | 3340d586c4743f6ee95367c2ffb0056f799c0987 (patch) | |
tree | f338a173d6e2a61bcc266e2f4d0912b006225b56 /lib | |
parent | 6380f1917a840bfe9fab6bc219c6806e30b7ce2a (diff) | |
download | spack-3340d586c4743f6ee95367c2ffb0056f799c0987.tar.gz spack-3340d586c4743f6ee95367c2ffb0056f799c0987.tar.bz2 spack-3340d586c4743f6ee95367c2ffb0056f799c0987.tar.xz spack-3340d586c4743f6ee95367c2ffb0056f799c0987.zip |
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
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/docs/conf.py | 4 | ||||
-rw-r--r-- | lib/spack/spack/cmd/list.py | 216 | ||||
-rw-r--r-- | lib/spack/spack/repo.py | 12 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/list.py | 27 | ||||
-rw-r--r-- | lib/spack/spack/test/repo.py | 8 |
5 files changed, 171 insertions, 96 deletions
diff --git a/lib/spack/docs/conf.py b/lib/spack/docs/conf.py index 4af4e13035..bb460e40aa 100644 --- a/lib/spack/docs/conf.py +++ b/lib/spack/docs/conf.py @@ -54,8 +54,8 @@ os.environ['COLIFY_SIZE'] = '25x120' 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([ diff --git a/lib/spack/spack/cmd/list.py b/lib/spack/spack/cmd/list.py index c93e39ad16..bba1fc5ceb 100644 --- a/lib/spack/spack/cmd/list.py +++ b/lib/spack/spack/cmd/list.py @@ -9,6 +9,7 @@ from __future__ import division 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 filter_by_name(pkgs, args): @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) diff --git a/lib/spack/spack/repo.py b/lib/spack/spack/repo.py index c90f0f9ad4..7a2e235a4e 100644 --- a/lib/spack/spack/repo.py +++ b/lib/spack/spack/repo.py @@ -183,6 +183,10 @@ class FastPackageChecker(Mapping): 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 @@ class RepoPath(object): 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 @@ class Repo(object): """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) diff --git a/lib/spack/spack/test/cmd/list.py b/lib/spack/spack/test/cmd/list.py index 6e2f18809a..2becd2e148 100644 --- a/lib/spack/spack/test/cmd/list.py +++ b/lib/spack/spack/test/cmd/list.py @@ -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' diff --git a/lib/spack/spack/test/repo.py b/lib/spack/spack/test/repo.py index 8752caa88c..8f37bc6754 100644 --- a/lib/spack/spack/test/repo.py +++ b/lib/spack/spack/test/repo.py @@ -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 |