Source code for spack.cmd.pkg

# Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

from __future__ import print_function

import os
import re

import llnl.util.tty as tty
from llnl.util.filesystem import working_dir
from llnl.util.tty.colify import colify

import spack.cmd
import spack.cmd.common.arguments as arguments
import spack.paths
import spack.repo
from spack.util.executable import which

description = "query packages associated with particular git revisions"
section = "developer"
level = "long"


[docs]def setup_parser(subparser): sp = subparser.add_subparsers( metavar='SUBCOMMAND', dest='pkg_command') add_parser = sp.add_parser('add', help=pkg_add.__doc__) arguments.add_common_arguments(add_parser, ['packages']) list_parser = sp.add_parser('list', help=pkg_list.__doc__) list_parser.add_argument('rev', default='HEAD', nargs='?', help="revision to list packages for") diff_parser = sp.add_parser('diff', help=pkg_diff.__doc__) diff_parser.add_argument( 'rev1', nargs='?', default='HEAD^', help="revision to compare against") diff_parser.add_argument( 'rev2', nargs='?', default='HEAD', help="revision to compare to rev1 (default is HEAD)") add_parser = sp.add_parser('added', help=pkg_added.__doc__) add_parser.add_argument( 'rev1', nargs='?', default='HEAD^', help="revision to compare against") add_parser.add_argument( 'rev2', nargs='?', default='HEAD', help="revision to compare to rev1 (default is HEAD)") add_parser = sp.add_parser('changed', help=pkg_changed.__doc__) add_parser.add_argument( 'rev1', nargs='?', default='HEAD^', help="revision to compare against") add_parser.add_argument( 'rev2', nargs='?', default='HEAD', help="revision to compare to rev1 (default is HEAD)") add_parser.add_argument( '-t', '--type', action='store', default='C', help="Types of changes to show (A: added, R: removed, " "C: changed); default is 'C'") rm_parser = sp.add_parser('removed', help=pkg_removed.__doc__) rm_parser.add_argument( 'rev1', nargs='?', default='HEAD^', help="revision to compare against") rm_parser.add_argument( 'rev2', nargs='?', default='HEAD', help="revision to compare to rev1 (default is HEAD)")
[docs]def packages_path(): """Get the test repo if it is active, otherwise the builtin repo.""" try: return spack.repo.path.get_repo('builtin.mock').packages_path except spack.repo.UnknownNamespaceError: return spack.repo.path.get_repo('builtin').packages_path
[docs]class GitExe: # Wrapper around Executable for git to set working directory for all # invocations. # # Not using -C as that is not supported for git < 1.8.5. def __init__(self): self._git_cmd = which('git', required=True) def __call__(self, *args, **kwargs): with working_dir(packages_path()): return self._git_cmd(*args, **kwargs)
_git = None
[docs]def get_git(): """Get a git executable that runs *within* the packages path.""" global _git if _git is None: _git = GitExe() return _git
[docs]def list_packages(rev): git = get_git() # git ls-tree does not support ... merge-base syntax, so do it manually if rev.endswith('...'): ref = rev.replace('...', '') rev = git('merge-base', ref, 'HEAD', output=str).strip() output = git('ls-tree', '--name-only', rev, output=str) return sorted(line for line in output.split('\n') if line and not line.startswith('.'))
[docs]def pkg_add(args): """add a package to the git stage with `git add`""" git = get_git() for pkg_name in args.packages: filename = spack.repo.path.filename_for_package_name(pkg_name) if not os.path.isfile(filename): tty.die("No such package: %s. Path does not exist:" % pkg_name, filename) git('add', filename)
[docs]def pkg_list(args): """list packages associated with a particular spack git revision""" colify(list_packages(args.rev))
[docs]def diff_packages(rev1, rev2): p1 = set(list_packages(rev1)) p2 = set(list_packages(rev2)) return p1.difference(p2), p2.difference(p1)
[docs]def pkg_diff(args): """compare packages available in two different git revisions""" u1, u2 = diff_packages(args.rev1, args.rev2) if u1: print("%s:" % args.rev1) colify(sorted(u1), indent=4) if u1: print() if u2: print("%s:" % args.rev2) colify(sorted(u2), indent=4)
[docs]def pkg_removed(args): """show packages removed since a commit""" u1, u2 = diff_packages(args.rev1, args.rev2) if u1: colify(sorted(u1))
[docs]def pkg_added(args): """show packages added since a commit""" u1, u2 = diff_packages(args.rev1, args.rev2) if u2: colify(sorted(u2))
[docs]def pkg_changed(args): """show packages changed since a commit""" lower_type = args.type.lower() if not re.match('^[arc]*$', lower_type): tty.die("Invald change type: '%s'." % args.type, "Can contain only A (added), R (removed), or C (changed)") removed, added = diff_packages(args.rev1, args.rev2) git = get_git() out = git('diff', '--relative', '--name-only', args.rev1, args.rev2, output=str).strip() lines = [] if not out else re.split(r'\s+', out) changed = set() for path in lines: pkg_name, _, _ = path.partition(os.sep) if pkg_name not in added and pkg_name not in removed: changed.add(pkg_name) packages = set() if 'a' in lower_type: packages |= added if 'r' in lower_type: packages |= removed if 'c' in lower_type: packages |= changed if packages: colify(sorted(packages))
[docs]def pkg(parser, args): if not spack.cmd.spack_is_git_repo(): tty.die("This spack is not a git clone. Can't use 'spack pkg'") action = {'add': pkg_add, 'diff': pkg_diff, 'list': pkg_list, 'removed': pkg_removed, 'added': pkg_added, 'changed': pkg_changed} action[args.pkg_command](args)