Repository: spack/spack

Issue:
`spack spec` doesn't honor concretizer options in the environment scope 
### Steps to reproduce

On a clean clone of Spack:
```console
$ spack config --scope user add concretizer:unify:false
$ spack env activate --temp
$ spack add zlib@1.2.13
$ spack add zlib@1.2.12
$ spack config add concretizer:unify:true
$ spack concretize
==> Error: concretization failed for the following reasons:

   1. Cannot select a single "version" for package "zlib"
   2. Cannot satisfy 'zlib@1.2.13'
   3. Cannot satisfy 'zlib@1.2.12'. You could consider setting `concretizer:unify` to `when_possible` or `false` to allow multiple versions of some packages.

$ spack spec
==> Starting concretization pool with 2 processes
==> Warning: using "zlib@1.2.12" which is a deprecated version
==> Environment concretized in 1.04 seconds.
Input spec
--------------------------------
zlib@1.2.13

Concretized
--------------------------------
zlib@1.2.13%gcc@9.4.0+optimize+pic+shared build_system=makefile arch=linux-ubuntu20.04-icelake

Input spec
--------------------------------
zlib@1.2.12

Concretized
--------------------------------
zlib@1.2.12%gcc@9.4.0+optimize+pic+shared build_system=makefile patches=0d38234 arch=linux-ubuntu20.04-icelake
```

### Error message

No error message, but the output of `spack spec` is wrong (should be the same as the output of `spack concretize`)

Environment is not using the `concretizer:unify` option set in the `spack.yaml` file so that `spack spec` does not follow the correct concretization path when provided.

_Originally posted by @tldahlgren in https://github.com/spack/spack/issues/35298#issuecomment-1581518885_

### Information on your system

* **Spack:** 0.21.0.dev0 (d91ec8500f7f8265c023b217bce0c0ca34a81a24)
* **Python:** 3.8.10
* **Platform:** linux-ubuntu20.04-icelake
* **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:

Resolving patch:
diff --git a/lib/spack/spack/environment/environment.py b/lib/spack/spack/environment/environment.py
--- a/lib/spack/spack/environment/environment.py
+++ b/lib/spack/spack/environment/environment.py
@@ -731,7 +731,7 @@ def __init__(self, manifest_dir: Union[str, pathlib.Path]) -> None:
 
         self.txlock = lk.Lock(self._transaction_lock_path)
 
-        self.unify = None
+        self._unify = None
         self.new_specs: List[Spec] = []
         self.new_installs: List[Spec] = []
         self.views: Dict[str, ViewDescriptor] = {}
@@ -755,6 +755,16 @@ def __init__(self, manifest_dir: Union[str, pathlib.Path]) -> None:
             self.manifest = EnvironmentManifestFile(manifest_dir)
             self._read()
 
+    @property
+    def unify(self):
+        if self._unify is None:
+            self._unify = spack.config.get("concretizer:unify", False)
+        return self._unify
+
+    @unify.setter
+    def unify(self, value):
+        self._unify = value
+
     def __reduce__(self):
         return _create_environment, (self.path,)
 
@@ -816,9 +826,6 @@ def _construct_state_from_manifest(self):
         else:
             self.views = {}
 
-        # Retrieve unification scheme for the concretizer
-        self.unify = spack.config.get("concretizer:unify", False)
-
         # Retrieve dev-build packages:
         self.dev_specs = copy.deepcopy(env_configuration.get("develop", {}))
         for name, entry in self.dev_specs.items():


Test patch:
diff --git a/lib/spack/spack/test/env.py b/lib/spack/spack/test/env.py
--- a/lib/spack/spack/test/env.py
+++ b/lib/spack/spack/test/env.py
@@ -500,3 +500,54 @@ def test_error_message_when_using_too_new_lockfile(tmp_path):
     ev.initialize_environment_dir(env_dir, init_file)
     with pytest.raises(ev.SpackEnvironmentError, match="You need to use a newer Spack version."):
         ev.Environment(env_dir)
+
+
+@pytest.mark.regression("38240")
+@pytest.mark.parametrize(
+    "unify_in_lower_scope,unify_in_spack_yaml",
+    [
+        (True, False),
+        (True, "when_possible"),
+        (False, True),
+        (False, "when_possible"),
+        ("when_possible", False),
+        ("when_possible", True),
+    ],
+)
+def test_environment_concretizer_scheme_used(tmp_path, unify_in_lower_scope, unify_in_spack_yaml):
+    """Tests that "unify" settings in spack.yaml always take precedence over settings in lower
+    configuration scopes.
+    """
+    manifest = tmp_path / "spack.yaml"
+    manifest.write_text(
+        f"""\
+spack:
+  specs:
+  - mpileaks
+  concretizer:
+    unify: {str(unify_in_spack_yaml).lower()}
+"""
+    )
+
+    with spack.config.override("concretizer:unify", unify_in_lower_scope):
+        with ev.Environment(manifest.parent) as e:
+            assert e.unify == unify_in_spack_yaml
+
+
+@pytest.mark.parametrize("unify_in_config", [True, False, "when_possible"])
+def test_environment_config_scheme_used(tmp_path, unify_in_config):
+    """Tests that "unify" settings in lower configuration scopes is taken into account,
+    if absent in spack.yaml.
+    """
+    manifest = tmp_path / "spack.yaml"
+    manifest.write_text(
+        """\
+spack:
+  specs:
+  - mpileaks
+"""
+    )
+
+    with spack.config.override("concretizer:unify", unify_in_config):
+        with ev.Environment(manifest.parent) as e:
+            assert e.unify == unify_in_config