Repository: spack/spack

Issue:
--reuse concretizes to a new architecture than the current target architecture
### Steps to reproduce

On a Power8 system I can create an environment that then fails to build because it tries to use Power9 installed packages.

```
spack env create lbann-gitlab-power8le

spack env activate -p lbann-gitlab-power8le

spack compiler find --scope env:lbann-gitlab-power8le

spack external find --scope env:lbann-gitlab-power8le bzip2 cmake cuda cudnn hipblas hwloc ninja libtool nccl ncurses openssl perl pkg-config python rccl rdma-core sqlite spectrum-mpi mvapich2 openmpi

spack add lbann@gitlab-power8le +gold cuda_arch=60 +cuda +deterministic +vision +numpy ^hydrogen@develop blas=openblas ^dihydrogen@develop blas=openblas ^aluminum@master ^spectrum-mpi ^openblas@0.3.12 threads=openmp ^cuda@11.1.105 ^libtool@2.4.2 ^python@3.9.10

spack develop --no-clone -p /usr/WS1/vanessen/lbann_bugfixes.git lbann@gitlab-power8le

spack add py-numpy@1.16.0:

spack concretize --reuse --test root

spack concretize -f --reuse --test root
```

### Error message

On a system where there is a an existing `power9le` set of software packages the above commands when using `--reuse` find packages that are incompatible with the `power8le` architecture.

```
==> [2022-03-22-11:55:14.119580] Concretized lbann@gitlab-power8le+cuda+deterministic+gold+numpy+vision cuda_arch=60 dev_path=/usr/WS1/vanessen/lbann_bugfixes.git ^aluminum@master ^cuda@11.1.105 ^dihydrogen@develop blas=openblas ^hydrogen@develop blas=openblas ^libtool@2.4.2 ^openblas@0.3.12 threads=openmp ^python@3.9.10 ^spectrum-mpi
 -   yvucsbc  lbann@gitlab-power8le%gcc@8.3.1+al~asan~boost+conduit+cuda+deterministic+dihydrogen~distconv~fft+gold~half+hwloc~ipo~lld+numpy~nvprof~nvshmem~onednn~onnx+pfe+python~rocm+vision~vtune build_type=Release cuda_arch=60 dev_path=/usr/WS1/vanessen/lbann_bugfixes.git dtype=float arch=linux-rhel7-power9le
 -   22c4mxt      ^aluminum@master%gcc@8.3.1+cuda+cuda_rma~ht~ipo+nccl~rccl~rocm build_type=RelWithDebInfo cuda_arch=60 arch=linux-rhel7-power9le
[+]  mhnavkh          ^cmake@3.22.2%gcc@8.3.1~doc+ncurses+openssl+ownlibs~qt build_type=Release arch=linux-rhel7-power9le
[+]  3uml3ee              ^ncurses@5.9.20130511%gcc@8.3.1~symlinks+termlib abi=5 patches=f84b270 arch=linux-rhel7-power9le
[+]  b6rgoro              ^openssl@1.0.2k-fips%gcc@8.3.1~docs certs=system arch=linux-rhel7-power9le
[+]  gdrhg2x          ^cuda@11.1.105%gcc@8.3.1~allow-unsupported-compilers~dev arch=linux-rhel7-power9le
```

### Information on your system

```
[lbann-gitlab-power8le] (1) vanessen@ray24 [lbann_bugfixes.git] %> spack debug report
* **Spack:** 0.17.1-1526-705107b9df
* **Python:** 3.7.2
* **Platform:** linux-rhel7-power8le
* **Concretizer:** clingo
* ```

### General information

- [X] I have run `spack debug report` and reported the version of Spack/Python/Platform
- [X] I have searched the issues of this repo and believe this is not a duplicate
- [X] I have run the failing commands in debug mode and reported the output


Discussion:
@becker33 Here is the issue for the bug that we talked about.

Resolving patch:
diff --git a/lib/spack/spack/schema/concretizer.py b/lib/spack/spack/schema/concretizer.py
--- a/lib/spack/spack/schema/concretizer.py
+++ b/lib/spack/spack/schema/concretizer.py
@@ -15,6 +15,16 @@
         'additionalProperties': False,
         'properties': {
             'reuse': {'type': 'boolean'},
+            'targets': {
+                'type': 'object',
+                'properties': {
+                    'host_compatible': {'type': 'boolean'},
+                    'granularity': {
+                        'type': 'string',
+                        'enum': ['generic', 'microarchitectures']
+                    }
+                }
+            },
         }
     }
 }
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -1413,23 +1413,48 @@ def target_defaults(self, specs):
 
         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
