Skip to content

Commit

Permalink
Merge pull request spack#549 from LLNL/bugfix/gh538-less-greedy-concr…
Browse files Browse the repository at this point in the history
…etize

Bugfix/gh538 less greedy concretize
  • Loading branch information
mplegendre committed Mar 15, 2016
2 parents 15bbd08 + f276127 commit 108ea15
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 120 deletions.
4 changes: 2 additions & 2 deletions lib/spack/llnl/util/lang.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,11 @@ def setter(name, value):
if not has_method(cls, '_cmp_key'):
raise TypeError("'%s' doesn't define _cmp_key()." % cls.__name__)

setter('__eq__', lambda s,o: o is not None and s._cmp_key() == o._cmp_key())
setter('__eq__', lambda s,o: (s is o) or (o is not None and s._cmp_key() == o._cmp_key()))
setter('__lt__', lambda s,o: o is not None and s._cmp_key() < o._cmp_key())
setter('__le__', lambda s,o: o is not None and s._cmp_key() <= o._cmp_key())

setter('__ne__', lambda s,o: o is None or s._cmp_key() != o._cmp_key())
setter('__ne__', lambda s,o: (s is not o) and (o is None or s._cmp_key() != o._cmp_key()))
setter('__gt__', lambda s,o: o is None or s._cmp_key() > o._cmp_key())
setter('__ge__', lambda s,o: o is None or s._cmp_key() >= o._cmp_key())

Expand Down
142 changes: 59 additions & 83 deletions lib/spack/spack/concretize.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ class DefaultConcretizer(object):
"""

def _valid_virtuals_and_externals(self, spec):
"""Returns a list of spec/external-path pairs for both virtuals and externals
that can concretize this spec."""
# Get a list of candidate packages that could satisfy this spec
packages = []
"""Returns a list of candidate virtual dep providers and external
packages that coiuld be used to concretize a spec."""
# First construct a list of concrete candidates to replace spec with.
candidates = [spec]
if spec.virtual:
providers = spack.repo.providers_for(spec)
if not providers:
Expand All @@ -64,96 +64,72 @@ def _valid_virtuals_and_externals(self, spec):
if not spec_w_preferred_providers:
spec_w_preferred_providers = spec
provider_cmp = partial(spack.pkgsort.provider_compare, spec_w_preferred_providers.name, spec.name)
packages = sorted(providers, cmp=provider_cmp)
else:
packages = [spec]

# For each candidate package, if it has externals add those to the candidates
# if it's not buildable, then only add the externals.
candidates = []
all_compilers = spack.compilers.all_compilers()
for pkg in packages:
externals = spec_externals(pkg)
buildable = is_spec_buildable(pkg)
if buildable:
candidates.append((pkg, None))
candidates = sorted(providers, cmp=provider_cmp)

# For each candidate package, if it has externals, add those to the usable list.
# if it's not buildable, then *only* add the externals.
usable = []
for cspec in candidates:
if is_spec_buildable(cspec):
usable.append(cspec)
externals = spec_externals(cspec)
for ext in externals:
if ext[0].satisfies(spec):
candidates.append(ext)
if not candidates:
if ext.satisfies(spec):
usable.append(ext)

# If nothing is in the usable list now, it's because we aren't
# allowed to build anything.
if not usable:
raise NoBuildError(spec)

def cmp_externals(a, b):
if a[0].name != b[0].name:
#We're choosing between different providers. Maintain order from above sort
if a.name != b.name:
# We're choosing between different providers, so
# maintain order from provider sort
return candidates.index(a) - candidates.index(b)
result = cmp_specs(a[0], b[0])

result = cmp_specs(a, b)
if result != 0:
return result
if not a[1] and b[1]:
return 1
if not b[1] and a[1]:
return -1
return cmp(a[1], b[1])

candidates = sorted(candidates, cmp=cmp_externals)
return candidates
# prefer external packages to internal packages.
if a.external is None or b.external is None:
return -cmp(a.external, b.external)
else:
return cmp(a.external, b.external)

usable.sort(cmp=cmp_externals)
return usable


def concretize_virtual_and_external(self, spec):
"""From a list of candidate virtual and external packages, concretize to one that
is ABI compatible with the rest of the DAG."""
def choose_virtual_or_external(self, spec):
"""Given a list of candidate virtual and external packages, try to
find one that is most ABI compatible.
"""
candidates = self._valid_virtuals_and_externals(spec)
if not candidates:
return False

# Find the nearest spec in the dag that has a compiler. We'll use that
# spec to test compiler compatibility.
other_spec = find_spec(spec, lambda(x): x.compiler)
if not other_spec:
other_spec = spec.root

# Choose an ABI-compatible candidate, or the first match otherwise.
candidate = None
if other_spec:
candidate = next((c for c in candidates if spack.abi.compatible(c[0], other_spec)), None)
if not candidate:
# Try a looser ABI matching
candidate = next((c for c in candidates if spack.abi.compatible(c[0], other_spec, loose=True)), None)
if not candidate:
# No ABI matches. Pick the top choice based on the orignal preferences.
candidate = candidates[0]
candidate_spec = candidate[0]
external = candidate[1]
changed = False

# If we're external then trim the dependencies
if external:
if (spec.dependencies):
changed = True
spec.dependencies = DependencyMap()
candidate_spec.dependencies = DependencyMap()

def fequal(candidate_field, spec_field):
return (not candidate_field) or (candidate_field == spec_field)
if (fequal(candidate_spec.name, spec.name) and
fequal(candidate_spec.versions, spec.versions) and
fequal(candidate_spec.compiler, spec.compiler) and
fequal(candidate_spec.architecture, spec.architecture) and
fequal(candidate_spec.dependencies, spec.dependencies) and
fequal(candidate_spec.variants, spec.variants) and
fequal(external, spec.external)):
return changed

# Refine this spec to the candidate.
if spec.virtual:
spec._replace_with(candidate_spec)
changed = True
if spec._dup(candidate_spec, deps=False, cleardeps=False):
changed = True
spec.external = external

return changed
return candidates

# Find the nearest spec in the dag that has a compiler. We'll
# use that spec to calibrate compiler compatibility.
abi_exemplar = find_spec(spec, lambda(x): x.compiler)
if not abi_exemplar:
abi_exemplar = spec.root

# Make a list including ABI compatibility of specs with the exemplar.
strict = [spack.abi.compatible(c, abi_exemplar) for c in candidates]
loose = [spack.abi.compatible(c, abi_exemplar, loose=True) for c in candidates]
keys = zip(strict, loose, candidates)

# Sort candidates from most to least compatibility.
# Note:
# 1. We reverse because True > False.
# 2. Sort is stable, so c's keep their order.
keys.sort(key=lambda k:k[:2], reverse=True)

# Pull the candidates back out and return them in order
candidates = [c for s,l,c in keys]
return candidates


def concretize_version(self, spec):
Expand Down Expand Up @@ -238,7 +214,7 @@ def concretize_variants(self, spec):
the default variants from the package specification.
"""
changed = False
for name, variant in spec.package.variants.items():
for name, variant in spec.package_class.variants.items():
if name not in spec.variants:
spec.variants[name] = spack.spec.VariantSpec(name, variant.default)
changed = True
Expand Down
17 changes: 10 additions & 7 deletions lib/spack/spack/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,22 +539,25 @@ def print_section(section):


