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 | ||||
| } | ||||
|  | ||||
| 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 { | ||||
|     if $list_options | ||||
|     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