+        # Construct the list of targets which are compatible with the host
+        candidate_targets = [uarch] + uarch.ancestors
+
+        # Get configuration options
+        granularity = spack.config.get('concretizer:targets:granularity')
+        host_compatible = spack.config.get('concretizer:targets:host_compatible')
+
+        # Add targets which are not compatible with the current host
+        if not host_compatible:
+            additional_targets_in_family = sorted([
+                t for t in archspec.cpu.TARGETS.values()
+                if (t.family.name == uarch.family.name and
+                    t not in candidate_targets)
+            ], key=lambda x: len(x.ancestors), reverse=True)
+            candidate_targets += additional_targets_in_family
+
+        # Check if we want only generic architecture
+        if granularity == 'generic':
+            candidate_targets = [t for t in candidate_targets if t.vendor == 'generic']
+
         compilers = self.possible_compilers
 
-        # this loop can be used to limit the number of targets
-        # considered. Right now we consider them all, but it seems that
-        # many targets can make things slow.
-        # TODO: investigate this.
+        # Add targets explicitly requested from specs
+        for spec in specs:
+            if not spec.architecture or not spec.architecture.target:
+                continue
+
+            target = archspec.cpu.TARGETS.get(spec.target.name)
+            if not target:
+                self.target_ranges(spec, None)
+                continue
+
+            if target not in candidate_targets and not host_compatible:
+                candidate_targets.append(target)
+                for ancestor in target.ancestors:
+                    if ancestor not in candidate_targets:
+                        candidate_targets.append(ancestor)
+
         best_targets = set([uarch.family.name])
         for compiler in sorted(compilers):
             supported = self._supported_targets(
-                compiler.name, compiler.version, compatible_targets
+                compiler.name, compiler.version, candidate_targets
             )
 
             # If we can't find supported targets it may be due to custom
@@ -1442,7 +1467,7 @@ def target_defaults(self, specs):
                 supported = self._supported_targets(
                     compiler.name,
                     compiler_obj.real_version,
-                    compatible_targets
+                    candidate_targets
                 )
 
             if not supported:
@@ -1458,21 +1483,8 @@ def target_defaults(self, specs):
                 compiler.name, compiler.version, uarch.family.name
             ))
 
-        # add any targets explicitly mentioned in specs
-        for spec in specs:
-            if not spec.architecture or not spec.architecture.target:
-                continue
-
-            target = archspec.cpu.TARGETS.get(spec.target.name)
-            if not target:
-                self.target_ranges(spec, None)
-                continue
-
-            if target not in compatible_targets:
-                compatible_targets.append(target)
-
         i = 0
-        for target in compatible_targets:
+        for target in candidate_targets:
             self.gen.fact(fn.target(target.name))
             self.gen.fact(fn.target_family(target.name, target.family.name))
             for parent in sorted(target.parents):


Test patch:
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -1526,6 +1526,48 @@ def test_conditional_values_in_conditional_variant(self):
         s = Spec('conditional-values-in-variant@1.60.0').concretized()
         assert 'cxxstd' in s.variants
 
+    def test_target_granularity(self):
+        if spack.config.get('config:concretizer') == 'original':
+            pytest.skip(
+                'Original concretizer cannot account for target granularity'
+            )
+
+        # The test architecture uses core2 as the default target. Check that when
+        # we configure Spack for "generic" granularity we concretize for x86_64
+        s = Spec('python')
+        assert s.concretized().satisfies('target=core2')
+        with spack.config.override('concretizer:targets', {'granularity': 'generic'}):
+            assert s.concretized().satisfies('target=x86_64')
+
+    def test_host_compatible_concretization(self):
+        if spack.config.get('config:concretizer') == 'original':
+            pytest.skip(
+                'Original concretizer cannot account for host compatibility'
+            )
+
+        # Check that after setting "host_compatible" to false we cannot concretize.
+        # Here we use "k10" to set a target non-compatible with the current host
+        # to avoid a lot of boilerplate when mocking the test platform. The issue
+        # is that the defaults for the test platform are very old, so there's no
+        # compiler supporting e.g. icelake etc.
+        s = Spec('python target=k10')
+        assert s.concretized()
+        with spack.config.override('concretizer:targets', {'host_compatible': True}):
+            with pytest.raises(spack.error.SpackError):
+                s.concretized()
+
+    def test_add_microarchitectures_on_explicit_request(self):
+        if spack.config.get('config:concretizer') == 'original':
+            pytest.skip(
+                'Original concretizer cannot account for host compatibility'
+            )
+
+        # Check that if we consider only "generic" targets, we can still solve for
+        # specific microarchitectures on explicit requests
+        with spack.config.override('concretizer:targets', {'granularity': 'generic'}):
+            s = Spec('python target=k10').concretized()
+        assert s.satisfies('target=k10')
+
     @pytest.mark.regression('29201')
     def test_delete_version_and_reuse(
             self, mutable_database, repo_with_changing_recipe
diff --git a/lib/spack/spack/test/data/config/concretizer.yaml b/lib/spack/spack/test/data/config/concretizer.yaml
new file mode 100644
--- /dev/null
+++ b/lib/spack/spack/test/data/config/concretizer.yaml
@@ -0,0 +1,5 @@
+concretizer:
+  # reuse is missing on purpose, see "test_concretizer_arguments"
+  targets:
+    granularity: microarchitectures
+    host_compatible: false