diff options
author | Todd Gamblin <tgamblin@llnl.gov> | 2019-07-24 00:17:06 -0700 |
---|---|---|
committer | Todd Gamblin <tgamblin@llnl.gov> | 2019-07-24 14:10:08 -0700 |
commit | b0abbfecb8595c8901855e679be0bbea64089fe8 (patch) | |
tree | 6331725ece5e582b58bf4bed2168d894ae3eb6ee /lib | |
parent | 7411347a2958167f61682b24c5a314e4645ac821 (diff) | |
download | spack-b0abbfecb8595c8901855e679be0bbea64089fe8.tar.gz spack-b0abbfecb8595c8901855e679be0bbea64089fe8.tar.bz2 spack-b0abbfecb8595c8901855e679be0bbea64089fe8.tar.xz spack-b0abbfecb8595c8901855e679be0bbea64089fe8.zip |
new command: `spack maintainers` queries package maintainers
- We don't currently make enough use of the maintainers field on
packages, though we could use it to assign reviews.
- add a command that allows maintainers to be queried
- can ask who is maintaining a package or packages
- can ask what packages users are maintaining
- can list all maintained or unmaintained packages
- add tests for the command
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/spack/cmd/maintainers.py | 134 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/maintainers.py | 117 |
2 files changed, 251 insertions, 0 deletions
diff --git a/lib/spack/spack/cmd/maintainers.py b/lib/spack/spack/cmd/maintainers.py new file mode 100644 index 0000000000..361437ece5 --- /dev/null +++ b/lib/spack/spack/cmd/maintainers.py @@ -0,0 +1,134 @@ +# Copyright 2013-2019 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 argparse +from collections import defaultdict + +import llnl.util.tty as tty +import llnl.util.tty.color as color +from llnl.util.tty.colify import colify + + +import spack.repo + +description = "get information about package maintainers" +section = "developer" +level = "long" + + +def setup_parser(subparser): + maintained_group = subparser.add_mutually_exclusive_group() + maintained_group.add_argument( + '--maintained', action='store_true', default=False, + help='show names of maintained packages') + + maintained_group.add_argument( + '--unmaintained', action='store_true', default=False, + help='show names of unmaintained packages') + + subparser.add_argument( + '-a', '--all', action='store_true', default=False, + help='show maintainers for all packages') + + subparser.add_argument( + '--by-user', action='store_true', default=False, + help='show packages for users instead of users for packages') + + # options for commands that take package arguments + subparser.add_argument( + 'pkg_or_user', nargs=argparse.REMAINDER, + help='names of packages or users to get info for') + + +def packages_to_maintainers(package_names=None): + if not package_names: + package_names = spack.repo.path.all_package_names() + + pkg_to_users = defaultdict(lambda: set()) + for name in package_names: + cls = spack.repo.path.get_pkg_class(name) + for user in cls.maintainers: + pkg_to_users[name].add(user) + + return pkg_to_users + + +def maintainers_to_packages(users=None): + user_to_pkgs = defaultdict(lambda: []) + for name in spack.repo.path.all_package_names(): + cls = spack.repo.path.get_pkg_class(name) + for user in cls.maintainers: + lower_users = [u.lower() for u in users] + if not users or user.lower() in lower_users: + user_to_pkgs[user].append(cls.name) + + return user_to_pkgs + + +def maintained_packages(): + maintained = [] + unmaintained = [] + for name in spack.repo.path.all_package_names(): + cls = spack.repo.path.get_pkg_class(name) + if cls.maintainers: + maintained.append(name) + else: + unmaintained.append(name) + + return maintained, unmaintained + + +def union_values(dictionary): + """Given a dictionary with values that are Collections, return their union. + + Arguments: + dictionary (dict): dictionary whose values are all collections. + + Return: + (set): the union of all collections in the dictionary's values. + """ + sets = [set(p) for p in dictionary.values()] + return sorted(set.union(*sets)) if sets else set() + + +def maintainers(parser, args): + if args.maintained or args.unmaintained: + maintained, unmaintained = maintained_packages() + pkgs = maintained if args.maintained else unmaintained + colify(pkgs) + return 0 if pkgs else 1 + + if args.all: + if args.by_user: + maintainers = maintainers_to_packages(args.pkg_or_user) + for user, packages in sorted(maintainers.items()): + color.cprint('@c{%s}: %s' + % (user, ', '.join(sorted(packages)))) + return 0 if maintainers else 1 + + else: + packages = packages_to_maintainers(args.pkg_or_user) + for pkg, maintainers in sorted(packages.items()): + color.cprint('@c{%s}: %s' + % (pkg, ', '.join(sorted(maintainers)))) + return 0 if packages else 1 + + if args.by_user: + if not args.pkg_or_user: + tty.die('spack maintainers --by-user requires a user or --all') + + packages = union_values(maintainers_to_packages(args.pkg_or_user)) + colify(packages) + return 0 if packages else 1 + + else: + if not args.pkg_or_user: + tty.die('spack maintainers requires a package or --all') + + users = union_values(packages_to_maintainers(args.pkg_or_user)) + colify(users) + return 0 if users else 1 diff --git a/lib/spack/spack/test/cmd/maintainers.py b/lib/spack/spack/test/cmd/maintainers.py new file mode 100644 index 0000000000..5ddf176c39 --- /dev/null +++ b/lib/spack/spack/test/cmd/maintainers.py @@ -0,0 +1,117 @@ +# Copyright 2013-2019 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 pytest +import re + +import spack.main +import spack.repo + +maintainers = spack.main.SpackCommand('maintainers') + + +def split(output): + """Split command line output into an array.""" + output = output.strip() + return re.split(r'\s+', output) if output else [] + + +def test_maintained(mock_packages): + out = split(maintainers('--maintained')) + assert out == ['maintainers-1', 'maintainers-2'] + + +def test_unmaintained(mock_packages): + out = split(maintainers('--unmaintained')) + assert out == sorted( + set(spack.repo.all_package_names()) - + set(['maintainers-1', 'maintainers-2'])) + + +def test_all(mock_packages, capfd): + with capfd.disabled(): + out = split(maintainers('--all')) + assert out == [ + 'maintainers-1:', 'user1,', 'user2', + 'maintainers-2:', 'user2,', 'user3', + ] + + with capfd.disabled(): + out = split(maintainers('--all', 'maintainers-1')) + assert out == [ + 'maintainers-1:', 'user1,', 'user2', + ] + + +def test_all_by_user(mock_packages, capfd): + with capfd.disabled(): + out = split(maintainers('--all', '--by-user')) + assert out == [ + 'user1:', 'maintainers-1', + 'user2:', 'maintainers-1,', 'maintainers-2', + 'user3:', 'maintainers-2', + ] + + with capfd.disabled(): + out = split(maintainers('--all', '--by-user', 'user1', 'user2')) + assert out == [ + 'user1:', 'maintainers-1', + 'user2:', 'maintainers-1,', 'maintainers-2', + ] + + +def test_no_args(mock_packages): + with pytest.raises(spack.main.SpackCommandError): + maintainers() + + +def test_no_args_by_user(mock_packages): + with pytest.raises(spack.main.SpackCommandError): + maintainers('--by-user') + + +def test_mutex_args_fail(mock_packages): + with pytest.raises(SystemExit): + maintainers('--maintained', '--unmaintained') + + +def test_maintainers_list_packages(mock_packages, capfd): + with capfd.disabled(): + out = split(maintainers('maintainers-1')) + assert out == ['user1', 'user2'] + + with capfd.disabled(): + out = split(maintainers('maintainers-1', 'maintainers-2')) + assert out == ['user1', 'user2', 'user3'] + + with capfd.disabled(): + out = split(maintainers('maintainers-2')) + assert out == ['user2', 'user3'] + + +def test_maintainers_list_fails(mock_packages, capfd): + out = maintainers('a', fail_on_error=False) + assert not out + assert maintainers.returncode == 1 + + +def test_maintainers_list_by_user(mock_packages, capfd): + with capfd.disabled(): + out = split(maintainers('--by-user', 'user1')) + assert out == ['maintainers-1'] + + with capfd.disabled(): + out = split(maintainers('--by-user', 'user1', 'user2')) + assert out == ['maintainers-1', 'maintainers-2'] + + with capfd.disabled(): + out = split(maintainers('--by-user', 'user2')) + assert out == ['maintainers-1', 'maintainers-2'] + + with capfd.disabled(): + out = split(maintainers('--by-user', 'user3')) + assert out == ['maintainers-2'] |