config: make path replacements lazy (#34758)

Currently, all of the replacements in `spack.util.path.replacements()` get evaluated for
each replacement. This makes it easy to get bootstrap issues, because config is used
very early on in Spack.

Right now, if I run `test_autotools_gnuconfig_replacement_no_gnuconfig` on my M1 mac, I
get the circular reference error below. This fixes the issue by making all of the path
replacements lazy lambdas.

As a bonus, this cleans up the way we do substitution for `$env` -- it's consistent with
other substitutions now.

- [x] make all path `replacements()` lazy
- [x] clean up handling of `$env`

```console
> spack unit-test -k test_autotools_gnuconfig_replacement_no_gnuconfig

...

==> [2022-12-31-15:44:21.771459] Error: AttributeError:

The 'autotools-config-replacement' package cannot find an attribute while trying to build from sources. This might be due to a change in Spack's package format to support multiple build-systems for a single package. You can fix this by updating the build recipe, and you can also report the issue as a bug. More information at https://spack.readthedocs.io/en/latest/packaging_guide.html#installation-procedure

/Users/gamblin2/src/spack/lib/spack/spack/package_base.py:1332, in prefix:
       1330    @property
       1331    def prefix(self):
  >>   1332        """Get the prefix into which this package should be installed."""
       1333        return self.spec.prefix

Traceback (most recent call last):
  File "/Users/gamblin2/src/spack/lib/spack/spack/build_environment.py", line 1030, in _setup_pkg_and_run
    kwargs["env_modifications"] = setup_package(
                                  ^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/build_environment.py", line 757, in setup_package
    set_module_variables_for_package(pkg)
  File "/Users/gamblin2/src/spack/lib/spack/spack/build_environment.py", line 596, in set_module_variables_for_package
    m.std_cmake_args = spack.build_systems.cmake.CMakeBuilder.std_args(pkg)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/build_systems/cmake.py", line 241, in std_args
    define("CMAKE_INSTALL_PREFIX", pkg.prefix),
                                   ^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/package_base.py", line 1333, in prefix
    return self.spec.prefix
           ^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/spec.py", line 1710, in prefix
    self.prefix = spack.store.layout.path_for_spec(self)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/directory_layout.py", line 336, in path_for_spec
    path = self.relative_path_for_spec(spec)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/directory_layout.py", line 106, in relative_path_for_spec
    projection = spack.projections.get_projection(self.projections, spec)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/projections.py", line 13, in get_projection
    if spec.satisfies(spec_like, strict=True):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/spec.py", line 3642, in satisfies
    if not self.virtual and other.virtual:
           ^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/spec.py", line 1622, in virtual
    return spack.repo.path.is_virtual(self.name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/repo.py", line 890, in is_virtual
    return have_name and pkg_name in self.provider_index
                                     ^^^^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/repo.py", line 770, in provider_index
    self._provider_index.merge(repo.provider_index)
                               ^^^^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/repo.py", line 1096, in provider_index
    return self.index["providers"]
           ~~~~~~~~~~^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/repo.py", line 592, in __getitem__
    self._build_all_indexes()
  File "/Users/gamblin2/src/spack/lib/spack/spack/repo.py", line 607, in _build_all_indexes
    self.indexes[name] = self._build_index(name, indexer)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/spack/repo.py", line 616, in _build_index
    index_mtime = self.cache.mtime(cache_filename)
                  ^^^^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/llnl/util/lang.py", line 826, in __getattr__
    return getattr(self.instance, name)
                   ^^^^^^^^^^^^^
  File "/Users/gamblin2/src/spack/lib/spack/llnl/util/lang.py", line 825, in __getattr__
    raise AttributeError()
AttributeError
```
This commit is contained in:
Todd Gamblin 2023-01-13 10:27:07 -08:00 committed by GitHub
parent 33859d3d5f
commit 88a604e7f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -51,26 +51,32 @@ def get_user():
return getpass.getuser() return getpass.getuser()
# return value for replacements with no match
NOMATCH = object()
# Substitutions to perform # Substitutions to perform
def replacements(): def replacements():
# break circular import from spack.util.executable # break circular imports
import spack.environment as ev
import spack.paths import spack.paths
arch = architecture() arch = architecture()
return { return {
"spack": spack.paths.prefix, "spack": lambda: spack.paths.prefix,
"user": get_user(), "user": lambda: get_user(),
"tempdir": tempfile.gettempdir(), "tempdir": lambda: tempfile.gettempdir(),
"user_cache_path": spack.paths.user_cache_path, "user_cache_path": lambda: spack.paths.user_cache_path,
"architecture": str(arch), "architecture": lambda: arch,
"arch": str(arch), "arch": lambda: arch,
"platform": str(arch.platform), "platform": lambda: arch.platform,
"operating_system": str(arch.os), "operating_system": lambda: arch.os,
"os": str(arch.os), "os": lambda: arch.os,
"target": str(arch.target), "target": lambda: arch.target,
"target_family": str(arch.target.microarchitecture.family), "target_family": lambda: arch.target.microarchitecture.family,
"date": date.today().strftime("%Y-%m-%d"), "date": lambda: date.today().strftime("%Y-%m-%d"),
"env": lambda: ev.active_environment().path if ev.active_environment() else NOMATCH,
} }
@ -293,20 +299,14 @@ def substitute_config_variables(path):
replaced if there is an active environment, and should only be used in replaced if there is an active environment, and should only be used in
environment yaml files. environment yaml files.
""" """
import spack.environment as ev # break circular
_replacements = replacements() _replacements = replacements()
env = ev.active_environment()
if env:
_replacements.update({"env": env.path})
else:
# If a previous invocation added env, remove it
_replacements.pop("env", None)
# Look up replacements # Look up replacements
def repl(match): def repl(match):
m = match.group(0).strip("${}") m = match.group(0)
return _replacements.get(m.lower(), match.group(0)) key = m.strip("${}").lower()
repl = _replacements.get(key, lambda: m)()
return m if repl is NOMATCH else str(repl)
# Replace $var or ${var}. # Replace $var or ${var}.
return re.sub(r"(\$\w+\b|\$\{\w+\})", repl, path) return re.sub(r"(\$\w+\b|\$\{\w+\})", repl, path)