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
This commit is contained in:
		
							
								
								
									
										134
									
								
								lib/spack/spack/cmd/maintainers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								lib/spack/spack/cmd/maintainers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										117
									
								
								lib/spack/spack/test/cmd/maintainers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								lib/spack/spack/test/cmd/maintainers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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'] | ||||||
| @@ -700,6 +700,16 @@ function _spack_log_parse { | |||||||
|     fi |     fi | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function _spack_maintainers { | ||||||
|  |     if $list_options | ||||||
|  |     then | ||||||
|  |         compgen -W "-h --help -a --all --maintained --unmaintained | ||||||
|  |                     --by-user" -- "$cur" | ||||||
|  |     else | ||||||
|  |         compgen -W "$(_all_packages)" -- "$cur" | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  |  | ||||||
| function _spack_mirror { | function _spack_mirror { | ||||||
|     if $list_options |     if $list_options | ||||||
|     then |     then | ||||||
|   | |||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | # 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 spack import * | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Maintainers1(Package): | ||||||
|  |     """Package with a maintainers field.""" | ||||||
|  |  | ||||||
|  |     homepage = "http://www.example.com" | ||||||
|  |     url      = "http://www.example.com/maintainers-1.0.tar.gz" | ||||||
|  |  | ||||||
|  |     maintainers = ['user1', 'user2'] | ||||||
|  |  | ||||||
|  |     version('1.0', '0123456789abcdef0123456789abcdef') | ||||||
|  |  | ||||||
|  |     def install(self, spec, prefix): | ||||||
|  |         pass | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | # 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 spack import * | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Maintainers2(Package): | ||||||
|  |     """A second package with a maintainers field.""" | ||||||
|  |  | ||||||
|  |     homepage = "http://www.example.com" | ||||||
|  |     url      = "http://www.example.com/maintainers2-1.0.tar.gz" | ||||||
|  |  | ||||||
|  |     maintainers = ['user2', 'user3'] | ||||||
|  |  | ||||||
|  |     version('1.0', '0123456789abcdef0123456789abcdef') | ||||||
|  |  | ||||||
|  |     def install(self, spec, prefix): | ||||||
|  |         pass | ||||||
		Reference in New Issue
	
	Block a user
	 Todd Gamblin
					Todd Gamblin