From b7993317ea8e9319d1b02a901fbfe58447faf910 Mon Sep 17 00:00:00 2001 From: Harmen Stoppels Date: Fri, 15 Nov 2024 09:13:10 +0100 Subject: [PATCH] Improve type hints for package API (#47576) by disentangling `package_base`, `builder` and `directives`. --- lib/spack/docs/conf.py | 2 +- lib/spack/docs/packaging_guide.rst | 4 +- lib/spack/spack/build_environment.py | 3 +- lib/spack/spack/build_systems/_checks.py | 11 +- lib/spack/spack/build_systems/autotools.py | 255 ++++++------ lib/spack/spack/build_systems/cached_cmake.py | 4 +- lib/spack/spack/build_systems/cargo.py | 7 +- lib/spack/spack/build_systems/cmake.py | 367 ++++++++++-------- lib/spack/spack/build_systems/generic.py | 9 +- lib/spack/spack/build_systems/go.py | 7 +- lib/spack/spack/build_systems/intel.py | 16 +- lib/spack/spack/build_systems/makefile.py | 40 +- lib/spack/spack/build_systems/maven.py | 4 +- lib/spack/spack/build_systems/meson.py | 40 +- lib/spack/spack/build_systems/msbuild.py | 4 +- lib/spack/spack/build_systems/nmake.py | 4 +- lib/spack/spack/build_systems/octave.py | 4 +- lib/spack/spack/build_systems/perl.py | 9 +- lib/spack/spack/build_systems/python.py | 9 +- lib/spack/spack/build_systems/qmake.py | 7 +- lib/spack/spack/build_systems/ruby.py | 4 +- lib/spack/spack/build_systems/scons.py | 7 +- lib/spack/spack/build_systems/sip.py | 7 +- lib/spack/spack/build_systems/waf.py | 9 +- lib/spack/spack/builder.py | 348 +++++++---------- lib/spack/spack/ci.py | 7 +- lib/spack/spack/cmd/info.py | 7 +- lib/spack/spack/directives.py | 45 +-- lib/spack/spack/install_test.py | 5 +- lib/spack/spack/installer.py | 6 +- lib/spack/spack/mirror.py | 1 + lib/spack/spack/mixins.py | 4 +- lib/spack/spack/package.py | 3 +- lib/spack/spack/package_base.py | 182 ++++----- lib/spack/spack/phase_callbacks.py | 105 +++++ lib/spack/spack/test/build_systems.py | 15 +- lib/spack/spack/test/conftest.py | 4 +- .../packages/builder-and-mixins/package.py | 4 +- .../packages/py-test-callback/package.py | 6 +- .../repos/builtin/packages/adios2/package.py | 5 +- .../repos/builtin/packages/assimp/package.py | 2 +- .../repos/builtin/packages/bacio/package.py | 2 +- .../repos/builtin/packages/blaspp/package.py | 4 +- .../repos/builtin/packages/boost/package.py | 12 - .../repos/builtin/packages/bufr/package.py | 2 +- .../repos/builtin/packages/g2/package.py | 2 +- .../repos/builtin/packages/g2c/package.py | 2 +- .../repos/builtin/packages/g2tmpl/package.py | 2 +- .../repos/builtin/packages/gfsio/package.py | 2 +- .../repos/builtin/packages/glib/package.py | 6 +- .../builtin/packages/grib-util/package.py | 2 +- .../repos/builtin/packages/hipblas/package.py | 4 +- .../builtin/packages/hipsolver/package.py | 2 +- .../repos/builtin/packages/ip/package.py | 2 +- .../builtin/packages/landsfcutil/package.py | 2 +- .../builtin/packages/lapackpp/package.py | 4 +- .../builtin/packages/lc-framework/package.py | 3 +- .../repos/builtin/packages/libxml2/package.py | 7 +- .../repos/builtin/packages/mapl/package.py | 2 +- .../repos/builtin/packages/mercury/package.py | 2 +- .../repos/builtin/packages/ncio/package.py | 2 +- .../repos/builtin/packages/nemsio/package.py | 2 +- .../builtin/packages/nemsiogfs/package.py | 2 +- .../builtin/packages/netcdf-c/package.py | 9 +- .../builtin/packages/nimrod-aai/package.py | 2 +- .../builtin/packages/prod-util/package.py | 2 +- .../repos/builtin/packages/proj/package.py | 7 +- .../repos/builtin/packages/raja/package.py | 2 +- .../builtin/packages/rocthrust/package.py | 2 +- .../repos/builtin/packages/sfcio/package.py | 2 +- .../repos/builtin/packages/sigio/package.py | 2 +- .../repos/builtin/packages/sp/package.py | 2 +- .../builtin/packages/strumpack/package.py | 2 +- .../builtin/packages/sundials/package.py | 2 +- .../repos/builtin/packages/superlu/package.py | 6 +- .../repos/builtin/packages/tcl/package.py | 36 +- .../repos/builtin/packages/vtk-m/package.py | 5 +- .../repos/builtin/packages/w3emc/package.py | 2 +- .../builtin/packages/xsdk-examples/package.py | 2 +- 79 files changed, 919 insertions(+), 818 deletions(-) create mode 100644 lib/spack/spack/phase_callbacks.py diff --git a/lib/spack/docs/conf.py b/lib/spack/docs/conf.py index 4d8592ffd93..688554fac6d 100644 --- a/lib/spack/docs/conf.py +++ b/lib/spack/docs/conf.py @@ -210,7 +210,7 @@ def setup(sphinx): # Spack classes that are private and we don't want to expose ("py:class", "spack.provider_index._IndexBase"), ("py:class", "spack.repo._PrependFileLoader"), - ("py:class", "spack.build_systems._checks.BaseBuilder"), + ("py:class", "spack.build_systems._checks.BuilderWithDefaults"), # Spack classes that intersphinx is unable to resolve ("py:class", "spack.version.StandardVersion"), ("py:class", "spack.spec.DependencySpec"), diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst index 9b3c2829f7a..a7564c2b039 100644 --- a/lib/spack/docs/packaging_guide.rst +++ b/lib/spack/docs/packaging_guide.rst @@ -2925,9 +2925,9 @@ make sense during the build phase may not be needed at runtime, and vice versa. it makes sense to let a dependency set the environment variables for its dependents. To allow all this, Spack provides four different methods that can be overridden in a package: -1. :meth:`setup_build_environment ` +1. :meth:`setup_build_environment ` 2. :meth:`setup_run_environment ` -3. :meth:`setup_dependent_build_environment ` +3. :meth:`setup_dependent_build_environment ` 4. :meth:`setup_dependent_run_environment ` The Qt package, for instance, uses this call: diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py index 33586eccde2..3a4f8b3fa94 100644 --- a/lib/spack/spack/build_environment.py +++ b/lib/spack/spack/build_environment.py @@ -56,7 +56,6 @@ from llnl.util.symlink import symlink from llnl.util.tty.color import cescape, colorize -import spack.build_systems._checks import spack.build_systems.cmake import spack.build_systems.meson import spack.build_systems.python @@ -1375,7 +1374,7 @@ def exitcode_msg(p): return child_result -CONTEXT_BASES = (spack.package_base.PackageBase, spack.build_systems._checks.BaseBuilder) +CONTEXT_BASES = (spack.package_base.PackageBase, spack.builder.Builder) def get_package_context(traceback, context=3): diff --git a/lib/spack/spack/build_systems/_checks.py b/lib/spack/spack/build_systems/_checks.py index e15409fc38d..2c88f15e2bf 100644 --- a/lib/spack/spack/build_systems/_checks.py +++ b/lib/spack/spack/build_systems/_checks.py @@ -9,6 +9,7 @@ import spack.builder import spack.error +import spack.phase_callbacks import spack.relocate import spack.spec import spack.store @@ -63,7 +64,7 @@ def apply_macos_rpath_fixups(builder: spack.builder.Builder): def ensure_build_dependencies_or_raise( - spec: spack.spec.Spec, dependencies: List[spack.spec.Spec], error_msg: str + spec: spack.spec.Spec, dependencies: List[str], error_msg: str ): """Ensure that some build dependencies are present in the concrete spec. @@ -71,7 +72,7 @@ def ensure_build_dependencies_or_raise( Args: spec: concrete spec to be checked. - dependencies: list of abstract specs to be satisfied + dependencies: list of package names of required build dependencies error_msg: brief error message to be prepended to a longer description Raises: @@ -127,8 +128,8 @@ def execute_install_time_tests(builder: spack.builder.Builder): builder.pkg.tester.phase_tests(builder, "install", builder.install_time_test_callbacks) -class BaseBuilder(spack.builder.Builder): - """Base class for builders to register common checks""" +class BuilderWithDefaults(spack.builder.Builder): + """Base class for all specific builders with common callbacks registered.""" # Check that self.prefix is there after installation - spack.builder.run_after("install")(sanity_check_prefix) + spack.phase_callbacks.run_after("install")(sanity_check_prefix) diff --git a/lib/spack/spack/build_systems/autotools.py b/lib/spack/spack/build_systems/autotools.py index 47911271fef..aad8a6ffb10 100644 --- a/lib/spack/spack/build_systems/autotools.py +++ b/lib/spack/spack/build_systems/autotools.py @@ -6,7 +6,7 @@ import os.path import stat import subprocess -from typing import List +from typing import Callable, List, Optional, Set, Tuple, Union import llnl.util.filesystem as fs import llnl.util.tty as tty @@ -15,6 +15,9 @@ import spack.builder import spack.error import spack.package_base +import spack.phase_callbacks +import spack.spec +import spack.util.prefix from spack.directives import build_system, conflicts, depends_on from spack.multimethod import when from spack.operating_systems.mac_os import macos_version @@ -22,7 +25,7 @@ from spack.version import Version from ._checks import ( - BaseBuilder, + BuilderWithDefaults, apply_macos_rpath_fixups, ensure_build_dependencies_or_raise, execute_build_time_tests, @@ -69,14 +72,14 @@ def flags_to_build_system_args(self, flags): # Legacy methods (used by too many packages to change them, # need to forward to the builder) def enable_or_disable(self, *args, **kwargs): - return self.builder.enable_or_disable(*args, **kwargs) + return spack.builder.create(self).enable_or_disable(*args, **kwargs) def with_or_without(self, *args, **kwargs): - return self.builder.with_or_without(*args, **kwargs) + return spack.builder.create(self).with_or_without(*args, **kwargs) @spack.builder.builder("autotools") -class AutotoolsBuilder(BaseBuilder): +class AutotoolsBuilder(BuilderWithDefaults): """The autotools builder encodes the default way of installing software built with autotools. It has four phases that can be overridden, if need be: @@ -157,7 +160,7 @@ class AutotoolsBuilder(BaseBuilder): install_libtool_archives = False @property - def patch_config_files(self): + def patch_config_files(self) -> bool: """Whether to update old ``config.guess`` and ``config.sub`` files distributed with the tarball. @@ -177,7 +180,7 @@ def patch_config_files(self): ) @property - def _removed_la_files_log(self): + def _removed_la_files_log(self) -> str: """File containing the list of removed libtool archives""" build_dir = self.build_directory if not os.path.isabs(self.build_directory): @@ -185,15 +188,15 @@ def _removed_la_files_log(self): return os.path.join(build_dir, "removed_la_files.txt") @property - def archive_files(self): + def archive_files(self) -> List[str]: """Files to archive for packages based on autotools""" files = [os.path.join(self.build_directory, "config.log")] if not self.install_libtool_archives: files.append(self._removed_la_files_log) return files - @spack.builder.run_after("autoreconf") - def _do_patch_config_files(self): + @spack.phase_callbacks.run_after("autoreconf") + def _do_patch_config_files(self) -> None: """Some packages ship with older config.guess/config.sub files and need to have these updated when installed on a newer architecture. @@ -294,7 +297,7 @@ def runs_ok(script_abs_path): and set the prefix to the directory containing the `config.guess` and `config.sub` files. """ - raise spack.error.InstallError(msg.format(", ".join(to_be_found), self.name)) + raise spack.error.InstallError(msg.format(", ".join(to_be_found), self.pkg.name)) # Copy the good files over the bad ones for abs_path in to_be_patched: @@ -304,8 +307,8 @@ def runs_ok(script_abs_path): fs.copy(substitutes[name], abs_path) os.chmod(abs_path, mode) - @spack.builder.run_before("configure") - def _patch_usr_bin_file(self): + @spack.phase_callbacks.run_before("configure") + def _patch_usr_bin_file(self) -> None: """On NixOS file is not available in /usr/bin/file. Patch configure scripts to use file from path.""" @@ -316,8 +319,8 @@ def _patch_usr_bin_file(self): with fs.keep_modification_time(*x.filenames): x.filter(regex="/usr/bin/file", repl="file", string=True) - @spack.builder.run_before("configure") - def _set_autotools_environment_variables(self): + @spack.phase_callbacks.run_before("configure") + def _set_autotools_environment_variables(self) -> None: """Many autotools builds use a version of mknod.m4 that fails when running as root unless FORCE_UNSAFE_CONFIGURE is set to 1. @@ -330,8 +333,8 @@ def _set_autotools_environment_variables(self): """ os.environ["FORCE_UNSAFE_CONFIGURE"] = "1" - @spack.builder.run_before("configure") - def _do_patch_libtool_configure(self): + @spack.phase_callbacks.run_before("configure") + def _do_patch_libtool_configure(self) -> None: """Patch bugs that propagate from libtool macros into "configure" and further into "libtool". Note that patches that can be fixed by patching "libtool" directly should be implemented in the _do_patch_libtool method @@ -358,8 +361,8 @@ def _do_patch_libtool_configure(self): # Support Libtool 2.4.2 and older: x.filter(regex=r'^(\s*test \$p = "-R")(; then\s*)$', repl=r'\1 || test x-l = x"$p"\2') - @spack.builder.run_after("configure") - def _do_patch_libtool(self): + @spack.phase_callbacks.run_after("configure") + def _do_patch_libtool(self) -> None: """If configure generates a "libtool" script that does not correctly detect the compiler (and patch_libtool is set), patch in the correct values for libtool variables. @@ -507,27 +510,64 @@ def _do_patch_libtool(self): ) @property - def configure_directory(self): + def configure_directory(self) -> str: """Return the directory where 'configure' resides.""" return self.pkg.stage.source_path @property - def configure_abs_path(self): + def configure_abs_path(self) -> str: # Absolute path to configure configure_abs_path = os.path.join(os.path.abspath(self.configure_directory), "configure") return configure_abs_path @property - def build_directory(self): + def build_directory(self) -> str: """Override to provide another place to build the package""" return self.configure_directory - @spack.builder.run_before("autoreconf") - def delete_configure_to_force_update(self): + @spack.phase_callbacks.run_before("autoreconf") + def delete_configure_to_force_update(self) -> None: if self.force_autoreconf: fs.force_remove(self.configure_abs_path) - def autoreconf(self, pkg, spec, prefix): + @property + def autoreconf_search_path_args(self) -> List[str]: + """Search path includes for autoreconf. Add an -I flag for all `aclocal` dirs + of build deps, skips the default path of automake, move external include + flags to the back, since they might pull in unrelated m4 files shadowing + spack dependencies.""" + return _autoreconf_search_path_args(self.spec) + + @spack.phase_callbacks.run_after("autoreconf") + def set_configure_or_die(self) -> None: + """Ensure the presence of a "configure" script, or raise. If the "configure" + is found, a module level attribute is set. + + Raises: + RuntimeError: if the "configure" script is not found + """ + # Check if the "configure" script is there. If not raise a RuntimeError. + if not os.path.exists(self.configure_abs_path): + msg = "configure script not found in {0}" + raise RuntimeError(msg.format(self.configure_directory)) + + # Monkey-patch the configure script in the corresponding module + globals_for_pkg = spack.build_environment.ModuleChangePropagator(self.pkg) + globals_for_pkg.configure = Executable(self.configure_abs_path) + globals_for_pkg.propagate_changes_to_mro() + + def configure_args(self) -> List[str]: + """Return the list of all the arguments that must be passed to configure, + except ``--prefix`` which will be pre-pended to the list. + """ + return [] + + def autoreconf( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Not needed usually, configure should be already there""" # If configure exists nothing needs to be done @@ -554,39 +594,12 @@ def autoreconf(self, pkg, spec, prefix): autoreconf_args += self.autoreconf_extra_args self.pkg.module.autoreconf(*autoreconf_args) - @property - def autoreconf_search_path_args(self): - """Search path includes for autoreconf. Add an -I flag for all `aclocal` dirs - of build deps, skips the default path of automake, move external include - flags to the back, since they might pull in unrelated m4 files shadowing - spack dependencies.""" - return _autoreconf_search_path_args(self.spec) - - @spack.builder.run_after("autoreconf") - def set_configure_or_die(self): - """Ensure the presence of a "configure" script, or raise. If the "configure" - is found, a module level attribute is set. - - Raises: - RuntimeError: if the "configure" script is not found - """ - # Check if the "configure" script is there. If not raise a RuntimeError. - if not os.path.exists(self.configure_abs_path): - msg = "configure script not found in {0}" - raise RuntimeError(msg.format(self.configure_directory)) - - # Monkey-patch the configure script in the corresponding module - globals_for_pkg = spack.build_environment.ModuleChangePropagator(self.pkg) - globals_for_pkg.configure = Executable(self.configure_abs_path) - globals_for_pkg.propagate_changes_to_mro() - - def configure_args(self): - """Return the list of all the arguments that must be passed to configure, - except ``--prefix`` which will be pre-pended to the list. - """ - return [] - - def configure(self, pkg, spec, prefix): + def configure( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Run "configure", with the arguments specified by the builder and an appropriately set prefix. """ @@ -597,7 +610,12 @@ def configure(self, pkg, spec, prefix): with fs.working_dir(self.build_directory, create=True): pkg.module.configure(*options) - def build(self, pkg, spec, prefix): + def build( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Run "make" on the build targets specified by the builder.""" # See https://autotools.io/automake/silent.html params = ["V=1"] @@ -605,41 +623,49 @@ def build(self, pkg, spec, prefix): with fs.working_dir(self.build_directory): pkg.module.make(*params) - def install(self, pkg, spec, prefix): + def install( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Run "make" on the install targets specified by the builder.""" with fs.working_dir(self.build_directory): pkg.module.make(*self.install_targets) - spack.builder.run_after("build")(execute_build_time_tests) + spack.phase_callbacks.run_after("build")(execute_build_time_tests) - def check(self): + def check(self) -> None: """Run "make" on the ``test`` and ``check`` targets, if found.""" with fs.working_dir(self.build_directory): self.pkg._if_make_target_execute("test") self.pkg._if_make_target_execute("check") def _activate_or_not( - self, name, activation_word, deactivation_word, activation_value=None, variant=None - ): + self, + name: str, + activation_word: str, + deactivation_word: str, + activation_value: Optional[Union[Callable, str]] = None, + variant=None, + ) -> List[str]: """This function contain the current implementation details of :meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without` and :meth:`~spack.build_systems.autotools.AutotoolsBuilder.enable_or_disable`. Args: - name (str): name of the option that is being activated or not - activation_word (str): the default activation word ('with' in the - case of ``with_or_without``) - deactivation_word (str): the default deactivation word ('without' - in the case of ``with_or_without``) - activation_value (typing.Callable): callable that accepts a single - value. This value is either one of the allowed values for a - multi-valued variant or the name of a bool-valued variant. + name: name of the option that is being activated or not + activation_word: the default activation word ('with' in the case of + ``with_or_without``) + deactivation_word: the default deactivation word ('without' in the case of + ``with_or_without``) + activation_value: callable that accepts a single value. This value is either one of the + allowed values for a multi-valued variant or the name of a bool-valued variant. Returns the parameter to be used when the value is activated. - The special value 'prefix' can also be assigned and will return + The special value "prefix" can also be assigned and will return ``spec[name].prefix`` as activation parameter. - variant (str): name of the variant that is being processed - (if different from option name) + variant: name of the variant that is being processed (if different from option name) Examples: @@ -647,19 +673,19 @@ def _activate_or_not( .. code-block:: python - variant('foo', values=('x', 'y'), description='') - variant('bar', default=True, description='') - variant('ba_z', default=True, description='') + variant("foo", values=("x", "y"), description=") + variant("bar", default=True, description=") + variant("ba_z", default=True, description=") calling this function like: .. code-block:: python _activate_or_not( - 'foo', 'with', 'without', activation_value='prefix' + "foo", "with", "without", activation_value="prefix" ) - _activate_or_not('bar', 'with', 'without') - _activate_or_not('ba-z', 'with', 'without', variant='ba_z') + _activate_or_not("bar", "with", "without") + _activate_or_not("ba-z", "with", "without", variant="ba_z") will generate the following configuration options: @@ -679,8 +705,8 @@ def _activate_or_not( Raises: KeyError: if name is not among known variants """ - spec = self.pkg.spec - args = [] + spec: spack.spec.Spec = self.pkg.spec + args: List[str] = [] if activation_value == "prefix": activation_value = lambda x: spec[x].prefix @@ -698,7 +724,7 @@ def _activate_or_not( # Create a list of pairs. Each pair includes a configuration # option and whether or not that option is activated vdef = self.pkg.get_variant(variant) - if set(vdef.values) == set((True, False)): + if set(vdef.values) == set((True, False)): # type: ignore # BoolValuedVariant carry information about a single option. # Nonetheless, for uniformity of treatment we'll package them # in an iterable of one element. @@ -709,14 +735,12 @@ def _activate_or_not( # package's build system. It excludes values which have special # meanings and do not correspond to features (e.g. "none") feature_values = getattr(vdef.values, "feature_values", None) or vdef.values - options = [(value, f"{variant}={value}" in spec) for value in feature_values] + options = [(v, f"{variant}={v}" in spec) for v in feature_values] # type: ignore # For each allowed value in the list of values for option_value, activated in options: # Search for an override in the package for this value - override_name = "{0}_or_{1}_{2}".format( - activation_word, deactivation_word, option_value - ) + override_name = f"{activation_word}_or_{deactivation_word}_{option_value}" line_generator = getattr(self, override_name, None) or getattr( self.pkg, override_name, None ) @@ -725,19 +749,24 @@ def _activate_or_not( def _default_generator(is_activated): if is_activated: - line = "--{0}-{1}".format(activation_word, option_value) + line = f"--{activation_word}-{option_value}" if activation_value is not None and activation_value( option_value ): # NOQA=ignore=E501 - line += "={0}".format(activation_value(option_value)) + line = f"{line}={activation_value(option_value)}" return line - return "--{0}-{1}".format(deactivation_word, option_value) + return f"--{deactivation_word}-{option_value}" line_generator = _default_generator args.append(line_generator(activated)) return args - def with_or_without(self, name, activation_value=None, variant=None): + def with_or_without( + self, + name: str, + activation_value: Optional[Union[Callable, str]] = None, + variant: Optional[str] = None, + ) -> List[str]: """Inspects a variant and returns the arguments that activate or deactivate the selected feature(s) for the configure options. @@ -752,12 +781,11 @@ def with_or_without(self, name, activation_value=None, variant=None): ``variant=value`` is in the spec. Args: - name (str): name of a valid multi-valued variant - activation_value (typing.Callable): callable that accepts a single - value and returns the parameter to be used leading to an entry - of the type ``--with-{name}={parameter}``. + name: name of a valid multi-valued variant + activation_value: callable that accepts a single value and returns the parameter to be + used leading to an entry of the type ``--with-{name}={parameter}``. - The special value 'prefix' can also be assigned and will return + The special value "prefix" can also be assigned and will return ``spec[name].prefix`` as activation parameter. Returns: @@ -765,18 +793,22 @@ def with_or_without(self, name, activation_value=None, variant=None): """ return self._activate_or_not(name, "with", "without", activation_value, variant) - def enable_or_disable(self, name, activation_value=None, variant=None): + def enable_or_disable( + self, + name: str, + activation_value: Optional[Union[Callable, str]] = None, + variant: Optional[str] = None, + ) -> List[str]: """Same as :meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without` but substitute ``with`` with ``enable`` and ``without`` with ``disable``. Args: - name (str): name of a valid multi-valued variant - activation_value (typing.Callable): if present accepts a single value - and returns the parameter to be used leading to an entry of the - type ``--enable-{name}={parameter}`` + name: name of a valid multi-valued variant + activation_value: if present accepts a single value and returns the parameter to be + used leading to an entry of the type ``--enable-{name}={parameter}`` - The special value 'prefix' can also be assigned and will return + The special value "prefix" can also be assigned and will return ``spec[name].prefix`` as activation parameter. Returns: @@ -784,15 +816,15 @@ def enable_or_disable(self, name, activation_value=None, variant=None): """ return self._activate_or_not(name, "enable", "disable", activation_value, variant) - spack.builder.run_after("install")(execute_install_time_tests) + spack.phase_callbacks.run_after("install")(execute_install_time_tests) - def installcheck(self): + def installcheck(self) -> None: """Run "make" on the ``installcheck`` target, if found.""" with fs.working_dir(self.build_directory): self.pkg._if_make_target_execute("installcheck") - @spack.builder.run_after("install") - def remove_libtool_archives(self): + @spack.phase_callbacks.run_after("install") + def remove_libtool_archives(self) -> None: """Remove all .la files in prefix sub-folders if the package sets ``install_libtool_archives`` to be False. """ @@ -814,12 +846,13 @@ def setup_build_environment(self, env): env.set("MACOSX_DEPLOYMENT_TARGET", "10.16") # On macOS, force rpaths for shared library IDs and remove duplicate rpaths - spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups) + spack.phase_callbacks.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups) -def _autoreconf_search_path_args(spec): - dirs_seen = set() - flags_spack, flags_external = [], [] +def _autoreconf_search_path_args(spec: spack.spec.Spec) -> List[str]: + dirs_seen: Set[Tuple[int, int]] = set() + flags_spack: List[str] = [] + flags_external: List[str] = [] # We don't want to add an include flag for automake's default search path. for automake in spec.dependencies(name="automake", deptype="build"): diff --git a/lib/spack/spack/build_systems/cached_cmake.py b/lib/spack/spack/build_systems/cached_cmake.py index d9b415cbc76..c22068e5ca3 100644 --- a/lib/spack/spack/build_systems/cached_cmake.py +++ b/lib/spack/spack/build_systems/cached_cmake.py @@ -10,7 +10,7 @@ import llnl.util.filesystem as fs import llnl.util.tty as tty -import spack.builder +import spack.phase_callbacks from .cmake import CMakeBuilder, CMakePackage @@ -332,7 +332,7 @@ def std_cmake_args(self): args.extend(["-C", self.cache_path]) return args - @spack.builder.run_after("install") + @spack.phase_callbacks.run_after("install") def install_cmake_cache(self): fs.mkdirp(self.pkg.spec.prefix.share.cmake) fs.install(self.cache_path, self.pkg.spec.prefix.share.cmake) diff --git a/lib/spack/spack/build_systems/cargo.py b/lib/spack/spack/build_systems/cargo.py index 4dded46559f..a27ded465c8 100644 --- a/lib/spack/spack/build_systems/cargo.py +++ b/lib/spack/spack/build_systems/cargo.py @@ -7,10 +7,11 @@ import spack.builder import spack.package_base +import spack.phase_callbacks from spack.directives import build_system, depends_on from spack.multimethod import when -from ._checks import BaseBuilder, execute_install_time_tests +from ._checks import BuilderWithDefaults, execute_install_time_tests class CargoPackage(spack.package_base.PackageBase): @@ -27,7 +28,7 @@ class CargoPackage(spack.package_base.PackageBase): @spack.builder.builder("cargo") -class CargoBuilder(BaseBuilder): +class CargoBuilder(BuilderWithDefaults): """The Cargo builder encodes the most common way of building software with a rust Cargo.toml file. It has two phases that can be overridden, if need be: @@ -77,7 +78,7 @@ def install(self, pkg, spec, prefix): with fs.working_dir(self.build_directory): fs.install_tree("out", prefix) - spack.builder.run_after("install")(execute_install_time_tests) + spack.phase_callbacks.run_after("install")(execute_install_time_tests) def check(self): """Run "cargo test".""" diff --git a/lib/spack/spack/build_systems/cmake.py b/lib/spack/spack/build_systems/cmake.py index f6d346155c2..dd1261c811c 100644 --- a/lib/spack/spack/build_systems/cmake.py +++ b/lib/spack/spack/build_systems/cmake.py @@ -9,7 +9,7 @@ import re import sys from itertools import chain -from typing import List, Optional, Set, Tuple +from typing import Any, List, Optional, Set, Tuple import llnl.util.filesystem as fs from llnl.util.lang import stable_partition @@ -18,11 +18,14 @@ import spack.deptypes as dt import spack.error import spack.package_base +import spack.phase_callbacks +import spack.spec +import spack.util.prefix from spack.directives import build_system, conflicts, depends_on, variant from spack.multimethod import when from spack.util.environment import filter_system_paths -from ._checks import BaseBuilder, execute_build_time_tests +from ._checks import BuilderWithDefaults, execute_build_time_tests # Regex to extract the primary generator from the CMake generator # string. @@ -48,9 +51,9 @@ def _maybe_set_python_hints(pkg: spack.package_base.PackageBase, args: List[str] python_executable = pkg.spec["python"].command.path args.extend( [ - CMakeBuilder.define("PYTHON_EXECUTABLE", python_executable), - CMakeBuilder.define("Python_EXECUTABLE", python_executable), - CMakeBuilder.define("Python3_EXECUTABLE", python_executable), + define("PYTHON_EXECUTABLE", python_executable), + define("Python_EXECUTABLE", python_executable), + define("Python3_EXECUTABLE", python_executable), ] ) @@ -85,7 +88,7 @@ def _conditional_cmake_defaults(pkg: spack.package_base.PackageBase, args: List[ ipo = False if cmake.satisfies("@3.9:"): - args.append(CMakeBuilder.define("CMAKE_INTERPROCEDURAL_OPTIMIZATION", ipo)) + args.append(define("CMAKE_INTERPROCEDURAL_OPTIMIZATION", ipo)) # Disable Package Registry: export(PACKAGE) may put files in the user's home directory, and # find_package may search there. This is not what we want. @@ -93,30 +96,36 @@ def _conditional_cmake_defaults(pkg: spack.package_base.PackageBase, args: List[ # Do not populate CMake User Package Registry if cmake.satisfies("@3.15:"): # see https://cmake.org/cmake/help/latest/policy/CMP0090.html - args.append(CMakeBuilder.define("CMAKE_POLICY_DEFAULT_CMP0090", "NEW")) + args.append(define("CMAKE_POLICY_DEFAULT_CMP0090", "NEW")) elif cmake.satisfies("@3.1:"): # see https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_NO_PACKAGE_REGISTRY.html - args.append(CMakeBuilder.define("CMAKE_EXPORT_NO_PACKAGE_REGISTRY", True)) + args.append(define("CMAKE_EXPORT_NO_PACKAGE_REGISTRY", True)) # Do not use CMake User/System Package Registry # https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#disabling-the-package-registry if cmake.satisfies("@3.16:"): - args.append(CMakeBuilder.define("CMAKE_FIND_USE_PACKAGE_REGISTRY", False)) + args.append(define("CMAKE_FIND_USE_PACKAGE_REGISTRY", False)) elif cmake.satisfies("@3.1:3.15"): - args.append(CMakeBuilder.define("CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY", False)) - args.append(CMakeBuilder.define("CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY", False)) + args.append(define("CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY", False)) + args.append(define("CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY", False)) # Export a compilation database if supported. if _supports_compilation_databases(pkg): - args.append(CMakeBuilder.define("CMAKE_EXPORT_COMPILE_COMMANDS", True)) + args.append(define("CMAKE_EXPORT_COMPILE_COMMANDS", True)) # Enable MACOSX_RPATH by default when cmake_minimum_required < 3 # https://cmake.org/cmake/help/latest/policy/CMP0042.html if pkg.spec.satisfies("platform=darwin") and cmake.satisfies("@3:"): - args.append(CMakeBuilder.define("CMAKE_POLICY_DEFAULT_CMP0042", "NEW")) + args.append(define("CMAKE_POLICY_DEFAULT_CMP0042", "NEW")) + + # Disable find package's config mode for versions of Boost that + # didn't provide it. See https://github.com/spack/spack/issues/20169 + # and https://cmake.org/cmake/help/latest/module/FindBoost.html + if pkg.spec.satisfies("^boost@:1.69.0"): + args.append(define("Boost_NO_BOOST_CMAKE", True)) -def generator(*names: str, default: Optional[str] = None): +def generator(*names: str, default: Optional[str] = None) -> None: """The build system generator to use. See ``cmake --help`` for a list of valid generators. @@ -263,15 +272,15 @@ def flags_to_build_system_args(self, flags): # Legacy methods (used by too many packages to change them, # need to forward to the builder) - def define(self, *args, **kwargs): - return self.builder.define(*args, **kwargs) + def define(self, cmake_var: str, value: Any) -> str: + return define(cmake_var, value) - def define_from_variant(self, *args, **kwargs): - return self.builder.define_from_variant(*args, **kwargs) + def define_from_variant(self, cmake_var: str, variant: Optional[str] = None) -> str: + return define_from_variant(self, cmake_var, variant) @spack.builder.builder("cmake") -class CMakeBuilder(BaseBuilder): +class CMakeBuilder(BuilderWithDefaults): """The cmake builder encodes the default way of building software with CMake. IT has three phases that can be overridden: @@ -321,15 +330,15 @@ class CMakeBuilder(BaseBuilder): build_time_test_callbacks = ["check"] @property - def archive_files(self): + def archive_files(self) -> List[str]: """Files to archive for packages based on CMake""" files = [os.path.join(self.build_directory, "CMakeCache.txt")] - if _supports_compilation_databases(self): + if _supports_compilation_databases(self.pkg): files.append(os.path.join(self.build_directory, "compile_commands.json")) return files @property - def root_cmakelists_dir(self): + def root_cmakelists_dir(self) -> str: """The relative path to the directory containing CMakeLists.txt This path is relative to the root of the extracted tarball, @@ -338,16 +347,17 @@ def root_cmakelists_dir(self): return self.pkg.stage.source_path @property - def generator(self): + def generator(self) -> str: if self.spec.satisfies("generator=make"): return "Unix Makefiles" if self.spec.satisfies("generator=ninja"): return "Ninja" - msg = f'{self.spec.format()} has an unsupported value for the "generator" variant' - raise ValueError(msg) + raise ValueError( + f'{self.spec.format()} has an unsupported value for the "generator" variant' + ) @property - def std_cmake_args(self): + def std_cmake_args(self) -> List[str]: """Standard cmake arguments provided as a property for convenience of package writers """ @@ -356,7 +366,9 @@ def std_cmake_args(self): return args @staticmethod - def std_args(pkg, generator=None): + def std_args( + pkg: spack.package_base.PackageBase, generator: Optional[str] = None + ) -> List[str]: """Computes the standard cmake arguments for a generic package""" default_generator = "Ninja" if sys.platform == "win32" else "Unix Makefiles" generator = generator or default_generator @@ -373,7 +385,6 @@ def std_args(pkg, generator=None): except KeyError: build_type = "RelWithDebInfo" - define = CMakeBuilder.define args = [ "-G", generator, @@ -405,152 +416,31 @@ def std_args(pkg, generator=None): return args @staticmethod - def define_cuda_architectures(pkg): - """Returns the str ``-DCMAKE_CUDA_ARCHITECTURES:STRING=(expanded cuda_arch)``. - - ``cuda_arch`` is variant composed of a list of target CUDA architectures and - it is declared in the cuda package. - - This method is no-op for cmake<3.18 and when ``cuda_arch`` variant is not set. - - """ - cmake_flag = str() - if "cuda_arch" in pkg.spec.variants and pkg.spec.satisfies("^cmake@3.18:"): - cmake_flag = CMakeBuilder.define( - "CMAKE_CUDA_ARCHITECTURES", pkg.spec.variants["cuda_arch"].value - ) - - return cmake_flag + def define_cuda_architectures(pkg: spack.package_base.PackageBase) -> str: + return define_cuda_architectures(pkg) @staticmethod - def define_hip_architectures(pkg): - """Returns the str ``-DCMAKE_HIP_ARCHITECTURES:STRING=(expanded amdgpu_target)``. - - ``amdgpu_target`` is variant composed of a list of the target HIP - architectures and it is declared in the rocm package. - - This method is no-op for cmake<3.18 and when ``amdgpu_target`` variant is - not set. - - """ - cmake_flag = str() - if "amdgpu_target" in pkg.spec.variants and pkg.spec.satisfies("^cmake@3.21:"): - cmake_flag = CMakeBuilder.define( - "CMAKE_HIP_ARCHITECTURES", pkg.spec.variants["amdgpu_target"].value - ) - - return cmake_flag + def define_hip_architectures(pkg: spack.package_base.PackageBase) -> str: + return define_hip_architectures(pkg) @staticmethod - def define(cmake_var, value): - """Return a CMake command line argument that defines a variable. + def define(cmake_var: str, value: Any) -> str: + return define(cmake_var, value) - The resulting argument will convert boolean values to OFF/ON - and lists/tuples to CMake semicolon-separated string lists. All other - values will be interpreted as strings. - - Examples: - - .. code-block:: python - - [define('BUILD_SHARED_LIBS', True), - define('CMAKE_CXX_STANDARD', 14), - define('swr', ['avx', 'avx2'])] - - will generate the following configuration options: - - .. code-block:: console - - ["-DBUILD_SHARED_LIBS:BOOL=ON", - "-DCMAKE_CXX_STANDARD:STRING=14", - "-DSWR:STRING=avx;avx2] - - """ - # Create a list of pairs. Each pair includes a configuration - # option and whether or not that option is activated - if isinstance(value, bool): - kind = "BOOL" - value = "ON" if value else "OFF" - else: - kind = "STRING" - if isinstance(value, collections.abc.Sequence) and not isinstance(value, str): - value = ";".join(str(v) for v in value) - else: - value = str(value) - - return "".join(["-D", cmake_var, ":", kind, "=", value]) - - def define_from_variant(self, cmake_var, variant=None): - """Return a CMake command line argument from the given variant's value. - - The optional ``variant`` argument defaults to the lower-case transform - of ``cmake_var``. - - This utility function is similar to - :meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without`. - - Examples: - - Given a package with: - - .. code-block:: python - - variant('cxxstd', default='11', values=('11', '14'), - multi=False, description='') - variant('shared', default=True, description='') - variant('swr', values=any_combination_of('avx', 'avx2'), - description='') - - calling this function like: - - .. code-block:: python - - [self.define_from_variant('BUILD_SHARED_LIBS', 'shared'), - self.define_from_variant('CMAKE_CXX_STANDARD', 'cxxstd'), - self.define_from_variant('SWR')] - - will generate the following configuration options: - - .. code-block:: console - - ["-DBUILD_SHARED_LIBS:BOOL=ON", - "-DCMAKE_CXX_STANDARD:STRING=14", - "-DSWR:STRING=avx;avx2] - - for `` cxxstd=14 +shared swr=avx,avx2`` - - Note: if the provided variant is conditional, and the condition is not met, - this function returns an empty string. CMake discards empty strings - provided on the command line. - """ - - if variant is None: - variant = cmake_var.lower() - - if not self.pkg.has_variant(variant): - raise KeyError('"{0}" is not a variant of "{1}"'.format(variant, self.pkg.name)) - - if variant not in self.pkg.spec.variants: - return "" - - value = self.pkg.spec.variants[variant].value - if isinstance(value, (tuple, list)): - # Sort multi-valued variants for reproducibility - value = sorted(value) - - return self.define(cmake_var, value) + def define_from_variant(self, cmake_var: str, variant: Optional[str] = None) -> str: + return define_from_variant(self.pkg, cmake_var, variant) @property - def build_dirname(self): + def build_dirname(self) -> str: """Directory name to use when building the package.""" - return "spack-build-%s" % self.pkg.spec.dag_hash(7) + return f"spack-build-{self.pkg.spec.dag_hash(7)}" @property - def build_directory(self): + def build_directory(self) -> str: """Full-path to the directory to use when building the package.""" return os.path.join(self.pkg.stage.path, self.build_dirname) - def cmake_args(self): + def cmake_args(self) -> List[str]: """List of all the arguments that must be passed to cmake, except: * CMAKE_INSTALL_PREFIX @@ -560,7 +450,12 @@ def cmake_args(self): """ return [] - def cmake(self, pkg, spec, prefix): + def cmake( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Runs ``cmake`` in the build directory""" # skip cmake phase if it is an incremental develop build @@ -575,7 +470,12 @@ def cmake(self, pkg, spec, prefix): with fs.working_dir(self.build_directory, create=True): pkg.module.cmake(*options) - def build(self, pkg, spec, prefix): + def build( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Make the build targets""" with fs.working_dir(self.build_directory): if self.generator == "Unix Makefiles": @@ -584,7 +484,12 @@ def build(self, pkg, spec, prefix): self.build_targets.append("-v") pkg.module.ninja(*self.build_targets) - def install(self, pkg, spec, prefix): + def install( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Make the install targets""" with fs.working_dir(self.build_directory): if self.generator == "Unix Makefiles": @@ -592,9 +497,9 @@ def install(self, pkg, spec, prefix): elif self.generator == "Ninja": pkg.module.ninja(*self.install_targets) - spack.builder.run_after("build")(execute_build_time_tests) + spack.phase_callbacks.run_after("build")(execute_build_time_tests) - def check(self): + def check(self) -> None: """Search the CMake-generated files for the targets ``test`` and ``check``, and runs them if found. """ @@ -605,3 +510,133 @@ def check(self): elif self.generator == "Ninja": self.pkg._if_ninja_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL") self.pkg._if_ninja_target_execute("check") + + +def define(cmake_var: str, value: Any) -> str: + """Return a CMake command line argument that defines a variable. + + The resulting argument will convert boolean values to OFF/ON and lists/tuples to CMake + semicolon-separated string lists. All other values will be interpreted as strings. + + Examples: + + .. code-block:: python + + [define("BUILD_SHARED_LIBS", True), + define("CMAKE_CXX_STANDARD", 14), + define("swr", ["avx", "avx2"])] + + will generate the following configuration options: + + .. code-block:: console + + ["-DBUILD_SHARED_LIBS:BOOL=ON", + "-DCMAKE_CXX_STANDARD:STRING=14", + "-DSWR:STRING=avx;avx2] + + """ + # Create a list of pairs. Each pair includes a configuration + # option and whether or not that option is activated + if isinstance(value, bool): + kind = "BOOL" + value = "ON" if value else "OFF" + else: + kind = "STRING" + if isinstance(value, collections.abc.Sequence) and not isinstance(value, str): + value = ";".join(str(v) for v in value) + else: + value = str(value) + + return "".join(["-D", cmake_var, ":", kind, "=", value]) + + +def define_from_variant( + pkg: spack.package_base.PackageBase, cmake_var: str, variant: Optional[str] = None +) -> str: + """Return a CMake command line argument from the given variant's value. + + The optional ``variant`` argument defaults to the lower-case transform + of ``cmake_var``. + + Examples: + + Given a package with: + + .. code-block:: python + + variant("cxxstd", default="11", values=("11", "14"), + multi=False, description="") + variant("shared", default=True, description="") + variant("swr", values=any_combination_of("avx", "avx2"), + description="") + + calling this function like: + + .. code-block:: python + + [ + self.define_from_variant("BUILD_SHARED_LIBS", "shared"), + self.define_from_variant("CMAKE_CXX_STANDARD", "cxxstd"), + self.define_from_variant("SWR"), + ] + + will generate the following configuration options: + + .. code-block:: console + + [ + "-DBUILD_SHARED_LIBS:BOOL=ON", + "-DCMAKE_CXX_STANDARD:STRING=14", + "-DSWR:STRING=avx;avx2", + ] + + for `` cxxstd=14 +shared swr=avx,avx2`` + + Note: if the provided variant is conditional, and the condition is not met, this function + returns an empty string. CMake discards empty strings provided on the command line. + """ + if variant is None: + variant = cmake_var.lower() + + if not pkg.has_variant(variant): + raise KeyError('"{0}" is not a variant of "{1}"'.format(variant, pkg.name)) + + if variant not in pkg.spec.variants: + return "" + + value = pkg.spec.variants[variant].value + if isinstance(value, (tuple, list)): + # Sort multi-valued variants for reproducibility + value = sorted(value) + + return define(cmake_var, value) + + +def define_hip_architectures(pkg: spack.package_base.PackageBase) -> str: + """Returns the str ``-DCMAKE_HIP_ARCHITECTURES:STRING=(expanded amdgpu_target)``. + + ``amdgpu_target`` is variant composed of a list of the target HIP + architectures and it is declared in the rocm package. + + This method is no-op for cmake<3.18 and when ``amdgpu_target`` variant is + not set. + + """ + if "amdgpu_target" in pkg.spec.variants and pkg.spec.satisfies("^cmake@3.21:"): + return define("CMAKE_HIP_ARCHITECTURES", pkg.spec.variants["amdgpu_target"].value) + + return "" + + +def define_cuda_architectures(pkg: spack.package_base.PackageBase) -> str: + """Returns the str ``-DCMAKE_CUDA_ARCHITECTURES:STRING=(expanded cuda_arch)``. + + ``cuda_arch`` is variant composed of a list of target CUDA architectures and + it is declared in the cuda package. + + This method is no-op for cmake<3.18 and when ``cuda_arch`` variant is not set. + + """ + if "cuda_arch" in pkg.spec.variants and pkg.spec.satisfies("^cmake@3.18:"): + return define("CMAKE_CUDA_ARCHITECTURES", pkg.spec.variants["cuda_arch"].value) + return "" diff --git a/lib/spack/spack/build_systems/generic.py b/lib/spack/spack/build_systems/generic.py index 5a0446a4ad6..781aba9b0fb 100644 --- a/lib/spack/spack/build_systems/generic.py +++ b/lib/spack/spack/build_systems/generic.py @@ -7,8 +7,9 @@ import spack.builder import spack.directives import spack.package_base +import spack.phase_callbacks -from ._checks import BaseBuilder, apply_macos_rpath_fixups, execute_install_time_tests +from ._checks import BuilderWithDefaults, apply_macos_rpath_fixups, execute_install_time_tests class Package(spack.package_base.PackageBase): @@ -26,7 +27,7 @@ class Package(spack.package_base.PackageBase): @spack.builder.builder("generic") -class GenericBuilder(BaseBuilder): +class GenericBuilder(BuilderWithDefaults): """A builder for a generic build system, that require packagers to implement an "install" phase. """ @@ -44,7 +45,7 @@ class GenericBuilder(BaseBuilder): install_time_test_callbacks = [] # On macOS, force rpaths for shared library IDs and remove duplicate rpaths - spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups) + spack.phase_callbacks.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups) # unconditionally perform any post-install phase tests - spack.builder.run_after("install")(execute_install_time_tests) + spack.phase_callbacks.run_after("install")(execute_install_time_tests) diff --git a/lib/spack/spack/build_systems/go.py b/lib/spack/spack/build_systems/go.py index ae588789c77..bcd38d65bd1 100644 --- a/lib/spack/spack/build_systems/go.py +++ b/lib/spack/spack/build_systems/go.py @@ -7,10 +7,11 @@ import spack.builder import spack.package_base +import spack.phase_callbacks from spack.directives import build_system, extends from spack.multimethod import when -from ._checks import BaseBuilder, execute_install_time_tests +from ._checks import BuilderWithDefaults, execute_install_time_tests class GoPackage(spack.package_base.PackageBase): @@ -32,7 +33,7 @@ class GoPackage(spack.package_base.PackageBase): @spack.builder.builder("go") -class GoBuilder(BaseBuilder): +class GoBuilder(BuilderWithDefaults): """The Go builder encodes the most common way of building software with a golang go.mod file. It has two phases that can be overridden, if need be: @@ -99,7 +100,7 @@ def install(self, pkg, spec, prefix): fs.mkdirp(prefix.bin) fs.install(pkg.name, prefix.bin) - spack.builder.run_after("install")(execute_install_time_tests) + spack.phase_callbacks.run_after("install")(execute_install_time_tests) def check(self): """Run ``go test .`` in the source directory""" diff --git a/lib/spack/spack/build_systems/intel.py b/lib/spack/spack/build_systems/intel.py index 9f82bae14d3..0a791f89993 100644 --- a/lib/spack/spack/build_systems/intel.py +++ b/lib/spack/spack/build_systems/intel.py @@ -22,8 +22,8 @@ install, ) -import spack.builder import spack.error +import spack.phase_callbacks from spack.build_environment import dso_suffix from spack.error import InstallError from spack.util.environment import EnvironmentModifications @@ -1163,7 +1163,7 @@ def _determine_license_type(self): debug_print(license_type) return license_type - @spack.builder.run_before("install") + @spack.phase_callbacks.run_before("install") def configure(self): """Generates the silent.cfg file to pass to installer.sh. @@ -1250,7 +1250,7 @@ def install(self, spec, prefix): for f in glob.glob("%s/intel*log" % tmpdir): install(f, dst) - @spack.builder.run_after("install") + @spack.phase_callbacks.run_after("install") def validate_install(self): # Sometimes the installer exits with an error but doesn't pass a # non-zero exit code to spack. Check for the existence of a 'bin' @@ -1258,7 +1258,7 @@ def validate_install(self): if not os.path.exists(self.prefix.bin): raise InstallError("The installer has failed to install anything.") - @spack.builder.run_after("install") + @spack.phase_callbacks.run_after("install") def configure_rpath(self): if "+rpath" not in self.spec: return @@ -1276,7 +1276,7 @@ def configure_rpath(self): with open(compiler_cfg, "w") as fh: fh.write("-Xlinker -rpath={0}\n".format(compilers_lib_dir)) - @spack.builder.run_after("install") + @spack.phase_callbacks.run_after("install") def configure_auto_dispatch(self): if self._has_compilers: if "auto_dispatch=none" in self.spec: @@ -1300,7 +1300,7 @@ def configure_auto_dispatch(self): with open(compiler_cfg, "a") as fh: fh.write("-ax{0}\n".format(",".join(ad))) - @spack.builder.run_after("install") + @spack.phase_callbacks.run_after("install") def filter_compiler_wrappers(self): if ("+mpi" in self.spec or self.provides("mpi")) and "~newdtags" in self.spec: bin_dir = self.component_bin_dir("mpi") @@ -1308,7 +1308,7 @@ def filter_compiler_wrappers(self): f = os.path.join(bin_dir, f) filter_file("-Xlinker --enable-new-dtags", " ", f, string=True) - @spack.builder.run_after("install") + @spack.phase_callbacks.run_after("install") def uninstall_ism(self): # The "Intel(R) Software Improvement Program" [ahem] gets installed, # apparently regardless of PHONEHOME_SEND_USAGE_DATA. @@ -1340,7 +1340,7 @@ def base_lib_dir(self): debug_print(d) return d - @spack.builder.run_after("install") + @spack.phase_callbacks.run_after("install") def modify_LLVMgold_rpath(self): """Add libimf.so and other required libraries to the RUNPATH of LLVMgold.so. diff --git a/lib/spack/spack/build_systems/makefile.py b/lib/spack/spack/build_systems/makefile.py index 25f25adfe3d..083cab3744f 100644 --- a/lib/spack/spack/build_systems/makefile.py +++ b/lib/spack/spack/build_systems/makefile.py @@ -8,11 +8,14 @@ import spack.builder import spack.package_base +import spack.phase_callbacks +import spack.spec +import spack.util.prefix from spack.directives import build_system, conflicts, depends_on from spack.multimethod import when from ._checks import ( - BaseBuilder, + BuilderWithDefaults, apply_macos_rpath_fixups, execute_build_time_tests, execute_install_time_tests, @@ -36,7 +39,7 @@ class MakefilePackage(spack.package_base.PackageBase): @spack.builder.builder("makefile") -class MakefileBuilder(BaseBuilder): +class MakefileBuilder(BuilderWithDefaults): """The Makefile builder encodes the most common way of building software with Makefiles. It has three phases that can be overridden, if need be: @@ -91,35 +94,50 @@ class MakefileBuilder(BaseBuilder): install_time_test_callbacks = ["installcheck"] @property - def build_directory(self): + def build_directory(self) -> str: """Return the directory containing the main Makefile.""" return self.pkg.stage.source_path - def edit(self, pkg, spec, prefix): + def edit( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Edit the Makefile before calling make. The default is a no-op.""" pass - def build(self, pkg, spec, prefix): + def build( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Run "make" on the build targets specified by the builder.""" with fs.working_dir(self.build_directory): pkg.module.make(*self.build_targets) - def install(self, pkg, spec, prefix): + def install( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Run "make" on the install targets specified by the builder.""" with fs.working_dir(self.build_directory): pkg.module.make(*self.install_targets) - spack.builder.run_after("build")(execute_build_time_tests) + spack.phase_callbacks.run_after("build")(execute_build_time_tests) - def check(self): + def check(self) -> None: """Run "make" on the ``test`` and ``check`` targets, if found.""" with fs.working_dir(self.build_directory): self.pkg._if_make_target_execute("test") self.pkg._if_make_target_execute("check") - spack.builder.run_after("install")(execute_install_time_tests) + spack.phase_callbacks.run_after("install")(execute_install_time_tests) - def installcheck(self): + def installcheck(self) -> None: """Searches the Makefile for an ``installcheck`` target and runs it if found. """ @@ -127,4 +145,4 @@ def installcheck(self): self.pkg._if_make_target_execute("installcheck") # On macOS, force rpaths for shared library IDs and remove duplicate rpaths - spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups) + spack.phase_callbacks.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups) diff --git a/lib/spack/spack/build_systems/maven.py b/lib/spack/spack/build_systems/maven.py index 809258d5dff..962d05f96c4 100644 --- a/lib/spack/spack/build_systems/maven.py +++ b/lib/spack/spack/build_systems/maven.py @@ -10,7 +10,7 @@ from spack.multimethod import when from spack.util.executable import which -from ._checks import BaseBuilder +from ._checks import BuilderWithDefaults class MavenPackage(spack.package_base.PackageBase): @@ -34,7 +34,7 @@ class MavenPackage(spack.package_base.PackageBase): @spack.builder.builder("maven") -class MavenBuilder(BaseBuilder): +class MavenBuilder(BuilderWithDefaults): """The Maven builder encodes the default way to build software with Maven. It has two phases that can be overridden, if need be: diff --git a/lib/spack/spack/build_systems/meson.py b/lib/spack/spack/build_systems/meson.py index cf3cd24e203..8a5ea5d850f 100644 --- a/lib/spack/spack/build_systems/meson.py +++ b/lib/spack/spack/build_systems/meson.py @@ -9,10 +9,13 @@ import spack.builder import spack.package_base +import spack.phase_callbacks +import spack.spec +import spack.util.prefix from spack.directives import build_system, conflicts, depends_on, variant from spack.multimethod import when -from ._checks import BaseBuilder, execute_build_time_tests +from ._checks import BuilderWithDefaults, execute_build_time_tests class MesonPackage(spack.package_base.PackageBase): @@ -62,7 +65,7 @@ def flags_to_build_system_args(self, flags): @spack.builder.builder("meson") -class MesonBuilder(BaseBuilder): +class MesonBuilder(BuilderWithDefaults): """The Meson builder encodes the default way to build software with Meson. The builder has three phases that can be overridden, if need be: @@ -112,7 +115,7 @@ def archive_files(self): return [os.path.join(self.build_directory, "meson-logs", "meson-log.txt")] @property - def root_mesonlists_dir(self): + def root_mesonlists_dir(self) -> str: """Relative path to the directory containing meson.build This path is relative to the root of the extracted tarball, @@ -121,7 +124,7 @@ def root_mesonlists_dir(self): return self.pkg.stage.source_path @property - def std_meson_args(self): + def std_meson_args(self) -> List[str]: """Standard meson arguments provided as a property for convenience of package writers. """ @@ -132,7 +135,7 @@ def std_meson_args(self): return std_meson_args @staticmethod - def std_args(pkg): + def std_args(pkg) -> List[str]: """Standard meson arguments for a generic package.""" try: build_type = pkg.spec.variants["buildtype"].value @@ -172,7 +175,7 @@ def build_directory(self): """Directory to use when building the package.""" return os.path.join(self.pkg.stage.path, self.build_dirname) - def meson_args(self): + def meson_args(self) -> List[str]: """List of arguments that must be passed to meson, except: * ``--prefix`` @@ -185,7 +188,12 @@ def meson_args(self): """ return [] - def meson(self, pkg, spec, prefix): + def meson( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Run ``meson`` in the build directory""" options = [] if self.spec["meson"].satisfies("@0.64:"): @@ -196,21 +204,31 @@ def meson(self, pkg, spec, prefix): with fs.working_dir(self.build_directory, create=True): pkg.module.meson(*options) - def build(self, pkg, spec, prefix): + def build( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Make the build targets""" options = ["-v"] options += self.build_targets with fs.working_dir(self.build_directory): pkg.module.ninja(*options) - def install(self, pkg, spec, prefix): + def install( + self, + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, + prefix: spack.util.prefix.Prefix, + ) -> None: """Make the install targets""" with fs.working_dir(self.build_directory): pkg.module.ninja(*self.install_targets) - spack.builder.run_after("build")(execute_build_time_tests) + spack.phase_callbacks.run_after("build")(execute_build_time_tests) - def check(self): + def check(self) -> None: """Search Meson-generated files for the target ``test`` and run it if found.""" with fs.working_dir(self.build_directory): self.pkg._if_ninja_target_execute("test") diff --git a/lib/spack/spack/build_systems/msbuild.py b/lib/spack/spack/build_systems/msbuild.py index 8fb4ef936e0..3ffc7212157 100644 --- a/lib/spack/spack/build_systems/msbuild.py +++ b/lib/spack/spack/build_systems/msbuild.py @@ -10,7 +10,7 @@ import spack.package_base from spack.directives import build_system, conflicts -from ._checks import BaseBuilder +from ._checks import BuilderWithDefaults class MSBuildPackage(spack.package_base.PackageBase): @@ -26,7 +26,7 @@ class MSBuildPackage(spack.package_base.PackageBase): @spack.builder.builder("msbuild") -class MSBuildBuilder(BaseBuilder): +class MSBuildBuilder(BuilderWithDefaults): """The MSBuild builder encodes the most common way of building software with Mircosoft's MSBuild tool. It has two phases that can be overridden, if need be: diff --git a/lib/spack/spack/build_systems/nmake.py b/lib/spack/spack/build_systems/nmake.py index 50232f3e2da..b6eb75e3e53 100644 --- a/lib/spack/spack/build_systems/nmake.py +++ b/lib/spack/spack/build_systems/nmake.py @@ -10,7 +10,7 @@ import spack.package_base from spack.directives import build_system, conflicts -from ._checks import BaseBuilder +from ._checks import BuilderWithDefaults class NMakePackage(spack.package_base.PackageBase): @@ -26,7 +26,7 @@ class NMakePackage(spack.package_base.PackageBase): @spack.builder.builder("nmake") -class NMakeBuilder(BaseBuilder): +class NMakeBuilder(BuilderWithDefaults): """The NMake builder encodes the most common way of building software with Mircosoft's NMake tool. It has two phases that can be overridden, if need be: diff --git a/lib/spack/spack/build_systems/octave.py b/lib/spack/spack/build_systems/octave.py index 1b0a88c6e76..e8f15767858 100644 --- a/lib/spack/spack/build_systems/octave.py +++ b/lib/spack/spack/build_systems/octave.py @@ -7,7 +7,7 @@ from spack.directives import build_system, extends from spack.multimethod import when -from ._checks import BaseBuilder +from ._checks import BuilderWithDefaults class OctavePackage(spack.package_base.PackageBase): @@ -29,7 +29,7 @@ class OctavePackage(spack.package_base.PackageBase): @spack.builder.builder("octave") -class OctaveBuilder(BaseBuilder): +class OctaveBuilder(BuilderWithDefaults): """The octave builder provides the following phases that can be overridden: 1. :py:meth:`~.OctaveBuilder.install` diff --git a/lib/spack/spack/build_systems/perl.py b/lib/spack/spack/build_systems/perl.py index 4caab7131b0..bb47adaaf54 100644 --- a/lib/spack/spack/build_systems/perl.py +++ b/lib/spack/spack/build_systems/perl.py @@ -10,11 +10,12 @@ import spack.builder import spack.package_base +import spack.phase_callbacks from spack.directives import build_system, extends from spack.install_test import SkipTest, test_part from spack.util.executable import Executable -from ._checks import BaseBuilder, execute_build_time_tests +from ._checks import BuilderWithDefaults, execute_build_time_tests class PerlPackage(spack.package_base.PackageBase): @@ -84,7 +85,7 @@ def test_use(self): @spack.builder.builder("perl") -class PerlBuilder(BaseBuilder): +class PerlBuilder(BuilderWithDefaults): """The perl builder provides four phases that can be overridden, if required: 1. :py:meth:`~.PerlBuilder.configure` @@ -163,7 +164,7 @@ def configure(self, pkg, spec, prefix): # Build.PL may be too long causing the build to fail. Patching the shebang # does not happen until after install so set '/usr/bin/env perl' here in # the Build script. - @spack.builder.run_after("configure") + @spack.phase_callbacks.run_after("configure") def fix_shebang(self): if self.build_method == "Build.PL": pattern = "#!{0}".format(self.spec["perl"].command.path) @@ -175,7 +176,7 @@ def build(self, pkg, spec, prefix): self.build_executable() # Ensure that tests run after build (if requested): - spack.builder.run_after("build")(execute_build_time_tests) + spack.phase_callbacks.run_after("build")(execute_build_time_tests) def check(self): """Runs built-in tests of a Perl package.""" diff --git a/lib/spack/spack/build_systems/python.py b/lib/spack/spack/build_systems/python.py index b951ec2a977..9f5db48f4d3 100644 --- a/lib/spack/spack/build_systems/python.py +++ b/lib/spack/spack/build_systems/python.py @@ -24,6 +24,7 @@ import spack.detection import spack.multimethod import spack.package_base +import spack.phase_callbacks import spack.platforms import spack.repo import spack.spec @@ -34,7 +35,7 @@ from spack.spec import Spec from spack.util.prefix import Prefix -from ._checks import BaseBuilder, execute_install_time_tests +from ._checks import BuilderWithDefaults, execute_install_time_tests def _flatten_dict(dictionary: Mapping[str, object]) -> Iterable[str]: @@ -374,7 +375,7 @@ def list_url(cls) -> Optional[str]: # type: ignore[override] return None @property - def python_spec(self): + def python_spec(self) -> Spec: """Get python-venv if it exists or python otherwise.""" python, *_ = self.spec.dependencies("python-venv") or self.spec.dependencies("python") return python @@ -425,7 +426,7 @@ def libs(self) -> LibraryList: @spack.builder.builder("python_pip") -class PythonPipBuilder(BaseBuilder): +class PythonPipBuilder(BuilderWithDefaults): phases = ("install",) #: Names associated with package methods in the old build-system format @@ -543,4 +544,4 @@ def install(self, pkg: PythonPackage, spec: Spec, prefix: Prefix) -> None: with fs.working_dir(self.build_directory): pip(*args) - spack.builder.run_after("install")(execute_install_time_tests) + spack.phase_callbacks.run_after("install")(execute_install_time_tests) diff --git a/lib/spack/spack/build_systems/qmake.py b/lib/spack/spack/build_systems/qmake.py index 75ad3860b4b..eb530f4b38a 100644 --- a/lib/spack/spack/build_systems/qmake.py +++ b/lib/spack/spack/build_systems/qmake.py @@ -6,9 +6,10 @@ import spack.builder import spack.package_base +import spack.phase_callbacks from spack.directives import build_system, depends_on -from ._checks import BaseBuilder, execute_build_time_tests +from ._checks import BuilderWithDefaults, execute_build_time_tests class QMakePackage(spack.package_base.PackageBase): @@ -30,7 +31,7 @@ class QMakePackage(spack.package_base.PackageBase): @spack.builder.builder("qmake") -class QMakeBuilder(BaseBuilder): +class QMakeBuilder(BuilderWithDefaults): """The qmake builder provides three phases that can be overridden: 1. :py:meth:`~.QMakeBuilder.qmake` @@ -81,4 +82,4 @@ def check(self): with working_dir(self.build_directory): self.pkg._if_make_target_execute("check") - spack.builder.run_after("build")(execute_build_time_tests) + spack.phase_callbacks.run_after("build")(execute_build_time_tests) diff --git a/lib/spack/spack/build_systems/ruby.py b/lib/spack/spack/build_systems/ruby.py index 77d7ef289c7..50228e21374 100644 --- a/lib/spack/spack/build_systems/ruby.py +++ b/lib/spack/spack/build_systems/ruby.py @@ -8,7 +8,7 @@ import spack.package_base from spack.directives import build_system, extends, maintainers -from ._checks import BaseBuilder +from ._checks import BuilderWithDefaults class RubyPackage(spack.package_base.PackageBase): @@ -28,7 +28,7 @@ class RubyPackage(spack.package_base.PackageBase): @spack.builder.builder("ruby") -class RubyBuilder(BaseBuilder): +class RubyBuilder(BuilderWithDefaults): """The Ruby builder provides two phases that can be overridden if required: #. :py:meth:`~.RubyBuilder.build` diff --git a/lib/spack/spack/build_systems/scons.py b/lib/spack/spack/build_systems/scons.py index 4a32b690f79..5e0211903da 100644 --- a/lib/spack/spack/build_systems/scons.py +++ b/lib/spack/spack/build_systems/scons.py @@ -4,9 +4,10 @@ # SPDX-License-Identifier: (Apache-2.0 OR MIT) import spack.builder import spack.package_base +import spack.phase_callbacks from spack.directives import build_system, depends_on -from ._checks import BaseBuilder, execute_build_time_tests +from ._checks import BuilderWithDefaults, execute_build_time_tests class SConsPackage(spack.package_base.PackageBase): @@ -28,7 +29,7 @@ class SConsPackage(spack.package_base.PackageBase): @spack.builder.builder("scons") -class SConsBuilder(BaseBuilder): +class SConsBuilder(BuilderWithDefaults): """The Scons builder provides the following phases that can be overridden: 1. :py:meth:`~.SConsBuilder.build` @@ -79,4 +80,4 @@ def build_test(self): """ pass - spack.builder.run_after("build")(execute_build_time_tests) + spack.phase_callbacks.run_after("build")(execute_build_time_tests) diff --git a/lib/spack/spack/build_systems/sip.py b/lib/spack/spack/build_systems/sip.py index f297c59a00b..54ce4c6d8e4 100644 --- a/lib/spack/spack/build_systems/sip.py +++ b/lib/spack/spack/build_systems/sip.py @@ -11,11 +11,12 @@ import spack.builder import spack.install_test import spack.package_base +import spack.phase_callbacks from spack.directives import build_system, depends_on, extends from spack.multimethod import when from spack.util.executable import Executable -from ._checks import BaseBuilder, execute_install_time_tests +from ._checks import BuilderWithDefaults, execute_install_time_tests class SIPPackage(spack.package_base.PackageBase): @@ -103,7 +104,7 @@ def test_imports(self): @spack.builder.builder("sip") -class SIPBuilder(BaseBuilder): +class SIPBuilder(BuilderWithDefaults): """The SIP builder provides the following phases that can be overridden: * configure @@ -170,4 +171,4 @@ def install_args(self): """Arguments to pass to install.""" return [] - spack.builder.run_after("install")(execute_install_time_tests) + spack.phase_callbacks.run_after("install")(execute_install_time_tests) diff --git a/lib/spack/spack/build_systems/waf.py b/lib/spack/spack/build_systems/waf.py index a0110c5c348..5161f40985a 100644 --- a/lib/spack/spack/build_systems/waf.py +++ b/lib/spack/spack/build_systems/waf.py @@ -6,9 +6,10 @@ import spack.builder import spack.package_base +import spack.phase_callbacks from spack.directives import build_system, depends_on -from ._checks import BaseBuilder, execute_build_time_tests, execute_install_time_tests +from ._checks import BuilderWithDefaults, execute_build_time_tests, execute_install_time_tests class WafPackage(spack.package_base.PackageBase): @@ -30,7 +31,7 @@ class WafPackage(spack.package_base.PackageBase): @spack.builder.builder("waf") -class WafBuilder(BaseBuilder): +class WafBuilder(BuilderWithDefaults): """The WAF builder provides the following phases that can be overridden: * configure @@ -136,7 +137,7 @@ def build_test(self): """ pass - spack.builder.run_after("build")(execute_build_time_tests) + spack.phase_callbacks.run_after("build")(execute_build_time_tests) def install_test(self): """Run unit tests after install. @@ -146,4 +147,4 @@ def install_test(self): """ pass - spack.builder.run_after("install")(execute_install_time_tests) + spack.phase_callbacks.run_after("install")(execute_install_time_tests) diff --git a/lib/spack/spack/builder.py b/lib/spack/spack/builder.py index b767f15b6dc..6dd84fab452 100644 --- a/lib/spack/spack/builder.py +++ b/lib/spack/spack/builder.py @@ -6,44 +6,30 @@ import collections.abc import copy import functools -from typing import List, Optional, Tuple - -from llnl.util import lang +from typing import Dict, List, Optional, Tuple, Type import spack.error import spack.multimethod +import spack.package_base +import spack.phase_callbacks import spack.repo +import spack.spec +import spack.util.environment #: Builder classes, as registered by the "builder" decorator -BUILDER_CLS = {} - -#: An object of this kind is a shared global state used to collect callbacks during -#: class definition time, and is flushed when the class object is created at the end -#: of the class definition -#: -#: Args: -#: attribute_name (str): name of the attribute that will be attached to the builder -#: callbacks (list): container used to temporarily aggregate the callbacks -CallbackTemporaryStage = collections.namedtuple( - "CallbackTemporaryStage", ["attribute_name", "callbacks"] -) - -#: Shared global state to aggregate "@run_before" callbacks -_RUN_BEFORE = CallbackTemporaryStage(attribute_name="run_before_callbacks", callbacks=[]) -#: Shared global state to aggregate "@run_after" callbacks -_RUN_AFTER = CallbackTemporaryStage(attribute_name="run_after_callbacks", callbacks=[]) +BUILDER_CLS: Dict[str, Type["Builder"]] = {} #: Map id(pkg) to a builder, to avoid creating multiple #: builders for the same package object. -_BUILDERS = {} +_BUILDERS: Dict[int, "Builder"] = {} -def builder(build_system_name): +def builder(build_system_name: str): """Class decorator used to register the default builder for a given build-system. Args: - build_system_name (str): name of the build-system + build_system_name: name of the build-system """ def _decorator(cls): @@ -54,13 +40,9 @@ def _decorator(cls): return _decorator -def create(pkg): - """Given a package object with an associated concrete spec, - return the builder object that can install it. - - Args: - pkg (spack.package_base.PackageBase): package for which we want the builder - """ +def create(pkg: spack.package_base.PackageBase) -> "Builder": + """Given a package object with an associated concrete spec, return the builder object that can + install it.""" if id(pkg) not in _BUILDERS: _BUILDERS[id(pkg)] = _create(pkg) return _BUILDERS[id(pkg)] @@ -75,7 +57,7 @@ def __call__(self, spec, prefix): return self.phase_fn(self.builder.pkg, spec, prefix) -def get_builder_class(pkg, name: str) -> Optional[type]: +def get_builder_class(pkg, name: str) -> Optional[Type["Builder"]]: """Return the builder class if a package module defines it.""" cls = getattr(pkg.module, name, None) if cls and cls.__module__.startswith(spack.repo.ROOT_PYTHON_NAMESPACE): @@ -83,7 +65,7 @@ def get_builder_class(pkg, name: str) -> Optional[type]: return None -def _create(pkg): +def _create(pkg: spack.package_base.PackageBase) -> "Builder": """Return a new builder object for the package object being passed as argument. The function inspects the build-system used by the package object and try to: @@ -103,7 +85,7 @@ class hierarchy (look at AspellDictPackage for an example of that) to look for build-related methods in the ``*Package``. Args: - pkg (spack.package_base.PackageBase): package object for which we need a builder + pkg: package object for which we need a builder """ package_buildsystem = buildsystem_name(pkg) default_builder_cls = BUILDER_CLS[package_buildsystem] @@ -168,8 +150,8 @@ def __forward(self, *args, **kwargs): # with the same name is defined in the Package, it will override this definition # (when _ForwardToBaseBuilder is initialized) for method_name in ( - base_cls.phases - + base_cls.legacy_methods + base_cls.phases # type: ignore + + base_cls.legacy_methods # type: ignore + getattr(base_cls, "legacy_long_methods", tuple()) + ("setup_build_environment", "setup_dependent_build_environment") ): @@ -181,14 +163,14 @@ def __forward(self): return __forward - for attribute_name in base_cls.legacy_attributes: + for attribute_name in base_cls.legacy_attributes: # type: ignore setattr( _ForwardToBaseBuilder, attribute_name, property(forward_property_to_getattr(attribute_name)), ) - class Adapter(base_cls, metaclass=_PackageAdapterMeta): + class Adapter(base_cls, metaclass=_PackageAdapterMeta): # type: ignore def __init__(self, pkg): # Deal with custom phases in packages here if hasattr(pkg, "phases"): @@ -213,99 +195,18 @@ def setup_dependent_build_environment(self, env, dependent_spec): return Adapter(pkg) -def buildsystem_name(pkg): +def buildsystem_name(pkg: spack.package_base.PackageBase) -> str: """Given a package object with an associated concrete spec, - return the name of its build system. - - Args: - pkg (spack.package_base.PackageBase): package for which we want - the build system name - """ + return the name of its build system.""" try: return pkg.spec.variants["build_system"].value except KeyError: # We are reading an old spec without the build_system variant - return pkg.legacy_buildsystem - - -class PhaseCallbacksMeta(type): - """Permit to register arbitrary functions during class definition and run them - later, before or after a given install phase. - - Each method decorated with ``run_before`` or ``run_after`` gets temporarily - stored in a global shared state when a class being defined is parsed by the Python - interpreter. At class definition time that temporary storage gets flushed and a list - of callbacks is attached to the class being defined. - """ - - def __new__(mcs, name, bases, attr_dict): - for temporary_stage in (_RUN_BEFORE, _RUN_AFTER): - staged_callbacks = temporary_stage.callbacks - - # Here we have an adapter from an old-style package. This means there is no - # hierarchy of builders, and every callback that had to be combined between - # *Package and *Builder has been combined already by _PackageAdapterMeta - if name == "Adapter": - continue - - # If we are here we have callbacks. To get a complete list, we accumulate all the - # callbacks from base classes, we deduplicate them, then prepend what we have - # registered here. - # - # The order should be: - # 1. Callbacks are registered in order within the same class - # 2. Callbacks defined in derived classes precede those defined in base - # classes - callbacks_from_base = [] - for base in bases: - current_callbacks = getattr(base, temporary_stage.attribute_name, None) - if not current_callbacks: - continue - callbacks_from_base.extend(current_callbacks) - callbacks_from_base = list(lang.dedupe(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 - del temporary_stage.callbacks[:] - - return super(PhaseCallbacksMeta, mcs).__new__(mcs, name, bases, attr_dict) - - @staticmethod - def run_after(phase, when=None): - """Decorator to register a function for running after a given phase. - - Args: - phase (str): phase after which the function must run. - when (str): condition under which the function is run (if None, it is always run). - """ - - def _decorator(fn): - key = (phase, when) - item = (key, fn) - _RUN_AFTER.callbacks.append(item) - return fn - - return _decorator - - @staticmethod - def run_before(phase, when=None): - """Decorator to register a function for running before a given phase. - - Args: - phase (str): phase before which the function must run. - when (str): condition under which the function is run (if None, it is always run). - """ - - def _decorator(fn): - key = (phase, when) - item = (key, fn) - _RUN_BEFORE.callbacks.append(item) - return fn - - return _decorator + return pkg.legacy_buildsystem # type: ignore class BuilderMeta( - PhaseCallbacksMeta, + spack.phase_callbacks.PhaseCallbacksMeta, spack.multimethod.MultiMethodMeta, type(collections.abc.Sequence), # type: ignore ): @@ -400,8 +301,12 @@ def __new__(mcs, name, bases, attr_dict): ) combine_callbacks = _PackageAdapterMeta.combine_callbacks - attr_dict[_RUN_BEFORE.attribute_name] = combine_callbacks(_RUN_BEFORE.attribute_name) - attr_dict[_RUN_AFTER.attribute_name] = combine_callbacks(_RUN_AFTER.attribute_name) + attr_dict[spack.phase_callbacks._RUN_BEFORE.attribute_name] = combine_callbacks( + spack.phase_callbacks._RUN_BEFORE.attribute_name + ) + attr_dict[spack.phase_callbacks._RUN_AFTER.attribute_name] = combine_callbacks( + spack.phase_callbacks._RUN_AFTER.attribute_name + ) return super(_PackageAdapterMeta, mcs).__new__(mcs, name, bases, attr_dict) @@ -421,8 +326,8 @@ def __init__(self, name, builder): self.name = name self.builder = builder self.phase_fn = self._select_phase_fn() - self.run_before = self._make_callbacks(_RUN_BEFORE.attribute_name) - self.run_after = self._make_callbacks(_RUN_AFTER.attribute_name) + self.run_before = self._make_callbacks(spack.phase_callbacks._RUN_BEFORE.attribute_name) + self.run_after = self._make_callbacks(spack.phase_callbacks._RUN_AFTER.attribute_name) def _make_callbacks(self, callbacks_attribute): result = [] @@ -483,15 +388,103 @@ def copy(self): return copy.deepcopy(self) -class Builder(collections.abc.Sequence, metaclass=BuilderMeta): - """A builder is a class that, given a package object (i.e. associated with - concrete spec), knows how to install it. +class BaseBuilder(metaclass=BuilderMeta): + """An interface for builders, without any phases defined. This class is exposed in the package + API, so that packagers can create a single class to define ``setup_build_environment`` and + ``@run_before`` and ``@run_after`` callbacks that can be shared among different builders. - The builder behaves like a sequence, and when iterated over return the - "phases" of the installation in the correct order. + Example: - Args: - pkg (spack.package_base.PackageBase): package object to be built + .. code-block:: python + + class AnyBuilder(BaseBuilder): + @run_after("install") + def fixup_install(self): + # do something after the package is installed + pass + + def setup_build_environment(self, env): + env.set("MY_ENV_VAR", "my_value") + + class CMakeBuilder(cmake.CMakeBuilder, AnyBuilder): + pass + + class AutotoolsBuilder(autotools.AutotoolsBuilder, AnyBuilder): + pass + """ + + def __init__(self, pkg: spack.package_base.PackageBase) -> None: + self.pkg = pkg + + @property + def spec(self) -> spack.spec.Spec: + return self.pkg.spec + + @property + def stage(self): + return self.pkg.stage + + @property + def prefix(self): + return self.pkg.prefix + + def setup_build_environment( + self, env: spack.util.environment.EnvironmentModifications + ) -> None: + """Sets up the build environment for a package. + + This method will be called before the current package prefix exists in + Spack's store. + + Args: + env: environment modifications to be applied when the package is built. Package authors + can call methods on it to alter the build environment. + """ + if not hasattr(super(), "setup_build_environment"): + return + super().setup_build_environment(env) # type: ignore + + def setup_dependent_build_environment( + self, env: spack.util.environment.EnvironmentModifications, dependent_spec: spack.spec.Spec + ) -> None: + """Sets up the build environment of a package that depends on this one. + + This is similar to ``setup_build_environment``, but it is used to modify the build + environment of a package that *depends* on this one. + + This gives packages the ability to set environment variables for the build of the + dependent, which can be useful to provide search hints for headers or libraries if they are + not in standard locations. + + This method will be called before the dependent package prefix exists in Spack's store. + + Args: + env: environment modifications to be applied when the dependent package is built. + Package authors can call methods on it to alter the build environment. + + dependent_spec: the spec of the dependent package about to be built. This allows the + extendee (self) to query the dependent's state. Note that *this* package's spec is + available as ``self.spec`` + """ + if not hasattr(super(), "setup_dependent_build_environment"): + return + super().setup_dependent_build_environment(env, dependent_spec) # type: ignore + + def __repr__(self): + fmt = "{name}{/hash:7}" + return f"{self.__class__.__name__}({self.spec.format(fmt)})" + + def __str__(self): + fmt = "{name}{/hash:7}" + return f'"{self.__class__.__name__}" builder for "{self.spec.format(fmt)}"' + + +class Builder(BaseBuilder, collections.abc.Sequence): + """A builder is a class that, given a package object (i.e. associated with concrete spec), + knows how to install it. + + The builder behaves like a sequence, and when iterated over return the "phases" of the + installation in the correct order. """ #: Sequence of phases. Must be defined in derived classes @@ -506,95 +499,22 @@ class Builder(collections.abc.Sequence, metaclass=BuilderMeta): build_time_test_callbacks: List[str] install_time_test_callbacks: List[str] - #: List of glob expressions. Each expression must either be - #: absolute or relative to the package source path. - #: Matching artifacts found at the end of the build process will be - #: copied in the same directory tree as _spack_build_logfile and - #: _spack_build_envfile. - archive_files: List[str] = [] + #: List of glob expressions. Each expression must either be absolute or relative to the package + #: source path. Matching artifacts found at the end of the build process will be copied in the + #: same directory tree as _spack_build_logfile and _spack_build_envfile. + @property + def archive_files(self) -> List[str]: + return [] - def __init__(self, pkg): - self.pkg = pkg + def __init__(self, pkg: spack.package_base.PackageBase) -> None: + super().__init__(pkg) self.callbacks = {} for phase in self.phases: self.callbacks[phase] = InstallationPhase(phase, self) - @property - def spec(self): - return self.pkg.spec - - @property - def stage(self): - return self.pkg.stage - - @property - def prefix(self): - return self.pkg.prefix - - def setup_build_environment(self, env): - """Sets up the build environment for a package. - - This method will be called before the current package prefix exists in - Spack's store. - - Args: - env (spack.util.environment.EnvironmentModifications): environment - modifications to be applied when the package is built. Package authors - can call methods on it to alter the build environment. - """ - if not hasattr(super(), "setup_build_environment"): - return - super().setup_build_environment(env) - - def setup_dependent_build_environment(self, env, dependent_spec): - """Sets up the build environment of packages that depend on this one. - - This is similar to ``setup_build_environment``, but it is used to - modify the build environments of packages that *depend* on this one. - - This gives packages like Python and others that follow the extension - model a way to implement common environment or compile-time settings - for dependencies. - - This method will be called before the dependent package prefix exists - in Spack's store. - - Examples: - 1. Installing python modules generally requires ``PYTHONPATH`` - to point to the ``lib/pythonX.Y/site-packages`` directory in the - module's install prefix. This method could be used to set that - variable. - - Args: - env (spack.util.environment.EnvironmentModifications): environment - modifications to be applied when the dependent package is built. - Package authors can call methods on it to alter the build environment. - - dependent_spec (spack.spec.Spec): the spec of the dependent package - about to be built. This allows the extendee (self) to query - the dependent's state. Note that *this* package's spec is - available as ``self.spec`` - """ - if not hasattr(super(), "setup_dependent_build_environment"): - return - super().setup_dependent_build_environment(env, dependent_spec) - def __getitem__(self, idx): key = self.phases[idx] return self.callbacks[key] def __len__(self): return len(self.phases) - - def __repr__(self): - msg = "{0}({1})" - return msg.format(type(self).__name__, self.pkg.spec.format("{name}/{hash:7}")) - - def __str__(self): - msg = '"{0}" builder for "{1}"' - return msg.format(type(self).build_system, self.pkg.spec.format("{name}/{hash:7}")) - - -# Export these names as standalone to be used in packages -run_after = PhaseCallbacksMeta.run_after -run_before = PhaseCallbacksMeta.run_before diff --git a/lib/spack/spack/ci.py b/lib/spack/spack/ci.py index 5a8b2ae1e7d..6fba4863561 100644 --- a/lib/spack/spack/ci.py +++ b/lib/spack/spack/ci.py @@ -32,6 +32,7 @@ import spack import spack.binary_distribution as bindist +import spack.builder import spack.concretize import spack.config as cfg import spack.error @@ -1387,7 +1388,11 @@ def copy_stage_logs_to_artifacts(job_spec: spack.spec.Spec, job_log_dir: str) -> stage_dir = job_pkg.stage.path tty.debug(f"stage dir: {stage_dir}") - for file in [job_pkg.log_path, job_pkg.env_mods_path, *job_pkg.builder.archive_files]: + for file in [ + job_pkg.log_path, + job_pkg.env_mods_path, + *spack.builder.create(job_pkg).archive_files, + ]: copy_files_to_artifacts(file, job_log_dir) diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py index 890b32b3002..25b113992d6 100644 --- a/lib/spack/spack/cmd/info.py +++ b/lib/spack/spack/cmd/info.py @@ -11,6 +11,7 @@ import llnl.util.tty.color as color from llnl.util.tty.colify import colify +import spack.builder import spack.deptypes as dt import spack.fetch_strategy as fs import spack.install_test @@ -202,11 +203,13 @@ def print_namespace(pkg, args): def print_phases(pkg, args): """output installation phases""" - if hasattr(pkg.builder, "phases") and pkg.builder.phases: + builder = spack.builder.create(pkg) + + if hasattr(builder, "phases") and builder.phases: color.cprint("") color.cprint(section_title("Installation Phases:")) phase_str = "" - for phase in pkg.builder.phases: + for phase in builder.phases: phase_str += " {0}".format(phase) color.cprint(phase_str) diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py index 0e3bb522cef..e8a89dc5a5d 100644 --- a/lib/spack/spack/directives.py +++ b/lib/spack/spack/directives.py @@ -34,12 +34,13 @@ class OpenMpi(Package): import collections.abc import os.path import re -from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union +from typing import Any, Callable, List, Optional, Tuple, Union import llnl.util.lang import llnl.util.tty.color import spack.deptypes as dt +import spack.package_base import spack.patch import spack.spec import spack.util.crypto @@ -56,13 +57,8 @@ class OpenMpi(Package): VersionLookupError, ) -if TYPE_CHECKING: - import spack.package_base - __all__ = [ "DirectiveError", - "DirectiveMeta", - "DisableRedistribute", "version", "conditional", "conflicts", @@ -85,15 +81,15 @@ class OpenMpi(Package): SpecType = str DepType = Union[Tuple[str, ...], str] -WhenType = Optional[Union["spack.spec.Spec", str, bool]] -Patcher = Callable[[Union["spack.package_base.PackageBase", Dependency]], None] +WhenType = Optional[Union[spack.spec.Spec, str, bool]] +Patcher = Callable[[Union[spack.package_base.PackageBase, Dependency]], None] PatchesType = Optional[Union[Patcher, str, List[Union[Patcher, str]]]] SUPPORTED_LANGUAGES = ("fortran", "cxx", "c") -def _make_when_spec(value: WhenType) -> Optional["spack.spec.Spec"]: +def _make_when_spec(value: WhenType) -> Optional[spack.spec.Spec]: """Create a ``Spec`` that indicates when a directive should be applied. Directives with ``when`` specs, e.g.: @@ -138,7 +134,7 @@ def _make_when_spec(value: WhenType) -> Optional["spack.spec.Spec"]: return spack.spec.Spec(value) -SubmoduleCallback = Callable[["spack.package_base.PackageBase"], Union[str, List[str], bool]] +SubmoduleCallback = Callable[[spack.package_base.PackageBase], Union[str, List[str], bool]] directive = DirectiveMeta.directive @@ -254,8 +250,8 @@ def _execute_version(pkg, ver, **kwargs): def _depends_on( - pkg: "spack.package_base.PackageBase", - spec: "spack.spec.Spec", + pkg: spack.package_base.PackageBase, + spec: spack.spec.Spec, *, when: WhenType = None, type: DepType = dt.DEFAULT_TYPES, @@ -334,7 +330,7 @@ def conflicts(conflict_spec: SpecType, when: WhenType = None, msg: Optional[str] msg (str): optional user defined message """ - def _execute_conflicts(pkg: "spack.package_base.PackageBase"): + def _execute_conflicts(pkg: spack.package_base.PackageBase): # If when is not specified the conflict always holds when_spec = _make_when_spec(when) if not when_spec: @@ -375,19 +371,12 @@ def depends_on( assert type == "build", "languages must be of 'build' type" return _language(lang_spec_str=spec, when=when) - def _execute_depends_on(pkg: "spack.package_base.PackageBase"): + def _execute_depends_on(pkg: spack.package_base.PackageBase): _depends_on(pkg, dep_spec, when=when, type=type, patches=patches) return _execute_depends_on -#: Store whether a given Spec source/binary should not be redistributed. -class DisableRedistribute: - def __init__(self, source, binary): - self.source = source - self.binary = binary - - @directive("disable_redistribute") def redistribute(source=None, binary=None, when: WhenType = None): """Can be used inside a Package definition to declare that @@ -404,7 +393,7 @@ def redistribute(source=None, binary=None, when: WhenType = None): def _execute_redistribute( - pkg: "spack.package_base.PackageBase", source=None, binary=None, when: WhenType = None + pkg: spack.package_base.PackageBase, source=None, binary=None, when: WhenType = None ): if source is None and binary is None: return @@ -434,7 +423,7 @@ def _execute_redistribute( if not binary: disable.binary = True else: - pkg.disable_redistribute[when_spec] = DisableRedistribute( + pkg.disable_redistribute[when_spec] = spack.package_base.DisableRedistribute( source=not source, binary=not binary ) @@ -480,7 +469,7 @@ def provides(*specs: SpecType, when: WhenType = None): when: condition when this provides clause needs to be considered """ - def _execute_provides(pkg: "spack.package_base.PackageBase"): + def _execute_provides(pkg: spack.package_base.PackageBase): import spack.parser # Avoid circular dependency when_spec = _make_when_spec(when) @@ -528,7 +517,7 @@ def can_splice( variants will be skipped by '*'. """ - def _execute_can_splice(pkg: "spack.package_base.PackageBase"): + def _execute_can_splice(pkg: spack.package_base.PackageBase): when_spec = _make_when_spec(when) if isinstance(match_variants, str) and match_variants != "*": raise ValueError( @@ -569,7 +558,7 @@ def patch( compressed URL patches) """ - def _execute_patch(pkg_or_dep: Union["spack.package_base.PackageBase", Dependency]): + def _execute_patch(pkg_or_dep: Union[spack.package_base.PackageBase, Dependency]): pkg = pkg_or_dep if isinstance(pkg, Dependency): pkg = pkg.pkg @@ -893,7 +882,7 @@ def requires(*requirement_specs: str, policy="one_of", when=None, msg=None): msg: optional user defined message """ - def _execute_requires(pkg: "spack.package_base.PackageBase"): + def _execute_requires(pkg: spack.package_base.PackageBase): if policy not in ("one_of", "any_of"): err_msg = ( f"the 'policy' argument of the 'requires' directive in {pkg.name} is set " @@ -918,7 +907,7 @@ def _execute_requires(pkg: "spack.package_base.PackageBase"): def _language(lang_spec_str: str, *, when: Optional[Union[str, bool]] = None): """Temporary implementation of language virtuals, until compilers are proper dependencies.""" - def _execute_languages(pkg: "spack.package_base.PackageBase"): + def _execute_languages(pkg: spack.package_base.PackageBase): when_spec = _make_when_spec(when) if not when_spec: return diff --git a/lib/spack/spack/install_test.py b/lib/spack/spack/install_test.py index d1c5197598b..a4401fc8787 100644 --- a/lib/spack/spack/install_test.py +++ b/lib/spack/spack/install_test.py @@ -23,7 +23,6 @@ from llnl.util.tty.color import colorize import spack.build_environment -import spack.builder import spack.config import spack.error import spack.package_base @@ -353,9 +352,7 @@ def status(self, name: str, status: "TestStatus", msg: Optional[str] = None): self.test_parts[part_name] = status self.counts[status] += 1 - def phase_tests( - self, builder: spack.builder.Builder, phase_name: str, method_names: List[str] - ): + def phase_tests(self, builder, phase_name: str, method_names: List[str]): """Execute the builder's package phase-time tests. Args: diff --git a/lib/spack/spack/installer.py b/lib/spack/spack/installer.py index be09973336b..1fffe251f35 100644 --- a/lib/spack/spack/installer.py +++ b/lib/spack/spack/installer.py @@ -50,6 +50,7 @@ import spack.binary_distribution as binary_distribution import spack.build_environment +import spack.builder import spack.config import spack.database import spack.deptypes as dt @@ -212,7 +213,7 @@ def _check_last_phase(pkg: "spack.package_base.PackageBase") -> None: Raises: ``BadInstallPhase`` if stop_before or last phase is invalid """ - phases = pkg.builder.phases # type: ignore[attr-defined] + phases = spack.builder.create(pkg).phases # type: ignore[attr-defined] if pkg.stop_before_phase and pkg.stop_before_phase not in phases: # type: ignore[attr-defined] raise BadInstallPhase(pkg.name, pkg.stop_before_phase) # type: ignore[attr-defined] @@ -661,7 +662,7 @@ def log(pkg: "spack.package_base.PackageBase") -> None: spack.store.STORE.layout.metadata_path(pkg.spec), "archived-files" ) - for glob_expr in pkg.builder.archive_files: + for glob_expr in spack.builder.create(pkg).archive_files: # Check that we are trying to copy things that are # in the stage tree (not arbitrary files) abs_expr = os.path.realpath(glob_expr) @@ -2394,7 +2395,6 @@ def _install_source(self) -> None: fs.install_tree(pkg.stage.source_path, src_target) def _real_install(self) -> None: - import spack.builder pkg = self.pkg diff --git a/lib/spack/spack/mirror.py b/lib/spack/spack/mirror.py index fe38d1f963a..a4fd8535c44 100644 --- a/lib/spack/spack/mirror.py +++ b/lib/spack/spack/mirror.py @@ -29,6 +29,7 @@ import spack.config import spack.error import spack.fetch_strategy +import spack.mirror import spack.oci.image import spack.repo import spack.spec diff --git a/lib/spack/spack/mixins.py b/lib/spack/spack/mixins.py index e0e25f42bb4..2db35fa1349 100644 --- a/lib/spack/spack/mixins.py +++ b/lib/spack/spack/mixins.py @@ -10,7 +10,7 @@ import llnl.util.filesystem -import spack.builder +import spack.phase_callbacks def filter_compiler_wrappers(*files, **kwargs): @@ -111,4 +111,4 @@ def _filter_compiler_wrappers_impl(pkg_or_builder): if pkg.compiler.name == "nag": x.filter("-Wl,--enable-new-dtags", "", **filter_kwargs) - spack.builder.run_after(after)(_filter_compiler_wrappers_impl) + spack.phase_callbacks.run_after(after)(_filter_compiler_wrappers_impl) diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index f8028d9ecaf..8a7795b2ce1 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -74,7 +74,7 @@ from spack.build_systems.sourceware import SourcewarePackage from spack.build_systems.waf import WafPackage from spack.build_systems.xorg import XorgPackage -from spack.builder import run_after, run_before +from spack.builder import BaseBuilder from spack.config import determine_number_of_jobs from spack.deptypes import ALL_TYPES as all_deptypes from spack.directives import * @@ -100,6 +100,7 @@ on_package_attributes, ) from spack.package_completions import * +from spack.phase_callbacks import run_after, run_before from spack.spec import InvalidSpecDetected, Spec from spack.util.executable import * from spack.util.filesystem import file_command, fix_darwin_install_name, mime_type diff --git a/lib/spack/spack/package_base.py b/lib/spack/spack/package_base.py index 5410f72ab16..6ebc948ca46 100644 --- a/lib/spack/spack/package_base.py +++ b/lib/spack/spack/package_base.py @@ -32,18 +32,18 @@ from llnl.util.lang import classproperty, memoized from llnl.util.link_tree import LinkTree -import spack.builder import spack.compilers import spack.config import spack.dependency import spack.deptypes as dt -import spack.directives +import spack.directives_meta import spack.error import spack.fetch_strategy as fs import spack.hooks import spack.mirror import spack.multimethod import spack.patch +import spack.phase_callbacks import spack.repo import spack.spec import spack.store @@ -51,9 +51,9 @@ import spack.util.environment import spack.util.path import spack.util.web +import spack.variant from spack.error import InstallError, NoURLError, PackageError from spack.filesystem_view import YamlFilesystemView -from spack.install_test import PackageTest, TestSuite from spack.solver.version_order import concretization_version_order from spack.stage import DevelopStage, ResourceStage, Stage, StageComposite, compute_stage_name from spack.util.package_hash import package_hash @@ -299,9 +299,9 @@ def determine_variants(cls, objs, version_str): class PackageMeta( - spack.builder.PhaseCallbacksMeta, + spack.phase_callbacks.PhaseCallbacksMeta, DetectablePackageMeta, - spack.directives.DirectiveMeta, + spack.directives_meta.DirectiveMeta, spack.multimethod.MultiMethodMeta, ): """ @@ -453,7 +453,7 @@ def _names(when_indexed_dictionary: WhenDict) -> List[str]: return sorted(all_names) -WhenVariantList = List[Tuple["spack.spec.Spec", "spack.variant.Variant"]] +WhenVariantList = List[Tuple[spack.spec.Spec, spack.variant.Variant]] def _remove_overridden_vdefs(variant_defs: WhenVariantList) -> None: @@ -492,41 +492,14 @@ class Hipblas: i += 1 -class RedistributionMixin: - """Logic for determining whether a Package is source/binary - redistributable. - """ - - #: Store whether a given Spec source/binary should not be - #: redistributed. - disable_redistribute: Dict["spack.spec.Spec", "spack.directives.DisableRedistribute"] - - # Source redistribution must be determined before concretization - # (because source mirrors work with un-concretized Specs). - @classmethod - def redistribute_source(cls, spec): - """Whether it should be possible to add the source of this - package to a Spack mirror. - """ - for when_spec, disable_redistribute in cls.disable_redistribute.items(): - if disable_redistribute.source and spec.satisfies(when_spec): - return False - - return True - - @property - def redistribute_binary(self): - """Whether it should be possible to create a binary out of an - installed instance of this package. - """ - for when_spec, disable_redistribute in self.__class__.disable_redistribute.items(): - if disable_redistribute.binary and self.spec.satisfies(when_spec): - return False - - return True +#: Store whether a given Spec source/binary should not be redistributed. +class DisableRedistribute: + def __init__(self, source, binary): + self.source = source + self.binary = binary -class PackageBase(WindowsRPath, PackageViewMixin, RedistributionMixin, metaclass=PackageMeta): +class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta): """This is the superclass for all spack packages. ***The Package class*** @@ -612,17 +585,20 @@ class PackageBase(WindowsRPath, PackageViewMixin, RedistributionMixin, metaclass # Declare versions dictionary as placeholder for values. # This allows analysis tools to correctly interpret the class attributes. versions: dict - dependencies: Dict["spack.spec.Spec", Dict[str, "spack.dependency.Dependency"]] - conflicts: Dict["spack.spec.Spec", List[Tuple["spack.spec.Spec", Optional[str]]]] + dependencies: Dict[spack.spec.Spec, Dict[str, spack.dependency.Dependency]] + conflicts: Dict[spack.spec.Spec, List[Tuple[spack.spec.Spec, Optional[str]]]] requirements: Dict[ - "spack.spec.Spec", List[Tuple[Tuple["spack.spec.Spec", ...], str, Optional[str]]] + spack.spec.Spec, List[Tuple[Tuple[spack.spec.Spec, ...], str, Optional[str]]] ] - provided: Dict["spack.spec.Spec", Set["spack.spec.Spec"]] - provided_together: Dict["spack.spec.Spec", List[Set[str]]] - patches: Dict["spack.spec.Spec", List["spack.patch.Patch"]] - variants: Dict["spack.spec.Spec", Dict[str, "spack.variant.Variant"]] - languages: Dict["spack.spec.Spec", Set[str]] - splice_specs: Dict["spack.spec.Spec", Tuple["spack.spec.Spec", Union[None, str, List[str]]]] + provided: Dict[spack.spec.Spec, Set[spack.spec.Spec]] + provided_together: Dict[spack.spec.Spec, List[Set[str]]] + patches: Dict[spack.spec.Spec, List[spack.patch.Patch]] + variants: Dict[spack.spec.Spec, Dict[str, spack.variant.Variant]] + languages: Dict[spack.spec.Spec, Set[str]] + splice_specs: Dict[spack.spec.Spec, Tuple[spack.spec.Spec, Union[None, str, List[str]]]] + + #: Store whether a given Spec source/binary should not be redistributed. + disable_redistribute: Dict[spack.spec.Spec, DisableRedistribute] #: By default, packages are not virtual #: Virtual packages override this attribute @@ -737,11 +713,11 @@ class PackageBase(WindowsRPath, PackageViewMixin, RedistributionMixin, metaclass test_requires_compiler: bool = False #: TestSuite instance used to manage stand-alone tests for 1+ specs. - test_suite: Optional["TestSuite"] = None + test_suite: Optional[Any] = None def __init__(self, spec): # this determines how the package should be built. - self.spec: "spack.spec.Spec" = spec + self.spec: spack.spec.Spec = spec # Allow custom staging paths for packages self.path = None @@ -759,7 +735,7 @@ def __init__(self, spec): # init internal variables self._stage: Optional[StageComposite] = None self._fetcher = None - self._tester: Optional["PackageTest"] = None + self._tester: Optional[Any] = None # Set up timing variables self._fetch_time = 0.0 @@ -809,9 +785,7 @@ def variant_definitions(cls, name: str) -> WhenVariantList: return defs @classmethod - def variant_items( - cls, - ) -> Iterable[Tuple["spack.spec.Spec", Dict[str, "spack.variant.Variant"]]]: + def variant_items(cls) -> Iterable[Tuple[spack.spec.Spec, Dict[str, spack.variant.Variant]]]: """Iterate over ``cls.variants.items()`` with overridden definitions removed.""" # Note: This is quadratic in the average number of variant definitions per name. # That is likely close to linear in practice, as there are few variants with @@ -829,7 +803,7 @@ def variant_items( if filtered_variants_by_name: yield when, filtered_variants_by_name - def get_variant(self, name: str) -> "spack.variant.Variant": + def get_variant(self, name: str) -> spack.variant.Variant: """Get the highest precedence variant definition matching this package's spec. Arguments: @@ -1004,6 +978,26 @@ def global_license_file(self): self.global_license_dir, self.name, os.path.basename(self.license_files[0]) ) + # Source redistribution must be determined before concretization (because source mirrors work + # with abstract specs). + @classmethod + def redistribute_source(cls, spec): + """Whether it should be possible to add the source of this + package to a Spack mirror.""" + for when_spec, disable_redistribute in cls.disable_redistribute.items(): + if disable_redistribute.source and spec.satisfies(when_spec): + return False + return True + + @property + def redistribute_binary(self): + """Whether it should be possible to create a binary out of an installed instance of this + package.""" + for when_spec, disable_redistribute in self.disable_redistribute.items(): + if disable_redistribute.binary and self.spec.satisfies(when_spec): + return False + return True + # NOTE: return type should be Optional[Literal['all', 'specific', 'none']] in # Python 3.8+, but we still support 3.6. @property @@ -1353,11 +1347,13 @@ def archive_install_test_log(self): @property def tester(self): + import spack.install_test + if not self.spec.versions.concrete: raise ValueError("Cannot retrieve tester for package without concrete version.") if not self._tester: - self._tester = PackageTest(self) + self._tester = spack.install_test.PackageTest(self) return self._tester @property @@ -2014,72 +2010,58 @@ def build_system_flags( """ return None, None, flags - def setup_run_environment(self, env): + def setup_run_environment(self, env: spack.util.environment.EnvironmentModifications) -> None: """Sets up the run environment for a package. Args: - env (spack.util.environment.EnvironmentModifications): environment - modifications to be applied when the package is run. Package authors + env: environment modifications to be applied when the package is run. Package authors can call methods on it to alter the run environment. """ pass - def setup_dependent_run_environment(self, env, dependent_spec): + def setup_dependent_run_environment( + self, env: spack.util.environment.EnvironmentModifications, dependent_spec: spack.spec.Spec + ) -> None: """Sets up the run environment of packages that depend on this one. - This is similar to ``setup_run_environment``, but it is used to - modify the run environments of packages that *depend* on this one. + This is similar to ``setup_run_environment``, but it is used to modify the run environment + of a package that *depends* on this one. - This gives packages like Python and others that follow the extension - model a way to implement common environment or run-time settings - for dependencies. + This gives packages like Python and others that follow the extension model a way to + implement common environment or run-time settings for dependencies. Args: - env (spack.util.environment.EnvironmentModifications): environment - modifications to be applied when the dependent package is run. - Package authors can call methods on it to alter the build environment. + env: environment modifications to be applied when the dependent package is run. Package + authors can call methods on it to alter the build environment. - dependent_spec (spack.spec.Spec): The spec of the dependent package - about to be run. This allows the extendee (self) to query - the dependent's state. Note that *this* package's spec is + dependent_spec: The spec of the dependent package about to be run. This allows the + extendee (self) to query the dependent's state. Note that *this* package's spec is available as ``self.spec`` """ pass - def setup_dependent_package(self, module, dependent_spec): - """Set up Python module-scope variables for dependent packages. + def setup_dependent_package(self, module, dependent_spec: spack.spec.Spec) -> None: + """Set up module-scope global variables for dependent packages. - Called before the install() method of dependents. - - Default implementation does nothing, but this can be - overridden by an extendable package to set up the module of - its extensions. This is useful if there are some common steps - to installing all extensions for a certain package. + This function is called when setting up the build and run environments of a DAG. Examples: - 1. Extensions often need to invoke the ``python`` interpreter - from the Python installation being extended. This routine - can put a ``python()`` Executable object in the module scope - for the extension package to simplify extension installs. + 1. Extensions often need to invoke the ``python`` interpreter from the Python installation + being extended. This routine can put a ``python`` Executable as a global in the module + scope for the extension package to simplify extension installs. - 2. MPI compilers could set some variables in the dependent's - scope that point to ``mpicc``, ``mpicxx``, etc., allowing - them to be called by common name regardless of which MPI is used. - - 3. BLAS/LAPACK implementations can set some variables - indicating the path to their libraries, since these - paths differ by BLAS/LAPACK implementation. + 2. MPI compilers could set some variables in the dependent's scope that point to ``mpicc``, + ``mpicxx``, etc., allowing them to be called by common name regardless of which MPI is + used. Args: - module (spack.package_base.PackageBase.module): The Python ``module`` - object of the dependent package. Packages can use this to set - module-scope variables for the dependent to use. + module: The Python ``module`` object of the dependent package. Packages can use this to + set module-scope variables for the dependent to use. - dependent_spec (spack.spec.Spec): The spec of the dependent package - about to be built. This allows the extendee (self) to - query the dependent's state. Note that *this* - package's spec is available as ``self.spec``. + dependent_spec: The spec of the dependent package about to be built. This allows the + extendee (self) to query the dependent's state. Note that *this* package's spec is + available as ``self.spec``. """ pass @@ -2106,7 +2088,7 @@ def flag_handler(self, var: FLAG_HANDLER_TYPE) -> None: # arguments. This is implemented for build system classes where # appropriate and will otherwise raise a NotImplementedError. - def flags_to_build_system_args(self, flags): + def flags_to_build_system_args(self, flags: Dict[str, List[str]]) -> None: # Takes flags as a dict name: list of values if any(v for v in flags.values()): msg = "The {0} build system".format(self.__class__.__name__) @@ -2309,10 +2291,6 @@ def rpath_args(self): """ return " ".join("-Wl,-rpath,%s" % p for p in self.rpath) - @property - def builder(self): - return spack.builder.create(self) - inject_flags = PackageBase.inject_flags env_flags = PackageBase.env_flags diff --git a/lib/spack/spack/phase_callbacks.py b/lib/spack/spack/phase_callbacks.py new file mode 100644 index 00000000000..58eec54ca1c --- /dev/null +++ b/lib/spack/spack/phase_callbacks.py @@ -0,0 +1,105 @@ +# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +import collections + +import llnl.util.lang as lang + +#: An object of this kind is a shared global state used to collect callbacks during +#: class definition time, and is flushed when the class object is created at the end +#: of the class definition +#: +#: Args: +#: attribute_name (str): name of the attribute that will be attached to the builder +#: callbacks (list): container used to temporarily aggregate the callbacks +CallbackTemporaryStage = collections.namedtuple( + "CallbackTemporaryStage", ["attribute_name", "callbacks"] +) + +#: Shared global state to aggregate "@run_before" callbacks +_RUN_BEFORE = CallbackTemporaryStage(attribute_name="run_before_callbacks", callbacks=[]) +#: Shared global state to aggregate "@run_after" callbacks +_RUN_AFTER = CallbackTemporaryStage(attribute_name="run_after_callbacks", callbacks=[]) + + +class PhaseCallbacksMeta(type): + """Permit to register arbitrary functions during class definition and run them + later, before or after a given install phase. + + Each method decorated with ``run_before`` or ``run_after`` gets temporarily + stored in a global shared state when a class being defined is parsed by the Python + interpreter. At class definition time that temporary storage gets flushed and a list + of callbacks is attached to the class being defined. + """ + + def __new__(mcs, name, bases, attr_dict): + for temporary_stage in (_RUN_BEFORE, _RUN_AFTER): + staged_callbacks = temporary_stage.callbacks + + # Here we have an adapter from an old-style package. This means there is no + # hierarchy of builders, and every callback that had to be combined between + # *Package and *Builder has been combined already by _PackageAdapterMeta + if name == "Adapter": + continue + + # If we are here we have callbacks. To get a complete list, we accumulate all the + # callbacks from base classes, we deduplicate them, then prepend what we have + # registered here. + # + # The order should be: + # 1. Callbacks are registered in order within the same class + # 2. Callbacks defined in derived classes precede those defined in base + # classes + callbacks_from_base = [] + for base in bases: + current_callbacks = getattr(base, temporary_stage.attribute_name, None) + if not current_callbacks: + continue + callbacks_from_base.extend(current_callbacks) + callbacks_from_base = list(lang.dedupe(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 + del temporary_stage.callbacks[:] + + return super(PhaseCallbacksMeta, mcs).__new__(mcs, name, bases, attr_dict) + + @staticmethod + def run_after(phase, when=None): + """Decorator to register a function for running after a given phase. + + Args: + phase (str): phase after which the function must run. + when (str): condition under which the function is run (if None, it is always run). + """ + + def _decorator(fn): + key = (phase, when) + item = (key, fn) + _RUN_AFTER.callbacks.append(item) + return fn + + return _decorator + + @staticmethod + def run_before(phase, when=None): + """Decorator to register a function for running before a given phase. + + Args: + phase (str): phase before which the function must run. + when (str): condition under which the function is run (if None, it is always run). + """ + + def _decorator(fn): + key = (phase, when) + item = (key, fn) + _RUN_BEFORE.callbacks.append(item) + return fn + + return _decorator + + +# Export these names as standalone to be used in packages +run_after = PhaseCallbacksMeta.run_after +run_before = PhaseCallbacksMeta.run_before diff --git a/lib/spack/spack/test/build_systems.py b/lib/spack/spack/test/build_systems.py index 212ec412d3e..47173b7dfc7 100644 --- a/lib/spack/spack/test/build_systems.py +++ b/lib/spack/spack/test/build_systems.py @@ -15,6 +15,7 @@ import spack.build_systems.autotools import spack.build_systems.cmake +import spack.builder import spack.environment import spack.error import spack.paths @@ -149,7 +150,7 @@ def test_libtool_archive_files_are_deleted_by_default(self, mutable_database): # Assert the libtool archive is not there and we have # a log of removed files - assert not os.path.exists(s.package.builder.libtool_archive_file) + assert not os.path.exists(spack.builder.create(s.package).libtool_archive_file) search_directory = os.path.join(s.prefix, ".spack") libtool_deletion_log = fs.find(search_directory, "removed_la_files.txt", recursive=True) assert libtool_deletion_log @@ -160,11 +161,13 @@ def test_libtool_archive_files_might_be_installed_on_demand( # Install a package that creates a mock libtool archive, # patch its package to preserve the installation s = Spec("libtool-deletion").concretized() - monkeypatch.setattr(type(s.package.builder), "install_libtool_archives", True) + monkeypatch.setattr( + type(spack.builder.create(s.package)), "install_libtool_archives", True + ) PackageInstaller([s.package], explicit=True).install() # Assert libtool archives are installed - assert os.path.exists(s.package.builder.libtool_archive_file) + assert os.path.exists(spack.builder.create(s.package).libtool_archive_file) def test_autotools_gnuconfig_replacement(self, mutable_database): """ @@ -261,7 +264,7 @@ def test_cmake_std_args(self, default_mock_concretization): # Call the function on a CMakePackage instance s = default_mock_concretization("cmake-client") expected = spack.build_systems.cmake.CMakeBuilder.std_args(s.package) - assert s.package.builder.std_cmake_args == expected + assert spack.builder.create(s.package).std_cmake_args == expected # Call it on another kind of package s = default_mock_concretization("mpich") @@ -381,7 +384,9 @@ def test_autotools_args_from_conditional_variant(default_mock_concretization): is not met. When this is the case, the variant is not set in the spec.""" s = default_mock_concretization("autotools-conditional-variants-test") assert "example" not in s.variants - assert len(s.package.builder._activate_or_not("example", "enable", "disable")) == 0 + assert ( + len(spack.builder.create(s.package)._activate_or_not("example", "enable", "disable")) == 0 + ) def test_autoreconf_search_path_args_multiple(default_mock_concretization, tmpdir): diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py index f6670157edf..b56d75c6fab 100644 --- a/lib/spack/spack/test/conftest.py +++ b/lib/spack/spack/test/conftest.py @@ -38,7 +38,7 @@ import spack.compiler import spack.compilers import spack.config -import spack.directives +import spack.directives_meta import spack.environment as ev import spack.error import spack.modules.common @@ -1754,7 +1754,7 @@ def clear_directive_functions(): # Make sure any directive functions overidden by tests are cleared before # proceeding with subsequent tests that may depend on the original # functions. - spack.directives.DirectiveMeta._directives_to_be_executed = [] + spack.directives_meta.DirectiveMeta._directives_to_be_executed = [] @pytest.fixture diff --git a/var/spack/repos/builder.test/packages/builder-and-mixins/package.py b/var/spack/repos/builder.test/packages/builder-and-mixins/package.py index 2e12b0e8061..354f1bcd1af 100644 --- a/var/spack/repos/builder.test/packages/builder-and-mixins/package.py +++ b/var/spack/repos/builder.test/packages/builder-and-mixins/package.py @@ -2,7 +2,7 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import spack.builder +import spack.phase_callbacks from spack.build_systems import generic from spack.package import * @@ -17,7 +17,7 @@ class BuilderAndMixins(Package): version("1.0", md5="0123456789abcdef0123456789abcdef") -class BuilderMixin(metaclass=spack.builder.PhaseCallbacksMeta): +class BuilderMixin(metaclass=spack.phase_callbacks.PhaseCallbacksMeta): @run_before("install") def before_install(self): pass diff --git a/var/spack/repos/builtin.mock/packages/py-test-callback/package.py b/var/spack/repos/builtin.mock/packages/py-test-callback/package.py index b152490895f..4abad2f679a 100644 --- a/var/spack/repos/builtin.mock/packages/py-test-callback/package.py +++ b/var/spack/repos/builtin.mock/packages/py-test-callback/package.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: (Apache-2.0 OR MIT) import spack.pkg.builtin.mock.python as mp -from spack.build_systems._checks import BaseBuilder, execute_install_time_tests +from spack.build_systems._checks import BuilderWithDefaults, execute_install_time_tests from spack.package import * @@ -31,7 +31,7 @@ def test_callback(self): @spack.builder.builder("testcallback") -class MyBuilder(BaseBuilder): +class MyBuilder(BuilderWithDefaults): phases = ("install",) #: Callback names for install-time test @@ -40,7 +40,7 @@ class MyBuilder(BaseBuilder): def install(self, pkg, spec, prefix): pkg.install(spec, prefix) - spack.builder.run_after("install")(execute_install_time_tests) + spack.phase_callbacks.run_after("install")(execute_install_time_tests) def test_callback(self): self.pkg.test_callback() diff --git a/var/spack/repos/builtin/packages/adios2/package.py b/var/spack/repos/builtin/packages/adios2/package.py index dabe5554a3f..b3a966110c5 100644 --- a/var/spack/repos/builtin/packages/adios2/package.py +++ b/var/spack/repos/builtin/packages/adios2/package.py @@ -6,6 +6,7 @@ import os import tempfile +from spack.build_systems.cmake import CMakeBuilder from spack.package import * @@ -313,11 +314,11 @@ def cmake_args(self): # hip support if spec.satisfies("+cuda"): - args.append(self.builder.define_cuda_architectures(self)) + args.append(CMakeBuilder.define_cuda_architectures(self)) # hip support if spec.satisfies("+rocm"): - args.append(self.builder.define_hip_architectures(self)) + args.append(CMakeBuilder.define_hip_architectures(self)) return args diff --git a/var/spack/repos/builtin/packages/assimp/package.py b/var/spack/repos/builtin/packages/assimp/package.py index 2e51f75e53e..ec8f239e4c5 100644 --- a/var/spack/repos/builtin/packages/assimp/package.py +++ b/var/spack/repos/builtin/packages/assimp/package.py @@ -70,7 +70,7 @@ def flag_handler(self, name, flags): return (None, None, flags) def check(self): - unit = Executable(join_path(self.builder.build_directory, "bin", "unit")) + unit = Executable(join_path(self.build_directory, "bin", "unit")) skipped_tests = [ "AssimpAPITest_aiMatrix3x3.aiMatrix3FromToTest", "AssimpAPITest_aiMatrix4x4.aiMatrix4FromToTest", diff --git a/var/spack/repos/builtin/packages/bacio/package.py b/var/spack/repos/builtin/packages/bacio/package.py index 676dab98494..18a80fdcd35 100644 --- a/var/spack/repos/builtin/packages/bacio/package.py +++ b/var/spack/repos/builtin/packages/bacio/package.py @@ -39,5 +39,5 @@ def patch(self): filter_file(".+", "2.4.1", "VERSION") def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/blaspp/package.py b/var/spack/repos/builtin/packages/blaspp/package.py index 2bdaf62c747..9974e1d3973 100644 --- a/var/spack/repos/builtin/packages/blaspp/package.py +++ b/var/spack/repos/builtin/packages/blaspp/package.py @@ -117,8 +117,8 @@ def cmake_args(self): def check(self): # If the tester fails to build, ensure that the check() fails. - if os.path.isfile(join_path(self.builder.build_directory, "test", "tester")): - with working_dir(self.builder.build_directory): + if os.path.isfile(join_path(self.build_directory, "test", "tester")): + with working_dir(self.build_directory): make("check") else: raise Exception("The tester was not built!") diff --git a/var/spack/repos/builtin/packages/boost/package.py b/var/spack/repos/builtin/packages/boost/package.py index dc7317c6bce..ecc493a3b02 100644 --- a/var/spack/repos/builtin/packages/boost/package.py +++ b/var/spack/repos/builtin/packages/boost/package.py @@ -823,18 +823,6 @@ def is_64bit(): def setup_run_environment(self, env): env.set("BOOST_ROOT", self.prefix) - def setup_dependent_package(self, module, dependent_spec): - # Disable find package's config mode for versions of Boost that - # didn't provide it. See https://github.com/spack/spack/issues/20169 - # and https://cmake.org/cmake/help/latest/module/FindBoost.html - if self.spec.satisfies("boost@:1.69.0") and dependent_spec.satisfies("build_system=cmake"): - args_fn = type(dependent_spec.package.builder).cmake_args - - def _cmake_args(self): - return ["-DBoost_NO_BOOST_CMAKE=ON"] + args_fn(self) - - type(dependent_spec.package.builder).cmake_args = _cmake_args - def setup_dependent_build_environment(self, env, dependent_spec): if "+context" in self.spec and "context-impl" in self.spec.variants: context_impl = self.spec.variants["context-impl"].value diff --git a/var/spack/repos/builtin/packages/bufr/package.py b/var/spack/repos/builtin/packages/bufr/package.py index be3431fe6a1..1d74b9aaf6b 100644 --- a/var/spack/repos/builtin/packages/bufr/package.py +++ b/var/spack/repos/builtin/packages/bufr/package.py @@ -129,5 +129,5 @@ def setup_run_environment(self, env): self._setup_bufr_environment(env, suffix) def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/g2/package.py b/var/spack/repos/builtin/packages/g2/package.py index 3f64de93c2e..b8d4a9be518 100644 --- a/var/spack/repos/builtin/packages/g2/package.py +++ b/var/spack/repos/builtin/packages/g2/package.py @@ -98,5 +98,5 @@ def setup_run_environment(self, env): env.set("G2_INC" + suffix, join_path(self.prefix, "include_" + suffix)) def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/g2c/package.py b/var/spack/repos/builtin/packages/g2c/package.py index d4cb7c74a25..0554ccf7b83 100644 --- a/var/spack/repos/builtin/packages/g2c/package.py +++ b/var/spack/repos/builtin/packages/g2c/package.py @@ -94,5 +94,5 @@ def setup_run_environment(self, env): env.set("G2C_INC", join_path(self.prefix, "include")) def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/g2tmpl/package.py b/var/spack/repos/builtin/packages/g2tmpl/package.py index b5ad734b014..b76804e8737 100644 --- a/var/spack/repos/builtin/packages/g2tmpl/package.py +++ b/var/spack/repos/builtin/packages/g2tmpl/package.py @@ -38,5 +38,5 @@ def cmake_args(self): return args def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/gfsio/package.py b/var/spack/repos/builtin/packages/gfsio/package.py index e478a8b8ed5..590f70dac58 100644 --- a/var/spack/repos/builtin/packages/gfsio/package.py +++ b/var/spack/repos/builtin/packages/gfsio/package.py @@ -46,5 +46,5 @@ def flag_handler(self, name, flags): return (None, None, flags) def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/glib/package.py b/var/spack/repos/builtin/packages/glib/package.py index efdcfa90c13..057d180dd6e 100644 --- a/var/spack/repos/builtin/packages/glib/package.py +++ b/var/spack/repos/builtin/packages/glib/package.py @@ -208,7 +208,7 @@ def libs(self): return find_libraries(["libglib*"], root=self.prefix, recursive=True) -class BaseBuilder(metaclass=spack.builder.PhaseCallbacksMeta): +class AnyBuilder(BaseBuilder): @property def dtrace_copy_path(self): return join_path(self.stage.source_path, "dtrace-copy") @@ -288,7 +288,7 @@ def gettext_libdir(self): filter_file(pattern, repl, myfile, backup=False) -class MesonBuilder(BaseBuilder, spack.build_systems.meson.MesonBuilder): +class MesonBuilder(AnyBuilder, spack.build_systems.meson.MesonBuilder): def meson_args(self): args = [] if self.spec.satisfies("@2.63.5:"): @@ -330,7 +330,7 @@ def meson_args(self): return args -class AutotoolsBuilder(BaseBuilder, spack.build_systems.autotools.AutotoolsBuilder): +class AutotoolsBuilder(AnyBuilder, spack.build_systems.autotools.AutotoolsBuilder): def configure_args(self): args = [] if self.spec.satisfies("+libmount"): diff --git a/var/spack/repos/builtin/packages/grib-util/package.py b/var/spack/repos/builtin/packages/grib-util/package.py index b8e2aaee371..b44e86134ea 100644 --- a/var/spack/repos/builtin/packages/grib-util/package.py +++ b/var/spack/repos/builtin/packages/grib-util/package.py @@ -56,5 +56,5 @@ def cmake_args(self): return args def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/hipblas/package.py b/var/spack/repos/builtin/packages/hipblas/package.py index 1e928b27d5f..e4b0ccc8d57 100644 --- a/var/spack/repos/builtin/packages/hipblas/package.py +++ b/var/spack/repos/builtin/packages/hipblas/package.py @@ -145,7 +145,5 @@ def cmake_args(self): return args def check(self): - exe = Executable( - join_path(self.builder.build_directory, "clients", "staging", "hipblas-test") - ) + exe = Executable(join_path(self.build_directory, "clients", "staging", "hipblas-test")) exe("--gtest_filter=-*known_bug*") diff --git a/var/spack/repos/builtin/packages/hipsolver/package.py b/var/spack/repos/builtin/packages/hipsolver/package.py index 9769f44ebb1..5df642cd904 100644 --- a/var/spack/repos/builtin/packages/hipsolver/package.py +++ b/var/spack/repos/builtin/packages/hipsolver/package.py @@ -119,7 +119,7 @@ class Hipsolver(CMakePackage, CudaPackage, ROCmPackage): patch("0001-suite-sparse-include-path-6.1.1.patch", when="@6.1.1:") def check(self): - exe = join_path(self.builder.build_directory, "clients", "staging", "hipsolver-test") + exe = join_path(self.build_directory, "clients", "staging", "hipsolver-test") exe = which(exe) exe(["--gtest_filter=-*known_bug*"]) diff --git a/var/spack/repos/builtin/packages/ip/package.py b/var/spack/repos/builtin/packages/ip/package.py index 5a55f37b81f..e87a5fb6f7f 100644 --- a/var/spack/repos/builtin/packages/ip/package.py +++ b/var/spack/repos/builtin/packages/ip/package.py @@ -121,5 +121,5 @@ def setup_run_environment(self, env): @when("@4:") def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/landsfcutil/package.py b/var/spack/repos/builtin/packages/landsfcutil/package.py index 9027d4eeda7..c137cb2e90e 100644 --- a/var/spack/repos/builtin/packages/landsfcutil/package.py +++ b/var/spack/repos/builtin/packages/landsfcutil/package.py @@ -48,5 +48,5 @@ def flag_handler(self, name, flags): return (None, None, flags) def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/lapackpp/package.py b/var/spack/repos/builtin/packages/lapackpp/package.py index a9757925558..7e3577aa28d 100644 --- a/var/spack/repos/builtin/packages/lapackpp/package.py +++ b/var/spack/repos/builtin/packages/lapackpp/package.py @@ -129,8 +129,8 @@ def cmake_args(self): def check(self): # If the tester fails to build, ensure that the check() fails. - if os.path.isfile(join_path(self.builder.build_directory, "test", "tester")): - with working_dir(self.builder.build_directory): + if os.path.isfile(join_path(self.build_directory, "test", "tester")): + with working_dir(self.build_directory): make("check") else: raise Exception("The tester was not built!") diff --git a/var/spack/repos/builtin/packages/lc-framework/package.py b/var/spack/repos/builtin/packages/lc-framework/package.py index 60ff03576b2..0f9f1b1be2e 100644 --- a/var/spack/repos/builtin/packages/lc-framework/package.py +++ b/var/spack/repos/builtin/packages/lc-framework/package.py @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) +from spack.build_systems.cmake import CMakeBuilder from spack.package import * @@ -45,6 +46,6 @@ def cmake_args(self): args = [self.define_from_variant("LC_BUILD_LIBPRESSIO_PLUGIN", "libpressio")] if self.spec.satisfies("+cuda"): args.append(self.define_from_variant("LC_BUILD_CUDA", "cuda")) - args.append(self.builder.define_cuda_architectures(self)) + args.append(CMakeBuilder.define_cuda_architectures(self)) return args diff --git a/var/spack/repos/builtin/packages/libxml2/package.py b/var/spack/repos/builtin/packages/libxml2/package.py index 048debd6589..8bd6a23b31c 100644 --- a/var/spack/repos/builtin/packages/libxml2/package.py +++ b/var/spack/repos/builtin/packages/libxml2/package.py @@ -6,7 +6,6 @@ import llnl.util.filesystem as fs -import spack.builder from spack.build_systems import autotools, nmake from spack.package import * @@ -225,7 +224,7 @@ def test_xmllint(self): xmllint("--dtdvalid", dtd_path, data_dir.join("info.xml")) -class BaseBuilder(metaclass=spack.builder.PhaseCallbacksMeta): +class AnyBuilder(BaseBuilder): @run_after("install") @on_package_attributes(run_tests=True) def import_module_test(self): @@ -234,7 +233,7 @@ def import_module_test(self): python("-c", "import libxml2") -class AutotoolsBuilder(BaseBuilder, autotools.AutotoolsBuilder): +class AutotoolsBuilder(AnyBuilder, autotools.AutotoolsBuilder): def configure_args(self): spec = self.spec @@ -260,7 +259,7 @@ def configure_args(self): return args -class NMakeBuilder(BaseBuilder, nmake.NMakeBuilder): +class NMakeBuilder(AnyBuilder, nmake.NMakeBuilder): phases = ("configure", "build", "install") @property diff --git a/var/spack/repos/builtin/packages/mapl/package.py b/var/spack/repos/builtin/packages/mapl/package.py index 9f31c3be66f..4fa64a712ea 100644 --- a/var/spack/repos/builtin/packages/mapl/package.py +++ b/var/spack/repos/builtin/packages/mapl/package.py @@ -423,7 +423,7 @@ def setup_build_environment(self, env): @run_after("build") @on_package_attributes(run_tests=True) def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): # The test suite contains a lot of tests. We select only those # that are cheap. Note this requires MPI and 6 processes ctest("--output-on-failure", "-L", "ESSENTIAL") diff --git a/var/spack/repos/builtin/packages/mercury/package.py b/var/spack/repos/builtin/packages/mercury/package.py index 9a533cde8c1..bf4de22b88b 100644 --- a/var/spack/repos/builtin/packages/mercury/package.py +++ b/var/spack/repos/builtin/packages/mercury/package.py @@ -160,5 +160,5 @@ def cmake_args(self): def check(self): """Unit tests fail when run in parallel.""" - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test", parallel=False) diff --git a/var/spack/repos/builtin/packages/ncio/package.py b/var/spack/repos/builtin/packages/ncio/package.py index d710a81923f..665742c6337 100644 --- a/var/spack/repos/builtin/packages/ncio/package.py +++ b/var/spack/repos/builtin/packages/ncio/package.py @@ -36,5 +36,5 @@ def setup_run_environment(self, env): env.set("NCIO_LIBDIR", lib[0]) def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/nemsio/package.py b/var/spack/repos/builtin/packages/nemsio/package.py index 0677376d154..20696a70f62 100644 --- a/var/spack/repos/builtin/packages/nemsio/package.py +++ b/var/spack/repos/builtin/packages/nemsio/package.py @@ -48,5 +48,5 @@ def cmake_args(self): return args def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/nemsiogfs/package.py b/var/spack/repos/builtin/packages/nemsiogfs/package.py index e38f5a0adf2..21bb43c2bac 100644 --- a/var/spack/repos/builtin/packages/nemsiogfs/package.py +++ b/var/spack/repos/builtin/packages/nemsiogfs/package.py @@ -26,5 +26,5 @@ class Nemsiogfs(CMakePackage): depends_on("nemsio") def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/netcdf-c/package.py b/var/spack/repos/builtin/packages/netcdf-c/package.py index 986fbfe7492..d2d05a29d61 100644 --- a/var/spack/repos/builtin/packages/netcdf-c/package.py +++ b/var/spack/repos/builtin/packages/netcdf-c/package.py @@ -9,7 +9,6 @@ from llnl.util.lang import dedupe -import spack.builder from spack.build_systems import autotools, cmake from spack.package import * from spack.util.environment import filter_system_paths @@ -291,7 +290,7 @@ def setup_run_environment(self, env): env.append_path("HDF5_PLUGIN_PATH", self.prefix.plugins) def flag_handler(self, name, flags): - if self.builder.build_system == "autotools": + if self.spec.satisfies("build_system=autotools"): if name == "cflags": if "+pic" in self.spec: flags.append(self.compiler.cc_pic_flag) @@ -305,7 +304,7 @@ def libs(self): return find_libraries("libnetcdf", root=self.prefix, shared=shared, recursive=True) -class BaseBuilder(metaclass=spack.builder.PhaseCallbacksMeta): +class AnyBuilder(BaseBuilder): def setup_dependent_build_environment(self, env, dependent_spec): # Some packages, e.g. ncview, refuse to build if the compiler path returned by nc-config # differs from the path to the compiler that the package should be built with. Therefore, @@ -329,7 +328,7 @@ def backup_nc_config(self): filter_compiler_wrappers("nc-config", relative_root="bin") -class CMakeBuilder(BaseBuilder, cmake.CMakeBuilder): +class CMakeBuilder(AnyBuilder, cmake.CMakeBuilder): def cmake_args(self): base_cmake_args = [ self.define_from_variant("BUILD_SHARED_LIBS", "shared"), @@ -376,7 +375,7 @@ def patch_hdf5_pkgconfigcmake(self): filter_file(f"hdf5_hl-{config}", "hdf5_hl", *files, ignore_absent=True) -class AutotoolsBuilder(BaseBuilder, autotools.AutotoolsBuilder): +class AutotoolsBuilder(AnyBuilder, autotools.AutotoolsBuilder): @property def force_autoreconf(self): return any(self.spec.satisfies(s) for s in self.pkg._force_autoreconf_when) diff --git a/var/spack/repos/builtin/packages/nimrod-aai/package.py b/var/spack/repos/builtin/packages/nimrod-aai/package.py index 0b24858ef86..2bfc608861b 100644 --- a/var/spack/repos/builtin/packages/nimrod-aai/package.py +++ b/var/spack/repos/builtin/packages/nimrod-aai/package.py @@ -69,5 +69,5 @@ def cmake_args(self): @run_after("build") @on_package_attributes(run_tests=True) def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): ctest("--output-on-failure") diff --git a/var/spack/repos/builtin/packages/prod-util/package.py b/var/spack/repos/builtin/packages/prod-util/package.py index e7c5b374857..0fe46740f13 100644 --- a/var/spack/repos/builtin/packages/prod-util/package.py +++ b/var/spack/repos/builtin/packages/prod-util/package.py @@ -34,5 +34,5 @@ class ProdUtil(CMakePackage): depends_on("w3emc", when="@2:") def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/proj/package.py b/var/spack/repos/builtin/packages/proj/package.py index d9ed3ef11d8..17d7da99ecc 100644 --- a/var/spack/repos/builtin/packages/proj/package.py +++ b/var/spack/repos/builtin/packages/proj/package.py @@ -3,7 +3,6 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import spack.builder from spack.build_systems import autotools, cmake from spack.package import * @@ -145,7 +144,7 @@ def setup_run_environment(self, env): env.set("PROJ_LIB", self.prefix.share.proj) -class BaseBuilder(metaclass=spack.builder.PhaseCallbacksMeta): +class AnyBuilder(BaseBuilder): def setup_build_environment(self, env): env.set("PROJ_LIB", join_path(self.pkg.stage.source_path, "nad")) @@ -154,7 +153,7 @@ def install_datum_grids(self): install_tree(join_path("share", "proj"), self.prefix.share.proj) -class CMakeBuilder(BaseBuilder, cmake.CMakeBuilder): +class CMakeBuilder(AnyBuilder, cmake.CMakeBuilder): def cmake_args(self): shared_arg = "BUILD_SHARED_LIBS" if self.spec.satisfies("@7:") else "BUILD_LIBPROJ_SHARED" args = [ @@ -177,7 +176,7 @@ def cmake_args(self): return args -class AutotoolsBuilder(BaseBuilder, autotools.AutotoolsBuilder): +class AutotoolsBuilder(AnyBuilder, autotools.AutotoolsBuilder): def configure_args(self): args = [] diff --git a/var/spack/repos/builtin/packages/raja/package.py b/var/spack/repos/builtin/packages/raja/package.py index 39f1d108629..1894598f769 100644 --- a/var/spack/repos/builtin/packages/raja/package.py +++ b/var/spack/repos/builtin/packages/raja/package.py @@ -448,7 +448,7 @@ def cmake_args(self): @property def build_relpath(self): """Relative path to the cmake build subdirectory.""" - return join_path("..", self.builder.build_dirname) + return join_path("..", self.build_dirname) @run_after("install") def setup_build_tests(self): diff --git a/var/spack/repos/builtin/packages/rocthrust/package.py b/var/spack/repos/builtin/packages/rocthrust/package.py index 93b017243fc..29029e992a9 100644 --- a/var/spack/repos/builtin/packages/rocthrust/package.py +++ b/var/spack/repos/builtin/packages/rocthrust/package.py @@ -77,7 +77,7 @@ class Rocthrust(CMakePackage): depends_on(f"rocm-cmake@{ver}:", type="build", when=f"@{ver}") def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") def setup_build_environment(self, env): diff --git a/var/spack/repos/builtin/packages/sfcio/package.py b/var/spack/repos/builtin/packages/sfcio/package.py index afe4ed84c7f..5ee64f70928 100644 --- a/var/spack/repos/builtin/packages/sfcio/package.py +++ b/var/spack/repos/builtin/packages/sfcio/package.py @@ -46,5 +46,5 @@ def flag_handler(self, name, flags): return (None, None, flags) def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/sigio/package.py b/var/spack/repos/builtin/packages/sigio/package.py index 305f0e9958b..b6d3020211b 100644 --- a/var/spack/repos/builtin/packages/sigio/package.py +++ b/var/spack/repos/builtin/packages/sigio/package.py @@ -44,5 +44,5 @@ def flag_handler(self, name, flags): return (None, None, flags) def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/sp/package.py b/var/spack/repos/builtin/packages/sp/package.py index 5b3b7b48407..7b22e688b8d 100644 --- a/var/spack/repos/builtin/packages/sp/package.py +++ b/var/spack/repos/builtin/packages/sp/package.py @@ -66,5 +66,5 @@ def cmake_args(self): return args def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/strumpack/package.py b/var/spack/repos/builtin/packages/strumpack/package.py index fc600ac42e6..28cc57f26d0 100644 --- a/var/spack/repos/builtin/packages/strumpack/package.py +++ b/var/spack/repos/builtin/packages/strumpack/package.py @@ -226,7 +226,7 @@ def _test_example(self, test_prog, test_cmd, pre_args=[]): ) with working_dir(test_dir): - opts = self.builder.std_cmake_args + self.cmake_args() + ["."] + opts = self.std_cmake_args + self.cmake_args() + ["."] cmake = self.spec["cmake"].command cmake(*opts) diff --git a/var/spack/repos/builtin/packages/sundials/package.py b/var/spack/repos/builtin/packages/sundials/package.py index 5be35b6bb04..41f939df5be 100644 --- a/var/spack/repos/builtin/packages/sundials/package.py +++ b/var/spack/repos/builtin/packages/sundials/package.py @@ -743,7 +743,7 @@ def libs(self): @on_package_attributes(run_tests=True) def check_test_install(self): """Perform test_install on the build.""" - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test_install") @property diff --git a/var/spack/repos/builtin/packages/superlu/package.py b/var/spack/repos/builtin/packages/superlu/package.py index 6a62ae9e174..92f7481befc 100644 --- a/var/spack/repos/builtin/packages/superlu/package.py +++ b/var/spack/repos/builtin/packages/superlu/package.py @@ -84,7 +84,7 @@ def test_example(self): superlu() -class BaseBuilder(metaclass=spack.builder.PhaseCallbacksMeta): +class AnyBuilder(BaseBuilder): @run_after("install") def setup_standalone_tests(self): """Set up and copy example source files after the package is installed @@ -138,7 +138,7 @@ def _make_hdr_for_test(self, lib): ] -class CMakeBuilder(BaseBuilder, spack.build_systems.cmake.CMakeBuilder): +class CMakeBuilder(AnyBuilder, spack.build_systems.cmake.CMakeBuilder): def cmake_args(self): if self.pkg.version > Version("5.2.1"): _blaslib_key = "enable_internal_blaslib" @@ -153,7 +153,7 @@ def cmake_args(self): return args -class GenericBuilder(BaseBuilder, spack.build_systems.generic.GenericBuilder): +class GenericBuilder(AnyBuilder, spack.build_systems.generic.GenericBuilder): def install(self, pkg, spec, prefix): """Use autotools before version 5""" # Define make.inc file diff --git a/var/spack/repos/builtin/packages/tcl/package.py b/var/spack/repos/builtin/packages/tcl/package.py index d95d80331e5..a6b243d4bf2 100644 --- a/var/spack/repos/builtin/packages/tcl/package.py +++ b/var/spack/repos/builtin/packages/tcl/package.py @@ -14,22 +14,20 @@ is_windows = sys.platform == "win32" -class TclHelper: - @staticmethod - def find_script_dir(spec): - # Put more-specific prefixes first - check_prefixes = [ - join_path(spec.prefix, "share", "tcl{0}".format(spec.package.version.up_to(2))), - spec.prefix, - ] - for prefix in check_prefixes: - result = find_first(prefix, "init.tcl") - if result: - return os.path.dirname(result) - raise RuntimeError("Cannot locate init.tcl") +def find_script_dir(spec: Spec) -> str: + # Put more-specific prefixes first + check_prefixes = [ + join_path(spec.prefix, "share", "tcl{0}".format(spec.package.version.up_to(2))), + spec.prefix, + ] + for prefix in check_prefixes: + result = find_first(prefix, "init.tcl") + if result: + return os.path.dirname(result) + raise RuntimeError("Cannot locate init.tcl") -class Tcl(AutotoolsPackage, NMakePackage, SourceforgePackage, TclHelper): +class Tcl(AutotoolsPackage, NMakePackage, SourceforgePackage): """Tcl (Tool Command Language) is a very powerful but easy to learn dynamic programming language, suitable for a very wide range of uses, including web and desktop applications, networking, administration, testing and many more. Open source @@ -105,7 +103,7 @@ def setup_run_environment(self, env): """ # When using tkinter from within spack provided python+tkinter, # python will not be able to find Tcl unless TCL_LIBRARY is set. - env.set("TCL_LIBRARY", TclHelper.find_script_dir(self.spec)) + env.set("TCL_LIBRARY", find_script_dir(self.spec)) def setup_dependent_run_environment(self, env, dependent_spec): """Set TCLLIBPATH to include the tcl-shipped directory for @@ -123,7 +121,7 @@ def setup_dependent_run_environment(self, env, dependent_spec): env.prepend_path("TCLLIBPATH", tcllibpath, separator=" ") -class BaseBuilder(TclHelper, metaclass=spack.builder.PhaseCallbacksMeta): +class AnyBuilder(BaseBuilder): @run_after("install") def symlink_tclsh(self): # There's some logic regarding this suffix in the build system @@ -151,7 +149,7 @@ def setup_dependent_build_environment(self, env, dependent_spec): * https://wiki.tcl-lang.org/page/TCL_LIBRARY * https://wiki.tcl-lang.org/page/TCLLIBPATH """ - env.set("TCL_LIBRARY", TclHelper.find_script_dir(self.spec)) + env.set("TCL_LIBRARY", find_script_dir(self.spec)) # If we set TCLLIBPATH, we must also ensure that the corresponding # tcl is found in the build environment. This to prevent cases @@ -182,7 +180,7 @@ def setup_dependent_build_environment(self, env, dependent_spec): env.prepend_path("TCLLIBPATH", tcllibpath, separator=" ") -class AutotoolsBuilder(BaseBuilder, spack.build_systems.autotools.AutotoolsBuilder): +class AutotoolsBuilder(AnyBuilder, spack.build_systems.autotools.AutotoolsBuilder): configure_directory = "unix" def install(self, pkg, spec, prefix): @@ -215,7 +213,7 @@ def install(self, pkg, spec, prefix): make("clean") -class NMakeBuilder(BaseBuilder, spack.build_systems.nmake.NMakeBuilder): +class NMakeBuilder(AnyBuilder, spack.build_systems.nmake.NMakeBuilder): build_targets = ["all"] install_targets = ["install"] diff --git a/var/spack/repos/builtin/packages/vtk-m/package.py b/var/spack/repos/builtin/packages/vtk-m/package.py index c5cef49c585..f896e2160e6 100644 --- a/var/spack/repos/builtin/packages/vtk-m/package.py +++ b/var/spack/repos/builtin/packages/vtk-m/package.py @@ -7,6 +7,7 @@ import os import sys +from spack.build_systems.cmake import CMakeBuilder from spack.package import * @@ -249,7 +250,7 @@ def cmake_args(self): options.append("-DCMAKE_CUDA_HOST_COMPILER={0}".format(env["SPACK_CXX"])) if spec.satisfies("@1.9.0:") and spec.satisfies("^cmake@3.18:"): - options.append(self.builder.define_cuda_architectures(self)) + options.append(CMakeBuilder.define_cuda_architectures(self)) else: # VTKm_CUDA_Architecture only accepts a single CUDA arch @@ -269,7 +270,7 @@ def cmake_args(self): # hip support if "+rocm" in spec: - options.append(self.builder.define_hip_architectures(self)) + options.append(CMakeBuilder.define_hip_architectures(self)) return options diff --git a/var/spack/repos/builtin/packages/w3emc/package.py b/var/spack/repos/builtin/packages/w3emc/package.py index 82dbf7bf8c6..7e0967bcde0 100644 --- a/var/spack/repos/builtin/packages/w3emc/package.py +++ b/var/spack/repos/builtin/packages/w3emc/package.py @@ -95,5 +95,5 @@ def cmake_args(self): return args def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): make("test") diff --git a/var/spack/repos/builtin/packages/xsdk-examples/package.py b/var/spack/repos/builtin/packages/xsdk-examples/package.py index 66f1ee05a7f..4cdb496b972 100644 --- a/var/spack/repos/builtin/packages/xsdk-examples/package.py +++ b/var/spack/repos/builtin/packages/xsdk-examples/package.py @@ -101,5 +101,5 @@ def enabled(pkg): return args def check(self): - with working_dir(self.builder.build_directory): + with working_dir(self.build_directory): ctest("--output-on-failure")