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:
parent
7411347a29
commit
b0abbfecb8
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
|
Loading…
Reference in New Issue
Block a user