summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2019-07-24 00:17:06 -0700
committerTodd Gamblin <tgamblin@llnl.gov>2019-07-24 14:10:08 -0700
commitb0abbfecb8595c8901855e679be0bbea64089fe8 (patch)
tree6331725ece5e582b58bf4bed2168d894ae3eb6ee
parent7411347a2958167f61682b24c5a314e4645ac821 (diff)
downloadspack-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
-rw-r--r--lib/spack/spack/cmd/maintainers.py134
-rw-r--r--lib/spack/spack/test/cmd/maintainers.py117
-rwxr-xr-xshare/spack/spack-completion.bash10
-rw-r--r--var/spack/repos/builtin.mock/packages/maintainers-1/package.py20
-rw-r--r--var/spack/repos/builtin.mock/packages/maintainers-2/package.py20
5 files changed, 301 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']
diff --git a/share/spack/spack-completion.bash b/share/spack/spack-completion.bash
index 362fe1e0bf..1edc262d0a 100755
--- a/share/spack/spack-completion.bash
+++ b/share/spack/spack-completion.bash
@@ -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
diff --git a/var/spack/repos/builtin.mock/packages/maintainers-1/package.py b/var/spack/repos/builtin.mock/packages/maintainers-1/package.py
new file mode 100644
index 0000000000..75917da005
--- /dev/null
+++ b/var/spack/repos/builtin.mock/packages/maintainers-1/package.py
@@ -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
diff --git a/var/spack/repos/builtin.mock/packages/maintainers-2/package.py b/var/spack/repos/builtin.mock/packages/maintainers-2/package.py
new file mode 100644
index 0000000000..a198bde1e3
--- /dev/null
+++ b/var/spack/repos/builtin.mock/packages/maintainers-2/package.py
@@ -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