def spec_externals(spec):
"""Return a list of spec, directory pairs for each external location for spec"""
"""Return a list of external specs (with external directory path filled in),
one for each known external installation."""
allpkgs = get_config('packages')
name = spec.name
spec_locations = []

external_specs = []
pkg_paths = allpkgs.get(name, {}).get('paths', None)
if not pkg_paths:
return []

for pkg,path in pkg_paths.iteritems():
if not spec.satisfies(pkg):
continue
for external_spec, path in pkg_paths.iteritems():
if not path:
# skip entries without paths (avoid creating extra Specs)
continue
spec_locations.append( (spack.spec.Spec(pkg), path) )
return spec_locations

external_spec = spack.spec.Spec(external_spec, external=path)
if external_spec.satisfies(spec):
external_specs.append(external_spec)
return external_specs


def is_spec_buildable(spec):
Expand Down
9 changes: 7 additions & 2 deletions lib/spack/spack/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,11 @@ def get(self, spec, new=False):
return self.repo_for_pkg(spec).get(spec)


def get_pkg_class(self, pkg_name):
"""Find a class for the spec's package and return the class object."""
return self.repo_for_pkg(pkg_name).get_pkg_class(pkg_name)


@_autospec
def dump_provenance(self, spec, path):
"""Dump provenance information for a spec to a particular path.
Expand Down Expand Up @@ -550,7 +555,7 @@ def get(self, spec, new=False):

key = hash(spec)
if new or key not in self._instances:
package_class = self._get_pkg_class(spec.name)
package_class = self.get_pkg_class(spec.name)
try:
copy = spec.copy() # defensive copy. Package owns its spec.
self._instances[key] = package_class(copy)
Expand Down Expand Up @@ -715,7 +720,7 @@ def _get_pkg_module(self, pkg_name):
return self._modules[pkg_name]


def _get_pkg_class(self, pkg_name):
def get_pkg_class(self, pkg_name):
"""Get the class for the package out of its module.
First loads (or fetches from cache) a module for the
Expand Down
Loading

0 comments on commit 108ea15

Please sign in to comment.