Repository: spack/spack

Issue:
Package requirements: target=... not respected.
### Steps to reproduce

This works/concretizes
```yaml
spack:
  specs: [ccache target=x86_64]
```

This does not work:

```yaml
spack:
  specs: [ccache]
  packages:
    all:
      require: target=x86_64
```





### Error message

```
$ spack -e . concretize 
==> Starting concretization
==> Error: Cannot satisfy requirement group for package 'ccache'
```



Discussion:
I can reproduce even without an environment using:
```yaml
packages:
  all:
    require: "target=x86_64"
```
and with a package as simple as `zlib:
```console
 spack spec zlib
==> Error: Cannot satisfy requirement group for package 'zlib'
```
Inserting other constraints, e.g. compilers, seem to work. 
It seems the issue stems from converting `target=X` to an `Archspec` and imposing also a condition on platforms. The bug seems to be in the platform part.
So this is due to:
```
% if no platform is set, fall back to the default
node_platform(Package, Platform)
 :- node(Package),
    not node_platform_set(Package),
    node_platform_default(Platform).

% setting platform on a node is a hard constraint
node_platform(Package, Platform)
 :- node(Package), node_platform_set(Package, Platform).
```
which doesn't give clingo a choice to pop `node_platform` into existence.
Summary, two bugs here. The first one is long-standing, but so far it never caused issues:
```python
Spack version 0.19.0.dev0
Python 3.8.10, Linux x86_64
>>> import spack.spec
>>> s = spack.spec.Spec('target=x86_64')
>>> str(s)
'arch=linux-None-x86_64'
```
The anonymous spec above got `linux` as a platform, when I said nothing about which platform to use. The second one is that currently we don't optimize on platforms as we do on operating systems and targets.
This should solve the specific issue:
```diff
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 8a463c2f78..9212a47194 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -1481,17 +1481,6 @@ def add_dependency_edge(self, dependency_spec, deptype):
         self._dependencies.add(edge)
         dependency_spec._dependents.add(edge)
 
-    def _add_default_platform(self):
-        """If a spec has an os or a target and no platform, give it
-        the default platform.
-
-        This is private because it is used by the parser -- it's not
-        expected to be used outside of ``spec.py``.
-        """
-        arch = self.architecture
-        if arch and not arch.platform and (arch.os or arch.target):
-            self._set_architecture(platform=spack.platforms.host().name)
-
     #
     # Public interface
     #
@@ -5249,7 +5238,6 @@ def spec(self, name):
             else:
                 break
 
-        spec._add_default_platform()
         return spec
 
     def variant(self, name=None):
