Repository: spack/spack

Issue:
Add $arch placeholder to spack.util.path.substitute_config_variables
### Summary

I would like to be able to have [included configurations](https://spack.readthedocs.io/en/latest/environments.html#included-configurations) in my YAML resolve to platform specific paths, e.g. something like `linux-ubuntu20.04-skylake_avx512/`.

### Rationale

The current implementation of platform-specific configuration scopes as described [in the docs](https://spack.readthedocs.io/en/latest/configuration.html#platform-specific-scopes) is insufficiently fine-grained.

> You can get the name to use for `<platform>` by running `spack arch --platform`. The system config scope has a `<platform>` section for sites at which `/etc` is mounted on multiple heterogeneous machines.

On my Ubuntu 20.04 machine, `spack arch --platform` returns "linux" which is insufficient to distinguish between different distributions or architectures which may require different behavior.

### Description

_No response_

### Additional information

Posting as requested by @becker33 on Slack.

Currently using spack 0.18.0.dev0 (739d17f339ba746b5138e4167953568013ab9b7b)

### General information

- [X] I have run `spack --version` and reported the version of Spack
- [X] I have searched the issues of this repo and believe this is not a duplicate


Discussion:

Resolving patch:
diff --git a/lib/spack/spack/util/path.py b/lib/spack/spack/util/path.py
--- a/lib/spack/spack/util/path.py
+++ b/lib/spack/spack/util/path.py
@@ -27,16 +27,37 @@
 __all__ = ["substitute_config_variables", "substitute_path_variables", "canonicalize_path"]
 
 
+def architecture():
+    # break circular import
+    import spack.platforms
+    import spack.spec
+
+    host_platform = spack.platforms.host()
+    host_os = host_platform.operating_system("default_os")
+    host_target = host_platform.target("default_target")
+
+    return spack.spec.ArchSpec((str(host_platform), str(host_os), str(host_target)))
+
+
 # Substitutions to perform
 def replacements():
     # break circular import from spack.util.executable
     import spack.paths
 
+    arch = architecture()
+
     return {
         "spack": spack.paths.prefix,
         "user": getpass.getuser(),
         "tempdir": tempfile.gettempdir(),
         "user_cache_path": spack.paths.user_cache_path,
+        "architecture": str(arch),
+        "arch": str(arch),
+        "platform": str(arch.platform),
+        "operating_system": str(arch.os),
+        "os": str(arch.os),
+        "target": str(arch.target),
+        "target_family": str(arch.target.microarchitecture.family),
     }
 
 
@@ -245,6 +266,13 @@ def substitute_config_variables(path):
     - $tempdir           Default temporary directory returned by tempfile.gettempdir()
     - $user              The current user's username
     - $user_cache_path   The user cache directory (~/.spack, unless overridden)
+    - $architecture      The spack architecture triple for the current system
+    - $arch              The spack architecture triple for the current system
+    - $platform          The spack platform for the current system
+    - $os                The OS of the current system
+    - $operating_system  The OS of the current system
+    - $target            The ISA target detected for the system
+    - $target_family     The family of the target detected for the system
 
     These are substituted case-insensitively into the path, and users can
     use either ``$var`` or ``${var}`` syntax for the variables. $env is only


Test patch:
diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py
--- a/lib/spack/spack/test/config.py
+++ b/lib/spack/spack/test/config.py
@@ -379,6 +379,17 @@ def test_substitute_config_variables(mock_low_high_config, monkeypatch):
         os.path.join(mock_low_high_config.scopes["low"].path, os.path.join("foo", "bar", "baz"))
     )
 
+    # test architecture information is in replacements
+    assert spack_path.canonicalize_path(
+        os.path.join("foo", "$platform", "bar")
+    ) == os.path.abspath(os.path.join("foo", "test", "bar"))
+
+    host_target = spack.platforms.host().target("default_target")
+    host_target_family = str(host_target.microarchitecture.family)
+    assert spack_path.canonicalize_path(
+        os.path.join("foo", "$target_family", "bar")
+    ) == os.path.abspath(os.path.join("foo", host_target_family, "bar"))
+
 
 packages_merge_low = {"packages": {"foo": {"variants": ["+v1"]}, "bar": {"variants": ["+v2"]}}}