Fixes for various hash issues (#2626)
* Better output for disambiguate_specs() * Fix wrong exception name. * Fix satsifies(): concrete specs require matching by hash. - Fixes uninstall by hash and other places where we need to match a specific spec. - Fix an error in provider_index (satisfies() call was backwards) - Fix an error in satisfies_dependencies(): checks were too shallow. * Fix default args in Spec.tree() * Move installed_dependents() to DB to avoid unknown package error. * Make `spack find` and `sapck.store.db.query()` faster for hashes. * Add a test to ensure satisfies() respects concrete Specs' hashes.
This commit is contained in:
parent
08d323b1f8
commit
a2b4146e10
10 changed files with 83 additions and 58 deletions
|
@ -144,7 +144,9 @@ def disambiguate_spec(spec):
|
||||||
elif len(matching_specs) > 1:
|
elif len(matching_specs) > 1:
|
||||||
args = ["%s matches multiple packages." % spec,
|
args = ["%s matches multiple packages." % spec,
|
||||||
"Matching packages:"]
|
"Matching packages:"]
|
||||||
args += [" " + str(s) for s in matching_specs]
|
color = sys.stdout.isatty()
|
||||||
|
args += [colorize(" @K{%s} " % s.dag_hash(7), color=color) +
|
||||||
|
s.format('$_$@$%@$=', color=color) for s in matching_specs]
|
||||||
args += ["Use a more specific spec."]
|
args += ["Use a more specific spec."]
|
||||||
tty.die(*args)
|
tty.die(*args)
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
import spack.cmd
|
||||||
import spack.store
|
import spack.store
|
||||||
import spack.modules
|
import spack.modules
|
||||||
from spack.util.pattern import Args
|
from spack.util.pattern import Args
|
||||||
|
@ -59,11 +60,18 @@ def __call__(self, parser, namespace, values, option_string=None):
|
||||||
namespace.specs = self._specs
|
namespace.specs = self._specs
|
||||||
|
|
||||||
def _specs(self, **kwargs):
|
def _specs(self, **kwargs):
|
||||||
specs = [s for s in spack.store.db.query(**kwargs)]
|
qspecs = spack.cmd.parse_specs(self.values)
|
||||||
values = ' '.join(self.values)
|
|
||||||
if values:
|
# return everything for an empty query.
|
||||||
specs = [x for x in specs if x.satisfies(values, strict=True)]
|
if not qspecs:
|
||||||
return specs
|
return spack.store.db.query()
|
||||||
|
|
||||||
|
# Return only matching stuff otherwise.
|
||||||
|
specs = set()
|
||||||
|
for spec in qspecs:
|
||||||
|
for s in spack.store.db.query(spec, **kwargs):
|
||||||
|
specs.add(s)
|
||||||
|
return sorted(specs)
|
||||||
|
|
||||||
|
|
||||||
_arguments['constraint'] = Args(
|
_arguments['constraint'] = Args(
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
|
||||||
import spack
|
import spack
|
||||||
|
import spack.store
|
||||||
import spack.cmd
|
import spack.cmd
|
||||||
|
|
||||||
description = "Show installed packages that depend on another."
|
description = "Show installed packages that depend on another."
|
||||||
|
@ -39,11 +40,14 @@ def setup_parser(subparser):
|
||||||
|
|
||||||
|
|
||||||
def dependents(parser, args):
|
def dependents(parser, args):
|
||||||
specs = spack.cmd.parse_specs(args.spec, concretize=True)
|
specs = spack.cmd.parse_specs(args.spec)
|
||||||
if len(specs) != 1:
|
if len(specs) != 1:
|
||||||
tty.die("spack dependents takes only one spec.")
|
tty.die("spack dependents takes only one spec.")
|
||||||
|
spec = spack.cmd.disambiguate_spec(specs[0])
|
||||||
|
|
||||||
fmt = '$_$@$%@$+$=$#'
|
tty.msg("Dependents of %s" % spec.format('$_$@$%@$#', color=True))
|
||||||
deps = [d.format(fmt, color=True)
|
deps = spack.store.db.installed_dependents(spec)
|
||||||
for d in specs[0].package.installed_dependents]
|
if deps:
|
||||||
tty.msg("Dependents of %s" % specs[0].format(fmt, color=True), *deps)
|
spack.cmd.display_specs(deps)
|
||||||
|
else:
|
||||||
|
print "No dependents"
|
||||||
|
|
|
@ -114,14 +114,17 @@ def query_arguments(args):
|
||||||
def find(parser, args):
|
def find(parser, args):
|
||||||
q_args = query_arguments(args)
|
q_args = query_arguments(args)
|
||||||
query_specs = args.specs(**q_args)
|
query_specs = args.specs(**q_args)
|
||||||
|
|
||||||
# Exit early if no package matches the constraint
|
# Exit early if no package matches the constraint
|
||||||
if not query_specs and args.constraint:
|
if not query_specs and args.constraint:
|
||||||
msg = "No package matches the query: {0}".format(args.constraint)
|
msg = "No package matches the query: {0}".format(args.constraint)
|
||||||
tty.msg(msg)
|
tty.msg(msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Display the result
|
# Display the result
|
||||||
if sys.stdout.isatty():
|
if sys.stdout.isatty():
|
||||||
tty.msg("%d installed packages." % len(query_specs))
|
tty.msg("%d installed packages." % len(query_specs))
|
||||||
|
|
||||||
display_specs(query_specs,
|
display_specs(query_specs,
|
||||||
mode=args.mode,
|
mode=args.mode,
|
||||||
long=args.long,
|
long=args.long,
|
||||||
|
|
|
@ -124,7 +124,8 @@ def installed_dependents(specs):
|
||||||
"""
|
"""
|
||||||
dependents = {}
|
dependents = {}
|
||||||
for item in specs:
|
for item in specs:
|
||||||
lst = [x for x in item.package.installed_dependents if x not in specs]
|
lst = [x for x in spack.store.db.installed_dependents(item)
|
||||||
|
if x not in specs]
|
||||||
if lst:
|
if lst:
|
||||||
lst = list(set(lst))
|
lst = list(set(lst))
|
||||||
dependents[item] = lst
|
dependents[item] = lst
|
||||||
|
@ -152,7 +153,7 @@ def do_uninstall(specs, force):
|
||||||
# Sort packages to be uninstalled by the number of installed dependents
|
# Sort packages to be uninstalled by the number of installed dependents
|
||||||
# This ensures we do things in the right order
|
# This ensures we do things in the right order
|
||||||
def num_installed_deps(pkg):
|
def num_installed_deps(pkg):
|
||||||
return len(pkg.installed_dependents)
|
return len(spack.store.db.installed_dependents(pkg.spec))
|
||||||
|
|
||||||
packages.sort(key=num_installed_deps)
|
packages.sort(key=num_installed_deps)
|
||||||
for item in packages:
|
for item in packages:
|
||||||
|
@ -163,11 +164,14 @@ def get_uninstall_list(args):
|
||||||
specs = [any]
|
specs = [any]
|
||||||
if args.packages:
|
if args.packages:
|
||||||
specs = spack.cmd.parse_specs(args.packages)
|
specs = spack.cmd.parse_specs(args.packages)
|
||||||
|
|
||||||
# Gets the list of installed specs that match the ones give via cli
|
# Gets the list of installed specs that match the ones give via cli
|
||||||
# takes care of '-a' is given in the cli
|
# takes care of '-a' is given in the cli
|
||||||
uninstall_list = concretize_specs(specs, args.all, args.force)
|
uninstall_list = concretize_specs(specs, args.all, args.force)
|
||||||
|
|
||||||
# Takes care of '-d'
|
# Takes care of '-d'
|
||||||
dependent_list = installed_dependents(uninstall_list)
|
dependent_list = installed_dependents(uninstall_list)
|
||||||
|
|
||||||
# Process dependent_list and update uninstall_list
|
# Process dependent_list and update uninstall_list
|
||||||
has_error = False
|
has_error = False
|
||||||
if dependent_list and not args.dependents and not args.force:
|
if dependent_list and not args.dependents and not args.force:
|
||||||
|
|
|
@ -604,6 +604,15 @@ def remove(self, spec):
|
||||||
with self.write_transaction():
|
with self.write_transaction():
|
||||||
return self._remove(spec)
|
return self._remove(spec)
|
||||||
|
|
||||||
|
@_autospec
|
||||||
|
def installed_dependents(self, spec):
|
||||||
|
"""List the installed specs that depend on this one."""
|
||||||
|
dependents = set()
|
||||||
|
for spec in self.query(spec):
|
||||||
|
for dependent in spec.traverse(direction='parents', root=False):
|
||||||
|
dependents.add(dependent)
|
||||||
|
return dependents
|
||||||
|
|
||||||
@_autospec
|
@_autospec
|
||||||
def installed_extensions_for(self, extendee_spec):
|
def installed_extensions_for(self, extendee_spec):
|
||||||
"""
|
"""
|
||||||
|
@ -655,6 +664,18 @@ def query(self, query_spec=any, known=any, installed=True, explicit=any):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
with self.read_transaction():
|
with self.read_transaction():
|
||||||
|
# Just look up concrete specs with hashes; no fancy search.
|
||||||
|
if (isinstance(query_spec, spack.spec.Spec) and
|
||||||
|
query_spec._concrete):
|
||||||
|
|
||||||
|
hash_key = query_spec.dag_hash()
|
||||||
|
if hash_key in self._data:
|
||||||
|
return [self._data[hash_key].spec]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Abstract specs require more work -- currently we test
|
||||||
|
# against everything.
|
||||||
results = []
|
results = []
|
||||||
for key, rec in self._data.items():
|
for key, rec in self._data.items():
|
||||||
if installed is not any and rec.installed != installed:
|
if installed is not any and rec.installed != installed:
|
||||||
|
|
|
@ -855,24 +855,6 @@ def provides(self, vpkg_name):
|
||||||
def installed(self):
|
def installed(self):
|
||||||
return os.path.isdir(self.prefix)
|
return os.path.isdir(self.prefix)
|
||||||
|
|
||||||
@property
|
|
||||||
def installed_dependents(self):
|
|
||||||
"""Return a list of the specs of all installed packages that depend
|
|
||||||
on this one.
|
|
||||||
|
|
||||||
TODO: move this method to database.py?
|
|
||||||
"""
|
|
||||||
dependents = []
|
|
||||||
for spec in spack.store.db.query():
|
|
||||||
if self.name == spec.name:
|
|
||||||
continue
|
|
||||||
# XXX(deptype): Should build dependencies not count here?
|
|
||||||
# for dep in spec.traverse(deptype=('run')):
|
|
||||||
for dep in spec.traverse(deptype=spack.alldeps):
|
|
||||||
if self.spec == dep:
|
|
||||||
dependents.append(spec)
|
|
||||||
return dependents
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def prefix_lock(self):
|
def prefix_lock(self):
|
||||||
"""Prefix lock is a byte range lock on the nth byte of a file.
|
"""Prefix lock is a byte range lock on the nth byte of a file.
|
||||||
|
@ -1528,7 +1510,7 @@ def do_uninstall(self, force=False):
|
||||||
raise InstallError(str(self.spec) + " is not installed.")
|
raise InstallError(str(self.spec) + " is not installed.")
|
||||||
|
|
||||||
if not force:
|
if not force:
|
||||||
dependents = self.installed_dependents
|
dependents = spack.store.db.installed_dependents(self.spec)
|
||||||
if dependents:
|
if dependents:
|
||||||
raise PackageStillNeededError(self.spec, dependents)
|
raise PackageStillNeededError(self.spec, dependents)
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,8 @@ def update(self, spec):
|
||||||
for provided_spec, provider_spec in pkg.provided.iteritems():
|
for provided_spec, provider_spec in pkg.provided.iteritems():
|
||||||
# We want satisfaction other than flags
|
# We want satisfaction other than flags
|
||||||
provider_spec.compiler_flags = spec.compiler_flags.copy()
|
provider_spec.compiler_flags = spec.compiler_flags.copy()
|
||||||
if provider_spec.satisfies(spec, deps=False):
|
|
||||||
|
if spec.satisfies(provider_spec, deps=False):
|
||||||
provided_name = provided_spec.name
|
provided_name = provided_spec.name
|
||||||
|
|
||||||
provider_map = self.providers.setdefault(provided_name, {})
|
provider_map = self.providers.setdefault(provided_name, {})
|
||||||
|
|
|
@ -818,7 +818,7 @@ def get_dependency(self, name):
|
||||||
dep = self._dependencies.get(name)
|
dep = self._dependencies.get(name)
|
||||||
if dep is not None:
|
if dep is not None:
|
||||||
return dep
|
return dep
|
||||||
raise InvalidDependencyException(
|
raise InvalidDependencyError(
|
||||||
self.name + " does not depend on " + comma_or(name))
|
self.name + " does not depend on " + comma_or(name))
|
||||||
|
|
||||||
def _find_deps(self, where, deptype):
|
def _find_deps(self, where, deptype):
|
||||||
|
@ -1395,26 +1395,6 @@ def _replace_with(self, concrete):
|
||||||
dependent._add_dependency(concrete, deptypes,
|
dependent._add_dependency(concrete, deptypes,
|
||||||
dep_spec.default_deptypes)
|
dep_spec.default_deptypes)
|
||||||
|
|
||||||
def _replace_node(self, replacement):
|
|
||||||
"""Replace this spec with another.
|
|
||||||
|
|
||||||
Connects all dependents of this spec to its replacement, and
|
|
||||||
disconnects this spec from any dependencies it has. New spec
|
|
||||||
will have any dependencies the replacement had, and may need
|
|
||||||
to be normalized.
|
|
||||||
|
|
||||||
"""
|
|
||||||
for name, dep_spec in self._dependents.items():
|
|
||||||
dependent = dep_spec.spec
|
|
||||||
deptypes = dep_spec.deptypes
|
|
||||||
del dependent._dependencies[self.name]
|
|
||||||
dependent._add_dependency(
|
|
||||||
replacement, deptypes, dep_spec.default_deptypes)
|
|
||||||
|
|
||||||
for name, dep_spec in self._dependencies.items():
|
|
||||||
del dep_spec.spec.dependents[self.name]
|
|
||||||
del self._dependencies[dep.name]
|
|
||||||
|
|
||||||
def _expand_virtual_packages(self):
|
def _expand_virtual_packages(self):
|
||||||
"""Find virtual packages in this spec, replace them with providers,
|
"""Find virtual packages in this spec, replace them with providers,
|
||||||
and normalize again to include the provider's (potentially virtual)
|
and normalize again to include the provider's (potentially virtual)
|
||||||
|
@ -2043,6 +2023,10 @@ def satisfies(self, other, deps=True, strict=False):
|
||||||
"""
|
"""
|
||||||
other = self._autospec(other)
|
other = self._autospec(other)
|
||||||
|
|
||||||
|
# The only way to satisfy a concrete spec is to match its hash exactly.
|
||||||
|
if other._concrete:
|
||||||
|
return self._concrete and self.dag_hash() == other.dag_hash()
|
||||||
|
|
||||||
# A concrete provider can satisfy a virtual dependency.
|
# A concrete provider can satisfy a virtual dependency.
|
||||||
if not self.virtual and other.virtual:
|
if not self.virtual and other.virtual:
|
||||||
pkg = spack.repo.get(self.fullname)
|
pkg = spack.repo.get(self.fullname)
|
||||||
|
@ -2113,8 +2097,9 @@ def satisfies_dependencies(self, other, strict=False):
|
||||||
if other._dependencies and not self._dependencies:
|
if other._dependencies and not self._dependencies:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not all(dep in self._dependencies
|
alldeps = set(d.name for d in self.traverse(root=False))
|
||||||
for dep in other._dependencies):
|
if not all(dep.name in alldeps
|
||||||
|
for dep in other.traverse(root=False)):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif not self._dependencies or not other._dependencies:
|
elif not self._dependencies or not other._dependencies:
|
||||||
|
@ -2620,9 +2605,9 @@ def tree(self, **kwargs):
|
||||||
with indentation."""
|
with indentation."""
|
||||||
color = kwargs.pop('color', False)
|
color = kwargs.pop('color', False)
|
||||||
depth = kwargs.pop('depth', False)
|
depth = kwargs.pop('depth', False)
|
||||||
hashes = kwargs.pop('hashes', True)
|
hashes = kwargs.pop('hashes', False)
|
||||||
hlen = kwargs.pop('hashlen', None)
|
hlen = kwargs.pop('hashlen', None)
|
||||||
install_status = kwargs.pop('install_status', True)
|
install_status = kwargs.pop('install_status', False)
|
||||||
cover = kwargs.pop('cover', 'nodes')
|
cover = kwargs.pop('cover', 'nodes')
|
||||||
indent = kwargs.pop('indent', 0)
|
indent = kwargs.pop('indent', 0)
|
||||||
fmt = kwargs.pop('format', '$_$@$%@+$+$=')
|
fmt = kwargs.pop('format', '$_$@$%@+$+$=')
|
||||||
|
|
|
@ -312,6 +312,21 @@ def test_satisfies_virtual_dep_with_virtual_constraint(self):
|
||||||
Spec('netlib-lapack ^netlib-blas').satisfies(
|
Spec('netlib-lapack ^netlib-blas').satisfies(
|
||||||
'netlib-lapack ^netlib-blas'))
|
'netlib-lapack ^netlib-blas'))
|
||||||
|
|
||||||
|
def test_satisfies_same_spec_with_different_hash(self):
|
||||||
|
"""Ensure that concrete specs are matched *exactly* by hash."""
|
||||||
|
s1 = Spec('mpileaks').concretized()
|
||||||
|
s2 = s1.copy()
|
||||||
|
|
||||||
|
self.assertTrue(s1.satisfies(s2))
|
||||||
|
self.assertTrue(s2.satisfies(s1))
|
||||||
|
|
||||||
|
# Simulate specs that were installed before and after a change to
|
||||||
|
# Spack's hashing algorithm. This just reverses s2's hash.
|
||||||
|
s2._hash = s1.dag_hash()[-1::-1]
|
||||||
|
|
||||||
|
self.assertFalse(s1.satisfies(s2))
|
||||||
|
self.assertFalse(s2.satisfies(s1))
|
||||||
|
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
# Indexing specs
|
# Indexing specs
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
|
|
Loading…
Reference in a new issue