summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/concretize.py47
-rw-r--r--lib/spack/spack/installer.py92
-rw-r--r--lib/spack/spack/solver/asp.py14
-rw-r--r--lib/spack/spack/solver/concretize.lp13
-rw-r--r--lib/spack/spack/spec.py100
-rw-r--r--lib/spack/spack/test/architecture.py27
-rw-r--r--lib/spack/spack/test/installer.py11
7 files changed, 241 insertions, 63 deletions
diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py
index 186cf8244f..3c3801bf5b 100644
--- a/lib/spack/spack/concretize.py
+++ b/lib/spack/spack/concretize.py
@@ -253,8 +253,7 @@ class Concretizer(object):
if spec.architecture is None:
spec.architecture = spack.spec.ArchSpec()
- if spec.architecture.platform and \
- (spec.architecture.os and spec.architecture.target):
+ if spec.architecture.concrete:
return False
# Get platform of nearest spec with a platform, including spec
@@ -294,22 +293,58 @@ class Concretizer(object):
# Get the nearest spec with relevant platform and a target
# Generally, same algorithm as finding os
+ curr_target = None
if spec.architecture.target:
+ curr_target = spec.architecture.target
+ if spec.architecture.target and spec.architecture.target_concrete:
new_target = spec.architecture.target
else:
new_target_spec = find_spec(
spec, lambda x: (x.architecture and
x.architecture.platform == str(new_plat) and
- x.architecture.target)
+ x.architecture.target and
+ x.architecture.target != curr_target)
)
if new_target_spec:
- new_target = new_target_spec.architecture.target
+ if curr_target:
+ # constrain one target by the other
+ new_target_arch = spack.spec.ArchSpec(
+ (None, None, new_target_spec.architecture.target))
+ curr_target_arch = spack.spec.ArchSpec(
+ (None, None, curr_target))
+ curr_target_arch.constrain(new_target_arch)
+ new_target = curr_target_arch.target
+ else:
+ new_target = new_target_spec.architecture.target
else:
# To get default platform, consider package prefs
if PackagePrefs.has_preferred_targets(spec.name):
new_target = self.target_from_package_preferences(spec)
else:
new_target = new_plat.target('default_target')
+ if curr_target:
+ # convert to ArchSpec to compare satisfaction
+ new_target_arch = spack.spec.ArchSpec(
+ (None, None, str(new_target)))
+ curr_target_arch = spack.spec.ArchSpec(
+ (None, None, str(curr_target)))
+
+ if not new_target_arch.satisfies(curr_target_arch):
+ # new_target is an incorrect guess based on preferences
+ # and/or default
+ valid_target_ranges = str(curr_target).split(',')
+ for target_range in valid_target_ranges:
+ t_min, t_sep, t_max = target_range.partition(':')
+ if not t_sep:
+ new_target = t_min
+ break
+ elif t_max:
+ new_target = t_max
+ break
+ elif t_min:
+ # TODO: something better than picking first
+ new_target = t_min
+ break
# Construct new architecture, compute whether spec changed
arch_spec = (str(new_plat), str(new_os), str(new_target))
@@ -384,7 +419,7 @@ class Concretizer(object):
"""
# Pass on concretizing the compiler if the target or operating system
# is not yet determined
- if not (spec.architecture.os and spec.architecture.target):
+ if not spec.architecture.concrete:
# We haven't changed, but other changes need to happen before we
# continue. `return True` here to force concretization to keep
# running.
@@ -482,7 +517,7 @@ class Concretizer(object):
"""
# Pass on concretizing the compiler flags if the target or operating
# system is not set.
- if not (spec.architecture.os and spec.architecture.target):
+ if not spec.architecture.concrete:
# We haven't changed, but other changes need to happen before we
# continue. `return True` here to force concretization to keep
# running.
diff --git a/lib/spack/spack/installer.py b/lib/spack/spack/installer.py
index b70528dea2..15bc23738b 100644
--- a/lib/spack/spack/installer.py
+++ b/lib/spack/spack/installer.py
@@ -182,7 +182,7 @@ def _do_fake_install(pkg):
dump_packages(pkg.spec, packages_dir)
-def _packages_needed_to_bootstrap_compiler(pkg):
+def _packages_needed_to_bootstrap_compiler(compiler, architecture, pkgs):
"""
Return a list of packages required to bootstrap `pkg`s compiler
@@ -190,7 +190,11 @@ def _packages_needed_to_bootstrap_compiler(pkg):
matches the package spec.
Args:
- pkg (Package): the package that may need its compiler installed
+ compiler (CompilerSpec): the compiler to bootstrap
+ architecture (ArchSpec): the architecture for which to boostrap the
+ compiler
+ pkgs (list of PackageBase): the packages that may need their compiler
+ installed
Return:
(list) list of tuples, (PackageBase, bool), for concretized compiler-
@@ -199,21 +203,27 @@ def _packages_needed_to_bootstrap_compiler(pkg):
(``True``) or one of its dependencies (``False``). The list
will be empty if there are no compilers.
"""
- tty.debug('Bootstrapping {0} compiler for {1}'
- .format(pkg.spec.compiler, package_id(pkg)))
+ tty.debug('Bootstrapping {0} compiler'.format(compiler))
compilers = spack.compilers.compilers_for_spec(
- pkg.spec.compiler, arch_spec=pkg.spec.architecture)
+ compiler, arch_spec=architecture)
if compilers:
return []
- dep = spack.compilers.pkg_spec_for_compiler(pkg.spec.compiler)
- dep.architecture = pkg.spec.architecture
+ dep = spack.compilers.pkg_spec_for_compiler(compiler)
+
+ # Set the architecture for the compiler package in a way that allows the
+ # concretizer to back off if needed for the older bootstrapping compiler
+ dep.constrain('platform=%s' % str(architecture.platform))
+ dep.constrain('os=%s' % str(architecture.os))
+ dep.constrain('target=%s:' %
+ architecture.target.microarchitecture.family.name)
# concrete CompilerSpec has less info than concrete Spec
# concretize as Spec to add that information
dep.concretize()
- # mark compiler as depended-on by the package that uses it
- dep._dependents[pkg.name] = spack.spec.DependencySpec(
- pkg.spec, dep, ('build',))
+ # mark compiler as depended-on by the packages that use it
+ for pkg in pkgs:
+ dep._dependents[pkg.name] = spack.spec.DependencySpec(
+ pkg.spec, dep, ('build',))
packages = [(s.package, False) for
s in dep.traverse(order='post', root=False)]
packages.append((dep.package, True))
@@ -647,17 +657,21 @@ class PackageInstaller(object):
return '{0}: {1}; {2}; {3}; {4}'.format(
self.pid, requests, tasks, installed, failed)
- def _add_bootstrap_compilers(self, pkg, request, all_deps):
+ def _add_bootstrap_compilers(
+ self, compiler, architecture, pkgs, request, all_deps):
"""
Add bootstrap compilers and dependencies to the build queue.
Args:
- pkg (PackageBase): the package with possible compiler dependencies
+ compiler: the compiler to boostrap
+ architecture: the architecture for which to bootstrap the compiler
+ pkgs (PackageBase): the package with possible compiler dependencies
request (BuildRequest): the associated install request
all_deps (defaultdict(set)): dictionary of all dependencies and
associated dependents
"""
- packages = _packages_needed_to_bootstrap_compiler(pkg)
+ packages = _packages_needed_to_bootstrap_compiler(
+ compiler, architecture, pkgs)
for (comp_pkg, is_compiler) in packages:
if package_id(comp_pkg) not in self.build_tasks:
self._add_init_task(comp_pkg, request, is_compiler, all_deps)
@@ -997,13 +1011,41 @@ class PackageInstaller(object):
'config:install_missing_compilers', False)
install_deps = request.install_args.get('install_deps')
- if install_deps:
+ # Bootstrap compilers first
+ if install_deps and install_compilers:
+ packages_per_compiler = {}
+
for dep in request.traverse_dependencies():
dep_pkg = dep.package
+ compiler = dep_pkg.spec.compiler
+ arch = dep_pkg.spec.architecture
+ if compiler not in packages_per_compiler:
+ packages_per_compiler[compiler] = {}
+
+ if arch not in packages_per_compiler[compiler]:
+ packages_per_compiler[compiler][arch] = []
+
+ packages_per_compiler[compiler][arch].append(dep_pkg)
+
+ compiler = request.pkg.spec.compiler
+ arch = request.pkg.spec.architecture
+
+ if compiler not in packages_per_compiler:
+ packages_per_compiler[compiler] = {}
+
+ if arch not in packages_per_compiler[compiler]:
+ packages_per_compiler[compiler][arch] = []
- # First push any missing compilers (if requested)
- if install_compilers:
- self._add_bootstrap_compilers(dep_pkg, request, all_deps)
+ packages_per_compiler[compiler][arch].append(request.pkg)
+
+ for compiler, archs in packages_per_compiler.items():
+ for arch, packages in archs.items():
+ self._add_bootstrap_compilers(
+ compiler, arch, packages, request, all_deps)
+
+ if install_deps:
+ for dep in request.traverse_dependencies():
+ dep_pkg = dep.package
dep_id = package_id(dep_pkg)
if dep_id not in self.build_tasks:
@@ -1014,13 +1056,9 @@ class PackageInstaller(object):
# of the spec.
spack.store.db.clear_failure(dep, force=False)
- # Push any missing compilers (if requested) as part of the
- # package dependencies.
- if install_compilers:
- self._add_bootstrap_compilers(request.pkg, request, all_deps)
-
install_package = request.install_args.get('install_package')
if install_package and request.pkg_id not in self.build_tasks:
+
# Be sure to clear any previous failure
spack.store.db.clear_failure(request.spec, force=True)
@@ -1752,6 +1790,11 @@ class BuildTask(object):
# to support tracking of parallel, multi-spec, environment installs.
self.dependents = set(get_dependent_ids(self.pkg.spec))
+ tty.debug(
+ 'Pkg id {0} has the following dependents:'.format(self.pkg_id))
+ for dep_id in self.dependents:
+ tty.debug('- {0}'.format(dep_id))
+
# Set of dependencies
#
# Be consistent wrt use of dependents and dependencies. That is,
@@ -1772,7 +1815,10 @@ class BuildTask(object):
arch_spec=arch_spec):
# The compiler is in the queue, identify it as dependency
dep = spack.compilers.pkg_spec_for_compiler(compiler_spec)
- dep.architecture = arch_spec
+ dep.constrain('platform=%s' % str(arch_spec.platform))
+ dep.constrain('os=%s' % str(arch_spec.os))
+ dep.constrain('target=%s:' %
+ arch_spec.target.microarchitecture.family.name)
dep.concretize()
dep_id = package_id(dep.package)
self.dependencies.add(dep_id)
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
index 8f2e8f3ede..8ab0809ca8 100644
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -1112,6 +1112,12 @@ class SpackSolverSetup(object):
self.gen.h2('Target compatibility')
compatible_targets = [uarch] + uarch.ancestors
+ additional_targets_in_family = sorted([
+ t for t in archspec.cpu.TARGETS.values()
+ if (t.family.name == uarch.family.name and
+ t not in compatible_targets)
+ ], key=lambda x: len(x.ancestors), reverse=True)
+ compatible_targets += additional_targets_in_family
compilers = self.possible_compilers
# this loop can be used to limit the number of targets
@@ -1155,7 +1161,9 @@ class SpackSolverSetup(object):
print("TTYPE:", type(platform.target(spec.target.name)))
target = archspec.cpu.TARGETS.get(spec.target.name)
if not target:
- raise ValueError("Invalid target: ", spec.target.name)
+ self.target_ranges(spec, None)
+ continue
+
if target not in compatible_targets:
compatible_targets.append(target)
@@ -1290,6 +1298,10 @@ class SpackSolverSetup(object):
def _all_targets_satisfiying(single_constraint):
allowed_targets = []
+
+ if ':' not in single_constraint:
+ return [single_constraint]
+
t_min, _, t_max = single_constraint.partition(':')
for test_target in archspec.cpu.TARGETS.values():
# Check lower bound
diff --git a/lib/spack/spack/solver/concretize.lp b/lib/spack/spack/solver/concretize.lp
index e991e66ff2..7c94b7a8a4 100644
--- a/lib/spack/spack/solver/concretize.lp
+++ b/lib/spack/spack/solver/concretize.lp
@@ -349,17 +349,22 @@ node_target_weight(Package, Weight)
target_weight(Target, Package, Weight).
% compatibility rules for targets among nodes
-node_target_match_pref(Package, Target) :- node_target_set(Package, Target).
node_target_match_pref(Dependency, Target)
- :- depends_on(Package, Dependency), node_target_match_pref(Package, Target),
+ :- depends_on(Package, Dependency),
+ node_target_match_pref(Package, Target),
not node_target_set(Dependency, _).
node_target_match_pref(Dependency, Target)
:- depends_on(Package, Dependency),
- root(Package), node_target(Package, Target),
- not node_target_match_pref(Package, _),
+ node_target_set(Package, Target),
+ not node_target_match_pref(Package, Target),
not node_target_set(Dependency, _).
+node_target_match_pref(Dependency, Target)
+ :- depends_on(Package, Dependency),
+ root(Package), node_target(Package, Target),
+ not node_target_match_pref(Package, _).
+
node_target_match(Package, 1)
:- node_target(Package, Target), node_target_match_pref(Package, Target).
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index a424dc60c9..f0ae6a1431 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -359,13 +359,11 @@ class ArchSpec(object):
return False
# Check target
- return self._satisfies_target(other.target, strict=strict)
+ return self.target_satisfies(other, strict=strict)
- def _satisfies_target(self, other_target, strict):
- self_target = self.target
-
- need_to_check = bool(other_target) if strict or self.concrete \
- else bool(other_target and self_target)
+ def target_satisfies(self, other, strict):
+ need_to_check = bool(other.target) if strict or self.concrete \
+ else bool(other.target and self.target)
# If there's no need to check we are fine
if not need_to_check:
@@ -375,24 +373,68 @@ class ArchSpec(object):
if self.target is None:
return False
- for target_range in str(other_target).split(','):
- t_min, sep, t_max = target_range.partition(':')
-
- # Checking against a single specific target
- if not sep and self_target == t_min:
- return True
-
- if not sep and self_target != t_min:
- return False
+ return bool(self.target_intersection(other))
- # Check against a range
- min_ok = self_target.microarchitecture >= t_min if t_min else True
- max_ok = self_target.microarchitecture <= t_max if t_max else True
+ def target_constrain(self, other):
+ if not other.target_satisfies(self, strict=False):
+ raise UnsatisfiableArchitectureSpecError(self, other)
- if min_ok and max_ok:
- return True
+ if self.target_concrete:
+ return False
+ elif other.target_concrete:
+ self.target = other.target
+ return True
- return False
+ # Compute the intersection of every combination of ranges in the lists
+ results = self.target_intersection(other)
+ # Do we need to dedupe here?
+ self.target = ','.join(results)
+
+ def target_intersection(self, other):
+ results = []
+
+ if not self.target or not other.target:
+ return results
+
+ for s_target_range in str(self.target).split(','):
+ s_min, s_sep, s_max = s_target_range.partition(':')
+ for o_target_range in str(other.target).split(','):
+ o_min, o_sep, o_max = o_target_range.partition(':')
+
+ if not s_sep:
+ # s_target_range is a concrete target
+ # get a microarchitecture reference for at least one side
+ # of each comparison so we can use archspec comparators
+ s_comp = spack.architecture.Target(s_min).microarchitecture
+ if not o_sep:
+ if s_min == o_min:
+ results.append(s_min)
+ elif (not o_min or s_comp >= o_min) and (
+ not o_max or s_comp <= o_max):
+ results.append(s_min)
+ elif not o_sep:
+ # "cast" to microarchitecture
+ o_comp = spack.architecture.Target(o_min).microarchitecture
+ if (not s_min or o_comp >= s_min) and (
+ not s_max or o_comp <= s_max):
+ results.append(o_min)
+ else:
+ # Take intersection of two ranges
+ # Lots of comparisons needed
+ _s_min = spack.architecture.Target(s_min).microarchitecture
+ _s_max = spack.architecture.Target(s_max).microarchitecture
+ _o_min = spack.architecture.Target(o_min).microarchitecture
+ _o_max = spack.architecture.Target(o_max).microarchitecture
+
+ n_min = s_min if _s_min >= _o_min else o_min
+ n_max = s_max if _s_max <= _o_max else o_max
+ _n_min = spack.architecture.Target(n_min).microarchitecture
+ _n_max = spack.architecture.Target(n_max).microarchitecture
+ if _n_min == _n_max:
+ results.append(n_min)
+ elif not n_min or not n_max or _n_min < _n_max:
+ results.append('%s:%s' % (n_min, n_max))
+ return results
def constrain(self, other):
"""Projects all architecture fields that are specified in the given
@@ -409,16 +451,18 @@ class ArchSpec(object):
"""
other = self._autospec(other)
- if not self.satisfies(other):
- raise UnsatisfiableArchitectureSpecError(self, other)
+ if not other.satisfies(self):
+ raise UnsatisfiableArchitectureSpecError(other, self)
constrained = False
- for attr in ('platform', 'os', 'target'):
+ for attr in ('platform', 'os'):
svalue, ovalue = getattr(self, attr), getattr(other, attr)
if svalue is None and ovalue is not None:
setattr(self, attr, ovalue)
constrained = True
+ self.target_constrain(other)
+
return constrained
def copy(self):
@@ -431,7 +475,13 @@ class ArchSpec(object):
def concrete(self):
"""True if the spec is concrete, False otherwise"""
# return all(v for k, v in six.iteritems(self.to_cmp_dict()))
- return self.platform and self.os and self.target
+ return (self.platform and self.os and self.target and
+ self.target_concrete)
+
+ @property
+ def target_concrete(self):
+ """True if the target is not a range or list."""
+ return ':' not in str(self.target) and ',' not in str(self.target)
def to_dict(self):
d = syaml.syaml_dict([
diff --git a/lib/spack/spack/test/architecture.py b/lib/spack/spack/test/architecture.py
index 7af5a8d150..66c87240a1 100644
--- a/lib/spack/spack/test/architecture.py
+++ b/lib/spack/spack/test/architecture.py
@@ -12,6 +12,7 @@ import platform as py_platform
import pytest
import spack.architecture
+import spack.concretize
from spack.spec import Spec
from spack.platforms.cray import Cray
from spack.platforms.linux import Linux
@@ -223,3 +224,29 @@ def test_satisfy_strict_constraint_when_not_concrete(
architecture = spack.spec.ArchSpec(architecture_tuple)
constraint = spack.spec.ArchSpec(constraint_tuple)
assert not architecture.satisfies(constraint, strict=True)
+
+
+@pytest.mark.parametrize('root_target_range,dep_target_range,result', [
+ (('x86_64:nocona', 'x86_64:core2', 'nocona')), # pref not in intersection
+ (('x86_64:core2', 'x86_64:nocona', 'nocona')),
+ (('x86_64:haswell', 'x86_64:mic_knl', 'core2')), # pref in intersection
+ (('ivybridge', 'nocona:skylake', 'ivybridge')), # one side concrete
+ (('haswell:icelake', 'broadwell', 'broadwell')),
+ # multiple ranges in lists with multiple overlaps
+ (('x86_64:nocona,haswell:broadwell', 'nocona:haswell,skylake:',
+ 'nocona')),
+ # lists with concrete targets, lists compared to ranges
+ (('x86_64,haswell', 'core2:broadwell', 'haswell'))
+])
+@pytest.mark.usefixtures('mock_packages', 'config')
+def test_concretize_target_ranges(
+ root_target_range, dep_target_range, result
+):
+ # use foobar=bar to make the problem simpler for the old concretizer
+ # the new concretizer should not need that help
+ spec = Spec('a %%gcc@10 foobar=bar target=%s ^b target=%s' %
+ (root_target_range, dep_target_range))
+ with spack.concretize.disable_compiler_existence_check():
+ spec.concretize()
+
+ assert str(spec).count('arch=test-debian6-%s' % result) == 2
diff --git a/lib/spack/spack/test/installer.py b/lib/spack/spack/test/installer.py
index cec3721d0d..3bf818544e 100644
--- a/lib/spack/spack/test/installer.py
+++ b/lib/spack/spack/test/installer.py
@@ -450,7 +450,8 @@ def test_packages_needed_to_bootstrap_compiler_none(install_mockery):
spec.concretize()
assert spec.concrete
- packages = inst._packages_needed_to_bootstrap_compiler(spec.package)
+ packages = inst._packages_needed_to_bootstrap_compiler(
+ spec.compiler, spec.architecture, [spec.package])
assert not packages
@@ -468,7 +469,8 @@ def test_packages_needed_to_bootstrap_compiler_packages(install_mockery,
monkeypatch.setattr(spack.compilers, 'pkg_spec_for_compiler', _conc_spec)
monkeypatch.setattr(spack.spec.Spec, 'concretize', _noop)
- packages = inst._packages_needed_to_bootstrap_compiler(spec.package)
+ packages = inst._packages_needed_to_bootstrap_compiler(
+ spec.compiler, spec.architecture, [spec.package])
assert packages
@@ -626,7 +628,7 @@ def test_check_deps_status_upstream(install_mockery, monkeypatch):
def test_add_bootstrap_compilers(install_mockery, monkeypatch):
from collections import defaultdict
- def _pkgs(pkg):
+ def _pkgs(compiler, architecture, pkgs):
spec = spack.spec.Spec('mpi').concretized()
return [(spec.package, True)]
@@ -636,7 +638,8 @@ def test_add_bootstrap_compilers(install_mockery, monkeypatch):
all_deps = defaultdict(set)
monkeypatch.setattr(inst, '_packages_needed_to_bootstrap_compiler', _pkgs)
- installer._add_bootstrap_compilers(request.pkg, request, all_deps)
+ installer._add_bootstrap_compilers(
+ 'fake', 'fake', [request.pkg], request, all_deps)
ids = list(installer.build_tasks)
assert len(ids) == 1