```
Now, I'm trying to have a look at 8 tests failing on the old concretizer.
would be nice if this was solved by removing code only

Resolving patch:
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -2,7 +2,6 @@
 # Spack Project Developers. See the top-level COPYRIGHT file for details.
 #
 # SPDX-License-Identifier: (Apache-2.0 OR MIT)
-
 """
 Spack allows very fine-grained control over how packages are installed and
 over how they are built and configured.  To make this easy, it has its own
@@ -1506,17 +1505,6 @@ def add_dependency_edge(self, dependency_spec, deptype):
         self._dependencies.add(edge)
         dependency_spec._dependents.add(edge)
 
-    def _add_default_platform(self):
-        """If a spec has an os or a target and no platform, give it
-        the default platform.
-
-        This is private because it is used by the parser -- it's not
-        expected to be used outside of ``spec.py``.
-        """
-        arch = self.architecture
-        if arch and not arch.platform and (arch.os or arch.target):
-            self._set_architecture(platform=spack.platforms.host().name)
-
     #
     # Public interface
     #
@@ -5318,7 +5306,6 @@ def spec(self, name):
             else:
                 break
 
-        spec._add_default_platform()
         return spec
 
     def variant(self, name=None):


Test patch:
diff --git a/lib/spack/spack/test/abi.py b/lib/spack/spack/test/abi.py
--- a/lib/spack/spack/test/abi.py
+++ b/lib/spack/spack/test/abi.py
@@ -22,7 +22,7 @@
         ("platform=linux", "arch=linux-fedora31-x86_64", True),
         ("platform=linux os=fedora31", "platform=linux", True),
         ("platform=darwin", "arch=linux-fedora31-x86_64", False),
-        ("os=fedora31", "platform=linux", False),  # TODO should be true ?
+        ("os=fedora31", "platform=linux", True),
     ],
 )
 def test_architecture_compatibility(target, constraint, expected):
diff --git a/lib/spack/spack/test/architecture.py b/lib/spack/spack/test/architecture.py
--- a/lib/spack/spack/test/architecture.py
+++ b/lib/spack/spack/test/architecture.py
@@ -199,6 +199,9 @@ def test_satisfy_strict_constraint_when_not_concrete(architecture_tuple, constra
 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
+    if spack.config.get("config:concretizer") == "original":
+        pytest.skip("Fixing the parser broke this test for the original concretizer.")
+
     spec_str = "a %%gcc@10 foobar=bar target=%s ^b target=%s" % (
         root_target_range,
         dep_target_range,
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
@@ -337,6 +337,9 @@ def test_architecture_deep_inheritance(self, mock_targets):
         information from the root even when partial architecture information
         is provided by an intermediate dependency.
         """
+        if spack.config.get("config:concretizer") == "original":
+            pytest.skip("Fixing the parser broke this test for the original concretizer.")
+
         spec_str = "mpileaks %gcc@4.5.0 os=CNL target=nocona" " ^dyninst os=CNL ^callpath os=CNL"
         spec = Spec(spec_str).concretized()
         for s in spec.traverse(root=False):
@@ -1837,3 +1840,19 @@ def test_installed_specs_disregard_conflicts(self, mutable_database, monkeypatch
         with spack.config.override("concretizer:reuse", True):
             s = Spec("mpich").concretized()
             assert s.satisfies("~debug")
+
+    @pytest.mark.regression("32471")
+    def test_require_targets_are_allowed(self, mutable_database):
+        """Test that users can set target constraints under the require attribute."""
+        if spack.config.get("config:concretizer") == "original":
+            pytest.xfail("Use case not supported by the original concretizer")
+
+        # Configuration to be added to packages.yaml
+        external_conf = {"all": {"require": "target=x86_64"}}
+        spack.config.set("packages", external_conf)
+
+        with spack.config.override("concretizer:reuse", False):
+            spec = Spec("mpich").concretized()
+
+        for s in spec.traverse():
+            assert s.satisfies("target=x86_64")
diff --git a/lib/spack/spack/test/concretize_preferences.py b/lib/spack/spack/test/concretize_preferences.py
--- a/lib/spack/spack/test/concretize_preferences.py
+++ b/lib/spack/spack/test/concretize_preferences.py
@@ -105,6 +105,9 @@ def test_preferred_variants_from_wildcard(self):
 
     def test_preferred_compilers(self):
         """Test preferred compilers are applied correctly"""
+        if spack.config.get("config:concretizer") == "original":
+            pytest.skip("Fixing the parser broke this test for the original concretizer.")
+
         # Need to make sure the test uses an available compiler
         compiler_list = spack.compilers.all_compiler_specs()
         assert compiler_list
diff --git a/lib/spack/spack/test/spec_syntax.py b/lib/spack/spack/test/spec_syntax.py
--- a/lib/spack/spack/test/spec_syntax.py
+++ b/lib/spack/spack/test/spec_syntax.py
@@ -912,3 +912,9 @@ def test_git_ref_spec_equivalences(self, mock_packages, mock_stage):
         assert not s_no_git.satisfies(s1)
         assert not s2.satisfies(s1)
         assert not s3.satisfies(s1)
+
+    @pytest.mark.regression("32471")
+    @pytest.mark.parametrize("spec_str", ["target=x86_64", "os=redhat6", "target=x86_64:"])
+    def test_platform_is_none_if_not_present(self, spec_str):
+        s = sp.Spec(spec_str)
+        assert s.architecture.platform is None, s