Repository: spack/spack

Issue:
Multiple build systems: second builders do not inherit phase callbacks
### Steps to reproduce

```console
$ spack install netcdf-c~mpi~zstd build_system=autotools ^hdf5~mpi
$ spack install netcdf-c~mpi~zstd build_system=cmake ^hdf5~mpi
```

### Error message

Both builders ([`CMakeBuilder`](https://github.com/spack/spack/blob/74ad92e16b8a5e56152e75d264b99f006763586b/var/spack/repos/builtin/packages/netcdf-c/package.py#L195) and [`AutotoolsBuilder`](https://github.com/spack/spack/blob/74ad92e16b8a5e56152e75d264b99f006763586b/var/spack/repos/builtin/packages/netcdf-c/package.py#L215)) of [`netcdf-c`](https://github.com/spack/spack/blob/74ad92e16b8a5e56152e75d264b99f006763586b/var/spack/repos/builtin/packages/netcdf-c/package.py) inherit from [`BackupStep`](https://github.com/spack/spack/blob/74ad92e16b8a5e56152e75d264b99f006763586b/var/spack/repos/builtin/packages/netcdf-c/package.py#L181), which implements a `run_after("install")` method [`backup_nc_config`](https://github.com/spack/spack/blob/74ad92e16b8a5e56152e75d264b99f006763586b/var/spack/repos/builtin/packages/netcdf-c/package.py#L187). However, the method is run only when `build_system=cmake`. If we swap the declaration of the builders, i.e. instead of
```python
class CMakeBuilder(...):
    ...

class AutotoolsBuilder(...):
    ...
```
we do
```python
class AutotoolsBuilder(...):
    ...

class CMakeBuilder(...):
    ...
```
the `backup_nc_config` will run only when `build_system=autotools`.

I see a similar pattern in [`proj`](https://github.com/spack/spack/blob/74ad92e16b8a5e56152e75d264b99f006763586b/var/spack/repos/builtin/packages/proj/package.py) (@adamjstewart FYI) where the [`install_datum_grids`](https://github.com/spack/spack/blob/74ad92e16b8a5e56152e75d264b99f006763586b/var/spack/repos/builtin/packages/proj/package.py#L116) method is run when `build_system=cmake` but is not run when `build_system=autotools` (given the issue reported in #36688 is resolved).

@alalazo is probably the right person to mention here.

### Information on your system

* **Spack:** 0.20.0.dev0 (74ad92e16b8a5e56152e75d264b99f006763586b)
* **Python:** 3.9.2
* **Platform:** linux-debian11-skylake
* **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:
Also, it looks like [`filter_compiler_wrappers`](https://github.com/spack/spack/blob/74ad92e16b8a5e56152e75d264b99f006763586b/var/spack/repos/builtin/packages/netcdf-c/package.py#L154) has no effect on both of the builders.
I noticed one more thing: `filter_compiler_wrappers` used to be run after all `@run_after`-decorated methods specified in a package. For example,
```python
class NetcdfC(...):
    ...
    filter_compiler_wrappers("nc-config", relative_root="bin")
    ...
    @run_after("install")
    def backup_nc_config(self):
        ...
    ...
```
resulted in `backup_nc_config` being run before `filter_compiler_wrappers`.
Now, we have to be careful and put `filter_compiler_wrappers` in the right place, i.e.
```python
class NetcdfC(...):    
    ...
    @run_after("install")
    def backup_nc_config(self):
        ...
    filter_compiler_wrappers("nc-config", relative_root="bin")
    ...
```
which, I think, goes against the original idea behind `filter_compiler_wrappers`.
@skosukhin I marked this one "low impact" since I see that the bugs introduced by #34935 are mostly due to an incorrect usage of a few (admittedly cryptic) mechanisms. I'll follow shortly with a bugfix, then we can have a look at #36485 

Resolving patch:
diff --git a/lib/spack/spack/builder.py b/lib/spack/spack/builder.py
--- a/lib/spack/spack/builder.py
+++ b/lib/spack/spack/builder.py
@@ -244,7 +244,8 @@ def __new__(mcs, name, bases, attr_dict):
                 callbacks_from_base = getattr(base, temporary_stage.attribute_name, None)
                 if callbacks_from_base:
                     break
-            callbacks_from_base = callbacks_from_base or []
+            else:
+                callbacks_from_base = []
 
             # Set the callbacks in this class and flush the temporary stage
             attr_dict[temporary_stage.attribute_name] = staged_callbacks[:] + callbacks_from_base
diff --git a/lib/spack/spack/mixins.py b/lib/spack/spack/mixins.py
--- a/lib/spack/spack/mixins.py
+++ b/lib/spack/spack/mixins.py
@@ -59,9 +59,10 @@ def filter_compiler_wrappers(*files, **kwargs):
 
     find_kwargs = {"recursive": kwargs.get("recursive", False)}
 
-    def _filter_compiler_wrappers_impl(self):
+    def _filter_compiler_wrappers_impl(pkg_or_builder):
+        pkg = getattr(pkg_or_builder, "pkg", pkg_or_builder)
         # Compute the absolute path of the search root
-        root = os.path.join(self.prefix, relative_root) if relative_root else self.prefix
+        root = os.path.join(pkg.prefix, relative_root) if relative_root else pkg.prefix
 
         # Compute the absolute path of the files to be filtered and
         # remove links from the list.
@@ -71,10 +72,10 @@ def _filter_compiler_wrappers_impl(self):
         x = llnl.util.filesystem.FileFilter(*abs_files)
 
         compiler_vars = [
-            ("CC", self.compiler.cc),
-            ("CXX", self.compiler.cxx),
-            ("F77", self.compiler.f77),
-            ("FC", self.compiler.fc),
+            ("CC", pkg.compiler.cc),
+            ("CXX", pkg.compiler.cxx),
+            ("F77", pkg.compiler.f77),
+            ("FC", pkg.compiler.fc),
         ]
 
         # Some paths to the compiler wrappers might be substrings of the others.
@@ -103,11 +104,11 @@ def _filter_compiler_wrappers_impl(self):
             x.filter(wrapper_path, compiler_path, **filter_kwargs)
 
         # Remove this linking flag if present (it turns RPATH into RUNPATH)
-        x.filter("{0}--enable-new-dtags".format(self.compiler.linker_arg), "", **filter_kwargs)
+        x.filter("{0}--enable-new-dtags".format(pkg.compiler.linker_arg), "", **filter_kwargs)
 
         # NAG compiler is usually mixed with GCC, which has a different
         # prefix for linker arguments.
-        if self.compiler.name == "nag":
+        if pkg.compiler.name == "nag":
             x.filter("-Wl,--enable-new-dtags", "", **filter_kwargs)
 
     spack.builder.run_after(after)(_filter_compiler_wrappers_impl)
diff --git a/var/spack/repos/builtin/packages/netcdf-c/package.py b/var/spack/repos/builtin/packages/netcdf-c/package.py
--- a/var/spack/repos/builtin/packages/netcdf-c/package.py
+++ b/var/spack/repos/builtin/packages/netcdf-c/package.py
@@ -6,8 +6,8 @@
 import os
 import sys
 
-from spack.build_systems.autotools import AutotoolsBuilder
-from spack.build_systems.cmake import CMakeBuilder
+import spack.builder
+from spack.build_systems import autotools, cmake
 from spack.package import *
 
 
@@ -151,8 +151,6 @@ class NetcdfC(CMakePackage, AutotoolsPackage):
     conflicts("+parallel-netcdf", when="@:4.0")
     conflicts("+hdf4", when="@:4.0")
 
-    filter_compiler_wrappers("nc-config", relative_root="bin")
-
     default_build_system = "cmake" if sys.platform == "win32" else "autotools"
 
     build_system("cmake", "autotools", default=default_build_system)
@@ -178,7 +176,7 @@ def setup_dependent_build_environment(self, env, dependent_spec):
             env.prepend_path("PATH", self._nc_config_backup_dir)
 
 
-class BackupStep:
+class BackupStep(metaclass=spack.builder.PhaseCallbacksMeta):
     @property
     def _nc_config_backup_dir(self):
         return join_path(self.pkg.metadata_dir, "spack-nc-config")
@@ -191,8 +189,10 @@ def backup_nc_config(self):
             mkdirp(self._nc_config_backup_dir)
             install(nc_config_file, self._nc_config_backup_dir)
 
+    filter_compiler_wrappers("nc-config", relative_root="bin")
+
 
-class CMakeBuilder(CMakeBuilder, BackupStep, Setup):
+class CMakeBuilder(BackupStep, Setup, cmake.CMakeBuilder):
     def cmake_args(self):
         base_cmake_args = [
             self.define_from_variant("BUILD_SHARED_LIBS", "shared"),
@@ -212,7 +212,7 @@ def cmake_args(self):
         return base_cmake_args
 
 
-class AutotoolsBuilder(AutotoolsBuilder, BackupStep, Setup):
+class AutotoolsBuilder(BackupStep, Setup, autotools.AutotoolsBuilder):
     @property
     def force_autoreconf(self):
         # The patch for 4.7.0 touches configure.ac.


Test patch: