Allow for packages with multiple build-systems (#30738)
This commit extends the DSL that can be used in packages to allow declaring that a package uses different build-systems under different conditions. It requires each spec to have a `build_system` single valued variant. The variant can be used in many context to query, manipulate or select the build system associated with a concrete spec. The knowledge to build a package has been moved out of the PackageBase hierarchy, into a new Builder hierarchy. Customization of the default behavior for a given builder can be obtained by coding a new derived builder in package.py. The "run_after" and "run_before" decorators are now applied to methods on the builder. They can also incorporate a "when=" argument to specify that a method is run only when certain conditions apply. For packages that do not define their own builder, forwarding logic is added between the builder and package (methods not found in one will be retrieved from the other); this PR is expected to be fully backwards compatible with unmodified packages that use a single build system.
This commit is contained in:
committed by
GitHub
parent
83ee500108
commit
30c9ff50dd
@@ -503,6 +503,33 @@ def invalid_sha256_digest(fetcher):
|
||||
return errors
|
||||
|
||||
|
||||
@package_properties
|
||||
def _ensure_env_methods_are_ported_to_builders(pkgs, error_cls):
|
||||
"""Ensure that methods modifying the build environment are ported to builder classes."""
|
||||
errors = []
|
||||
for pkg_name in pkgs:
|
||||
pkg_cls = spack.repo.path.get_pkg_class(pkg_name)
|
||||
buildsystem_variant, _ = pkg_cls.variants["build_system"]
|
||||
buildsystem_names = [getattr(x, "value", x) for x in buildsystem_variant.values]
|
||||
builder_cls_names = [spack.builder.BUILDER_CLS[x].__name__ for x in buildsystem_names]
|
||||
module = pkg_cls.module
|
||||
has_builders_in_package_py = any(
|
||||
getattr(module, name, False) for name in builder_cls_names
|
||||
)
|
||||
if not has_builders_in_package_py:
|
||||
continue
|
||||
|
||||
for method_name in ("setup_build_environment", "setup_dependent_build_environment"):
|
||||
if hasattr(pkg_cls, method_name):
|
||||
msg = (
|
||||
"Package '{}' need to move the '{}' method from the package class to the"
|
||||
" appropriate builder class".format(pkg_name, method_name)
|
||||
)
|
||||
errors.append(error_cls(msg, []))
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
@package_https_directives
|
||||
def _linting_package_file(pkgs, error_cls):
|
||||
"""Check for correctness of links"""
|
||||
@@ -660,7 +687,13 @@ def _ensure_variant_defaults_are_parsable(pkgs, error_cls):
|
||||
errors.append(error_cls(error_msg.format(variant_name, pkg_name), []))
|
||||
continue
|
||||
|
||||
vspec = variant.make_default()
|
||||
try:
|
||||
vspec = variant.make_default()
|
||||
except spack.variant.MultipleValuesInExclusiveVariantError:
|
||||
error_msg = "Cannot create a default value for the variant '{}' in package '{}'"
|
||||
errors.append(error_cls(error_msg.format(variant_name, pkg_name), []))
|
||||
continue
|
||||
|
||||
try:
|
||||
variant.validate_or_raise(vspec, pkg_cls=pkg_cls)
|
||||
except spack.variant.InvalidVariantValueError:
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
|
||||
import spack.build_systems.cmake
|
||||
import spack.build_systems.meson
|
||||
import spack.builder
|
||||
import spack.config
|
||||
import spack.install_test
|
||||
import spack.main
|
||||
@@ -558,9 +559,9 @@ def _set_variables_for_single_module(pkg, module):
|
||||
if sys.platform == "win32":
|
||||
m.nmake = Executable("nmake")
|
||||
# Standard CMake arguments
|
||||
m.std_cmake_args = spack.build_systems.cmake.CMakePackage._std_args(pkg)
|
||||
m.std_meson_args = spack.build_systems.meson.MesonPackage._std_args(pkg)
|
||||
m.std_pip_args = spack.build_systems.python.PythonPackage._std_args(pkg)
|
||||
m.std_cmake_args = spack.build_systems.cmake.CMakeBuilder.std_args(pkg)
|
||||
m.std_meson_args = spack.build_systems.meson.MesonBuilder.std_args(pkg)
|
||||
m.std_pip_args = spack.build_systems.python.PythonPipBuilder.std_args(pkg)
|
||||
|
||||
# Put spack compiler paths in module scope.
|
||||
link_dir = spack.paths.build_env_path
|
||||
@@ -727,38 +728,6 @@ def get_rpaths(pkg):
|
||||
return list(dedupe(filter_system_paths(rpaths)))
|
||||
|
||||
|
||||
def get_std_cmake_args(pkg):
|
||||
"""List of standard arguments used if a package is a CMakePackage.
|
||||
|
||||
Returns:
|
||||
list: standard arguments that would be used if this
|
||||
package were a CMakePackage instance.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package under consideration
|
||||
|
||||
Returns:
|
||||
list: arguments for cmake
|
||||
"""
|
||||
return spack.build_systems.cmake.CMakePackage._std_args(pkg)
|
||||
|
||||
|
||||
def get_std_meson_args(pkg):
|
||||
"""List of standard arguments used if a package is a MesonPackage.
|
||||
|
||||
Returns:
|
||||
list: standard arguments that would be used if this
|
||||
package were a MesonPackage instance.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package under consideration
|
||||
|
||||
Returns:
|
||||
list: arguments for meson
|
||||
"""
|
||||
return spack.build_systems.meson.MesonPackage._std_args(pkg)
|
||||
|
||||
|
||||
def parent_class_modules(cls):
|
||||
"""
|
||||
Get list of superclass modules that descend from spack.package_base.PackageBase
|
||||
@@ -819,7 +788,8 @@ def setup_package(pkg, dirty, context="build"):
|
||||
platform.setup_platform_environment(pkg, env_mods)
|
||||
|
||||
if context == "build":
|
||||
pkg.setup_build_environment(env_mods)
|
||||
builder = spack.builder.create(pkg)
|
||||
builder.setup_build_environment(env_mods)
|
||||
|
||||
if (not dirty) and (not env_mods.is_unset("CPATH")):
|
||||
tty.debug(
|
||||
@@ -1015,7 +985,8 @@ def add_modifications_for_dep(dep):
|
||||
module.__dict__.update(changes.__dict__)
|
||||
|
||||
if context == "build":
|
||||
dpkg.setup_dependent_build_environment(env, spec)
|
||||
builder = spack.builder.create(dpkg)
|
||||
builder.setup_dependent_build_environment(env, spec)
|
||||
else:
|
||||
dpkg.setup_dependent_run_environment(env, spec)
|
||||
|
||||
@@ -1117,8 +1088,20 @@ def _setup_pkg_and_run(
|
||||
pkg.test_suite.stage, spack.install_test.TestSuite.test_log_name(pkg.spec)
|
||||
)
|
||||
|
||||
error_msg = str(exc)
|
||||
if isinstance(exc, (spack.multimethod.NoSuchMethodError, AttributeError)):
|
||||
error_msg = (
|
||||
"The '{}' package cannot find an attribute while trying to build "
|
||||
"from sources. This might be due to a change in Spack's package format "
|
||||
"to support multiple build-systems for a single package. You can fix this "
|
||||
"by updating the build recipe, and you can also report the issue as a bug. "
|
||||
"More information at https://spack.readthedocs.io/en/latest/packaging_guide.html#installation-procedure"
|
||||
).format(pkg.name)
|
||||
error_msg = colorize("@*R{{{}}}".format(error_msg))
|
||||
error_msg = "{}\n\n{}".format(str(exc), error_msg)
|
||||
|
||||
# make a pickleable exception to send to parent.
|
||||
msg = "%s: %s" % (exc_type.__name__, str(exc))
|
||||
msg = "%s: %s" % (exc_type.__name__, error_msg)
|
||||
|
||||
ce = ChildError(
|
||||
msg,
|
||||
|
||||
124
lib/spack/spack/build_systems/_checks.py
Normal file
124
lib/spack/spack/build_systems/_checks.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# Copyright 2013-2022 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 os
|
||||
|
||||
import six
|
||||
|
||||
import llnl.util.lang
|
||||
|
||||
import spack.builder
|
||||
import spack.installer
|
||||
import spack.relocate
|
||||
import spack.store
|
||||
|
||||
|
||||
def sanity_check_prefix(builder):
|
||||
"""Check that specific directories and files are created after installation.
|
||||
|
||||
The files to be checked are in the ``sanity_check_is_file`` attribute of the
|
||||
package object, while the directories are in the ``sanity_check_is_dir``.
|
||||
|
||||
Args:
|
||||
builder (spack.builder.Builder): builder that installed the package
|
||||
"""
|
||||
pkg = builder.pkg
|
||||
|
||||
def check_paths(path_list, filetype, predicate):
|
||||
if isinstance(path_list, six.string_types):
|
||||
path_list = [path_list]
|
||||
|
||||
for path in path_list:
|
||||
abs_path = os.path.join(pkg.prefix, path)
|
||||
if not predicate(abs_path):
|
||||
msg = "Install failed for {0}. No such {1} in prefix: {2}"
|
||||
msg = msg.format(pkg.name, filetype, path)
|
||||
raise spack.installer.InstallError(msg)
|
||||
|
||||
check_paths(pkg.sanity_check_is_file, "file", os.path.isfile)
|
||||
check_paths(pkg.sanity_check_is_dir, "directory", os.path.isdir)
|
||||
|
||||
ignore_file = llnl.util.lang.match_predicate(spack.store.layout.hidden_file_regexes)
|
||||
if all(map(ignore_file, os.listdir(pkg.prefix))):
|
||||
msg = "Install failed for {0}. Nothing was installed!"
|
||||
raise spack.installer.InstallError(msg.format(pkg.name))
|
||||
|
||||
|
||||
def apply_macos_rpath_fixups(builder):
|
||||
"""On Darwin, make installed libraries more easily relocatable.
|
||||
|
||||
Some build systems (handrolled, autotools, makefiles) can set their own
|
||||
rpaths that are duplicated by spack's compiler wrapper. This fixup
|
||||
interrogates, and postprocesses if necessary, all libraries installed
|
||||
by the code.
|
||||
|
||||
It should be added as a @run_after to packaging systems (or individual
|
||||
packages) that do not install relocatable libraries by default.
|
||||
|
||||
Args:
|
||||
builder (spack.builder.Builder): builder that installed the package
|
||||
"""
|
||||
spack.relocate.fixup_macos_rpaths(builder.spec)
|
||||
|
||||
|
||||
def ensure_build_dependencies_or_raise(spec, dependencies, error_msg):
|
||||
"""Ensure that some build dependencies are present in the concrete spec.
|
||||
|
||||
If not, raise a RuntimeError with a helpful error message.
|
||||
|
||||
Args:
|
||||
spec (spack.spec.Spec): concrete spec to be checked.
|
||||
dependencies (list of spack.spec.Spec): list of abstract specs to be satisfied
|
||||
error_msg (str): brief error message to be prepended to a longer description
|
||||
|
||||
Raises:
|
||||
RuntimeError: when the required build dependencies are not found
|
||||
"""
|
||||
assert spec.concrete, "Can ensure build dependencies only on concrete specs"
|
||||
build_deps = [d.name for d in spec.dependencies(deptype="build")]
|
||||
missing_deps = [x for x in dependencies if x not in build_deps]
|
||||
|
||||
if not missing_deps:
|
||||
return
|
||||
|
||||
# Raise an exception on missing deps.
|
||||
msg = (
|
||||
"{0}: missing dependencies: {1}.\n\nPlease add "
|
||||
"the following lines to the package:\n\n".format(error_msg, ", ".join(missing_deps))
|
||||
)
|
||||
|
||||
for dep in missing_deps:
|
||||
msg += " depends_on('{0}', type='build', when='@{1} {2}')\n".format(
|
||||
dep, spec.version, "build_system=autotools"
|
||||
)
|
||||
|
||||
msg += "\nUpdate the version (when='@{0}') as needed.".format(spec.version)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
|
||||
def execute_build_time_tests(builder):
|
||||
"""Execute the build-time tests prescribed by builder.
|
||||
|
||||
Args:
|
||||
builder (Builder): builder prescribing the test callbacks. The name of the callbacks is
|
||||
stored as a list of strings in the ``build_time_test_callbacks`` attribute.
|
||||
"""
|
||||
builder.pkg.run_test_callbacks(builder, builder.build_time_test_callbacks, "build")
|
||||
|
||||
|
||||
def execute_install_time_tests(builder):
|
||||
"""Execute the install-time tests prescribed by builder.
|
||||
|
||||
Args:
|
||||
builder (Builder): builder prescribing the test callbacks. The name of the callbacks is
|
||||
stored as a list of strings in the ``install_time_test_callbacks`` attribute.
|
||||
"""
|
||||
builder.pkg.run_test_callbacks(builder, builder.install_time_test_callbacks, "install")
|
||||
|
||||
|
||||
class BaseBuilder(spack.builder.Builder):
|
||||
"""Base class for builders to register common checks"""
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
spack.builder.run_after("install")(sanity_check_prefix)
|
||||
@@ -2,18 +2,36 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
# Why doesn't this work for me?
|
||||
# from spack import *
|
||||
from llnl.util.filesystem import filter_file
|
||||
import spack.directives
|
||||
import spack.package_base
|
||||
import spack.util.executable
|
||||
|
||||
from spack.build_systems.autotools import AutotoolsPackage
|
||||
from spack.directives import extends
|
||||
from spack.package_base import ExtensionError
|
||||
from spack.util.executable import which
|
||||
from .autotools import AutotoolsBuilder, AutotoolsPackage
|
||||
|
||||
|
||||
class AspellBuilder(AutotoolsBuilder):
|
||||
"""The Aspell builder is close enough to an autotools builder to allow
|
||||
specializing the builder class, so to use variables that are specific
|
||||
to the Aspell extensions.
|
||||
"""
|
||||
|
||||
def configure(self, pkg, spec, prefix):
|
||||
aspell = spec["aspell"].prefix.bin.aspell
|
||||
prezip = spec["aspell"].prefix.bin.prezip
|
||||
destdir = prefix
|
||||
|
||||
sh = spack.util.executable.which("sh")
|
||||
sh(
|
||||
"./configure",
|
||||
"--vars",
|
||||
"ASPELL={0}".format(aspell),
|
||||
"PREZIP={0}".format(prezip),
|
||||
"DESTDIR={0}".format(destdir),
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Aspell dictionaries install their bits into their prefix.lib
|
||||
# and when activated they'll get symlinked into the appropriate aspell's
|
||||
# dict dir (see aspell's {de,}activate methods).
|
||||
@@ -23,12 +41,17 @@
|
||||
class AspellDictPackage(AutotoolsPackage):
|
||||
"""Specialized class for building aspell dictionairies."""
|
||||
|
||||
extends("aspell")
|
||||
spack.directives.extends("aspell", when="build_system=autotools")
|
||||
|
||||
#: Override the default autotools builder
|
||||
AutotoolsBuilder = AspellBuilder
|
||||
|
||||
def view_destination(self, view):
|
||||
aspell_spec = self.spec["aspell"]
|
||||
if view.get_projection_for_spec(aspell_spec) != aspell_spec.prefix:
|
||||
raise ExtensionError("aspell does not support non-global extensions")
|
||||
raise spack.package_base.ExtensionError(
|
||||
"aspell does not support non-global extensions"
|
||||
)
|
||||
aspell = aspell_spec.command
|
||||
return aspell("dump", "config", "dict-dir", output=str).strip()
|
||||
|
||||
@@ -36,19 +59,5 @@ def view_source(self):
|
||||
return self.prefix.lib
|
||||
|
||||
def patch(self):
|
||||
filter_file(r"^dictdir=.*$", "dictdir=/lib", "configure")
|
||||
filter_file(r"^datadir=.*$", "datadir=/lib", "configure")
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
aspell = spec["aspell"].prefix.bin.aspell
|
||||
prezip = spec["aspell"].prefix.bin.prezip
|
||||
destdir = prefix
|
||||
|
||||
sh = which("sh")
|
||||
sh(
|
||||
"./configure",
|
||||
"--vars",
|
||||
"ASPELL={0}".format(aspell),
|
||||
"PREZIP={0}".format(prezip),
|
||||
"DESTDIR={0}".format(destdir),
|
||||
)
|
||||
fs.filter_file(r"^dictdir=.*$", "dictdir=/lib", "configure")
|
||||
fs.filter_file(r"^datadir=.*$", "datadir=/lib", "configure")
|
||||
|
||||
@@ -6,87 +6,140 @@
|
||||
import os
|
||||
import os.path
|
||||
import stat
|
||||
from subprocess import PIPE, check_call
|
||||
import subprocess
|
||||
from typing import List # novm
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import force_remove, working_dir
|
||||
|
||||
from spack.build_environment import InstallError
|
||||
from spack.directives import conflicts, depends_on
|
||||
import spack.build_environment
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, conflicts, depends_on
|
||||
from spack.multimethod import when
|
||||
from spack.operating_systems.mac_os import macos_version
|
||||
from spack.package_base import PackageBase, run_after, run_before
|
||||
from spack.util.executable import Executable
|
||||
from spack.version import Version
|
||||
|
||||
from ._checks import (
|
||||
BaseBuilder,
|
||||
apply_macos_rpath_fixups,
|
||||
ensure_build_dependencies_or_raise,
|
||||
execute_build_time_tests,
|
||||
execute_install_time_tests,
|
||||
)
|
||||
|
||||
class AutotoolsPackage(PackageBase):
|
||||
"""Specialized class for packages built using GNU Autotools.
|
||||
|
||||
This class provides four phases that can be overridden:
|
||||
class AutotoolsPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using GNU Autotools."""
|
||||
|
||||
1. :py:meth:`~.AutotoolsPackage.autoreconf`
|
||||
2. :py:meth:`~.AutotoolsPackage.configure`
|
||||
3. :py:meth:`~.AutotoolsPackage.build`
|
||||
4. :py:meth:`~.AutotoolsPackage.install`
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "AutotoolsPackage"
|
||||
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "autotools"
|
||||
|
||||
build_system("autotools")
|
||||
|
||||
with when("build_system=autotools"):
|
||||
depends_on("gnuconfig", type="build", when="target=ppc64le:")
|
||||
depends_on("gnuconfig", type="build", when="target=aarch64:")
|
||||
depends_on("gnuconfig", type="build", when="target=riscv64:")
|
||||
conflicts("platform=windows")
|
||||
|
||||
def flags_to_build_system_args(self, flags):
|
||||
"""Produces a list of all command line arguments to pass specified
|
||||
compiler flags to configure."""
|
||||
# Has to be dynamic attribute due to caching.
|
||||
setattr(self, "configure_flag_args", [])
|
||||
for flag, values in flags.items():
|
||||
if values:
|
||||
values_str = "{0}={1}".format(flag.upper(), " ".join(values))
|
||||
self.configure_flag_args.append(values_str)
|
||||
# Spack's fflags are meant for both F77 and FC, therefore we
|
||||
# additionaly set FCFLAGS if required.
|
||||
values = flags.get("fflags", None)
|
||||
if values:
|
||||
values_str = "FCFLAGS={0}".format(" ".join(values))
|
||||
self.configure_flag_args.append(values_str)
|
||||
|
||||
# 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)
|
||||
|
||||
def with_or_without(self, *args, **kwargs):
|
||||
return self.builder.with_or_without(*args, **kwargs)
|
||||
|
||||
|
||||
@spack.builder.builder("autotools")
|
||||
class AutotoolsBuilder(BaseBuilder):
|
||||
"""The autotools builder encodes the default way of installing software built
|
||||
with autotools. It has four phases that can be overridden, if need be:
|
||||
|
||||
1. :py:meth:`~.AutotoolsBuilder.autoreconf`
|
||||
2. :py:meth:`~.AutotoolsBuilder.configure`
|
||||
3. :py:meth:`~.AutotoolsBuilder.build`
|
||||
4. :py:meth:`~.AutotoolsBuilder.install`
|
||||
|
||||
They all have sensible defaults and for many packages the only thing necessary
|
||||
is to override the helper method
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsBuilder.configure_args`.
|
||||
|
||||
They all have sensible defaults and for many packages the only thing
|
||||
necessary will be to override the helper method
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.configure_args`.
|
||||
For a finer tuning you may also override:
|
||||
|
||||
+-----------------------------------------------+--------------------+
|
||||
| **Method** | **Purpose** |
|
||||
+===============================================+====================+
|
||||
| :py:attr:`~.AutotoolsPackage.build_targets` | Specify ``make`` |
|
||||
| :py:attr:`~.AutotoolsBuilder.build_targets` | Specify ``make`` |
|
||||
| | targets for the |
|
||||
| | build phase |
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:attr:`~.AutotoolsPackage.install_targets` | Specify ``make`` |
|
||||
| :py:attr:`~.AutotoolsBuilder.install_targets` | Specify ``make`` |
|
||||
| | targets for the |
|
||||
| | install phase |
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:meth:`~.AutotoolsPackage.check` | Run build time |
|
||||
| :py:meth:`~.AutotoolsBuilder.check` | Run build time |
|
||||
| | tests if required |
|
||||
+-----------------------------------------------+--------------------+
|
||||
|
||||
"""
|
||||
|
||||
#: Phases of a GNU Autotools package
|
||||
phases = ["autoreconf", "configure", "build", "install"]
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "AutotoolsPackage"
|
||||
phases = ("autoreconf", "configure", "build", "install")
|
||||
|
||||
@property
|
||||
def patch_config_files(self):
|
||||
"""
|
||||
Whether or not to update old ``config.guess`` and ``config.sub`` files
|
||||
distributed with the tarball. This currently only applies to
|
||||
``ppc64le:``, ``aarch64:``, and ``riscv64`` target architectures. The
|
||||
substitutes are taken from the ``gnuconfig`` package, which is
|
||||
automatically added as a build dependency for these architectures. In
|
||||
case system versions of these config files are required, the
|
||||
``gnuconfig`` package can be marked external with a prefix pointing to
|
||||
the directory containing the system ``config.guess`` and ``config.sub``
|
||||
files.
|
||||
"""
|
||||
return (
|
||||
self.spec.satisfies("target=ppc64le:")
|
||||
or self.spec.satisfies("target=aarch64:")
|
||||
or self.spec.satisfies("target=riscv64:")
|
||||
)
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = (
|
||||
"configure_args",
|
||||
"check",
|
||||
"installcheck",
|
||||
)
|
||||
|
||||
#: Whether or not to update ``libtool``
|
||||
#: (currently only for Arm/Clang/Fujitsu/NVHPC compilers)
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"archive_files",
|
||||
"patch_libtool",
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"install_time_test_callbacks",
|
||||
"force_autoreconf",
|
||||
"autoreconf_extra_args",
|
||||
"install_libtool_archives",
|
||||
"patch_config_files",
|
||||
"configure_directory",
|
||||
"configure_abs_path",
|
||||
"build_directory",
|
||||
"autoreconf_search_path_args",
|
||||
)
|
||||
|
||||
#: Whether to update ``libtool`` (e.g. for Arm/Clang/Fujitsu/NVHPC compilers)
|
||||
patch_libtool = True
|
||||
|
||||
#: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.build`
|
||||
#: phase
|
||||
#: Targets for ``make`` during the :py:meth:`~.AutotoolsBuilder.build` phase
|
||||
build_targets = [] # type: List[str]
|
||||
#: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.install`
|
||||
#: phase
|
||||
#: Targets for ``make`` during the :py:meth:`~.AutotoolsBuilder.install` phase
|
||||
install_targets = ["install"]
|
||||
|
||||
#: Callback names for build-time test
|
||||
@@ -97,24 +150,40 @@ def patch_config_files(self):
|
||||
|
||||
#: Set to true to force the autoreconf step even if configure is present
|
||||
force_autoreconf = False
|
||||
|
||||
#: Options to be passed to autoreconf when using the default implementation
|
||||
autoreconf_extra_args = [] # type: List[str]
|
||||
|
||||
#: If False deletes all the .la files in the prefix folder
|
||||
#: after the installation. If True instead it installs them.
|
||||
#: If False deletes all the .la files in the prefix folder after the installation.
|
||||
#: If True instead it installs them.
|
||||
install_libtool_archives = False
|
||||
|
||||
depends_on("gnuconfig", type="build", when="target=ppc64le:")
|
||||
depends_on("gnuconfig", type="build", when="target=aarch64:")
|
||||
depends_on("gnuconfig", type="build", when="target=riscv64:")
|
||||
conflicts("platform=windows")
|
||||
@property
|
||||
def patch_config_files(self):
|
||||
"""Whether to update old ``config.guess`` and ``config.sub`` files
|
||||
distributed with the tarball.
|
||||
|
||||
This currently only applies to ``ppc64le:``, ``aarch64:``, and
|
||||
``riscv64`` target architectures.
|
||||
|
||||
The substitutes are taken from the ``gnuconfig`` package, which is
|
||||
automatically added as a build dependency for these architectures. In case
|
||||
system versions of these config files are required, the ``gnuconfig`` package
|
||||
can be marked external, with a prefix pointing to the directory containing the
|
||||
system ``config.guess`` and ``config.sub`` files.
|
||||
"""
|
||||
return (
|
||||
self.pkg.spec.satisfies("target=ppc64le:")
|
||||
or self.pkg.spec.satisfies("target=aarch64:")
|
||||
or self.pkg.spec.satisfies("target=riscv64:")
|
||||
)
|
||||
|
||||
@property
|
||||
def _removed_la_files_log(self):
|
||||
"""File containing the list of remove libtool archives"""
|
||||
"""File containing the list of removed libtool archives"""
|
||||
build_dir = self.build_directory
|
||||
if not os.path.isabs(self.build_directory):
|
||||
build_dir = os.path.join(self.stage.path, build_dir)
|
||||
build_dir = os.path.join(self.pkg.stage.path, build_dir)
|
||||
return os.path.join(build_dir, "removed_la_files.txt")
|
||||
|
||||
@property
|
||||
@@ -125,13 +194,13 @@ def archive_files(self):
|
||||
files.append(self._removed_la_files_log)
|
||||
return files
|
||||
|
||||
@run_after("autoreconf")
|
||||
@spack.builder.run_after("autoreconf")
|
||||
def _do_patch_config_files(self):
|
||||
"""Some packages ship with older config.guess/config.sub files and
|
||||
need to have these updated when installed on a newer architecture.
|
||||
In particular, config.guess fails for PPC64LE for version prior
|
||||
to a 2013-06-10 build date (automake 1.13.4) and for ARM (aarch64) and
|
||||
RISC-V (riscv64).
|
||||
"""Some packages ship with older config.guess/config.sub files and need to
|
||||
have these updated when installed on a newer architecture.
|
||||
|
||||
In particular, config.guess fails for PPC64LE for version prior to a
|
||||
2013-06-10 build date (automake 1.13.4) and for AArch64 and RISC-V.
|
||||
"""
|
||||
if not self.patch_config_files:
|
||||
return
|
||||
@@ -139,11 +208,11 @@ def _do_patch_config_files(self):
|
||||
# TODO: Expand this to select the 'config.sub'-compatible architecture
|
||||
# for each platform (e.g. 'config.sub' doesn't accept 'power9le', but
|
||||
# does accept 'ppc64le').
|
||||
if self.spec.satisfies("target=ppc64le:"):
|
||||
if self.pkg.spec.satisfies("target=ppc64le:"):
|
||||
config_arch = "ppc64le"
|
||||
elif self.spec.satisfies("target=aarch64:"):
|
||||
elif self.pkg.spec.satisfies("target=aarch64:"):
|
||||
config_arch = "aarch64"
|
||||
elif self.spec.satisfies("target=riscv64:"):
|
||||
elif self.pkg.spec.satisfies("target=riscv64:"):
|
||||
config_arch = "riscv64"
|
||||
else:
|
||||
config_arch = "local"
|
||||
@@ -155,7 +224,7 @@ def runs_ok(script_abs_path):
|
||||
args = [script_abs_path] + additional_args.get(script_name, [])
|
||||
|
||||
try:
|
||||
check_call(args, stdout=PIPE, stderr=PIPE)
|
||||
subprocess.check_call(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except Exception as e:
|
||||
tty.debug(e)
|
||||
return False
|
||||
@@ -163,7 +232,7 @@ def runs_ok(script_abs_path):
|
||||
return True
|
||||
|
||||
# Get the list of files that needs to be patched
|
||||
to_be_patched = fs.find(self.stage.path, files=["config.sub", "config.guess"])
|
||||
to_be_patched = fs.find(self.pkg.stage.path, files=["config.sub", "config.guess"])
|
||||
to_be_patched = [f for f in to_be_patched if not runs_ok(f)]
|
||||
|
||||
# If there are no files to be patched, return early
|
||||
@@ -171,22 +240,21 @@ def runs_ok(script_abs_path):
|
||||
return
|
||||
|
||||
# Otherwise, require `gnuconfig` to be a build dependency
|
||||
self._require_build_deps(
|
||||
pkgs=["gnuconfig"], spec=self.spec, err="Cannot patch config files"
|
||||
ensure_build_dependencies_or_raise(
|
||||
spec=self.pkg.spec, dependencies=["gnuconfig"], error_msg="Cannot patch config files"
|
||||
)
|
||||
|
||||
# Get the config files we need to patch (config.sub / config.guess).
|
||||
to_be_found = list(set(os.path.basename(f) for f in to_be_patched))
|
||||
gnuconfig = self.spec["gnuconfig"]
|
||||
gnuconfig = self.pkg.spec["gnuconfig"]
|
||||
gnuconfig_dir = gnuconfig.prefix
|
||||
|
||||
# An external gnuconfig may not not have a prefix.
|
||||
if gnuconfig_dir is None:
|
||||
raise InstallError(
|
||||
"Spack could not find substitutes for GNU config "
|
||||
"files because no prefix is available for the "
|
||||
"`gnuconfig` package. Make sure you set a prefix "
|
||||
"path instead of modules for external `gnuconfig`."
|
||||
raise spack.build_environment.InstallError(
|
||||
"Spack could not find substitutes for GNU config files because no "
|
||||
"prefix is available for the `gnuconfig` package. Make sure you set a "
|
||||
"prefix path instead of modules for external `gnuconfig`."
|
||||
)
|
||||
|
||||
candidates = fs.find(gnuconfig_dir, files=to_be_found, recursive=False)
|
||||
@@ -203,7 +271,7 @@ def runs_ok(script_abs_path):
|
||||
msg += (
|
||||
" or the `gnuconfig` package prefix is misconfigured as" " an external package"
|
||||
)
|
||||
raise InstallError(msg)
|
||||
raise spack.build_environment.InstallError(msg)
|
||||
|
||||
# Filter working substitutes
|
||||
candidates = [f for f in candidates if runs_ok(f)]
|
||||
@@ -228,7 +296,9 @@ def runs_ok(script_abs_path):
|
||||
and set the prefix to the directory containing the `config.guess` and
|
||||
`config.sub` files.
|
||||
"""
|
||||
raise InstallError(msg.format(", ".join(to_be_found), self.name))
|
||||
raise spack.build_environment.InstallError(
|
||||
msg.format(", ".join(to_be_found), self.name)
|
||||
)
|
||||
|
||||
# Copy the good files over the bad ones
|
||||
for abs_path in to_be_patched:
|
||||
@@ -238,7 +308,7 @@ def runs_ok(script_abs_path):
|
||||
fs.copy(substitutes[name], abs_path)
|
||||
os.chmod(abs_path, mode)
|
||||
|
||||
@run_before("configure")
|
||||
@spack.builder.run_before("configure")
|
||||
def _patch_usr_bin_file(self):
|
||||
"""On NixOS file is not available in /usr/bin/file. Patch configure
|
||||
scripts to use file from path."""
|
||||
@@ -250,7 +320,7 @@ def _patch_usr_bin_file(self):
|
||||
with fs.keep_modification_time(*x.filenames):
|
||||
x.filter(regex="/usr/bin/file", repl="file", string=True)
|
||||
|
||||
@run_before("configure")
|
||||
@spack.builder.run_before("configure")
|
||||
def _set_autotools_environment_variables(self):
|
||||
"""Many autotools builds use a version of mknod.m4 that fails when
|
||||
running as root unless FORCE_UNSAFE_CONFIGURE is set to 1.
|
||||
@@ -261,11 +331,10 @@ def _set_autotools_environment_variables(self):
|
||||
Without it, configure just fails halfway through, but it can
|
||||
still run things *before* this check. Forcing this just removes a
|
||||
nuisance -- this is not circumventing any real protection.
|
||||
|
||||
"""
|
||||
os.environ["FORCE_UNSAFE_CONFIGURE"] = "1"
|
||||
|
||||
@run_before("configure")
|
||||
@spack.builder.run_before("configure")
|
||||
def _do_patch_libtool_configure(self):
|
||||
"""Patch bugs that propagate from libtool macros into "configure" and
|
||||
further into "libtool". Note that patches that can be fixed by patching
|
||||
@@ -293,7 +362,7 @@ 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')
|
||||
|
||||
@run_after("configure")
|
||||
@spack.builder.run_after("configure")
|
||||
def _do_patch_libtool(self):
|
||||
"""If configure generates a "libtool" script that does not correctly
|
||||
detect the compiler (and patch_libtool is set), patch in the correct
|
||||
@@ -328,31 +397,33 @@ def _do_patch_libtool(self):
|
||||
markers[tag] = "LIBTOOL TAG CONFIG: {0}".format(tag.upper())
|
||||
|
||||
# Replace empty linker flag prefixes:
|
||||
if self.compiler.name == "nag":
|
||||
if self.pkg.compiler.name == "nag":
|
||||
# Nag is mixed with gcc and g++, which are recognized correctly.
|
||||
# Therefore, we change only Fortran values:
|
||||
for tag in ["fc", "f77"]:
|
||||
marker = markers[tag]
|
||||
x.filter(
|
||||
regex='^wl=""$',
|
||||
repl='wl="{0}"'.format(self.compiler.linker_arg),
|
||||
repl='wl="{0}"'.format(self.pkg.compiler.linker_arg),
|
||||
start_at="# ### BEGIN {0}".format(marker),
|
||||
stop_at="# ### END {0}".format(marker),
|
||||
)
|
||||
else:
|
||||
x.filter(regex='^wl=""$', repl='wl="{0}"'.format(self.compiler.linker_arg))
|
||||
x.filter(regex='^wl=""$', repl='wl="{0}"'.format(self.pkg.compiler.linker_arg))
|
||||
|
||||
# Replace empty PIC flag values:
|
||||
for cc, marker in markers.items():
|
||||
x.filter(
|
||||
regex='^pic_flag=""$',
|
||||
repl='pic_flag="{0}"'.format(getattr(self.compiler, "{0}_pic_flag".format(cc))),
|
||||
repl='pic_flag="{0}"'.format(
|
||||
getattr(self.pkg.compiler, "{0}_pic_flag".format(cc))
|
||||
),
|
||||
start_at="# ### BEGIN {0}".format(marker),
|
||||
stop_at="# ### END {0}".format(marker),
|
||||
)
|
||||
|
||||
# Other compiler-specific patches:
|
||||
if self.compiler.name == "fj":
|
||||
if self.pkg.compiler.name == "fj":
|
||||
x.filter(regex="-nostdlib", repl="", string=True)
|
||||
rehead = r"/\S*/"
|
||||
for o in [
|
||||
@@ -365,12 +436,12 @@ def _do_patch_libtool(self):
|
||||
"crtendS.o",
|
||||
]:
|
||||
x.filter(regex=(rehead + o), repl="", string=True)
|
||||
elif self.compiler.name == "dpcpp":
|
||||
elif self.pkg.compiler.name == "dpcpp":
|
||||
# Hack to filter out spurious predep_objects when building with Intel dpcpp
|
||||
# (see https://github.com/spack/spack/issues/32863):
|
||||
x.filter(regex=r"^(predep_objects=.*)/tmp/conftest-[0-9A-Fa-f]+\.o", repl=r"\1")
|
||||
x.filter(regex=r"^(predep_objects=.*)/tmp/a-[0-9A-Fa-f]+\.o", repl=r"\1")
|
||||
elif self.compiler.name == "nag":
|
||||
elif self.pkg.compiler.name == "nag":
|
||||
for tag in ["fc", "f77"]:
|
||||
marker = markers[tag]
|
||||
start_at = "# ### BEGIN {0}".format(marker)
|
||||
@@ -446,11 +517,8 @@ def _do_patch_libtool(self):
|
||||
|
||||
@property
|
||||
def configure_directory(self):
|
||||
"""Returns the directory where 'configure' resides.
|
||||
|
||||
:return: directory where to find configure
|
||||
"""
|
||||
return self.stage.source_path
|
||||
"""Return the directory where 'configure' resides."""
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
@property
|
||||
def configure_abs_path(self):
|
||||
@@ -463,34 +531,12 @@ def build_directory(self):
|
||||
"""Override to provide another place to build the package"""
|
||||
return self.configure_directory
|
||||
|
||||
@run_before("autoreconf")
|
||||
@spack.builder.run_before("autoreconf")
|
||||
def delete_configure_to_force_update(self):
|
||||
if self.force_autoreconf:
|
||||
force_remove(self.configure_abs_path)
|
||||
fs.force_remove(self.configure_abs_path)
|
||||
|
||||
def _require_build_deps(self, pkgs, spec, err):
|
||||
"""Require `pkgs` to be direct build dependencies of `spec`. Raises a
|
||||
RuntimeError with a helpful error messages when any dep is missing."""
|
||||
|
||||
build_deps = [d.name for d in spec.dependencies(deptype="build")]
|
||||
missing_deps = [x for x in pkgs if x not in build_deps]
|
||||
|
||||
if not missing_deps:
|
||||
return
|
||||
|
||||
# Raise an exception on missing deps.
|
||||
msg = (
|
||||
"{0}: missing dependencies: {1}.\n\nPlease add "
|
||||
"the following lines to the package:\n\n".format(err, ", ".join(missing_deps))
|
||||
)
|
||||
|
||||
for dep in missing_deps:
|
||||
msg += " depends_on('{0}', type='build', when='@{1}')\n".format(dep, spec.version)
|
||||
|
||||
msg += "\nUpdate the version (when='@{0}') as needed.".format(spec.version)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
def autoreconf(self, spec, prefix):
|
||||
def autoreconf(self, pkg, spec, prefix):
|
||||
"""Not needed usually, configure should be already there"""
|
||||
|
||||
# If configure exists nothing needs to be done
|
||||
@@ -498,8 +544,10 @@ def autoreconf(self, spec, prefix):
|
||||
return
|
||||
|
||||
# Else try to regenerate it, which reuquires a few build dependencies
|
||||
self._require_build_deps(
|
||||
pkgs=["autoconf", "automake", "libtool"], spec=spec, err="Cannot generate configure"
|
||||
ensure_build_dependencies_or_raise(
|
||||
spec=spec,
|
||||
dependencies=["autoconf", "automake", "libtool"],
|
||||
error_msg="Cannot generate configure",
|
||||
)
|
||||
|
||||
tty.msg("Configure script not found: trying to generate it")
|
||||
@@ -507,8 +555,8 @@ def autoreconf(self, spec, prefix):
|
||||
tty.warn("* If the default procedure fails, consider implementing *")
|
||||
tty.warn("* a custom AUTORECONF phase in the package *")
|
||||
tty.warn("*********************************************************")
|
||||
with working_dir(self.configure_directory):
|
||||
m = inspect.getmodule(self)
|
||||
with fs.working_dir(self.configure_directory):
|
||||
m = inspect.getmodule(self.pkg)
|
||||
# This line is what is needed most of the time
|
||||
# --install, --verbose, --force
|
||||
autoreconf_args = ["-ivf"]
|
||||
@@ -524,98 +572,66 @@ def autoreconf_search_path_args(self):
|
||||
spack dependencies."""
|
||||
return _autoreconf_search_path_args(self.spec)
|
||||
|
||||
@run_after("autoreconf")
|
||||
@spack.builder.run_after("autoreconf")
|
||||
def set_configure_or_die(self):
|
||||
"""Checks the presence of a ``configure`` file after the
|
||||
autoreconf phase. If it is found sets a module attribute
|
||||
appropriately, otherwise raises an error.
|
||||
"""Ensure the presence of a "configure" script, or raise. If the "configure"
|
||||
is found, a module level attribute is set.
|
||||
|
||||
:raises RuntimeError: if a configure script is not found in
|
||||
:py:meth:`~AutotoolsPackage.configure_directory`
|
||||
Raises:
|
||||
RuntimeError: if the "configure" script is not found
|
||||
"""
|
||||
# Check if a configure script is there. If not raise a RuntimeError.
|
||||
# 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
|
||||
inspect.getmodule(self).configure = Executable(self.configure_abs_path)
|
||||
inspect.getmodule(self.pkg).configure = Executable(self.configure_abs_path)
|
||||
|
||||
def configure_args(self):
|
||||
"""Produces a list containing all the arguments that must be passed to
|
||||
configure, except ``--prefix`` which will be pre-pended to the list.
|
||||
|
||||
:return: list of arguments for configure
|
||||
"""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 flags_to_build_system_args(self, flags):
|
||||
"""Produces a list of all command line arguments to pass specified
|
||||
compiler flags to configure."""
|
||||
# Has to be dynamic attribute due to caching.
|
||||
setattr(self, "configure_flag_args", [])
|
||||
for flag, values in flags.items():
|
||||
if values:
|
||||
values_str = "{0}={1}".format(flag.upper(), " ".join(values))
|
||||
self.configure_flag_args.append(values_str)
|
||||
# Spack's fflags are meant for both F77 and FC, therefore we
|
||||
# additionaly set FCFLAGS if required.
|
||||
values = flags.get("fflags", None)
|
||||
if values:
|
||||
values_str = "FCFLAGS={0}".format(" ".join(values))
|
||||
self.configure_flag_args.append(values_str)
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
"""Runs configure with the arguments specified in
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.configure_args`
|
||||
and an appropriately set prefix.
|
||||
def configure(self, pkg, spec, prefix):
|
||||
"""Run "configure", with the arguments specified by the builder and an
|
||||
appropriately set prefix.
|
||||
"""
|
||||
options = getattr(self, "configure_flag_args", [])
|
||||
options = getattr(self.pkg, "configure_flag_args", [])
|
||||
options += ["--prefix={0}".format(prefix)]
|
||||
options += self.configure_args()
|
||||
|
||||
with working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self).configure(*options)
|
||||
with fs.working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self.pkg).configure(*options)
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
if self.spec.platform == "darwin" and macos_version() >= Version("11"):
|
||||
# Many configure files rely on matching '10.*' for macOS version
|
||||
# detection and fail to add flags if it shows as version 11.
|
||||
env.set("MACOSX_DEPLOYMENT_TARGET", "10.16")
|
||||
|
||||
def build(self, spec, prefix):
|
||||
"""Makes the build targets specified by
|
||||
:py:attr:``~.AutotoolsPackage.build_targets``
|
||||
"""
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Run "make" on the build targets specified by the builder."""
|
||||
# See https://autotools.io/automake/silent.html
|
||||
params = ["V=1"]
|
||||
params += self.build_targets
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make(*params)
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*params)
|
||||
|
||||
def install(self, spec, prefix):
|
||||
"""Makes the install targets specified by
|
||||
:py:attr:``~.AutotoolsPackage.install_targets``
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make(*self.install_targets)
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Run "make" on the install targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*self.install_targets)
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def check(self):
|
||||
"""Searches the Makefile for targets ``test`` and ``check``
|
||||
and runs them if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
self._if_make_target_execute("test")
|
||||
self._if_make_target_execute("check")
|
||||
"""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
|
||||
):
|
||||
"""This function contains the current implementation details of
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without` and
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.enable_or_disable`.
|
||||
"""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
|
||||
@@ -671,7 +687,7 @@ def _activate_or_not(
|
||||
Raises:
|
||||
KeyError: if name is not among known variants
|
||||
"""
|
||||
spec = self.spec
|
||||
spec = self.pkg.spec
|
||||
args = []
|
||||
|
||||
if activation_value == "prefix":
|
||||
@@ -681,16 +697,16 @@ def _activate_or_not(
|
||||
|
||||
# Defensively look that the name passed as argument is among
|
||||
# variants
|
||||
if variant not in self.variants:
|
||||
if variant not in self.pkg.variants:
|
||||
msg = '"{0}" is not a variant of "{1}"'
|
||||
raise KeyError(msg.format(variant, self.name))
|
||||
raise KeyError(msg.format(variant, self.pkg.name))
|
||||
|
||||
if variant not in spec.variants:
|
||||
return []
|
||||
|
||||
# Create a list of pairs. Each pair includes a configuration
|
||||
# option and whether or not that option is activated
|
||||
variant_desc, _ = self.variants[variant]
|
||||
variant_desc, _ = self.pkg.variants[variant]
|
||||
if set(variant_desc.values) == set((True, False)):
|
||||
# BoolValuedVariant carry information about a single option.
|
||||
# Nonetheless, for uniformity of treatment we'll package them
|
||||
@@ -718,14 +734,18 @@ def _activate_or_not(
|
||||
override_name = "{0}_or_{1}_{2}".format(
|
||||
activation_word, deactivation_word, option_value
|
||||
)
|
||||
line_generator = getattr(self, override_name, None)
|
||||
line_generator = getattr(self, override_name, None) or getattr(
|
||||
self.pkg, override_name, None
|
||||
)
|
||||
# If not available use a sensible default
|
||||
if line_generator is None:
|
||||
|
||||
def _default_generator(is_activated):
|
||||
if is_activated:
|
||||
line = "--{0}-{1}".format(activation_word, option_value)
|
||||
if activation_value is not None and activation_value(option_value):
|
||||
if activation_value is not None and activation_value(
|
||||
option_value
|
||||
): # NOQA=ignore=E501
|
||||
line += "={0}".format(activation_value(option_value))
|
||||
return line
|
||||
return "--{0}-{1}".format(deactivation_word, option_value)
|
||||
@@ -764,7 +784,7 @@ def with_or_without(self, name, activation_value=None, variant=None):
|
||||
|
||||
def enable_or_disable(self, name, activation_value=None, variant=None):
|
||||
"""Same as
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without`
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without`
|
||||
but substitute ``with`` with ``enable`` and ``without`` with ``disable``.
|
||||
|
||||
Args:
|
||||
@@ -781,19 +801,14 @@ def enable_or_disable(self, name, activation_value=None, variant=None):
|
||||
"""
|
||||
return self._activate_or_not(name, "enable", "disable", activation_value, variant)
|
||||
|
||||
run_after("install")(PackageBase._run_default_install_time_test_callbacks)
|
||||
spack.builder.run_after("install")(execute_install_time_tests)
|
||||
|
||||
def installcheck(self):
|
||||
"""Searches the Makefile for an ``installcheck`` target
|
||||
and runs it if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
self._if_make_target_execute("installcheck")
|
||||
"""Run "make" on the ``installcheck`` target, if found."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
self.pkg._if_make_target_execute("installcheck")
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def remove_libtool_archives(self):
|
||||
"""Remove all .la files in prefix sub-folders if the package sets
|
||||
``install_libtool_archives`` to be False.
|
||||
@@ -803,14 +818,20 @@ def remove_libtool_archives(self):
|
||||
return
|
||||
|
||||
# Remove the files and create a log of what was removed
|
||||
libtool_files = fs.find(str(self.prefix), "*.la", recursive=True)
|
||||
libtool_files = fs.find(str(self.pkg.prefix), "*.la", recursive=True)
|
||||
with fs.safe_remove(*libtool_files):
|
||||
fs.mkdirp(os.path.dirname(self._removed_la_files_log))
|
||||
with open(self._removed_la_files_log, mode="w") as f:
|
||||
f.write("\n".join(libtool_files))
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
if self.spec.platform == "darwin" and macos_version() >= Version("11"):
|
||||
# Many configure files rely on matching '10.*' for macOS version
|
||||
# detection and fail to add flags if it shows as version 11.
|
||||
env.set("MACOSX_DEPLOYMENT_TARGET", "10.16")
|
||||
|
||||
# On macOS, force rpaths for shared library IDs and remove duplicate rpaths
|
||||
run_after("install")(PackageBase.apply_macos_rpath_fixups)
|
||||
spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups)
|
||||
|
||||
|
||||
def _autoreconf_search_path_args(spec):
|
||||
|
||||
31
lib/spack/spack/build_systems/bundle.py
Normal file
31
lib/spack/spack/build_systems/bundle.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Copyright 2013-2022 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 spack.builder
|
||||
import spack.directives
|
||||
import spack.package_base
|
||||
|
||||
|
||||
class BundlePackage(spack.package_base.PackageBase):
|
||||
"""General purpose bundle, or no-code, package class."""
|
||||
|
||||
#: This attribute is used in UI queries that require to know which
|
||||
#: build-system class we are using
|
||||
build_system_class = "BundlePackage"
|
||||
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "bundle"
|
||||
|
||||
#: Bundle packages do not have associated source or binary code.
|
||||
has_code = False
|
||||
|
||||
spack.directives.build_system("bundle")
|
||||
|
||||
|
||||
@spack.builder.builder("bundle")
|
||||
class BundleBuilder(spack.builder.Builder):
|
||||
phases = ("install",)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
pass
|
||||
@@ -3,12 +3,14 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import os
|
||||
from typing import Tuple
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import install, mkdirp
|
||||
|
||||
from spack.build_systems.cmake import CMakePackage
|
||||
from spack.package_base import run_after
|
||||
import spack.builder
|
||||
|
||||
from .cmake import CMakeBuilder, CMakePackage
|
||||
|
||||
|
||||
def cmake_cache_path(name, value, comment=""):
|
||||
@@ -28,44 +30,46 @@ def cmake_cache_option(name, boolean_value, comment=""):
|
||||
return 'set({0} {1} CACHE BOOL "{2}")\n'.format(name, value, comment)
|
||||
|
||||
|
||||
class CachedCMakePackage(CMakePackage):
|
||||
"""Specialized class for packages built using CMake initial cache.
|
||||
class CachedCMakeBuilder(CMakeBuilder):
|
||||
|
||||
This feature of CMake allows packages to increase reproducibility,
|
||||
especially between Spack- and manual builds. It also allows packages to
|
||||
sidestep certain parsing bugs in extremely long ``cmake`` commands, and to
|
||||
avoid system limits on the length of the command line."""
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = CMakeBuilder.legacy_methods + (
|
||||
"initconfig_compiler_entries",
|
||||
"initconfig_mpi_entries",
|
||||
"initconfig_hardware_entries",
|
||||
"std_initconfig_entries",
|
||||
"initconfig_package_entries",
|
||||
) # type: Tuple[str, ...]
|
||||
|
||||
phases = ["initconfig", "cmake", "build", "install"]
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = CMakeBuilder.legacy_attributes + (
|
||||
"cache_name",
|
||||
"cache_path",
|
||||
) # type: Tuple[str, ...]
|
||||
|
||||
@property
|
||||
def cache_name(self):
|
||||
return "{0}-{1}-{2}@{3}.cmake".format(
|
||||
self.name,
|
||||
self.spec.architecture,
|
||||
self.spec.compiler.name,
|
||||
self.spec.compiler.version,
|
||||
self.pkg.name,
|
||||
self.pkg.spec.architecture,
|
||||
self.pkg.spec.compiler.name,
|
||||
self.pkg.spec.compiler.version,
|
||||
)
|
||||
|
||||
@property
|
||||
def cache_path(self):
|
||||
return os.path.join(self.stage.source_path, self.cache_name)
|
||||
|
||||
def flag_handler(self, name, flags):
|
||||
if name in ("cflags", "cxxflags", "cppflags", "fflags"):
|
||||
return (None, None, None) # handled in the cmake cache
|
||||
return (flags, None, None)
|
||||
return os.path.join(self.pkg.stage.source_path, self.cache_name)
|
||||
|
||||
def initconfig_compiler_entries(self):
|
||||
# This will tell cmake to use the Spack compiler wrappers when run
|
||||
# through Spack, but use the underlying compiler when run outside of
|
||||
# Spack
|
||||
spec = self.spec
|
||||
spec = self.pkg.spec
|
||||
|
||||
# Fortran compiler is optional
|
||||
if "FC" in os.environ:
|
||||
spack_fc_entry = cmake_cache_path("CMAKE_Fortran_COMPILER", os.environ["FC"])
|
||||
system_fc_entry = cmake_cache_path("CMAKE_Fortran_COMPILER", self.compiler.fc)
|
||||
system_fc_entry = cmake_cache_path("CMAKE_Fortran_COMPILER", self.pkg.compiler.fc)
|
||||
else:
|
||||
spack_fc_entry = "# No Fortran compiler defined in spec"
|
||||
system_fc_entry = "# No Fortran compiler defined in spec"
|
||||
@@ -81,8 +85,8 @@ def initconfig_compiler_entries(self):
|
||||
" " + cmake_cache_path("CMAKE_CXX_COMPILER", os.environ["CXX"]),
|
||||
" " + spack_fc_entry,
|
||||
"else()\n",
|
||||
" " + cmake_cache_path("CMAKE_C_COMPILER", self.compiler.cc),
|
||||
" " + cmake_cache_path("CMAKE_CXX_COMPILER", self.compiler.cxx),
|
||||
" " + cmake_cache_path("CMAKE_C_COMPILER", self.pkg.compiler.cc),
|
||||
" " + cmake_cache_path("CMAKE_CXX_COMPILER", self.pkg.compiler.cxx),
|
||||
" " + system_fc_entry,
|
||||
"endif()\n",
|
||||
]
|
||||
@@ -126,7 +130,7 @@ def initconfig_compiler_entries(self):
|
||||
return entries
|
||||
|
||||
def initconfig_mpi_entries(self):
|
||||
spec = self.spec
|
||||
spec = self.pkg.spec
|
||||
|
||||
if not spec.satisfies("^mpi"):
|
||||
return []
|
||||
@@ -160,13 +164,13 @@ def initconfig_mpi_entries(self):
|
||||
mpiexec = os.path.join(spec["mpi"].prefix.bin, "mpiexec")
|
||||
|
||||
if not os.path.exists(mpiexec):
|
||||
msg = "Unable to determine MPIEXEC, %s tests may fail" % self.name
|
||||
msg = "Unable to determine MPIEXEC, %s tests may fail" % self.pkg.name
|
||||
entries.append("# {0}\n".format(msg))
|
||||
tty.warn(msg)
|
||||
else:
|
||||
# starting with cmake 3.10, FindMPI expects MPIEXEC_EXECUTABLE
|
||||
# vs the older versions which expect MPIEXEC
|
||||
if self.spec["cmake"].satisfies("@3.10:"):
|
||||
if self.pkg.spec["cmake"].satisfies("@3.10:"):
|
||||
entries.append(cmake_cache_path("MPIEXEC_EXECUTABLE", mpiexec))
|
||||
else:
|
||||
entries.append(cmake_cache_path("MPIEXEC", mpiexec))
|
||||
@@ -180,7 +184,7 @@ def initconfig_mpi_entries(self):
|
||||
return entries
|
||||
|
||||
def initconfig_hardware_entries(self):
|
||||
spec = self.spec
|
||||
spec = self.pkg.spec
|
||||
|
||||
entries = [
|
||||
"#------------------{0}".format("-" * 60),
|
||||
@@ -212,7 +216,7 @@ def std_initconfig_entries(self):
|
||||
"#------------------{0}".format("-" * 60),
|
||||
"# !!!! This is a generated file, edit at own risk !!!!",
|
||||
"#------------------{0}".format("-" * 60),
|
||||
"# CMake executable path: {0}".format(self.spec["cmake"].command.path),
|
||||
"# CMake executable path: {0}".format(self.pkg.spec["cmake"].command.path),
|
||||
"#------------------{0}\n".format("-" * 60),
|
||||
]
|
||||
|
||||
@@ -220,7 +224,8 @@ def initconfig_package_entries(self):
|
||||
"""This method is to be overwritten by the package"""
|
||||
return []
|
||||
|
||||
def initconfig(self, spec, prefix):
|
||||
@spack.builder.run_before("cmake")
|
||||
def initconfig(self):
|
||||
cache_entries = (
|
||||
self.std_initconfig_entries()
|
||||
+ self.initconfig_compiler_entries()
|
||||
@@ -236,11 +241,28 @@ def initconfig(self, spec, prefix):
|
||||
|
||||
@property
|
||||
def std_cmake_args(self):
|
||||
args = super(CachedCMakePackage, self).std_cmake_args
|
||||
args = super(CachedCMakeBuilder, self).std_cmake_args
|
||||
args.extend(["-C", self.cache_path])
|
||||
return args
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def install_cmake_cache(self):
|
||||
mkdirp(self.spec.prefix.share.cmake)
|
||||
install(self.cache_path, self.spec.prefix.share.cmake)
|
||||
fs.mkdirp(self.pkg.spec.prefix.share.cmake)
|
||||
fs.install(self.cache_path, self.pkg.spec.prefix.share.cmake)
|
||||
|
||||
|
||||
class CachedCMakePackage(CMakePackage):
|
||||
"""Specialized class for packages built using CMake initial cache.
|
||||
|
||||
This feature of CMake allows packages to increase reproducibility,
|
||||
especially between Spack- and manual builds. It also allows packages to
|
||||
sidestep certain parsing bugs in extremely long ``cmake`` commands, and to
|
||||
avoid system limits on the length of the command line.
|
||||
"""
|
||||
|
||||
CMakeBuilder = CachedCMakeBuilder
|
||||
|
||||
def flag_handler(self, name, flags):
|
||||
if name in ("cflags", "cxxflags", "cppflags", "fflags"):
|
||||
return None, None, None # handled in the cmake cache
|
||||
return flags, None, None
|
||||
|
||||
@@ -2,23 +2,26 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
from typing import List
|
||||
from typing import List, Tuple
|
||||
|
||||
import six
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
from llnl.util.compat import Sequence
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
import spack.build_environment
|
||||
from spack.directives import conflicts, depends_on, variant
|
||||
from spack.package_base import InstallError, PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
import spack.util.path
|
||||
from spack.directives import build_system, depends_on, variant
|
||||
from spack.multimethod import when
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests
|
||||
|
||||
# Regex to extract the primary generator from the CMake generator
|
||||
# string.
|
||||
@@ -34,56 +37,141 @@ def _extract_primary_generator(generator):
|
||||
return primary_generator
|
||||
|
||||
|
||||
class CMakePackage(PackageBase):
|
||||
class CMakePackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using CMake
|
||||
|
||||
For more information on the CMake build system, see:
|
||||
https://cmake.org/cmake/help/latest/
|
||||
"""
|
||||
|
||||
This class provides three phases that can be overridden:
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "CMakePackage"
|
||||
|
||||
1. :py:meth:`~.CMakePackage.cmake`
|
||||
2. :py:meth:`~.CMakePackage.build`
|
||||
3. :py:meth:`~.CMakePackage.install`
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "cmake"
|
||||
|
||||
build_system("cmake")
|
||||
|
||||
with when("build_system=cmake"):
|
||||
# https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
|
||||
variant(
|
||||
"build_type",
|
||||
default="RelWithDebInfo",
|
||||
description="CMake build type",
|
||||
values=("Debug", "Release", "RelWithDebInfo", "MinSizeRel"),
|
||||
)
|
||||
# CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9
|
||||
# https://cmake.org/cmake/help/latest/variable/CMAKE_INTERPROCEDURAL_OPTIMIZATION.html
|
||||
variant(
|
||||
"ipo",
|
||||
default=False,
|
||||
when="^cmake@3.9:",
|
||||
description="CMake interprocedural optimization",
|
||||
)
|
||||
depends_on("cmake", type="build")
|
||||
depends_on("ninja", type="build", when="platform=windows")
|
||||
|
||||
def flags_to_build_system_args(self, flags):
|
||||
"""Return a list of all command line arguments to pass the specified
|
||||
compiler flags to cmake. Note CMAKE does not have a cppflags option,
|
||||
so cppflags will be added to cflags, cxxflags, and fflags to mimic the
|
||||
behavior in other tools.
|
||||
"""
|
||||
# Has to be dynamic attribute due to caching
|
||||
setattr(self, "cmake_flag_args", [])
|
||||
|
||||
flag_string = "-DCMAKE_{0}_FLAGS={1}"
|
||||
langs = {"C": "c", "CXX": "cxx", "Fortran": "f"}
|
||||
|
||||
# Handle language compiler flags
|
||||
for lang, pre in langs.items():
|
||||
flag = pre + "flags"
|
||||
# cmake has no explicit cppflags support -> add it to all langs
|
||||
lang_flags = " ".join(flags.get(flag, []) + flags.get("cppflags", []))
|
||||
if lang_flags:
|
||||
self.cmake_flag_args.append(flag_string.format(lang, lang_flags))
|
||||
|
||||
# Cmake has different linker arguments for different build types.
|
||||
# We specify for each of them.
|
||||
if flags["ldflags"]:
|
||||
ldflags = " ".join(flags["ldflags"])
|
||||
ld_string = "-DCMAKE_{0}_LINKER_FLAGS={1}"
|
||||
# cmake has separate linker arguments for types of builds.
|
||||
for type in ["EXE", "MODULE", "SHARED", "STATIC"]:
|
||||
self.cmake_flag_args.append(ld_string.format(type, ldflags))
|
||||
|
||||
# CMake has libs options separated by language. Apply ours to each.
|
||||
if flags["ldlibs"]:
|
||||
libs_flags = " ".join(flags["ldlibs"])
|
||||
libs_string = "-DCMAKE_{0}_STANDARD_LIBRARIES={1}"
|
||||
for lang in langs:
|
||||
self.cmake_flag_args.append(libs_string.format(lang, libs_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_from_variant(self, *args, **kwargs):
|
||||
return self.builder.define_from_variant(*args, **kwargs)
|
||||
|
||||
|
||||
@spack.builder.builder("cmake")
|
||||
class CMakeBuilder(BaseBuilder):
|
||||
"""The cmake builder encodes the default way of building software with CMake. IT
|
||||
has three phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.CMakeBuilder.cmake`
|
||||
2. :py:meth:`~.CMakeBuilder.build`
|
||||
3. :py:meth:`~.CMakeBuilder.install`
|
||||
|
||||
They all have sensible defaults and for many packages the only thing
|
||||
necessary will be to override :py:meth:`~.CMakePackage.cmake_args`.
|
||||
necessary will be to override :py:meth:`~.CMakeBuilder.cmake_args`.
|
||||
|
||||
For a finer tuning you may also override:
|
||||
|
||||
+-----------------------------------------------+--------------------+
|
||||
| **Method** | **Purpose** |
|
||||
+===============================================+====================+
|
||||
| :py:meth:`~.CMakePackage.root_cmakelists_dir` | Location of the |
|
||||
| :py:meth:`~.CMakeBuilder.root_cmakelists_dir` | Location of the |
|
||||
| | root CMakeLists.txt|
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:meth:`~.CMakePackage.build_directory` | Directory where to |
|
||||
| :py:meth:`~.CMakeBuilder.build_directory` | Directory where to |
|
||||
| | build the package |
|
||||
+-----------------------------------------------+--------------------+
|
||||
|
||||
|
||||
The generator used by CMake can be specified by providing the
|
||||
generator attribute. Per
|
||||
The generator used by CMake can be specified by providing the ``generator``
|
||||
attribute. Per
|
||||
https://cmake.org/cmake/help/git-master/manual/cmake-generators.7.html,
|
||||
the format is: [<secondary-generator> - ]<primary_generator>. The
|
||||
full list of primary and secondary generators supported by CMake may
|
||||
be found in the documentation for the version of CMake used;
|
||||
however, at this time Spack supports only the primary generators
|
||||
"Unix Makefiles" and "Ninja." Spack's CMake support is agnostic with
|
||||
respect to primary generators. Spack will generate a runtime error
|
||||
if the generator string does not follow the prescribed format, or if
|
||||
the format is: [<secondary-generator> - ]<primary_generator>.
|
||||
|
||||
The full list of primary and secondary generators supported by CMake may be found
|
||||
in the documentation for the version of CMake used; however, at this time Spack
|
||||
supports only the primary generators "Unix Makefiles" and "Ninja." Spack's CMake
|
||||
support is agnostic with respect to primary generators. Spack will generate a
|
||||
runtime error if the generator string does not follow the prescribed format, or if
|
||||
the primary generator is not supported.
|
||||
"""
|
||||
|
||||
#: Phases of a CMake package
|
||||
phases = ["cmake", "build", "install"]
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "CMakePackage"
|
||||
phases = ("cmake", "build", "install")
|
||||
|
||||
build_targets = [] # type: List[str]
|
||||
install_targets = ["install"]
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("cmake_args", "check") # type: Tuple[str, ...]
|
||||
|
||||
build_time_test_callbacks = ["check"]
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"generator",
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"archive_files",
|
||||
"root_cmakelists_dir",
|
||||
"std_cmake_args",
|
||||
"build_dirname",
|
||||
"build_directory",
|
||||
) # type: Tuple[str, ...]
|
||||
|
||||
#: The build system generator to use.
|
||||
#:
|
||||
@@ -93,27 +181,14 @@ class CMakePackage(PackageBase):
|
||||
#:
|
||||
#: See https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html
|
||||
#: for more information.
|
||||
generator = "Ninja" if sys.platform == "win32" else "Unix Makefiles"
|
||||
|
||||
generator = "Unix Makefiles"
|
||||
|
||||
if sys.platform == "win32":
|
||||
generator = "Ninja"
|
||||
depends_on("ninja")
|
||||
|
||||
# https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
|
||||
variant(
|
||||
"build_type",
|
||||
default="RelWithDebInfo",
|
||||
description="CMake build type",
|
||||
values=("Debug", "Release", "RelWithDebInfo", "MinSizeRel"),
|
||||
)
|
||||
|
||||
# https://cmake.org/cmake/help/latest/variable/CMAKE_INTERPROCEDURAL_OPTIMIZATION.html
|
||||
variant("ipo", default=False, description="CMake interprocedural optimization")
|
||||
# CMAKE_INTERPROCEDURAL_OPTIMIZATION only exists for CMake >= 3.9
|
||||
conflicts("+ipo", when="^cmake@:3.8", msg="+ipo is not supported by CMake < 3.9")
|
||||
|
||||
depends_on("cmake", type="build")
|
||||
#: Targets to be used during the build phase
|
||||
build_targets = [] # type: List[str]
|
||||
#: Targets to be used during the install phase
|
||||
install_targets = ["install"]
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
@property
|
||||
def archive_files(self):
|
||||
@@ -126,40 +201,30 @@ def root_cmakelists_dir(self):
|
||||
|
||||
This path is relative to the root of the extracted tarball,
|
||||
not to the ``build_directory``. Defaults to the current directory.
|
||||
|
||||
:return: directory containing CMakeLists.txt
|
||||
"""
|
||||
return self.stage.source_path
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
@property
|
||||
def std_cmake_args(self):
|
||||
"""Standard cmake arguments provided as a property for
|
||||
convenience of package writers
|
||||
|
||||
:return: standard cmake arguments
|
||||
"""
|
||||
# standard CMake arguments
|
||||
std_cmake_args = CMakePackage._std_args(self)
|
||||
std_cmake_args += getattr(self, "cmake_flag_args", [])
|
||||
std_cmake_args = CMakeBuilder.std_args(self.pkg, generator=self.generator)
|
||||
std_cmake_args += getattr(self.pkg, "cmake_flag_args", [])
|
||||
return std_cmake_args
|
||||
|
||||
@staticmethod
|
||||
def _std_args(pkg):
|
||||
def std_args(pkg, generator=None):
|
||||
"""Computes the standard cmake arguments for a generic package"""
|
||||
|
||||
try:
|
||||
generator = pkg.generator
|
||||
except AttributeError:
|
||||
generator = CMakePackage.generator
|
||||
|
||||
# Make sure a valid generator was chosen
|
||||
generator = generator or "Unix Makefiles"
|
||||
valid_primary_generators = ["Unix Makefiles", "Ninja"]
|
||||
primary_generator = _extract_primary_generator(generator)
|
||||
if primary_generator not in valid_primary_generators:
|
||||
msg = "Invalid CMake generator: '{0}'\n".format(generator)
|
||||
msg += "CMakePackage currently supports the following "
|
||||
msg += "primary generators: '{0}'".format("', '".join(valid_primary_generators))
|
||||
raise InstallError(msg)
|
||||
raise spack.package_base.InstallError(msg)
|
||||
|
||||
try:
|
||||
build_type = pkg.spec.variants["build_type"].value
|
||||
@@ -171,7 +236,7 @@ def _std_args(pkg):
|
||||
except KeyError:
|
||||
ipo = False
|
||||
|
||||
define = CMakePackage.define
|
||||
define = CMakeBuilder.define
|
||||
args = [
|
||||
"-G",
|
||||
generator,
|
||||
@@ -251,7 +316,7 @@ def define_from_variant(self, cmake_var, variant=None):
|
||||
of ``cmake_var``.
|
||||
|
||||
This utility function is similar to
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsPackage.with_or_without`.
|
||||
:meth:`~spack.build_systems.autotools.AutotoolsBuilder.with_or_without`.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -291,122 +356,75 @@ def define_from_variant(self, cmake_var, variant=None):
|
||||
if variant is None:
|
||||
variant = cmake_var.lower()
|
||||
|
||||
if variant not in self.variants:
|
||||
raise KeyError('"{0}" is not a variant of "{1}"'.format(variant, self.name))
|
||||
if variant not in self.pkg.variants:
|
||||
raise KeyError('"{0}" is not a variant of "{1}"'.format(variant, self.pkg.name))
|
||||
|
||||
if variant not in self.spec.variants:
|
||||
if variant not in self.pkg.spec.variants:
|
||||
return ""
|
||||
|
||||
value = self.spec.variants[variant].value
|
||||
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 flags_to_build_system_args(self, flags):
|
||||
"""Produces a list of all command line arguments to pass the specified
|
||||
compiler flags to cmake. Note CMAKE does not have a cppflags option,
|
||||
so cppflags will be added to cflags, cxxflags, and fflags to mimic the
|
||||
behavior in other tools."""
|
||||
# Has to be dynamic attribute due to caching
|
||||
setattr(self, "cmake_flag_args", [])
|
||||
|
||||
flag_string = "-DCMAKE_{0}_FLAGS={1}"
|
||||
langs = {"C": "c", "CXX": "cxx", "Fortran": "f"}
|
||||
|
||||
# Handle language compiler flags
|
||||
for lang, pre in langs.items():
|
||||
flag = pre + "flags"
|
||||
# cmake has no explicit cppflags support -> add it to all langs
|
||||
lang_flags = " ".join(flags.get(flag, []) + flags.get("cppflags", []))
|
||||
if lang_flags:
|
||||
self.cmake_flag_args.append(flag_string.format(lang, lang_flags))
|
||||
|
||||
# Cmake has different linker arguments for different build types.
|
||||
# We specify for each of them.
|
||||
if flags["ldflags"]:
|
||||
ldflags = " ".join(flags["ldflags"])
|
||||
ld_string = "-DCMAKE_{0}_LINKER_FLAGS={1}"
|
||||
# cmake has separate linker arguments for types of builds.
|
||||
for type in ["EXE", "MODULE", "SHARED", "STATIC"]:
|
||||
self.cmake_flag_args.append(ld_string.format(type, ldflags))
|
||||
|
||||
# CMake has libs options separated by language. Apply ours to each.
|
||||
if flags["ldlibs"]:
|
||||
libs_flags = " ".join(flags["ldlibs"])
|
||||
libs_string = "-DCMAKE_{0}_STANDARD_LIBRARIES={1}"
|
||||
for lang in langs:
|
||||
self.cmake_flag_args.append(libs_string.format(lang, libs_flags))
|
||||
|
||||
@property
|
||||
def build_dirname(self):
|
||||
"""Returns the directory name to use when building the package
|
||||
|
||||
:return: name of the subdirectory for building the package
|
||||
"""
|
||||
return "spack-build-%s" % self.spec.dag_hash(7)
|
||||
"""Directory name to use when building the package."""
|
||||
return "spack-build-%s" % self.pkg.spec.dag_hash(7)
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""Returns the directory to use when building the package
|
||||
|
||||
:return: directory where to build the package
|
||||
"""
|
||||
return os.path.join(self.stage.path, self.build_dirname)
|
||||
"""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):
|
||||
"""Produces a list containing all the arguments that must be passed to
|
||||
cmake, except:
|
||||
"""List of all the arguments that must be passed to cmake, except:
|
||||
|
||||
* CMAKE_INSTALL_PREFIX
|
||||
* CMAKE_BUILD_TYPE
|
||||
* BUILD_TESTING
|
||||
|
||||
which will be set automatically.
|
||||
|
||||
:return: list of arguments for cmake
|
||||
"""
|
||||
return []
|
||||
|
||||
def cmake(self, spec, prefix):
|
||||
def cmake(self, pkg, spec, prefix):
|
||||
"""Runs ``cmake`` in the build directory"""
|
||||
options = self.std_cmake_args
|
||||
options += self.cmake_args()
|
||||
options.append(os.path.abspath(self.root_cmakelists_dir))
|
||||
with working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self).cmake(*options)
|
||||
with fs.working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self.pkg).cmake(*options)
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Make the build targets"""
|
||||
with working_dir(self.build_directory):
|
||||
with fs.working_dir(self.build_directory):
|
||||
if self.generator == "Unix Makefiles":
|
||||
inspect.getmodule(self).make(*self.build_targets)
|
||||
inspect.getmodule(self.pkg).make(*self.build_targets)
|
||||
elif self.generator == "Ninja":
|
||||
self.build_targets.append("-v")
|
||||
inspect.getmodule(self).ninja(*self.build_targets)
|
||||
inspect.getmodule(self.pkg).ninja(*self.build_targets)
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Make the install targets"""
|
||||
with working_dir(self.build_directory):
|
||||
with fs.working_dir(self.build_directory):
|
||||
if self.generator == "Unix Makefiles":
|
||||
inspect.getmodule(self).make(*self.install_targets)
|
||||
inspect.getmodule(self.pkg).make(*self.install_targets)
|
||||
elif self.generator == "Ninja":
|
||||
inspect.getmodule(self).ninja(*self.install_targets)
|
||||
inspect.getmodule(self.pkg).ninja(*self.install_targets)
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def check(self):
|
||||
"""Searches the CMake-generated Makefile for the target ``test``
|
||||
and runs it if found.
|
||||
"""Search the CMake-generated files for the targets ``test`` and ``check``,
|
||||
and runs them if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
with fs.working_dir(self.build_directory):
|
||||
if self.generator == "Unix Makefiles":
|
||||
self._if_make_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL")
|
||||
self._if_make_target_execute("check")
|
||||
self.pkg._if_make_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL")
|
||||
self.pkg._if_make_target_execute("check")
|
||||
elif self.generator == "Ninja":
|
||||
self._if_ninja_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL")
|
||||
self._if_ninja_target_execute("check")
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
self.pkg._if_ninja_target_execute("test", jobs_env="CTEST_PARALLEL_LEVEL")
|
||||
self.pkg._if_ninja_target_execute("check")
|
||||
|
||||
44
lib/spack/spack/build_systems/generic.py
Normal file
44
lib/spack/spack/build_systems/generic.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# Copyright 2013-2022 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)
|
||||
from typing import Tuple
|
||||
|
||||
import spack.builder
|
||||
import spack.directives
|
||||
import spack.package_base
|
||||
|
||||
from ._checks import BaseBuilder, apply_macos_rpath_fixups
|
||||
|
||||
|
||||
class Package(spack.package_base.PackageBase):
|
||||
"""General purpose class with a single ``install`` phase that needs to be
|
||||
coded by packagers.
|
||||
"""
|
||||
|
||||
#: This attribute is used in UI queries that require to know which
|
||||
#: build-system class we are using
|
||||
build_system_class = "Package"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "generic"
|
||||
|
||||
spack.directives.build_system("generic")
|
||||
|
||||
|
||||
@spack.builder.builder("generic")
|
||||
class GenericBuilder(BaseBuilder):
|
||||
"""A builder for a generic build system, that require packagers
|
||||
to implement an "install" phase.
|
||||
"""
|
||||
|
||||
#: A generic package has only the "install" phase
|
||||
phases = ("install",)
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = () # type: Tuple[str, ...]
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("archive_files",) # type: Tuple[str, ...]
|
||||
|
||||
# On macOS, force rpaths for shared library IDs and remove duplicate rpaths
|
||||
spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups)
|
||||
@@ -2,8 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import glob
|
||||
import inspect
|
||||
import os
|
||||
@@ -26,12 +24,14 @@
|
||||
|
||||
import spack.error
|
||||
from spack.build_environment import dso_suffix
|
||||
from spack.package_base import InstallError, PackageBase, run_after
|
||||
from spack.package_base import InstallError
|
||||
from spack.util.environment import EnvironmentModifications
|
||||
from spack.util.executable import Executable
|
||||
from spack.util.prefix import Prefix
|
||||
from spack.version import Version, ver
|
||||
|
||||
from .generic import Package
|
||||
|
||||
# A couple of utility functions that might be useful in general. If so, they
|
||||
# should really be defined elsewhere, unless deemed heretical.
|
||||
# (Or na"ive on my part).
|
||||
@@ -86,7 +86,7 @@ def _expand_fields(s):
|
||||
return s
|
||||
|
||||
|
||||
class IntelPackage(PackageBase):
|
||||
class IntelPackage(Package):
|
||||
"""Specialized class for licensed Intel software.
|
||||
|
||||
This class provides two phases that can be overridden:
|
||||
@@ -99,9 +99,6 @@ class IntelPackage(PackageBase):
|
||||
to set the appropriate environment variables.
|
||||
"""
|
||||
|
||||
#: Phases of an Intel package
|
||||
phases = ["configure", "install"]
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "IntelPackage"
|
||||
@@ -1184,12 +1181,13 @@ def _determine_license_type(self):
|
||||
debug_print(license_type)
|
||||
return license_type
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
@spack.builder.run_before("install")
|
||||
def configure(self):
|
||||
"""Generates the silent.cfg file to pass to installer.sh.
|
||||
|
||||
See https://software.intel.com/en-us/articles/configuration-file-format
|
||||
"""
|
||||
|
||||
prefix = self.prefix
|
||||
# Both tokens AND values of the configuration file are validated during
|
||||
# the run of the underlying binary installer. Any unknown token or
|
||||
# unacceptable value will cause that installer to fail. Notably, this
|
||||
@@ -1270,7 +1268,7 @@ def install(self, spec, prefix):
|
||||
for f in glob.glob("%s/intel*log" % tmpdir):
|
||||
install(f, dst)
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.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'
|
||||
@@ -1278,7 +1276,7 @@ def validate_install(self):
|
||||
if not os.path.exists(self.prefix.bin):
|
||||
raise InstallError("The installer has failed to install anything.")
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def configure_rpath(self):
|
||||
if "+rpath" not in self.spec:
|
||||
return
|
||||
@@ -1296,7 +1294,7 @@ def configure_rpath(self):
|
||||
with open(compiler_cfg, "w") as fh:
|
||||
fh.write("-Xlinker -rpath={0}\n".format(compilers_lib_dir))
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def configure_auto_dispatch(self):
|
||||
if self._has_compilers:
|
||||
if "auto_dispatch=none" in self.spec:
|
||||
@@ -1320,7 +1318,7 @@ def configure_auto_dispatch(self):
|
||||
with open(compiler_cfg, "a") as fh:
|
||||
fh.write("-ax{0}\n".format(",".join(ad)))
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.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")
|
||||
@@ -1328,7 +1326,7 @@ def filter_compiler_wrappers(self):
|
||||
f = os.path.join(bin_dir, f)
|
||||
filter_file("-Xlinker --enable-new-dtags", " ", f, string=True)
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def uninstall_ism(self):
|
||||
# The "Intel(R) Software Improvement Program" [ahem] gets installed,
|
||||
# apparently regardless of PHONEHOME_SEND_USAGE_DATA.
|
||||
@@ -1360,7 +1358,7 @@ def base_lib_dir(self):
|
||||
debug_print(d)
|
||||
return d
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def modify_LLVMgold_rpath(self):
|
||||
"""Add libimf.so and other required libraries to the RUNPATH of LLVMgold.so.
|
||||
|
||||
@@ -1391,6 +1389,3 @@ def modify_LLVMgold_rpath(self):
|
||||
]
|
||||
)
|
||||
patchelf("--set-rpath", rpath, lib)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
@@ -2,59 +2,79 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from llnl.util.filesystem import find
|
||||
|
||||
from spack.directives import depends_on, extends
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
import spack.util.executable
|
||||
from spack.directives import build_system, depends_on, extends
|
||||
from spack.multimethod import when
|
||||
from spack.package_base import PackageBase
|
||||
from spack.util.executable import Executable
|
||||
|
||||
|
||||
class LuaPackage(PackageBase):
|
||||
class LuaPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for lua packages"""
|
||||
|
||||
phases = ["unpack", "generate_luarocks_config", "preprocess", "install"]
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "LuaPackage"
|
||||
|
||||
list_depth = 1 # LuaRocks requires at least one level of spidering to find versions
|
||||
depends_on("lua-lang")
|
||||
extends("lua", when="^lua")
|
||||
with when("^lua-luajit"):
|
||||
extends("lua-luajit")
|
||||
depends_on("luajit")
|
||||
depends_on("lua-luajit+lualinks")
|
||||
with when("^lua-luajit-openresty"):
|
||||
extends("lua-luajit-openresty")
|
||||
depends_on("luajit")
|
||||
depends_on("lua-luajit-openresty+lualinks")
|
||||
|
||||
def unpack(self, spec, prefix):
|
||||
if os.path.splitext(self.stage.archive_file)[1] == ".rock":
|
||||
directory = self.luarocks("unpack", self.stage.archive_file, output=str)
|
||||
build_system("lua")
|
||||
|
||||
with when("build_system=lua"):
|
||||
depends_on("lua-lang")
|
||||
extends("lua", when="^lua")
|
||||
with when("^lua-luajit"):
|
||||
extends("lua-luajit")
|
||||
depends_on("luajit")
|
||||
depends_on("lua-luajit+lualinks")
|
||||
with when("^lua-luajit-openresty"):
|
||||
extends("lua-luajit-openresty")
|
||||
depends_on("luajit")
|
||||
depends_on("lua-luajit-openresty+lualinks")
|
||||
|
||||
@property
|
||||
def lua(self):
|
||||
return spack.util.executable.Executable(self.spec["lua-lang"].prefix.bin.lua)
|
||||
|
||||
@property
|
||||
def luarocks(self):
|
||||
lr = spack.util.executable.Executable(self.spec["lua-lang"].prefix.bin.luarocks)
|
||||
return lr
|
||||
|
||||
|
||||
@spack.builder.builder("lua")
|
||||
class LuaBuilder(spack.builder.Builder):
|
||||
phases = ("unpack", "generate_luarocks_config", "preprocess", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("luarocks_args",)
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ()
|
||||
|
||||
def unpack(self, pkg, spec, prefix):
|
||||
if os.path.splitext(pkg.stage.archive_file)[1] == ".rock":
|
||||
directory = pkg.luarocks("unpack", pkg.stage.archive_file, output=str)
|
||||
dirlines = directory.split("\n")
|
||||
# TODO: figure out how to scope this better
|
||||
os.chdir(dirlines[2])
|
||||
|
||||
def _generate_tree_line(self, name, prefix):
|
||||
@staticmethod
|
||||
def _generate_tree_line(name, prefix):
|
||||
return """{{ name = "{name}", root = "{prefix}" }};""".format(
|
||||
name=name,
|
||||
prefix=prefix,
|
||||
)
|
||||
|
||||
def _luarocks_config_path(self):
|
||||
return os.path.join(self.stage.source_path, "spack_luarocks.lua")
|
||||
|
||||
def generate_luarocks_config(self, spec, prefix):
|
||||
spec = self.spec
|
||||
def generate_luarocks_config(self, pkg, spec, prefix):
|
||||
spec = self.pkg.spec
|
||||
table_entries = []
|
||||
for d in spec.traverse(deptypes=("build", "run"), deptype_query="run"):
|
||||
if d.package.extends(self.extendee_spec):
|
||||
if d.package.extends(self.pkg.extendee_spec):
|
||||
table_entries.append(self._generate_tree_line(d.name, d.prefix))
|
||||
|
||||
path = self._luarocks_config_path()
|
||||
@@ -71,30 +91,24 @@ def generate_luarocks_config(self, spec, prefix):
|
||||
)
|
||||
return path
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
env.set("LUAROCKS_CONFIG", self._luarocks_config_path())
|
||||
|
||||
def preprocess(self, spec, prefix):
|
||||
def preprocess(self, pkg, spec, prefix):
|
||||
"""Override this to preprocess source before building with luarocks"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def lua(self):
|
||||
return Executable(self.spec["lua-lang"].prefix.bin.lua)
|
||||
|
||||
@property
|
||||
def luarocks(self):
|
||||
lr = Executable(self.spec["lua-lang"].prefix.bin.luarocks)
|
||||
return lr
|
||||
|
||||
def luarocks_args(self):
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
rock = "."
|
||||
specs = find(".", "*.rockspec", recursive=False)
|
||||
if specs:
|
||||
rock = specs[0]
|
||||
rocks_args = self.luarocks_args()
|
||||
rocks_args.append(rock)
|
||||
self.luarocks("--tree=" + prefix, "make", *rocks_args)
|
||||
self.pkg.luarocks("--tree=" + prefix, "make", *rocks_args)
|
||||
|
||||
def _luarocks_config_path(self):
|
||||
return os.path.join(self.pkg.stage.source_path, "spack_luarocks.lua")
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
env.set("LUAROCKS_CONFIG", self._luarocks_config_path())
|
||||
|
||||
@@ -2,62 +2,85 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
from typing import List # novm
|
||||
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import working_dir
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
from spack.directives import conflicts
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, conflicts
|
||||
|
||||
from ._checks import (
|
||||
BaseBuilder,
|
||||
apply_macos_rpath_fixups,
|
||||
execute_build_time_tests,
|
||||
execute_install_time_tests,
|
||||
)
|
||||
|
||||
|
||||
class MakefilePackage(PackageBase):
|
||||
"""Specialized class for packages that are built using editable Makefiles
|
||||
class MakefilePackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using a Makefiles."""
|
||||
|
||||
This class provides three phases that can be overridden:
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "MakefilePackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "makefile"
|
||||
|
||||
1. :py:meth:`~.MakefilePackage.edit`
|
||||
2. :py:meth:`~.MakefilePackage.build`
|
||||
3. :py:meth:`~.MakefilePackage.install`
|
||||
build_system("makefile")
|
||||
conflicts("platform=windows", when="build_system=makefile")
|
||||
|
||||
|
||||
@spack.builder.builder("makefile")
|
||||
class MakefileBuilder(BaseBuilder):
|
||||
"""The Makefile builder encodes the most common way of building software with
|
||||
Makefiles. It has three phases that can be overridden, if need be:
|
||||
|
||||
1. :py:meth:`~.MakefileBuilder.edit`
|
||||
2. :py:meth:`~.MakefileBuilder.build`
|
||||
3. :py:meth:`~.MakefileBuilder.install`
|
||||
|
||||
It is usually necessary to override the :py:meth:`~.MakefileBuilder.edit`
|
||||
phase (which is by default a no-op), while the other two have sensible defaults.
|
||||
|
||||
It is usually necessary to override the :py:meth:`~.MakefilePackage.edit`
|
||||
phase, while :py:meth:`~.MakefilePackage.build` and
|
||||
:py:meth:`~.MakefilePackage.install` have sensible defaults.
|
||||
For a finer tuning you may override:
|
||||
|
||||
+-----------------------------------------------+--------------------+
|
||||
| **Method** | **Purpose** |
|
||||
+===============================================+====================+
|
||||
| :py:attr:`~.MakefilePackage.build_targets` | Specify ``make`` |
|
||||
| :py:attr:`~.MakefileBuilder.build_targets` | Specify ``make`` |
|
||||
| | targets for the |
|
||||
| | build phase |
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:attr:`~.MakefilePackage.install_targets` | Specify ``make`` |
|
||||
| :py:attr:`~.MakefileBuilder.install_targets` | Specify ``make`` |
|
||||
| | targets for the |
|
||||
| | install phase |
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:meth:`~.MakefilePackage.build_directory` | Directory where the|
|
||||
| :py:meth:`~.MakefileBuilder.build_directory` | Directory where the|
|
||||
| | Makefile is located|
|
||||
+-----------------------------------------------+--------------------+
|
||||
"""
|
||||
|
||||
#: Phases of a package that is built with an hand-written Makefile
|
||||
phases = ["edit", "build", "install"]
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "MakefilePackage"
|
||||
phases = ("edit", "build", "install")
|
||||
|
||||
#: Targets for ``make`` during the :py:meth:`~.MakefilePackage.build`
|
||||
#: phase
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("check", "installcheck")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"install_time_test_callbacks",
|
||||
"build_directory",
|
||||
)
|
||||
|
||||
#: Targets for ``make`` during the :py:meth:`~.MakefileBuilder.build` phase
|
||||
build_targets = [] # type: List[str]
|
||||
#: Targets for ``make`` during the :py:meth:`~.MakefilePackage.install`
|
||||
#: phase
|
||||
#: Targets for ``make`` during the :py:meth:`~.MakefileBuilder.install` phase
|
||||
install_targets = ["install"]
|
||||
|
||||
conflicts("platform=windows")
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
@@ -66,53 +89,39 @@ class MakefilePackage(PackageBase):
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""Returns the directory containing the main Makefile
|
||||
"""Return the directory containing the main Makefile."""
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
:return: build directory
|
||||
"""
|
||||
return self.stage.source_path
|
||||
def edit(self, pkg, spec, prefix):
|
||||
"""Edit the Makefile before calling make. The default is a no-op."""
|
||||
pass
|
||||
|
||||
def edit(self, spec, prefix):
|
||||
"""Edits the Makefile before calling make. This phase cannot
|
||||
be defaulted.
|
||||
"""
|
||||
tty.msg("Using default implementation: skipping edit phase.")
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Run "make" on the build targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*self.build_targets)
|
||||
|
||||
def build(self, spec, prefix):
|
||||
"""Calls make, passing :py:attr:`~.MakefilePackage.build_targets`
|
||||
as targets.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make(*self.build_targets)
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Run "make" on the install targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).make(*self.install_targets)
|
||||
|
||||
def install(self, spec, prefix):
|
||||
"""Calls make, passing :py:attr:`~.MakefilePackage.install_targets`
|
||||
as targets.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make(*self.install_targets)
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def check(self):
|
||||
"""Searches the Makefile for targets ``test`` and ``check``
|
||||
and runs them if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
self._if_make_target_execute("test")
|
||||
self._if_make_target_execute("check")
|
||||
"""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")
|
||||
|
||||
run_after("install")(PackageBase._run_default_install_time_test_callbacks)
|
||||
spack.builder.run_after("install")(execute_install_time_tests)
|
||||
|
||||
def installcheck(self):
|
||||
"""Searches the Makefile for an ``installcheck`` target
|
||||
and runs it if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
self._if_make_target_execute("installcheck")
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
with fs.working_dir(self.build_directory):
|
||||
self.pkg._if_make_target_execute("installcheck")
|
||||
|
||||
# On macOS, force rpaths for shared library IDs and remove duplicate rpaths
|
||||
run_after("install")(PackageBase.apply_macos_rpath_fixups)
|
||||
spack.builder.run_after("install", when="platform=darwin")(apply_macos_rpath_fixups)
|
||||
|
||||
@@ -2,60 +2,73 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
|
||||
from llnl.util.filesystem import install_tree, working_dir
|
||||
|
||||
from spack.directives import depends_on
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on
|
||||
from spack.multimethod import when
|
||||
from spack.util.executable import which
|
||||
|
||||
from ._checks import BaseBuilder
|
||||
|
||||
class MavenPackage(PackageBase):
|
||||
|
||||
class MavenPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages that are built using the
|
||||
Maven build system. See https://maven.apache.org/index.html
|
||||
for more information.
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
|
||||
* build
|
||||
* install
|
||||
"""
|
||||
|
||||
# Default phases
|
||||
phases = ["build", "install"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "MavenPackage"
|
||||
|
||||
depends_on("java", type=("build", "run"))
|
||||
depends_on("maven", type="build")
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "maven"
|
||||
|
||||
build_system("maven")
|
||||
|
||||
with when("build_system=maven"):
|
||||
depends_on("java", type=("build", "run"))
|
||||
depends_on("maven", type="build")
|
||||
|
||||
|
||||
@spack.builder.builder("maven")
|
||||
class MavenBuilder(BaseBuilder):
|
||||
"""The Maven builder encodes the default way to build software with Maven.
|
||||
It has two phases that can be overridden, if need be:
|
||||
|
||||
1. :py:meth:`~.MavenBuilder.build`
|
||||
2. :py:meth:`~.MavenBuilder.install`
|
||||
"""
|
||||
|
||||
phases = ("build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("build_args",)
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("build_directory",)
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""The directory containing the ``pom.xml`` file."""
|
||||
return self.stage.source_path
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
def build_args(self):
|
||||
"""List of args to pass to build phase."""
|
||||
return []
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Compile code and package into a JAR file."""
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
with fs.working_dir(self.build_directory):
|
||||
mvn = which("mvn")
|
||||
if self.run_tests:
|
||||
if self.pkg.run_tests:
|
||||
mvn("verify", *self.build_args())
|
||||
else:
|
||||
mvn("package", "-DskipTests", *self.build_args())
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Copy to installation prefix."""
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
install_tree(".", prefix)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
with fs.working_dir(self.build_directory):
|
||||
fs.install_tree(".", prefix)
|
||||
|
||||
@@ -2,76 +2,104 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
import os
|
||||
from typing import List # novm
|
||||
|
||||
from llnl.util.filesystem import working_dir
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
from spack.directives import depends_on, variant
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on, variant
|
||||
from spack.multimethod import when
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests
|
||||
|
||||
|
||||
class MesonPackage(PackageBase):
|
||||
"""Specialized class for packages built using Meson
|
||||
class MesonPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using Meson. For more information
|
||||
on the Meson build system, see https://mesonbuild.com/
|
||||
"""
|
||||
|
||||
For more information on the Meson build system, see:
|
||||
https://mesonbuild.com/
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "MesonPackage"
|
||||
|
||||
This class provides three phases that can be overridden:
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "meson"
|
||||
|
||||
1. :py:meth:`~.MesonPackage.meson`
|
||||
2. :py:meth:`~.MesonPackage.build`
|
||||
3. :py:meth:`~.MesonPackage.install`
|
||||
build_system("meson")
|
||||
|
||||
with when("build_system=meson"):
|
||||
variant(
|
||||
"buildtype",
|
||||
default="debugoptimized",
|
||||
description="Meson build type",
|
||||
values=("plain", "debug", "debugoptimized", "release", "minsize"),
|
||||
)
|
||||
variant(
|
||||
"default_library",
|
||||
default="shared",
|
||||
values=("shared", "static"),
|
||||
multi=True,
|
||||
description="Build shared libs, static libs or both",
|
||||
)
|
||||
variant("strip", default=False, description="Strip targets on install")
|
||||
depends_on("meson", type="build")
|
||||
depends_on("ninja", type="build")
|
||||
|
||||
def flags_to_build_system_args(self, flags):
|
||||
"""Produces a list of all command line arguments to pass the specified
|
||||
compiler flags to meson."""
|
||||
# Has to be dynamic attribute due to caching
|
||||
setattr(self, "meson_flag_args", [])
|
||||
|
||||
|
||||
@spack.builder.builder("meson")
|
||||
class MesonBuilder(BaseBuilder):
|
||||
"""The Meson builder encodes the default way to build software with Meson.
|
||||
The builder has three phases that can be overridden, if need be:
|
||||
|
||||
1. :py:meth:`~.MesonBuilder.meson`
|
||||
2. :py:meth:`~.MesonBuilder.build`
|
||||
3. :py:meth:`~.MesonBuilder.install`
|
||||
|
||||
They all have sensible defaults and for many packages the only thing
|
||||
necessary will be to override :py:meth:`~.MesonPackage.meson_args`.
|
||||
necessary will be to override :py:meth:`~.MesonBuilder.meson_args`.
|
||||
|
||||
For a finer tuning you may also override:
|
||||
|
||||
+-----------------------------------------------+--------------------+
|
||||
| **Method** | **Purpose** |
|
||||
+===============================================+====================+
|
||||
| :py:meth:`~.MesonPackage.root_mesonlists_dir` | Location of the |
|
||||
| :py:meth:`~.MesonBuilder.root_mesonlists_dir` | Location of the |
|
||||
| | root MesonLists.txt|
|
||||
+-----------------------------------------------+--------------------+
|
||||
| :py:meth:`~.MesonPackage.build_directory` | Directory where to |
|
||||
| :py:meth:`~.MesonBuilder.build_directory` | Directory where to |
|
||||
| | build the package |
|
||||
+-----------------------------------------------+--------------------+
|
||||
|
||||
|
||||
"""
|
||||
|
||||
#: Phases of a Meson package
|
||||
phases = ["meson", "build", "install"]
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "MesonPackage"
|
||||
phases = ("meson", "build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("meson_args", "check")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"root_mesonlists_dir",
|
||||
"std_meson_args",
|
||||
"build_directory",
|
||||
)
|
||||
|
||||
build_targets = [] # type: List[str]
|
||||
install_targets = ["install"]
|
||||
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
variant(
|
||||
"buildtype",
|
||||
default="debugoptimized",
|
||||
description="Meson build type",
|
||||
values=("plain", "debug", "debugoptimized", "release", "minsize"),
|
||||
)
|
||||
variant(
|
||||
"default_library",
|
||||
default="shared",
|
||||
values=("shared", "static"),
|
||||
multi=True,
|
||||
description="Build shared libs, static libs or both",
|
||||
)
|
||||
variant("strip", default=False, description="Strip targets on install")
|
||||
|
||||
depends_on("meson", type="build")
|
||||
depends_on("ninja", type="build")
|
||||
|
||||
@property
|
||||
def archive_files(self):
|
||||
"""Files to archive for packages based on Meson"""
|
||||
@@ -79,31 +107,26 @@ def archive_files(self):
|
||||
|
||||
@property
|
||||
def root_mesonlists_dir(self):
|
||||
"""The relative path to the directory containing meson.build
|
||||
"""Relative path to the directory containing meson.build
|
||||
|
||||
This path is relative to the root of the extracted tarball,
|
||||
not to the ``build_directory``. Defaults to the current directory.
|
||||
|
||||
:return: directory containing meson.build
|
||||
"""
|
||||
return self.stage.source_path
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
@property
|
||||
def std_meson_args(self):
|
||||
"""Standard meson arguments provided as a property for
|
||||
convenience of package writers
|
||||
|
||||
:return: standard meson arguments
|
||||
"""Standard meson arguments provided as a property for convenience
|
||||
of package writers.
|
||||
"""
|
||||
# standard Meson arguments
|
||||
std_meson_args = MesonPackage._std_args(self)
|
||||
std_meson_args = MesonBuilder.std_args(self.pkg)
|
||||
std_meson_args += getattr(self, "meson_flag_args", [])
|
||||
return std_meson_args
|
||||
|
||||
@staticmethod
|
||||
def _std_args(pkg):
|
||||
"""Computes the standard meson arguments for a generic package"""
|
||||
|
||||
def std_args(pkg):
|
||||
"""Standard meson arguments for a generic package."""
|
||||
try:
|
||||
build_type = pkg.spec.variants["buildtype"].value
|
||||
except KeyError:
|
||||
@@ -132,31 +155,18 @@ def _std_args(pkg):
|
||||
|
||||
return args
|
||||
|
||||
def flags_to_build_system_args(self, flags):
|
||||
"""Produces a list of all command line arguments to pass the specified
|
||||
compiler flags to meson."""
|
||||
# Has to be dynamic attribute due to caching
|
||||
setattr(self, "meson_flag_args", [])
|
||||
|
||||
@property
|
||||
def build_dirname(self):
|
||||
"""Returns the directory name to use when building the package
|
||||
|
||||
:return: name of the subdirectory for building the package
|
||||
"""
|
||||
return "spack-build-%s" % self.spec.dag_hash(7)
|
||||
"""Returns the directory name to use when building the package."""
|
||||
return "spack-build-{}".format(self.spec.dag_hash(7))
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""Returns the directory to use when building the package
|
||||
|
||||
:return: directory where to build the package
|
||||
"""
|
||||
return os.path.join(self.stage.path, self.build_dirname)
|
||||
"""Directory to use when building the package."""
|
||||
return os.path.join(self.pkg.stage.path, self.build_dirname)
|
||||
|
||||
def meson_args(self):
|
||||
"""Produces a list containing all the arguments that must be passed to
|
||||
meson, except:
|
||||
"""List of arguments that must be passed to meson, except:
|
||||
|
||||
* ``--prefix``
|
||||
* ``--libdir``
|
||||
@@ -165,40 +175,33 @@ def meson_args(self):
|
||||
* ``--default_library``
|
||||
|
||||
which will be set automatically.
|
||||
|
||||
:return: list of arguments for meson
|
||||
"""
|
||||
return []
|
||||
|
||||
def meson(self, spec, prefix):
|
||||
"""Runs ``meson`` in the build directory"""
|
||||
def meson(self, pkg, spec, prefix):
|
||||
"""Run ``meson`` in the build directory"""
|
||||
options = [os.path.abspath(self.root_mesonlists_dir)]
|
||||
options += self.std_meson_args
|
||||
options += self.meson_args()
|
||||
with working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self).meson(*options)
|
||||
with fs.working_dir(self.build_directory, create=True):
|
||||
inspect.getmodule(self.pkg).meson(*options)
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Make the build targets"""
|
||||
options = ["-v"]
|
||||
options += self.build_targets
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).ninja(*options)
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).ninja(*options)
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Make the install targets"""
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).ninja(*self.install_targets)
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).ninja(*self.install_targets)
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def check(self):
|
||||
"""Searches the Meson-generated file for the target ``test``
|
||||
and runs it if found.
|
||||
"""
|
||||
with working_dir(self.build_directory):
|
||||
"""Search Meson-generated files for the target ``test`` and run it if found."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
self._if_ninja_target_execute("test")
|
||||
self._if_ninja_target_execute("check")
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
102
lib/spack/spack/build_systems/nmake.py
Normal file
102
lib/spack/spack/build_systems/nmake.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# Copyright 2013-2022 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 inspect
|
||||
from typing import List # novm
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, conflicts
|
||||
|
||||
from ._checks import BaseBuilder
|
||||
|
||||
|
||||
class NMakePackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using a Makefiles."""
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "NmakePackage"
|
||||
|
||||
build_system("nmake")
|
||||
conflicts("platform=linux", when="build_system=nmake")
|
||||
conflicts("platform=darwin", when="build_system=nmake")
|
||||
conflicts("platform=cray", when="build_system=nmake")
|
||||
|
||||
|
||||
@spack.builder.builder("nmake")
|
||||
class NMakeBuilder(BaseBuilder):
|
||||
"""The NMake builder encodes the most common way of building software with
|
||||
NMake on Windows. It has three phases that can be overridden, if need be:
|
||||
|
||||
1. :py:meth:`~.NMakeBuilder.edit`
|
||||
2. :py:meth:`~.NMakeBuilder.build`
|
||||
3. :py:meth:`~.NMakeBuilder.install`
|
||||
|
||||
It is usually necessary to override the :py:meth:`~.NMakeBuilder.edit`
|
||||
phase (which is by default a no-op), while the other two have sensible defaults.
|
||||
|
||||
For a finer tuning you may override:
|
||||
|
||||
+--------------------------------------------+--------------------+
|
||||
| **Method** | **Purpose** |
|
||||
+============================================+====================+
|
||||
| :py:attr:`~.NMakeBuilder.build_targets` | Specify ``nmake`` |
|
||||
| | targets for the |
|
||||
| | build phase |
|
||||
+--------------------------------------------+--------------------+
|
||||
| :py:attr:`~.NMakeBuilder.install_targets` | Specify ``nmake`` |
|
||||
| | targets for the |
|
||||
| | install phase |
|
||||
+--------------------------------------------+--------------------+
|
||||
| :py:meth:`~.NMakeBuilder.build_directory` | Directory where the|
|
||||
| | Makefile is located|
|
||||
+--------------------------------------------+--------------------+
|
||||
"""
|
||||
|
||||
phases = ("edit", "build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("check", "installcheck")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"install_time_test_callbacks",
|
||||
"build_directory",
|
||||
)
|
||||
|
||||
#: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.build` phase
|
||||
build_targets = [] # type: List[str]
|
||||
#: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.install` phase
|
||||
install_targets = ["install"]
|
||||
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
#: Callback names for install-time test
|
||||
install_time_test_callbacks = ["installcheck"]
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""Return the directory containing the main Makefile."""
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
def edit(self, pkg, spec, prefix):
|
||||
"""Edit the Makefile before calling make. The default is a no-op."""
|
||||
pass
|
||||
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Run "make" on the build targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).nmake(*self.build_targets)
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Run "make" on the install targets specified by the builder."""
|
||||
with fs.working_dir(self.build_directory):
|
||||
inspect.getmodule(self.pkg).nmake(*self.install_targets)
|
||||
@@ -2,51 +2,62 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import inspect
|
||||
|
||||
from spack.directives import extends
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, extends
|
||||
from spack.multimethod import when
|
||||
|
||||
from ._checks import BaseBuilder
|
||||
|
||||
|
||||
class OctavePackage(PackageBase):
|
||||
class OctavePackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for Octave packages. See
|
||||
https://www.gnu.org/software/octave/doc/v4.2.0/Installing-and-Removing-Packages.html
|
||||
for more information.
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.OctavePackage.install`
|
||||
|
||||
"""
|
||||
|
||||
# Default phases
|
||||
phases = ["install"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "OctavePackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "octave"
|
||||
|
||||
extends("octave")
|
||||
build_system("octave")
|
||||
|
||||
with when("build_system=octave"):
|
||||
extends("octave")
|
||||
|
||||
|
||||
@spack.builder.builder("octave")
|
||||
class OctaveBuilder(BaseBuilder):
|
||||
"""The octave builder provides the following phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.OctaveBuilder.install`
|
||||
"""
|
||||
|
||||
phases = ("install",)
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ()
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ()
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install the package from the archive file"""
|
||||
inspect.getmodule(self.pkg).octave(
|
||||
"--quiet",
|
||||
"--norc",
|
||||
"--built-in-docstrings-file=/dev/null",
|
||||
"--texi-macros-file=/dev/null",
|
||||
"--eval",
|
||||
"pkg prefix %s; pkg install %s" % (prefix, self.pkg.stage.archive_file),
|
||||
)
|
||||
|
||||
def setup_build_environment(self, env):
|
||||
# octave does not like those environment variables to be set:
|
||||
env.unset("CC")
|
||||
env.unset("CXX")
|
||||
env.unset("FC")
|
||||
|
||||
def install(self, spec, prefix):
|
||||
"""Install the package from the archive file"""
|
||||
inspect.getmodule(self).octave(
|
||||
"--quiet",
|
||||
"--norc",
|
||||
"--built-in-docstrings-file=/dev/null",
|
||||
"--texi-macros-file=/dev/null",
|
||||
"--eval",
|
||||
"pkg prefix %s; pkg install %s" % (prefix, self.stage.archive_file),
|
||||
)
|
||||
|
||||
# Testing
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
"""Common utilities for managing intel oneapi packages.
|
||||
|
||||
"""
|
||||
|
||||
"""Common utilities for managing intel oneapi packages."""
|
||||
import getpass
|
||||
import platform
|
||||
import shutil
|
||||
@@ -14,18 +10,17 @@
|
||||
|
||||
from llnl.util.filesystem import find_headers, find_libraries, join_path
|
||||
|
||||
from spack.package_base import Package
|
||||
from spack.util.environment import EnvironmentModifications
|
||||
from spack.util.executable import Executable
|
||||
|
||||
from .generic import Package
|
||||
|
||||
|
||||
class IntelOneApiPackage(Package):
|
||||
"""Base class for Intel oneAPI packages."""
|
||||
|
||||
homepage = "https://software.intel.com/oneapi"
|
||||
|
||||
phases = ["install"]
|
||||
|
||||
# oneAPI license does not allow mirroring outside of the
|
||||
# organization (e.g. University/Company).
|
||||
redistribute_source = False
|
||||
|
||||
@@ -2,73 +2,87 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from llnl.util.filesystem import filter_file
|
||||
|
||||
from spack.directives import extends
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, extends
|
||||
from spack.package_base import PackageBase
|
||||
from spack.util.executable import Executable
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests
|
||||
|
||||
|
||||
class PerlPackage(PackageBase):
|
||||
"""Specialized class for packages that are built using Perl.
|
||||
"""Specialized class for packages that are built using Perl."""
|
||||
|
||||
This class provides four phases that can be overridden if required:
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "PerlPackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "perl"
|
||||
|
||||
1. :py:meth:`~.PerlPackage.configure`
|
||||
2. :py:meth:`~.PerlPackage.build`
|
||||
3. :py:meth:`~.PerlPackage.check`
|
||||
4. :py:meth:`~.PerlPackage.install`
|
||||
build_system("perl")
|
||||
|
||||
extends("perl", when="build_system=perl")
|
||||
|
||||
|
||||
@spack.builder.builder("perl")
|
||||
class PerlBuilder(BaseBuilder):
|
||||
"""The perl builder provides four phases that can be overridden, if required:
|
||||
|
||||
1. :py:meth:`~.PerlBuilder.configure`
|
||||
2. :py:meth:`~.PerlBuilder.build`
|
||||
3. :py:meth:`~.PerlBuilder.check`
|
||||
4. :py:meth:`~.PerlBuilder.install`
|
||||
|
||||
The default methods use, in order of preference:
|
||||
(1) Makefile.PL,
|
||||
(2) Build.PL.
|
||||
|
||||
Some packages may need to override
|
||||
:py:meth:`~.PerlPackage.configure_args`,
|
||||
which produces a list of arguments for
|
||||
:py:meth:`~.PerlPackage.configure`.
|
||||
Some packages may need to override :py:meth:`~.PerlBuilder.configure_args`,
|
||||
which produces a list of arguments for :py:meth:`~.PerlBuilder.configure`.
|
||||
|
||||
Arguments should not include the installation base directory.
|
||||
"""
|
||||
|
||||
#: Phases of a Perl package
|
||||
phases = ["configure", "build", "install"]
|
||||
phases = ("configure", "build", "install")
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "PerlPackage"
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("configure_args", "check")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ()
|
||||
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
extends("perl")
|
||||
|
||||
def configure_args(self):
|
||||
"""Produces a list containing the arguments that must be passed to
|
||||
:py:meth:`~.PerlPackage.configure`. Arguments should not include
|
||||
the installation base directory, which is prepended automatically.
|
||||
"""List of arguments passed to :py:meth:`~.PerlBuilder.configure`.
|
||||
|
||||
:return: list of arguments for Makefile.PL or Build.PL
|
||||
Arguments should not include the installation base directory, which
|
||||
is prepended automatically.
|
||||
"""
|
||||
return []
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
"""Runs Makefile.PL or Build.PL with arguments consisting of
|
||||
def configure(self, pkg, spec, prefix):
|
||||
"""Run Makefile.PL or Build.PL with arguments consisting of
|
||||
an appropriate installation base directory followed by the
|
||||
list returned by :py:meth:`~.PerlPackage.configure_args`.
|
||||
list returned by :py:meth:`~.PerlBuilder.configure_args`.
|
||||
|
||||
:raise RuntimeError: if neither Makefile.PL or Build.PL exist
|
||||
Raises:
|
||||
RuntimeError: if neither Makefile.PL nor Build.PL exist
|
||||
"""
|
||||
if os.path.isfile("Makefile.PL"):
|
||||
self.build_method = "Makefile.PL"
|
||||
self.build_executable = inspect.getmodule(self).make
|
||||
self.build_executable = inspect.getmodule(self.pkg).make
|
||||
elif os.path.isfile("Build.PL"):
|
||||
self.build_method = "Build.PL"
|
||||
self.build_executable = Executable(os.path.join(self.stage.source_path, "Build"))
|
||||
self.build_executable = Executable(os.path.join(self.pkg.stage.source_path, "Build"))
|
||||
else:
|
||||
raise RuntimeError("Unknown build_method for perl package")
|
||||
|
||||
@@ -78,33 +92,30 @@ def configure(self, spec, prefix):
|
||||
options = ["Build.PL", "--install_base", prefix]
|
||||
options += self.configure_args()
|
||||
|
||||
inspect.getmodule(self).perl(*options)
|
||||
inspect.getmodule(self.pkg).perl(*options)
|
||||
|
||||
# It is possible that the shebang in the Build script that is created from
|
||||
# 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.
|
||||
@run_after("configure")
|
||||
@spack.builder.run_after("configure")
|
||||
def fix_shebang(self):
|
||||
if self.build_method == "Build.PL":
|
||||
pattern = "#!{0}".format(self.spec["perl"].command.path)
|
||||
repl = "#!/usr/bin/env perl"
|
||||
filter_file(pattern, repl, "Build", backup=False)
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Builds a Perl package."""
|
||||
self.build_executable()
|
||||
|
||||
# Ensure that tests run after build (if requested):
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def check(self):
|
||||
"""Runs built-in tests of a Perl package."""
|
||||
self.build_executable("test")
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Installs a Perl package."""
|
||||
self.build_executable("install")
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
@@ -8,93 +8,22 @@
|
||||
import shutil
|
||||
from typing import Optional
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.lang as lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import (
|
||||
filter_file,
|
||||
find,
|
||||
find_all_headers,
|
||||
find_libraries,
|
||||
is_nonsymlink_exe_with_shebang,
|
||||
path_contains_subdirectory,
|
||||
same_path,
|
||||
working_dir,
|
||||
)
|
||||
from llnl.util.lang import classproperty, match_predicate
|
||||
|
||||
from spack.directives import depends_on, extends
|
||||
import spack.builder
|
||||
import spack.multimethod
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on, extends
|
||||
from spack.error import NoHeadersError, NoLibrariesError, SpecError
|
||||
from spack.package_base import PackageBase, run_after
|
||||
from spack.version import Version
|
||||
|
||||
from ._checks import BaseBuilder, execute_install_time_tests
|
||||
|
||||
class PythonPackage(PackageBase):
|
||||
"""Specialized class for packages that are built using pip."""
|
||||
|
||||
#: Package name, version, and extension on PyPI
|
||||
pypi = None # type: Optional[str]
|
||||
|
||||
maintainers = ["adamjstewart", "pradyunsg"]
|
||||
|
||||
# Default phases
|
||||
phases = ["install"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "PythonPackage"
|
||||
|
||||
#: Callback names for install-time test
|
||||
install_time_test_callbacks = ["test"]
|
||||
|
||||
extends("python")
|
||||
depends_on("py-pip", type="build")
|
||||
# FIXME: technically wheel is only needed when building from source, not when
|
||||
# installing a downloaded wheel, but I don't want to add wheel as a dep to every
|
||||
# package manually
|
||||
depends_on("py-wheel", type="build")
|
||||
|
||||
py_namespace = None # type: Optional[str]
|
||||
|
||||
@staticmethod
|
||||
def _std_args(cls):
|
||||
return [
|
||||
# Verbose
|
||||
"-vvv",
|
||||
# Disable prompting for input
|
||||
"--no-input",
|
||||
# Disable the cache
|
||||
"--no-cache-dir",
|
||||
# Don't check to see if pip is up-to-date
|
||||
"--disable-pip-version-check",
|
||||
# Install packages
|
||||
"install",
|
||||
# Don't install package dependencies
|
||||
"--no-deps",
|
||||
# Overwrite existing packages
|
||||
"--ignore-installed",
|
||||
# Use env vars like PYTHONPATH
|
||||
"--no-build-isolation",
|
||||
# Don't warn that prefix.bin is not in PATH
|
||||
"--no-warn-script-location",
|
||||
# Ignore the PyPI package index
|
||||
"--no-index",
|
||||
]
|
||||
|
||||
@classproperty
|
||||
def homepage(cls):
|
||||
if cls.pypi:
|
||||
name = cls.pypi.split("/")[0]
|
||||
return "https://pypi.org/project/" + name + "/"
|
||||
|
||||
@classproperty
|
||||
def url(cls):
|
||||
if cls.pypi:
|
||||
return "https://files.pythonhosted.org/packages/source/" + cls.pypi[0] + "/" + cls.pypi
|
||||
|
||||
@classproperty
|
||||
def list_url(cls):
|
||||
if cls.pypi:
|
||||
name = cls.pypi.split("/")[0]
|
||||
return "https://pypi.org/simple/" + name + "/"
|
||||
class PythonExtension(spack.package_base.PackageBase):
|
||||
maintainers = ["adamjstewart"]
|
||||
|
||||
@property
|
||||
def import_modules(self):
|
||||
@@ -124,7 +53,7 @@ def import_modules(self):
|
||||
|
||||
# Some Python libraries are packages: collections of modules
|
||||
# distributed in directories containing __init__.py files
|
||||
for path in find(root, "__init__.py", recursive=True):
|
||||
for path in fs.find(root, "__init__.py", recursive=True):
|
||||
modules.append(
|
||||
path.replace(root + os.sep, "", 1)
|
||||
.replace(os.sep + "__init__.py", "")
|
||||
@@ -133,7 +62,7 @@ def import_modules(self):
|
||||
|
||||
# Some Python libraries are modules: individual *.py files
|
||||
# found in the site-packages directory
|
||||
for path in find(root, "*.py", recursive=False):
|
||||
for path in fs.find(root, "*.py", recursive=False):
|
||||
modules.append(
|
||||
path.replace(root + os.sep, "", 1).replace(".py", "").replace("/", ".")
|
||||
)
|
||||
@@ -160,6 +89,208 @@ def skip_modules(self):
|
||||
"""
|
||||
return []
|
||||
|
||||
def view_file_conflicts(self, view, merge_map):
|
||||
"""Report all file conflicts, excepting special cases for python.
|
||||
Specifically, this does not report errors for duplicate
|
||||
__init__.py files for packages in the same namespace.
|
||||
"""
|
||||
conflicts = list(dst for src, dst in merge_map.items() if os.path.exists(dst))
|
||||
|
||||
if conflicts and self.py_namespace:
|
||||
ext_map = view.extensions_layout.extension_map(self.extendee_spec)
|
||||
namespaces = set(x.package.py_namespace for x in ext_map.values())
|
||||
namespace_re = r"site-packages/{0}/__init__.py".format(self.py_namespace)
|
||||
find_namespace = lang.match_predicate(namespace_re)
|
||||
if self.py_namespace in namespaces:
|
||||
conflicts = list(x for x in conflicts if not find_namespace(x))
|
||||
|
||||
return conflicts
|
||||
|
||||
def add_files_to_view(self, view, merge_map, skip_if_exists=True):
|
||||
bin_dir = self.spec.prefix.bin
|
||||
python_prefix = self.extendee_spec.prefix
|
||||
python_is_external = self.extendee_spec.external
|
||||
global_view = fs.same_path(python_prefix, view.get_projection_for_spec(self.spec))
|
||||
for src, dst in merge_map.items():
|
||||
if os.path.exists(dst):
|
||||
continue
|
||||
elif global_view or not fs.path_contains_subdirectory(src, bin_dir):
|
||||
view.link(src, dst)
|
||||
elif not os.path.islink(src):
|
||||
shutil.copy2(src, dst)
|
||||
is_script = fs.is_nonsymlink_exe_with_shebang(src)
|
||||
if is_script and not python_is_external:
|
||||
fs.filter_file(
|
||||
python_prefix,
|
||||
os.path.abspath(view.get_projection_for_spec(self.spec)),
|
||||
dst,
|
||||
)
|
||||
else:
|
||||
orig_link_target = os.path.realpath(src)
|
||||
new_link_target = os.path.abspath(merge_map[orig_link_target])
|
||||
view.link(new_link_target, dst)
|
||||
|
||||
def remove_files_from_view(self, view, merge_map):
|
||||
ignore_namespace = False
|
||||
if self.py_namespace:
|
||||
ext_map = view.extensions_layout.extension_map(self.extendee_spec)
|
||||
remaining_namespaces = set(
|
||||
spec.package.py_namespace for name, spec in ext_map.items() if name != self.name
|
||||
)
|
||||
if self.py_namespace in remaining_namespaces:
|
||||
namespace_init = lang.match_predicate(
|
||||
r"site-packages/{0}/__init__.py".format(self.py_namespace)
|
||||
)
|
||||
ignore_namespace = True
|
||||
|
||||
bin_dir = self.spec.prefix.bin
|
||||
global_view = self.extendee_spec.prefix == view.get_projection_for_spec(self.spec)
|
||||
|
||||
to_remove = []
|
||||
for src, dst in merge_map.items():
|
||||
if ignore_namespace and namespace_init(dst):
|
||||
continue
|
||||
|
||||
if global_view or not fs.path_contains_subdirectory(src, bin_dir):
|
||||
to_remove.append(dst)
|
||||
else:
|
||||
os.remove(dst)
|
||||
|
||||
view.remove_files(to_remove)
|
||||
|
||||
def test(self):
|
||||
"""Attempts to import modules of the installed package."""
|
||||
|
||||
# Make sure we are importing the installed modules,
|
||||
# not the ones in the source directory
|
||||
for module in self.import_modules:
|
||||
self.run_test(
|
||||
inspect.getmodule(self).python.path,
|
||||
["-c", "import {0}".format(module)],
|
||||
purpose="checking import of {0}".format(module),
|
||||
work_dir="spack-test",
|
||||
)
|
||||
|
||||
|
||||
class PythonPackage(PythonExtension):
|
||||
"""Specialized class for packages that are built using pip."""
|
||||
|
||||
#: Package name, version, and extension on PyPI
|
||||
pypi = None # type: Optional[str]
|
||||
|
||||
maintainers = ["adamjstewart", "pradyunsg"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "PythonPackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "python_pip"
|
||||
|
||||
#: Callback names for install-time test
|
||||
install_time_test_callbacks = ["test"]
|
||||
|
||||
build_system("python_pip")
|
||||
|
||||
with spack.multimethod.when("build_system=python_pip"):
|
||||
extends("python")
|
||||
depends_on("py-pip", type="build")
|
||||
# FIXME: technically wheel is only needed when building from source, not when
|
||||
# installing a downloaded wheel, but I don't want to add wheel as a dep to every
|
||||
# package manually
|
||||
depends_on("py-wheel", type="build")
|
||||
|
||||
py_namespace = None # type: Optional[str]
|
||||
|
||||
@lang.classproperty
|
||||
def homepage(cls):
|
||||
if cls.pypi:
|
||||
name = cls.pypi.split("/")[0]
|
||||
return "https://pypi.org/project/" + name + "/"
|
||||
|
||||
@lang.classproperty
|
||||
def url(cls):
|
||||
if cls.pypi:
|
||||
return "https://files.pythonhosted.org/packages/source/" + cls.pypi[0] + "/" + cls.pypi
|
||||
|
||||
@lang.classproperty
|
||||
def list_url(cls):
|
||||
if cls.pypi:
|
||||
name = cls.pypi.split("/")[0]
|
||||
return "https://pypi.org/simple/" + name + "/"
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
"""Discover header files in platlib."""
|
||||
|
||||
# Headers may be in either location
|
||||
include = self.prefix.join(self.spec["python"].package.include)
|
||||
platlib = self.prefix.join(self.spec["python"].package.platlib)
|
||||
headers = fs.find_all_headers(include) + fs.find_all_headers(platlib)
|
||||
|
||||
if headers:
|
||||
return headers
|
||||
|
||||
msg = "Unable to locate {} headers in {} or {}"
|
||||
raise NoHeadersError(msg.format(self.spec.name, include, platlib))
|
||||
|
||||
@property
|
||||
def libs(self):
|
||||
"""Discover libraries in platlib."""
|
||||
|
||||
# Remove py- prefix in package name
|
||||
library = "lib" + self.spec.name[3:].replace("-", "?")
|
||||
root = self.prefix.join(self.spec["python"].package.platlib)
|
||||
|
||||
for shared in [True, False]:
|
||||
libs = fs.find_libraries(library, root, shared=shared, recursive=True)
|
||||
if libs:
|
||||
return libs
|
||||
|
||||
msg = "Unable to recursively locate {} libraries in {}"
|
||||
raise NoLibrariesError(msg.format(self.spec.name, root))
|
||||
|
||||
|
||||
@spack.builder.builder("python_pip")
|
||||
class PythonPipBuilder(BaseBuilder):
|
||||
phases = ("install",)
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("test",)
|
||||
|
||||
#: Same as legacy_methods, but the signature is different
|
||||
legacy_long_methods = ("install_options", "global_options", "config_settings")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("build_directory", "install_time_test_callbacks")
|
||||
|
||||
#: Callback names for install-time test
|
||||
install_time_test_callbacks = ["test"]
|
||||
|
||||
@staticmethod
|
||||
def std_args(cls):
|
||||
return [
|
||||
# Verbose
|
||||
"-vvv",
|
||||
# Disable prompting for input
|
||||
"--no-input",
|
||||
# Disable the cache
|
||||
"--no-cache-dir",
|
||||
# Don't check to see if pip is up-to-date
|
||||
"--disable-pip-version-check",
|
||||
# Install packages
|
||||
"install",
|
||||
# Don't install package dependencies
|
||||
"--no-deps",
|
||||
# Overwrite existing packages
|
||||
"--ignore-installed",
|
||||
# Use env vars like PYTHONPATH
|
||||
"--no-build-isolation",
|
||||
# Don't warn that prefix.bin is not in PATH
|
||||
"--no-warn-script-location",
|
||||
# Ignore the PyPI package index
|
||||
"--no-index",
|
||||
]
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""The root directory of the Python package.
|
||||
@@ -170,11 +301,10 @@ def build_directory(self):
|
||||
* ``setup.cfg``
|
||||
* ``setup.py``
|
||||
"""
|
||||
return self.stage.source_path
|
||||
return self.pkg.stage.source_path
|
||||
|
||||
def config_settings(self, spec, prefix):
|
||||
"""Configuration settings to be passed to the PEP 517 build backend.
|
||||
|
||||
Requires pip 22.1+, which requires Python 3.7+.
|
||||
|
||||
Args:
|
||||
@@ -211,10 +341,10 @@ def global_options(self, spec, prefix):
|
||||
"""
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install everything from build directory."""
|
||||
|
||||
args = PythonPackage._std_args(self) + ["--prefix=" + prefix]
|
||||
args = PythonPipBuilder.std_args(pkg) + ["--prefix=" + prefix]
|
||||
|
||||
for key, value in self.config_settings(spec, prefix).items():
|
||||
if spec["py-pip"].version < Version("22.1"):
|
||||
@@ -223,137 +353,21 @@ def install(self, spec, prefix):
|
||||
"pip 22.1+. Add the following line to the package to fix this:\n\n"
|
||||
' depends_on("py-pip@22.1:", type="build")'.format(spec.name)
|
||||
)
|
||||
|
||||
args.append("--config-settings={}={}".format(key, value))
|
||||
|
||||
for option in self.install_options(spec, prefix):
|
||||
args.append("--install-option=" + option)
|
||||
for option in self.global_options(spec, prefix):
|
||||
args.append("--global-option=" + option)
|
||||
|
||||
if self.stage.archive_file and self.stage.archive_file.endswith(".whl"):
|
||||
args.append(self.stage.archive_file)
|
||||
if pkg.stage.archive_file and pkg.stage.archive_file.endswith(".whl"):
|
||||
args.append(pkg.stage.archive_file)
|
||||
else:
|
||||
args.append(".")
|
||||
|
||||
pip = inspect.getmodule(self).pip
|
||||
with working_dir(self.build_directory):
|
||||
pip = inspect.getmodule(pkg).pip
|
||||
with fs.working_dir(self.build_directory):
|
||||
pip(*args)
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
"""Discover header files in platlib."""
|
||||
|
||||
# Headers may be in either location
|
||||
include = self.prefix.join(self.spec["python"].package.include)
|
||||
platlib = self.prefix.join(self.spec["python"].package.platlib)
|
||||
headers = find_all_headers(include) + find_all_headers(platlib)
|
||||
|
||||
if headers:
|
||||
return headers
|
||||
|
||||
msg = "Unable to locate {} headers in {} or {}"
|
||||
raise NoHeadersError(msg.format(self.spec.name, include, platlib))
|
||||
|
||||
@property
|
||||
def libs(self):
|
||||
"""Discover libraries in platlib."""
|
||||
|
||||
# Remove py- prefix in package name
|
||||
library = "lib" + self.spec.name[3:].replace("-", "?")
|
||||
root = self.prefix.join(self.spec["python"].package.platlib)
|
||||
|
||||
for shared in [True, False]:
|
||||
libs = find_libraries(library, root, shared=shared, recursive=True)
|
||||
if libs:
|
||||
return libs
|
||||
|
||||
msg = "Unable to recursively locate {} libraries in {}"
|
||||
raise NoLibrariesError(msg.format(self.spec.name, root))
|
||||
|
||||
# Testing
|
||||
|
||||
def test(self):
|
||||
"""Attempts to import modules of the installed package."""
|
||||
|
||||
# Make sure we are importing the installed modules,
|
||||
# not the ones in the source directory
|
||||
for module in self.import_modules:
|
||||
self.run_test(
|
||||
inspect.getmodule(self).python.path,
|
||||
["-c", "import {0}".format(module)],
|
||||
purpose="checking import of {0}".format(module),
|
||||
work_dir="spack-test",
|
||||
)
|
||||
|
||||
run_after("install")(PackageBase._run_default_install_time_test_callbacks)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
def view_file_conflicts(self, view, merge_map):
|
||||
"""Report all file conflicts, excepting special cases for python.
|
||||
Specifically, this does not report errors for duplicate
|
||||
__init__.py files for packages in the same namespace.
|
||||
"""
|
||||
conflicts = list(dst for src, dst in merge_map.items() if os.path.exists(dst))
|
||||
|
||||
if conflicts and self.py_namespace:
|
||||
ext_map = view.extensions_layout.extension_map(self.extendee_spec)
|
||||
namespaces = set(x.package.py_namespace for x in ext_map.values())
|
||||
namespace_re = r"site-packages/{0}/__init__.py".format(self.py_namespace)
|
||||
find_namespace = match_predicate(namespace_re)
|
||||
if self.py_namespace in namespaces:
|
||||
conflicts = list(x for x in conflicts if not find_namespace(x))
|
||||
|
||||
return conflicts
|
||||
|
||||
def add_files_to_view(self, view, merge_map, skip_if_exists=True):
|
||||
bin_dir = self.spec.prefix.bin
|
||||
python_prefix = self.extendee_spec.prefix
|
||||
python_is_external = self.extendee_spec.external
|
||||
global_view = same_path(python_prefix, view.get_projection_for_spec(self.spec))
|
||||
for src, dst in merge_map.items():
|
||||
if os.path.exists(dst):
|
||||
continue
|
||||
elif global_view or not path_contains_subdirectory(src, bin_dir):
|
||||
view.link(src, dst)
|
||||
elif not os.path.islink(src):
|
||||
shutil.copy2(src, dst)
|
||||
is_script = is_nonsymlink_exe_with_shebang(src)
|
||||
if is_script and not python_is_external:
|
||||
filter_file(
|
||||
python_prefix,
|
||||
os.path.abspath(view.get_projection_for_spec(self.spec)),
|
||||
dst,
|
||||
)
|
||||
else:
|
||||
orig_link_target = os.path.realpath(src)
|
||||
new_link_target = os.path.abspath(merge_map[orig_link_target])
|
||||
view.link(new_link_target, dst)
|
||||
|
||||
def remove_files_from_view(self, view, merge_map):
|
||||
ignore_namespace = False
|
||||
if self.py_namespace:
|
||||
ext_map = view.extensions_layout.extension_map(self.extendee_spec)
|
||||
remaining_namespaces = set(
|
||||
spec.package.py_namespace for name, spec in ext_map.items() if name != self.name
|
||||
)
|
||||
if self.py_namespace in remaining_namespaces:
|
||||
namespace_init = match_predicate(
|
||||
r"site-packages/{0}/__init__.py".format(self.py_namespace)
|
||||
)
|
||||
ignore_namespace = True
|
||||
|
||||
bin_dir = self.spec.prefix.bin
|
||||
global_view = self.extendee_spec.prefix == view.get_projection_for_spec(self.spec)
|
||||
|
||||
to_remove = []
|
||||
for src, dst in merge_map.items():
|
||||
if ignore_namespace and namespace_init(dst):
|
||||
continue
|
||||
|
||||
if global_view or not path_contains_subdirectory(src, bin_dir):
|
||||
to_remove.append(dst)
|
||||
else:
|
||||
os.remove(dst)
|
||||
|
||||
view.remove_files(to_remove)
|
||||
spack.builder.run_after("install")(execute_install_time_tests)
|
||||
|
||||
@@ -2,82 +2,85 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
from spack.directives import depends_on
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests
|
||||
|
||||
|
||||
class QMakePackage(PackageBase):
|
||||
class QMakePackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using qmake.
|
||||
|
||||
For more information on the qmake build system, see:
|
||||
http://doc.qt.io/qt-5/qmake-manual.html
|
||||
|
||||
This class provides three phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.QMakePackage.qmake`
|
||||
2. :py:meth:`~.QMakePackage.build`
|
||||
3. :py:meth:`~.QMakePackage.install`
|
||||
|
||||
They all have sensible defaults and for many packages the only thing
|
||||
necessary will be to override :py:meth:`~.QMakePackage.qmake_args`.
|
||||
"""
|
||||
|
||||
#: Phases of a qmake package
|
||||
phases = ["qmake", "build", "install"]
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "QMakePackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "qmake"
|
||||
|
||||
build_system("qmake")
|
||||
|
||||
depends_on("qt", type="build", when="build_system=qmake")
|
||||
|
||||
|
||||
@spack.builder.builder("qmake")
|
||||
class QMakeBuilder(BaseBuilder):
|
||||
"""The qmake builder provides three phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.QMakeBuilder.qmake`
|
||||
2. :py:meth:`~.QMakeBuilder.build`
|
||||
3. :py:meth:`~.QMakeBuilder.install`
|
||||
|
||||
They all have sensible defaults and for many packages the only thing
|
||||
necessary will be to override :py:meth:`~.QMakeBuilder.qmake_args`.
|
||||
"""
|
||||
|
||||
phases = ("qmake", "build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("qmake_args", "check")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("build_directory", "build_time_test_callbacks")
|
||||
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
depends_on("qt", type="build")
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""The directory containing the ``*.pro`` file."""
|
||||
return self.stage.source_path
|
||||
|
||||
def qmake_args(self):
|
||||
"""Produces a list containing all the arguments that must be passed to
|
||||
qmake
|
||||
"""
|
||||
"""List of arguments passed to qmake."""
|
||||
return []
|
||||
|
||||
def qmake(self, spec, prefix):
|
||||
def qmake(self, pkg, spec, prefix):
|
||||
"""Run ``qmake`` to configure the project and generate a Makefile."""
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).qmake(*self.qmake_args())
|
||||
inspect.getmodule(self.pkg).qmake(*self.qmake_args())
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Make the build targets"""
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make()
|
||||
inspect.getmodule(self.pkg).make()
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Make the install targets"""
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
inspect.getmodule(self).make("install")
|
||||
|
||||
# Tests
|
||||
inspect.getmodule(self.pkg).make("install")
|
||||
|
||||
def check(self):
|
||||
"""Searches the Makefile for a ``check:`` target and runs it if found."""
|
||||
|
||||
"""Search the Makefile for a ``check:`` target and runs it if found."""
|
||||
with working_dir(self.build_directory):
|
||||
self._if_make_target_execute("check")
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
@@ -3,30 +3,64 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import inspect
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import llnl.util.lang as lang
|
||||
|
||||
from spack.directives import extends
|
||||
from spack.package_base import PackageBase, run_after
|
||||
|
||||
from .generic import GenericBuilder, Package
|
||||
|
||||
|
||||
class RPackage(PackageBase):
|
||||
class RBuilder(GenericBuilder):
|
||||
"""The R builder provides a single phase that can be overridden:
|
||||
|
||||
1. :py:meth:`~.RBuilder.install`
|
||||
|
||||
It has sensible defaults, and for many packages the only thing
|
||||
necessary will be to add dependencies.
|
||||
"""
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = (
|
||||
"configure_args",
|
||||
"configure_vars",
|
||||
) + GenericBuilder.legacy_methods # type: Tuple[str, ...]
|
||||
|
||||
def configure_args(self):
|
||||
"""Arguments to pass to install via ``--configure-args``."""
|
||||
return []
|
||||
|
||||
def configure_vars(self):
|
||||
"""Arguments to pass to install via ``--configure-vars``."""
|
||||
return []
|
||||
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Installs an R package."""
|
||||
|
||||
config_args = self.configure_args()
|
||||
config_vars = self.configure_vars()
|
||||
|
||||
args = ["--vanilla", "CMD", "INSTALL"]
|
||||
|
||||
if config_args:
|
||||
args.append("--configure-args={0}".format(" ".join(config_args)))
|
||||
|
||||
if config_vars:
|
||||
args.append("--configure-vars={0}".format(" ".join(config_vars)))
|
||||
|
||||
args.extend(["--library={0}".format(self.pkg.module.r_lib_dir), self.stage.source_path])
|
||||
|
||||
inspect.getmodule(self.pkg).R(*args)
|
||||
|
||||
|
||||
class RPackage(Package):
|
||||
"""Specialized class for packages that are built using R.
|
||||
|
||||
For more information on the R build system, see:
|
||||
https://stat.ethz.ch/R-manual/R-devel/library/utils/html/INSTALL.html
|
||||
|
||||
This class provides a single phase that can be overridden:
|
||||
|
||||
1. :py:meth:`~.RPackage.install`
|
||||
|
||||
It has sensible defaults, and for many packages the only thing
|
||||
necessary will be to add dependencies
|
||||
"""
|
||||
|
||||
phases = ["install"]
|
||||
|
||||
# package attributes that can be expanded to set the homepage, url,
|
||||
# list_url, and git values
|
||||
# For CRAN packages
|
||||
@@ -35,6 +69,8 @@ class RPackage(PackageBase):
|
||||
# For Bioconductor packages
|
||||
bioc = None # type: Optional[str]
|
||||
|
||||
GenericBuilder = RBuilder
|
||||
|
||||
maintainers = ["glennpj"]
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
@@ -70,32 +106,3 @@ def list_url(cls):
|
||||
def git(self):
|
||||
if self.bioc:
|
||||
return "https://git.bioconductor.org/packages/" + self.bioc
|
||||
|
||||
def configure_args(self):
|
||||
"""Arguments to pass to install via ``--configure-args``."""
|
||||
return []
|
||||
|
||||
def configure_vars(self):
|
||||
"""Arguments to pass to install via ``--configure-vars``."""
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
"""Installs an R package."""
|
||||
|
||||
config_args = self.configure_args()
|
||||
config_vars = self.configure_vars()
|
||||
|
||||
args = ["--vanilla", "CMD", "INSTALL"]
|
||||
|
||||
if config_args:
|
||||
args.append("--configure-args={0}".format(" ".join(config_args)))
|
||||
|
||||
if config_vars:
|
||||
args.append("--configure-vars={0}".format(" ".join(config_vars)))
|
||||
|
||||
args.extend(["--library={0}".format(self.module.r_lib_dir), self.stage.source_path])
|
||||
|
||||
inspect.getmodule(self).R(*args)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
import os
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.lang as lang
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
import spack.builder
|
||||
from spack.build_environment import SPACK_NO_PARALLEL_MAKE, determine_number_of_jobs
|
||||
from spack.directives import extends
|
||||
from spack.directives import build_system, extends
|
||||
from spack.package_base import PackageBase
|
||||
from spack.util.environment import env_flag
|
||||
from spack.util.executable import Executable, ProcessError
|
||||
@@ -19,34 +20,52 @@
|
||||
class RacketPackage(PackageBase):
|
||||
"""Specialized class for packages that are built using Racket's
|
||||
`raco pkg install` and `raco setup` commands.
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
|
||||
* install
|
||||
* setup
|
||||
"""
|
||||
|
||||
#: Package name, version, and extension on PyPI
|
||||
maintainers = ["elfprince13"]
|
||||
|
||||
# Default phases
|
||||
phases = ["install"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "RacketPackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "racket"
|
||||
|
||||
extends("racket")
|
||||
build_system("racket")
|
||||
|
||||
extends("racket", when="build_system=racket")
|
||||
|
||||
pkgs = False
|
||||
subdirectory = None # type: Optional[str]
|
||||
racket_name = None # type: Optional[str]
|
||||
parallel = True
|
||||
|
||||
@lang.classproperty
|
||||
def homepage(cls):
|
||||
if cls.pkgs:
|
||||
if cls.racket_name:
|
||||
return "https://pkgs.racket-lang.org/package/{0}".format(cls.racket_name)
|
||||
return None
|
||||
|
||||
|
||||
@spack.builder.builder("racket")
|
||||
class RacketBuilder(spack.builder.Builder):
|
||||
"""The Racket builder provides an ``install`` phase that can be overridden."""
|
||||
|
||||
phases = ("install",)
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = tuple() # type: Tuple[str, ...]
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ("build_directory", "build_time_test_callbacks", "subdirectory")
|
||||
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["check"]
|
||||
|
||||
racket_name = None # type: Optional[str]
|
||||
|
||||
@property
|
||||
def subdirectory(self):
|
||||
if self.racket_name:
|
||||
return "pkgs/{0}".format(self.pkg.racket_name)
|
||||
return None
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
@@ -55,25 +74,25 @@ def build_directory(self):
|
||||
ret = os.path.join(ret, self.subdirectory)
|
||||
return ret
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install everything from build directory."""
|
||||
raco = Executable("raco")
|
||||
with working_dir(self.build_directory):
|
||||
allow_parallel = self.parallel and (not env_flag(SPACK_NO_PARALLEL_MAKE))
|
||||
with fs.working_dir(self.build_directory):
|
||||
parallel = self.pkg.parallel and (not env_flag(SPACK_NO_PARALLEL_MAKE))
|
||||
args = [
|
||||
"pkg",
|
||||
"install",
|
||||
"-t",
|
||||
"dir",
|
||||
"-n",
|
||||
self.racket_name,
|
||||
self.pkg.racket_name,
|
||||
"--deps",
|
||||
"fail",
|
||||
"--ignore-implies",
|
||||
"--copy",
|
||||
"-i",
|
||||
"-j",
|
||||
str(determine_number_of_jobs(allow_parallel)),
|
||||
str(determine_number_of_jobs(parallel)),
|
||||
"--",
|
||||
os.getcwd(),
|
||||
]
|
||||
@@ -82,9 +101,8 @@ def install(self, spec, prefix):
|
||||
except ProcessError:
|
||||
args.insert(-2, "--skip-installed")
|
||||
raco(*args)
|
||||
tty.warn(
|
||||
(
|
||||
"Racket package {0} was already installed, uninstalling via "
|
||||
"Spack may make someone unhappy!"
|
||||
).format(self.racket_name)
|
||||
msg = (
|
||||
"Racket package {0} was already installed, uninstalling via "
|
||||
"Spack may make someone unhappy!"
|
||||
)
|
||||
tty.warn(msg.format(self.pkg.racket_name))
|
||||
|
||||
@@ -2,35 +2,49 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import glob
|
||||
import inspect
|
||||
|
||||
from spack.directives import extends
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, extends
|
||||
|
||||
from ._checks import BaseBuilder
|
||||
|
||||
|
||||
class RubyPackage(PackageBase):
|
||||
"""Specialized class for building Ruby gems.
|
||||
|
||||
This class provides two phases that can be overridden if required:
|
||||
|
||||
#. :py:meth:`~.RubyPackage.build`
|
||||
#. :py:meth:`~.RubyPackage.install`
|
||||
"""
|
||||
class RubyPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for building Ruby gems."""
|
||||
|
||||
maintainers = ["Kerilk"]
|
||||
|
||||
#: Phases of a Ruby package
|
||||
phases = ["build", "install"]
|
||||
|
||||
#: This attribute is used in UI queries that need to know the build
|
||||
#: system base class
|
||||
build_system_class = "RubyPackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "ruby"
|
||||
|
||||
extends("ruby")
|
||||
build_system("ruby")
|
||||
|
||||
def build(self, spec, prefix):
|
||||
extends("ruby", when="build_system=ruby")
|
||||
|
||||
|
||||
@spack.builder.builder("ruby")
|
||||
class RubyBuilder(BaseBuilder):
|
||||
"""The Ruby builder provides two phases that can be overridden if required:
|
||||
|
||||
#. :py:meth:`~.RubyBuilder.build`
|
||||
#. :py:meth:`~.RubyBuilder.install`
|
||||
"""
|
||||
|
||||
phases = ("build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ()
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ()
|
||||
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Build a Ruby gem."""
|
||||
|
||||
# ruby-rake provides both rake.gemspec and Rakefile, but only
|
||||
@@ -38,15 +52,15 @@ def build(self, spec, prefix):
|
||||
gemspecs = glob.glob("*.gemspec")
|
||||
rakefiles = glob.glob("Rakefile")
|
||||
if gemspecs:
|
||||
inspect.getmodule(self).gem("build", "--norc", gemspecs[0])
|
||||
inspect.getmodule(self.pkg).gem("build", "--norc", gemspecs[0])
|
||||
elif rakefiles:
|
||||
jobs = inspect.getmodule(self).make_jobs
|
||||
inspect.getmodule(self).rake("package", "-j{0}".format(jobs))
|
||||
jobs = inspect.getmodule(self.pkg).make_jobs
|
||||
inspect.getmodule(self.pkg).rake("package", "-j{0}".format(jobs))
|
||||
else:
|
||||
# Some Ruby packages only ship `*.gem` files, so nothing to build
|
||||
pass
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install a Ruby gem.
|
||||
|
||||
The ruby package sets ``GEM_HOME`` to tell gem where to install to."""
|
||||
@@ -56,9 +70,6 @@ def install(self, spec, prefix):
|
||||
# if --install-dir is not used, GEM_PATH is deleted from the
|
||||
# environement, and Gems required to build native extensions will
|
||||
# not be found. Those extensions are built during `gem install`.
|
||||
inspect.getmodule(self).gem(
|
||||
inspect.getmodule(self.pkg).gem(
|
||||
"install", "--norc", "--ignore-dependencies", "--install-dir", prefix, gems[0]
|
||||
)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
@@ -2,63 +2,75 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
|
||||
from spack.directives import depends_on
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests
|
||||
|
||||
|
||||
class SConsPackage(PackageBase):
|
||||
class SConsPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages built using SCons.
|
||||
|
||||
See http://scons.org/documentation.html for more information.
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.SConsPackage.build`
|
||||
2. :py:meth:`~.SConsPackage.install`
|
||||
|
||||
Packages that use SCons as a build system are less uniform than packages
|
||||
that use other build systems. Developers can add custom subcommands or
|
||||
variables that control the build. You will likely need to override
|
||||
:py:meth:`~.SConsPackage.build_args` to pass the appropriate variables.
|
||||
"""
|
||||
|
||||
#: Phases of a SCons package
|
||||
phases = ["build", "install"]
|
||||
|
||||
#: To be used in UI queries that require to know which
|
||||
#: build-system class we are using
|
||||
build_system_class = "SConsPackage"
|
||||
|
||||
#: Callback names for build-time test
|
||||
build_time_test_callbacks = ["build_test"]
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "scons"
|
||||
|
||||
depends_on("scons", type="build")
|
||||
build_system("scons")
|
||||
|
||||
def build_args(self, spec, prefix):
|
||||
depends_on("scons", type="build", when="build_system=scons")
|
||||
|
||||
|
||||
@spack.builder.builder("scons")
|
||||
class SConsBuilder(BaseBuilder):
|
||||
"""The Scons builder provides the following phases that can be overridden:
|
||||
|
||||
1. :py:meth:`~.SConsBuilder.build`
|
||||
2. :py:meth:`~.SConsBuilder.install`
|
||||
|
||||
Packages that use SCons as a build system are less uniform than packages that use
|
||||
other build systems. Developers can add custom subcommands or variables that
|
||||
control the build. You will likely need to override
|
||||
:py:meth:`~.SConsBuilder.build_args` to pass the appropriate variables.
|
||||
"""
|
||||
|
||||
#: Phases of a SCons package
|
||||
phases = ("build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("build_args", "install_args", "build_test")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = ()
|
||||
|
||||
def build_args(self):
|
||||
"""Arguments to pass to build."""
|
||||
return []
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Build the package."""
|
||||
args = self.build_args(spec, prefix)
|
||||
args = self.build_args()
|
||||
inspect.getmodule(self.pkg).scons(*args)
|
||||
|
||||
inspect.getmodule(self).scons(*args)
|
||||
|
||||
def install_args(self, spec, prefix):
|
||||
def install_args(self):
|
||||
"""Arguments to pass to install."""
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install the package."""
|
||||
args = self.install_args(spec, prefix)
|
||||
args = self.install_args()
|
||||
|
||||
inspect.getmodule(self).scons("install", *args)
|
||||
|
||||
# Testing
|
||||
inspect.getmodule(self.pkg).scons("install", *args)
|
||||
|
||||
def build_test(self):
|
||||
"""Run unit tests after build.
|
||||
@@ -68,7 +80,4 @@ def build_test(self):
|
||||
"""
|
||||
pass
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
@@ -10,28 +9,20 @@
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.filesystem import find, join_path, working_dir
|
||||
|
||||
from spack.directives import depends_on, extends
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on, extends
|
||||
from spack.multimethod import when
|
||||
|
||||
from ._checks import BaseBuilder, execute_install_time_tests
|
||||
|
||||
|
||||
class SIPPackage(PackageBase):
|
||||
class SIPPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages that are built using the
|
||||
SIP build system. See https://www.riverbankcomputing.com/software/sip/intro
|
||||
for more information.
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
|
||||
* configure
|
||||
* build
|
||||
* install
|
||||
|
||||
The configure phase already adds a set of default flags. To see more
|
||||
options, run ``python configure.py --help``.
|
||||
"""
|
||||
|
||||
# Default phases
|
||||
phases = ["configure", "build", "install"]
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "SIPPackage"
|
||||
@@ -41,11 +32,15 @@ class SIPPackage(PackageBase):
|
||||
|
||||
#: Callback names for install-time test
|
||||
install_time_test_callbacks = ["test"]
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "sip"
|
||||
|
||||
extends("python")
|
||||
build_system("sip")
|
||||
|
||||
depends_on("qt")
|
||||
depends_on("py-sip")
|
||||
with when("build_system=sip"):
|
||||
extends("python")
|
||||
depends_on("qt")
|
||||
depends_on("py-sip")
|
||||
|
||||
@property
|
||||
def import_modules(self):
|
||||
@@ -95,11 +90,51 @@ def python(self, *args, **kwargs):
|
||||
"""The python ``Executable``."""
|
||||
inspect.getmodule(self).python(*args, **kwargs)
|
||||
|
||||
def test(self):
|
||||
"""Attempts to import modules of the installed package."""
|
||||
|
||||
# Make sure we are importing the installed modules,
|
||||
# not the ones in the source directory
|
||||
for module in self.import_modules:
|
||||
self.run_test(
|
||||
inspect.getmodule(self).python.path,
|
||||
["-c", "import {0}".format(module)],
|
||||
purpose="checking import of {0}".format(module),
|
||||
work_dir="spack-test",
|
||||
)
|
||||
|
||||
|
||||
@spack.builder.builder("sip")
|
||||
class SIPBuilder(BaseBuilder):
|
||||
"""The SIP builder provides the following phases that can be overridden:
|
||||
|
||||
* configure
|
||||
* build
|
||||
* install
|
||||
|
||||
The configure phase already adds a set of default flags. To see more
|
||||
options, run ``python configure.py --help``.
|
||||
"""
|
||||
|
||||
phases = ("configure", "build", "install")
|
||||
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = ("configure_file", "configure_args", "build_args", "install_args")
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"build_targets",
|
||||
"install_targets",
|
||||
"build_time_test_callbacks",
|
||||
"install_time_test_callbacks",
|
||||
"build_directory",
|
||||
)
|
||||
|
||||
def configure_file(self):
|
||||
"""Returns the name of the configure file to use."""
|
||||
return "configure.py"
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
def configure(self, pkg, spec, prefix):
|
||||
"""Configure the package."""
|
||||
configure = self.configure_file()
|
||||
|
||||
@@ -118,7 +153,7 @@ def configure(self, spec, prefix):
|
||||
"--bindir",
|
||||
prefix.bin,
|
||||
"--destdir",
|
||||
inspect.getmodule(self).python_platlib,
|
||||
inspect.getmodule(self.pkg).python_platlib,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -128,53 +163,35 @@ def configure_args(self):
|
||||
"""Arguments to pass to configure."""
|
||||
return []
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Build the package."""
|
||||
args = self.build_args()
|
||||
|
||||
inspect.getmodule(self).make(*args)
|
||||
inspect.getmodule(self.pkg).make(*args)
|
||||
|
||||
def build_args(self):
|
||||
"""Arguments to pass to build."""
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Install the package."""
|
||||
args = self.install_args()
|
||||
|
||||
inspect.getmodule(self).make("install", parallel=False, *args)
|
||||
inspect.getmodule(self.pkg).make("install", parallel=False, *args)
|
||||
|
||||
def install_args(self):
|
||||
"""Arguments to pass to install."""
|
||||
return []
|
||||
|
||||
# Testing
|
||||
spack.builder.run_after("install")(execute_install_time_tests)
|
||||
|
||||
def test(self):
|
||||
"""Attempts to import modules of the installed package."""
|
||||
|
||||
# Make sure we are importing the installed modules,
|
||||
# not the ones in the source directory
|
||||
for module in self.import_modules:
|
||||
self.run_test(
|
||||
inspect.getmodule(self).python.path,
|
||||
["-c", "import {0}".format(module)],
|
||||
purpose="checking import of {0}".format(module),
|
||||
work_dir="spack-test",
|
||||
)
|
||||
|
||||
run_after("install")(PackageBase._run_default_install_time_test_callbacks)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
|
||||
@run_after("install")
|
||||
@spack.builder.run_after("install")
|
||||
def extend_path_setup(self):
|
||||
# See github issue #14121 and PR #15297
|
||||
module = self.spec["py-sip"].variants["module"].value
|
||||
module = self.pkg.spec["py-sip"].variants["module"].value
|
||||
if module != "sip":
|
||||
module = module.split(".")[0]
|
||||
with working_dir(inspect.getmodule(self).python_platlib):
|
||||
with working_dir(inspect.getmodule(self.pkg).python_platlib):
|
||||
with open(os.path.join(module, "__init__.py"), "a") as f:
|
||||
f.write("from pkgutil import extend_path\n")
|
||||
f.write("__path__ = extend_path(__path__, __name__)\n")
|
||||
|
||||
@@ -2,21 +2,38 @@
|
||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||
#
|
||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||
|
||||
|
||||
import inspect
|
||||
|
||||
from llnl.util.filesystem import working_dir
|
||||
|
||||
from spack.directives import depends_on
|
||||
from spack.package_base import PackageBase, run_after
|
||||
import spack.builder
|
||||
import spack.package_base
|
||||
from spack.directives import build_system, depends_on
|
||||
|
||||
from ._checks import BaseBuilder, execute_build_time_tests, execute_install_time_tests
|
||||
|
||||
|
||||
class WafPackage(PackageBase):
|
||||
class WafPackage(spack.package_base.PackageBase):
|
||||
"""Specialized class for packages that are built using the
|
||||
Waf build system. See https://waf.io/book/ for more information.
|
||||
"""
|
||||
|
||||
This class provides the following phases that can be overridden:
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "WafPackage"
|
||||
#: Legacy buildsystem attribute used to deserialize and install old specs
|
||||
legacy_buildsystem = "waf"
|
||||
|
||||
build_system("waf")
|
||||
# Much like AutotoolsPackage does not require automake and autoconf
|
||||
# to build, WafPackage does not require waf to build. It only requires
|
||||
# python to run the waf build script.
|
||||
depends_on("python@2.5:", type="build", when="build_system=waf")
|
||||
|
||||
|
||||
@spack.builder.builder("waf")
|
||||
class WafBuilder(BaseBuilder):
|
||||
"""The WAF builder provides the following phases that can be overridden:
|
||||
|
||||
* configure
|
||||
* build
|
||||
@@ -40,12 +57,25 @@ class WafPackage(PackageBase):
|
||||
function, which passes ``--prefix=/path/to/installation/prefix``.
|
||||
"""
|
||||
|
||||
# Default phases
|
||||
phases = ["configure", "build", "install"]
|
||||
phases = ("configure", "build", "install")
|
||||
|
||||
# To be used in UI queries that require to know which
|
||||
# build-system class we are using
|
||||
build_system_class = "WafPackage"
|
||||
#: Names associated with package methods in the old build-system format
|
||||
legacy_methods = (
|
||||
"build_test",
|
||||
"install_test",
|
||||
"configure_args",
|
||||
"build_args",
|
||||
"install_args",
|
||||
"build_test",
|
||||
"install_test",
|
||||
)
|
||||
|
||||
#: Names associated with package attributes in the old build-system format
|
||||
legacy_attributes = (
|
||||
"build_time_test_callbacks",
|
||||
"build_time_test_callbacks",
|
||||
"build_directory",
|
||||
)
|
||||
|
||||
# Callback names for build-time test
|
||||
build_time_test_callbacks = ["build_test"]
|
||||
@@ -53,11 +83,6 @@ class WafPackage(PackageBase):
|
||||
# Callback names for install-time test
|
||||
install_time_test_callbacks = ["install_test"]
|
||||
|
||||
# Much like AutotoolsPackage does not require automake and autoconf
|
||||
# to build, WafPackage does not require waf to build. It only requires
|
||||
# python to run the waf build script.
|
||||
depends_on("python@2.5:", type="build")
|
||||
|
||||
@property
|
||||
def build_directory(self):
|
||||
"""The directory containing the ``waf`` file."""
|
||||
@@ -65,18 +90,18 @@ def build_directory(self):
|
||||
|
||||
def python(self, *args, **kwargs):
|
||||
"""The python ``Executable``."""
|
||||
inspect.getmodule(self).python(*args, **kwargs)
|
||||
inspect.getmodule(self.pkg).python(*args, **kwargs)
|
||||
|
||||
def waf(self, *args, **kwargs):
|
||||
"""Runs the waf ``Executable``."""
|
||||
jobs = inspect.getmodule(self).make_jobs
|
||||
jobs = inspect.getmodule(self.pkg).make_jobs
|
||||
|
||||
with working_dir(self.build_directory):
|
||||
self.python("waf", "-j{0}".format(jobs), *args, **kwargs)
|
||||
|
||||
def configure(self, spec, prefix):
|
||||
def configure(self, pkg, spec, prefix):
|
||||
"""Configures the project."""
|
||||
args = ["--prefix={0}".format(self.prefix)]
|
||||
args = ["--prefix={0}".format(self.pkg.prefix)]
|
||||
args += self.configure_args()
|
||||
|
||||
self.waf("configure", *args)
|
||||
@@ -85,7 +110,7 @@ def configure_args(self):
|
||||
"""Arguments to pass to configure."""
|
||||
return []
|
||||
|
||||
def build(self, spec, prefix):
|
||||
def build(self, pkg, spec, prefix):
|
||||
"""Executes the build."""
|
||||
args = self.build_args()
|
||||
|
||||
@@ -95,7 +120,7 @@ def build_args(self):
|
||||
"""Arguments to pass to build."""
|
||||
return []
|
||||
|
||||
def install(self, spec, prefix):
|
||||
def install(self, pkg, spec, prefix):
|
||||
"""Installs the targets on the system."""
|
||||
args = self.install_args()
|
||||
|
||||
@@ -105,8 +130,6 @@ def install_args(self):
|
||||
"""Arguments to pass to install."""
|
||||
return []
|
||||
|
||||
# Testing
|
||||
|
||||
def build_test(self):
|
||||
"""Run unit tests after build.
|
||||
|
||||
@@ -115,7 +138,7 @@ def build_test(self):
|
||||
"""
|
||||
pass
|
||||
|
||||
run_after("build")(PackageBase._run_default_build_time_test_callbacks)
|
||||
spack.builder.run_after("build")(execute_build_time_tests)
|
||||
|
||||
def install_test(self):
|
||||
"""Run unit tests after install.
|
||||
@@ -125,7 +148,4 @@ def install_test(self):
|
||||
"""
|
||||
pass
|
||||
|
||||
run_after("install")(PackageBase._run_default_install_time_test_callbacks)
|
||||
|
||||
# Check that self.prefix is there after installation
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
spack.builder.run_after("install")(execute_install_time_tests)
|
||||
|
||||
574
lib/spack/spack/builder.py
Normal file
574
lib/spack/spack/builder.py
Normal file
@@ -0,0 +1,574 @@
|
||||
# Copyright 2013-2022 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 copy
|
||||
import functools
|
||||
import inspect
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import six
|
||||
|
||||
import llnl.util.compat
|
||||
|
||||
import spack.build_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=[])
|
||||
|
||||
#: Map id(pkg) to a builder, to avoid creating multiple
|
||||
#: builders for the same package object.
|
||||
_BUILDERS = {}
|
||||
|
||||
|
||||
def builder(build_system_name):
|
||||
"""Class decorator used to register the default builder
|
||||
for a given build-system.
|
||||
|
||||
Args:
|
||||
build_system_name (str): name of the build-system
|
||||
"""
|
||||
|
||||
def _decorator(cls):
|
||||
cls.build_system = build_system_name
|
||||
BUILDER_CLS[build_system_name] = cls
|
||||
return 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
|
||||
"""
|
||||
if id(pkg) not in _BUILDERS:
|
||||
_BUILDERS[id(pkg)] = _create(pkg)
|
||||
return _BUILDERS[id(pkg)]
|
||||
|
||||
|
||||
class _PhaseAdapter(object):
|
||||
def __init__(self, builder, phase_fn):
|
||||
self.builder = builder
|
||||
self.phase_fn = phase_fn
|
||||
|
||||
def __call__(self, spec, prefix):
|
||||
return self.phase_fn(self.builder.pkg, spec, prefix)
|
||||
|
||||
|
||||
def _create(pkg):
|
||||
"""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:
|
||||
|
||||
1. Return a custom builder, if any is defined in the same ``package.py`` file.
|
||||
2. Return a customization of more generic builders, if any is defined in the
|
||||
class hierarchy (look at AspellDictPackage for an example of that)
|
||||
3. Return a run-time generated adapter builder otherwise
|
||||
|
||||
The run-time generated adapter builder is capable of adapting an old-style package
|
||||
to the new architecture, where the installation procedure has been extracted from
|
||||
the ``*Package`` hierarchy into a ``*Builder`` hierarchy. This means that the
|
||||
adapter looks for attribute or method overrides preferably in the ``*Package``
|
||||
before using the default builder implementation.
|
||||
|
||||
Note that in case a builder is explicitly coded in ``package.py``, no attempt is made
|
||||
to look for build-related methods in the ``*Package``.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package object for which we need a builder
|
||||
"""
|
||||
package_module = inspect.getmodule(pkg)
|
||||
package_buildsystem = buildsystem_name(pkg)
|
||||
default_builder_cls = BUILDER_CLS[package_buildsystem]
|
||||
builder_cls_name = default_builder_cls.__name__
|
||||
builder_cls = getattr(package_module, builder_cls_name, None)
|
||||
if builder_cls:
|
||||
return builder_cls(pkg)
|
||||
|
||||
# Specialized version of a given buildsystem can subclass some
|
||||
# base classes and specialize certain phases or methods or attributes.
|
||||
# In that case they can store their builder class as a class level attribute.
|
||||
# See e.g. AspellDictPackage as an example.
|
||||
base_cls = getattr(pkg, builder_cls_name, default_builder_cls)
|
||||
|
||||
# From here on we define classes to construct a special builder that adapts to the
|
||||
# old, single class, package format. The adapter forwards any call or access to an
|
||||
# attribute related to the installation procedure to a package object wrapped in
|
||||
# a class that falls-back on calling the base builder if no override is found on the
|
||||
# package. The semantic should be the same as the method in the base builder were still
|
||||
# present in the base class of the package.
|
||||
|
||||
class _ForwardToBaseBuilder(object):
|
||||
def __init__(self, wrapped_pkg_object, root_builder):
|
||||
self.wrapped_package_object = wrapped_pkg_object
|
||||
self.root_builder = root_builder
|
||||
|
||||
package_cls = type(wrapped_pkg_object)
|
||||
wrapper_cls = type(self)
|
||||
bases = (package_cls, wrapper_cls)
|
||||
new_cls_name = package_cls.__name__ + "Wrapper"
|
||||
new_cls = type(new_cls_name, bases, {})
|
||||
new_cls.__module__ = package_cls.__module__
|
||||
self.__class__ = new_cls
|
||||
self.__dict__.update(wrapped_pkg_object.__dict__)
|
||||
|
||||
def __getattr__(self, item):
|
||||
result = getattr(super(type(self.root_builder), self.root_builder), item)
|
||||
if item in super(type(self.root_builder), self.root_builder).phases:
|
||||
result = _PhaseAdapter(self.root_builder, result)
|
||||
return result
|
||||
|
||||
def forward_method_to_getattr(fn_name):
|
||||
def __forward(self, *args, **kwargs):
|
||||
return self.__getattr__(fn_name)(*args, **kwargs)
|
||||
|
||||
return __forward
|
||||
|
||||
# Add fallback methods for the Package object to refer to the builder. If a method
|
||||
# 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
|
||||
+ getattr(base_cls, "legacy_long_methods", tuple())
|
||||
+ ("setup_build_environment", "setup_dependent_build_environment")
|
||||
):
|
||||
setattr(_ForwardToBaseBuilder, method_name, forward_method_to_getattr(method_name))
|
||||
|
||||
def forward_property_to_getattr(property_name):
|
||||
def __forward(self):
|
||||
return self.__getattr__(property_name)
|
||||
|
||||
return __forward
|
||||
|
||||
for attribute_name in base_cls.legacy_attributes:
|
||||
setattr(
|
||||
_ForwardToBaseBuilder,
|
||||
attribute_name,
|
||||
property(forward_property_to_getattr(attribute_name)),
|
||||
)
|
||||
|
||||
class Adapter(six.with_metaclass(_PackageAdapterMeta, base_cls)):
|
||||
def __init__(self, pkg):
|
||||
# Deal with custom phases in packages here
|
||||
if hasattr(pkg, "phases"):
|
||||
self.phases = pkg.phases
|
||||
for phase in self.phases:
|
||||
setattr(Adapter, phase, _PackageAdapterMeta.phase_method_adapter(phase))
|
||||
|
||||
# Attribute containing the package wrapped in dispatcher with a `__getattr__`
|
||||
# method that will forward certain calls to the default builder.
|
||||
self.pkg_with_dispatcher = _ForwardToBaseBuilder(pkg, root_builder=self)
|
||||
super(Adapter, self).__init__(pkg)
|
||||
|
||||
# These two methods don't follow the (self, spec, prefix) signature of phases nor
|
||||
# the (self) signature of methods, so they are added explicitly to avoid using a
|
||||
# catch-all (*args, **kwargs)
|
||||
def setup_build_environment(self, env):
|
||||
return self.pkg_with_dispatcher.setup_build_environment(env)
|
||||
|
||||
def setup_dependent_build_environment(self, env, dependent_spec):
|
||||
return self.pkg_with_dispatcher.setup_dependent_build_environment(env, dependent_spec)
|
||||
|
||||
return Adapter(pkg)
|
||||
|
||||
|
||||
def buildsystem_name(pkg):
|
||||
"""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
|
||||
"""
|
||||
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
|
||||
|
||||
# We don't have callbacks in this class, move on
|
||||
if not staged_callbacks:
|
||||
continue
|
||||
|
||||
# If we are here we have callbacks. To get a complete list, get first what
|
||||
# was attached to parent classes, 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
|
||||
for base in bases:
|
||||
callbacks_from_base = getattr(base, temporary_stage.attribute_name, None)
|
||||
if callbacks_from_base:
|
||||
break
|
||||
callbacks_from_base = callbacks_from_base or []
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
class BuilderMeta(PhaseCallbacksMeta, type(llnl.util.compat.Sequence)): # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class _PackageAdapterMeta(BuilderMeta):
|
||||
"""Metaclass to adapt old-style packages to the new architecture based on builders
|
||||
for the installation phase.
|
||||
|
||||
This class does the necessary mangling to function argument so that a call to a
|
||||
builder object can delegate to a package object.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def phase_method_adapter(phase_name):
|
||||
def _adapter(self, pkg, spec, prefix):
|
||||
phase_fn = getattr(self.pkg_with_dispatcher, phase_name)
|
||||
return phase_fn(spec, prefix)
|
||||
|
||||
return _adapter
|
||||
|
||||
@staticmethod
|
||||
def legacy_long_method_adapter(method_name):
|
||||
def _adapter(self, spec, prefix):
|
||||
bind_method = getattr(self.pkg_with_dispatcher, method_name)
|
||||
return bind_method(spec, prefix)
|
||||
|
||||
return _adapter
|
||||
|
||||
@staticmethod
|
||||
def legacy_method_adapter(method_name):
|
||||
def _adapter(self):
|
||||
bind_method = getattr(self.pkg_with_dispatcher, method_name)
|
||||
return bind_method()
|
||||
|
||||
return _adapter
|
||||
|
||||
@staticmethod
|
||||
def legacy_attribute_adapter(attribute_name):
|
||||
def _adapter(self):
|
||||
return getattr(self.pkg_with_dispatcher, attribute_name)
|
||||
|
||||
return property(_adapter)
|
||||
|
||||
@staticmethod
|
||||
def combine_callbacks(pipeline_attribute_name):
|
||||
"""This function combines callbacks from old-style packages with callbacks that might
|
||||
be registered for the default builder.
|
||||
|
||||
It works by:
|
||||
1. Extracting the callbacks from the old-style package
|
||||
2. Transforming those callbacks by adding an adapter that receives a builder as argument
|
||||
and calls the wrapped function with ``builder.pkg``
|
||||
3. Combining the list of transformed callbacks with those that might be present in the
|
||||
default builder
|
||||
"""
|
||||
|
||||
def _adapter(self):
|
||||
def unwrap_pkg(fn):
|
||||
@functools.wraps(fn)
|
||||
def _wrapped(builder):
|
||||
return fn(builder.pkg_with_dispatcher)
|
||||
|
||||
return _wrapped
|
||||
|
||||
# Concatenate the current list with the one from package
|
||||
callbacks_from_package = getattr(self.pkg, pipeline_attribute_name, [])
|
||||
callbacks_from_package = [(key, unwrap_pkg(x)) for key, x in callbacks_from_package]
|
||||
callbacks_from_builder = getattr(super(type(self), self), pipeline_attribute_name, [])
|
||||
return callbacks_from_package + callbacks_from_builder
|
||||
|
||||
return property(_adapter)
|
||||
|
||||
def __new__(mcs, name, bases, attr_dict):
|
||||
# Add ways to intercept methods and attribute calls and dispatch
|
||||
# them first to a package object
|
||||
default_builder_cls = bases[0]
|
||||
for phase_name in default_builder_cls.phases:
|
||||
attr_dict[phase_name] = _PackageAdapterMeta.phase_method_adapter(phase_name)
|
||||
|
||||
for method_name in default_builder_cls.legacy_methods:
|
||||
attr_dict[method_name] = _PackageAdapterMeta.legacy_method_adapter(method_name)
|
||||
|
||||
# These exist e.g. for Python, see discussion in https://github.com/spack/spack/pull/32068
|
||||
for method_name in getattr(default_builder_cls, "legacy_long_methods", []):
|
||||
attr_dict[method_name] = _PackageAdapterMeta.legacy_long_method_adapter(method_name)
|
||||
|
||||
for attribute_name in default_builder_cls.legacy_attributes:
|
||||
attr_dict[attribute_name] = _PackageAdapterMeta.legacy_attribute_adapter(
|
||||
attribute_name
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
return super(_PackageAdapterMeta, mcs).__new__(mcs, name, bases, attr_dict)
|
||||
|
||||
|
||||
class InstallationPhase(object):
|
||||
"""Manages a single phase of the installation.
|
||||
|
||||
This descriptor stores at creation time the name of the method it should
|
||||
search for execution. The method is retrieved at __get__ time, so that
|
||||
it can be overridden by subclasses of whatever class declared the phases.
|
||||
|
||||
It also provides hooks to execute arbitrary callbacks before and after
|
||||
the phase.
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
def _make_callbacks(self, callbacks_attribute):
|
||||
result = []
|
||||
callbacks = getattr(self.builder, callbacks_attribute, [])
|
||||
for (phase, condition), fn in callbacks:
|
||||
# Same if it is for another phase
|
||||
if phase != self.name:
|
||||
continue
|
||||
|
||||
# If we have no condition or the callback satisfies a condition, register it
|
||||
if condition is None or self.builder.pkg.spec.satisfies(condition):
|
||||
result.append(fn)
|
||||
return result
|
||||
|
||||
def __str__(self):
|
||||
msg = '{0}: executing "{1}" phase'
|
||||
return msg.format(self.builder, self.name)
|
||||
|
||||
def execute(self):
|
||||
pkg = self.builder.pkg
|
||||
self._on_phase_start(pkg)
|
||||
|
||||
for callback in self.run_before:
|
||||
callback(self.builder)
|
||||
|
||||
self.phase_fn(pkg, pkg.spec, pkg.prefix)
|
||||
|
||||
for callback in self.run_after:
|
||||
callback(self.builder)
|
||||
|
||||
self._on_phase_exit(pkg)
|
||||
|
||||
def _select_phase_fn(self):
|
||||
phase_fn = getattr(self.builder, self.name, None)
|
||||
|
||||
if not phase_fn:
|
||||
msg = (
|
||||
'unexpected error: package "{0.fullname}" must implement an '
|
||||
'"{1}" phase for the "{2}" build system'
|
||||
)
|
||||
raise RuntimeError(msg.format(self.builder.pkg, self.name, self.builder.build_system))
|
||||
|
||||
return phase_fn
|
||||
|
||||
def _on_phase_start(self, instance):
|
||||
# If a phase has a matching stop_before_phase attribute,
|
||||
# stop the installation process raising a StopPhase
|
||||
if getattr(instance, "stop_before_phase", None) == self.name:
|
||||
raise spack.build_environment.StopPhase(
|
||||
"Stopping before '{0}' phase".format(self.name)
|
||||
)
|
||||
|
||||
def _on_phase_exit(self, instance):
|
||||
# If a phase has a matching last_phase attribute,
|
||||
# stop the installation process raising a StopPhase
|
||||
if getattr(instance, "last_phase", None) == self.name:
|
||||
raise spack.build_environment.StopPhase("Stopping at '{0}' phase".format(self.name))
|
||||
|
||||
def copy(self):
|
||||
return copy.deepcopy(self)
|
||||
|
||||
|
||||
class Builder(six.with_metaclass(BuilderMeta, llnl.util.compat.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.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.PackageBase): package object to be built
|
||||
"""
|
||||
|
||||
#: Sequence of phases. Must be defined in derived classes
|
||||
phases = None # type: Optional[Tuple[str, ...]]
|
||||
#: Build system name. Must also be defined in derived classes.
|
||||
build_system = None # type: Optional[str]
|
||||
|
||||
legacy_methods = () # type: Tuple[str, ...]
|
||||
legacy_attributes = () # type: Tuple[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 = [] # type: List[str]
|
||||
|
||||
def __init__(self, pkg):
|
||||
self.pkg = 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 test(self):
|
||||
# Defer tests to virtual and concrete packages
|
||||
pass
|
||||
|
||||
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(Builder, self), "setup_build_environment"):
|
||||
return
|
||||
super(Builder, self).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(Builder, self), "setup_dependent_build_environment"):
|
||||
return
|
||||
super(Builder, self).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
|
||||
@@ -210,11 +210,11 @@ def print_maintainers(pkg):
|
||||
def print_phases(pkg):
|
||||
"""output installation phases"""
|
||||
|
||||
if hasattr(pkg, "phases") and pkg.phases:
|
||||
if hasattr(pkg.builder, "phases") and pkg.builder.phases:
|
||||
color.cprint("")
|
||||
color.cprint(section_title("Installation Phases:"))
|
||||
phase_str = ""
|
||||
for phase in pkg.phases:
|
||||
for phase in pkg.builder.phases:
|
||||
phase_str += " {0}".format(phase)
|
||||
color.cprint(phase_str)
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ def do_uninstall(env, specs, force):
|
||||
except spack.repo.UnknownEntityError:
|
||||
# The package.py file has gone away -- but still
|
||||
# want to uninstall.
|
||||
spack.package_base.Package.uninstall_by_spec(item, force=True)
|
||||
spack.package_base.PackageBase.uninstall_by_spec(item, force=True)
|
||||
|
||||
# A package is ready to be uninstalled when nothing else references it,
|
||||
# unless we are requested to force uninstall it.
|
||||
|
||||
@@ -228,7 +228,7 @@ def compute_windows_program_path_for_package(pkg):
|
||||
program files location, return list of best guesses
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.Package): package for which
|
||||
pkg (spack.package_base.PackageBase): package for which
|
||||
Program Files location is to be computed
|
||||
"""
|
||||
if not is_windows:
|
||||
|
||||
@@ -17,6 +17,7 @@ class OpenMpi(Package):
|
||||
|
||||
The available directives are:
|
||||
|
||||
* ``build_system``
|
||||
* ``conflicts``
|
||||
* ``depends_on``
|
||||
* ``extends``
|
||||
@@ -59,13 +60,15 @@ class OpenMpi(Package):
|
||||
"patch",
|
||||
"variant",
|
||||
"resource",
|
||||
"build_system",
|
||||
]
|
||||
|
||||
#: These are variant names used by Spack internally; packages can't use them
|
||||
reserved_names = ["patches", "dev_path"]
|
||||
|
||||
#: Names of possible directives. This list is populated elsewhere in the file.
|
||||
directive_names = []
|
||||
#: Names of possible directives. This list is mostly populated using the @directive decorator.
|
||||
#: Some directives leverage others and in that case are not automatically added.
|
||||
directive_names = ["build_system"]
|
||||
|
||||
_patch_order_index = 0
|
||||
|
||||
@@ -758,6 +761,17 @@ def _execute_resource(pkg):
|
||||
return _execute_resource
|
||||
|
||||
|
||||
def build_system(*values, **kwargs):
|
||||
default = kwargs.get("default", None) or values[0]
|
||||
return variant(
|
||||
"build_system",
|
||||
values=tuple(values),
|
||||
description="Build systems supported by the package",
|
||||
default=default,
|
||||
multi=False,
|
||||
)
|
||||
|
||||
|
||||
class DirectiveError(spack.error.SpackError):
|
||||
"""This is raised when something is wrong with a package directive."""
|
||||
|
||||
|
||||
@@ -112,14 +112,15 @@ def _check_last_phase(pkg):
|
||||
Raises:
|
||||
``BadInstallPhase`` if stop_before or last phase is invalid
|
||||
"""
|
||||
if pkg.stop_before_phase and pkg.stop_before_phase not in pkg.phases:
|
||||
phases = pkg.builder.phases
|
||||
if pkg.stop_before_phase and pkg.stop_before_phase not in phases:
|
||||
raise BadInstallPhase(pkg.name, pkg.stop_before_phase)
|
||||
|
||||
if pkg.last_phase and pkg.last_phase not in pkg.phases:
|
||||
if pkg.last_phase and pkg.last_phase not in phases:
|
||||
raise BadInstallPhase(pkg.name, pkg.last_phase)
|
||||
|
||||
# If we got a last_phase, make sure it's not already last
|
||||
if pkg.last_phase and pkg.last_phase == pkg.phases[-1]:
|
||||
if pkg.last_phase and pkg.last_phase == phases[-1]:
|
||||
pkg.last_phase = None
|
||||
|
||||
|
||||
@@ -129,7 +130,7 @@ def _handle_external_and_upstream(pkg, explicit):
|
||||
database if it is external package.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.Package): the package whose installation is under
|
||||
pkg (spack.package_base.PackageBase): the package whose installation is under
|
||||
consideration
|
||||
explicit (bool): the package was explicitly requested by the user
|
||||
Return:
|
||||
@@ -559,7 +560,7 @@ def log(pkg):
|
||||
Copy provenance into the install directory on success
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.Package): the package that was built and installed
|
||||
pkg (spack.package_base.PackageBase): the package that was built and installed
|
||||
"""
|
||||
packages_dir = spack.store.layout.build_packages_path(pkg.spec)
|
||||
|
||||
@@ -596,7 +597,7 @@ def log(pkg):
|
||||
errors = six.StringIO()
|
||||
target_dir = os.path.join(spack.store.layout.metadata_path(pkg.spec), "archived-files")
|
||||
|
||||
for glob_expr in pkg.archive_files:
|
||||
for glob_expr in pkg.builder.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)
|
||||
@@ -810,7 +811,7 @@ def _add_init_task(self, pkg, request, is_compiler, all_deps):
|
||||
Creates and queus the initial build task for the package.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.Package): the package to be built and installed
|
||||
pkg (spack.package_base.PackageBase): the package to be built and installed
|
||||
request (BuildRequest or None): the associated install request
|
||||
where ``None`` can be used to indicate the package was
|
||||
explicitly requested by the user
|
||||
@@ -1404,7 +1405,7 @@ def _setup_install_dir(self, pkg):
|
||||
Write a small metadata file with the current spack environment.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.Package): the package to be built and installed
|
||||
pkg (spack.package_base.PackageBase): the package to be built and installed
|
||||
"""
|
||||
if not os.path.exists(pkg.spec.prefix):
|
||||
path = spack.util.path.debug_padded_filter(pkg.spec.prefix)
|
||||
@@ -1477,8 +1478,8 @@ def _flag_installed(self, pkg, dependent_ids=None):
|
||||
known dependents.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.Package): Package that has been installed locally,
|
||||
externally or upstream
|
||||
pkg (spack.package_base.PackageBase): Package that has been installed
|
||||
locally, externally or upstream
|
||||
dependent_ids (list or None): list of the package's
|
||||
dependent ids, or None if the dependent ids are limited to
|
||||
those maintained in the package (dependency DAG)
|
||||
@@ -1562,11 +1563,7 @@ def _install_action(self, task):
|
||||
return InstallAction.OVERWRITE
|
||||
|
||||
def install(self):
|
||||
"""
|
||||
Install the requested package(s) and or associated dependencies.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.Package): the package to be built and installed"""
|
||||
"""Install the requested package(s) and or associated dependencies."""
|
||||
|
||||
self._init_queue()
|
||||
fail_fast_err = "Terminating after first install failure"
|
||||
@@ -1951,6 +1948,8 @@ def _install_source(self):
|
||||
fs.install_tree(pkg.stage.source_path, src_target)
|
||||
|
||||
def _real_install(self):
|
||||
import spack.builder
|
||||
|
||||
pkg = self.pkg
|
||||
|
||||
# Do the real install in the source directory.
|
||||
@@ -1981,13 +1980,11 @@ def _real_install(self):
|
||||
|
||||
# Spawn a daemon that reads from a pipe and redirects
|
||||
# everything to log_path, and provide the phase for logging
|
||||
for i, (phase_name, phase_attr) in enumerate(
|
||||
zip(pkg.phases, pkg._InstallPhase_phases)
|
||||
):
|
||||
|
||||
builder = spack.builder.create(pkg)
|
||||
for i, phase_fn in enumerate(builder):
|
||||
# Keep a log file for each phase
|
||||
log_dir = os.path.dirname(pkg.log_path)
|
||||
log_file = "spack-build-%02d-%s-out.txt" % (i + 1, phase_name.lower())
|
||||
log_file = "spack-build-%02d-%s-out.txt" % (i + 1, phase_fn.name.lower())
|
||||
log_file = os.path.join(log_dir, log_file)
|
||||
|
||||
try:
|
||||
@@ -2005,20 +2002,20 @@ def _real_install(self):
|
||||
with logger.force_echo():
|
||||
inner_debug_level = tty.debug_level()
|
||||
tty.set_debug(debug_level)
|
||||
tty.msg("{0} Executing phase: '{1}'".format(self.pre, phase_name))
|
||||
msg = "{0} Executing phase: '{1}'"
|
||||
tty.msg(msg.format(self.pre, phase_fn.name))
|
||||
tty.set_debug(inner_debug_level)
|
||||
|
||||
# Redirect stdout and stderr to daemon pipe
|
||||
phase = getattr(pkg, phase_attr)
|
||||
self.timer.phase(phase_name)
|
||||
self.timer.phase(phase_fn.name)
|
||||
|
||||
# Catch any errors to report to logging
|
||||
phase(pkg.spec, pkg.prefix)
|
||||
spack.hooks.on_phase_success(pkg, phase_name, log_file)
|
||||
phase_fn.execute()
|
||||
spack.hooks.on_phase_success(pkg, phase_fn.name, log_file)
|
||||
|
||||
except BaseException:
|
||||
combine_phase_logs(pkg.phase_log_files, pkg.log_path)
|
||||
spack.hooks.on_phase_error(pkg, phase_name, log_file)
|
||||
spack.hooks.on_phase_error(pkg, phase_fn.name, log_file)
|
||||
|
||||
# phase error indicates install error
|
||||
spack.hooks.on_install_failure(pkg.spec)
|
||||
@@ -2094,7 +2091,7 @@ def __init__(self, pkg, request, compiler, start, attempts, status, installed):
|
||||
Instantiate a build task for a package.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.Package): the package to be built and installed
|
||||
pkg (spack.package_base.PackageBase): the package to be built and installed
|
||||
request (BuildRequest or None): the associated install request
|
||||
where ``None`` can be used to indicate the package was
|
||||
explicitly requested by the user
|
||||
@@ -2310,7 +2307,7 @@ def __init__(self, pkg, install_args):
|
||||
Instantiate a build request for a package.
|
||||
|
||||
Args:
|
||||
pkg (spack.package_base.Package): the package to be built and installed
|
||||
pkg (spack.package_base.PackageBase): the package to be built and installed
|
||||
install_args (dict): the install arguments associated with ``pkg``
|
||||
"""
|
||||
# Ensure dealing with a package that has a concrete spec
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
"""This module contains additional behavior that can be attached to any given
|
||||
package.
|
||||
"""
|
||||
import collections
|
||||
import os
|
||||
import sys
|
||||
from typing import Callable, DefaultDict, Dict, List # novm
|
||||
from typing import Callable, DefaultDict, List # novm
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
CallbackDict = DefaultDict[str, List[Callable]]
|
||||
@@ -18,105 +17,7 @@
|
||||
|
||||
import llnl.util.filesystem
|
||||
|
||||
__all__ = [
|
||||
"filter_compiler_wrappers",
|
||||
"PackageMixinsMeta",
|
||||
]
|
||||
|
||||
|
||||
class PackageMixinsMeta(type):
|
||||
"""This metaclass serves the purpose of implementing a declarative syntax
|
||||
for package mixins.
|
||||
|
||||
Mixins are implemented below in the form of a function. Each one of them
|
||||
needs to register a callable that takes a single argument to be run
|
||||
before or after a certain phase. This callable is basically a method that
|
||||
gets implicitly attached to the package class by calling the mixin.
|
||||
"""
|
||||
|
||||
_methods_to_be_added = {} # type: Dict[str, Callable]
|
||||
_add_method_before = collections.defaultdict(list) # type: CallbackDict
|
||||
_add_method_after = collections.defaultdict(list) # type: CallbackDict
|
||||
|
||||
@staticmethod
|
||||
def register_method_before(fn, phase): # type: (Callable, str) -> None
|
||||
"""Registers a method to be run before a certain phase.
|
||||
|
||||
Args:
|
||||
fn: function taking a single argument (self)
|
||||
phase (str): phase before which fn must run
|
||||
"""
|
||||
PackageMixinsMeta._methods_to_be_added[fn.__name__] = fn
|
||||
PackageMixinsMeta._add_method_before[phase].append(fn)
|
||||
|
||||
@staticmethod
|
||||
def register_method_after(fn, phase): # type: (Callable, str) -> None
|
||||
"""Registers a method to be run after a certain phase.
|
||||
|
||||
Args:
|
||||
fn: function taking a single argument (self)
|
||||
phase (str): phase after which fn must run
|
||||
"""
|
||||
PackageMixinsMeta._methods_to_be_added[fn.__name__] = fn
|
||||
PackageMixinsMeta._add_method_after[phase].append(fn)
|
||||
|
||||
def __init__(cls, name, bases, attr_dict):
|
||||
|
||||
# Add the methods to the class being created
|
||||
if PackageMixinsMeta._methods_to_be_added:
|
||||
attr_dict.update(PackageMixinsMeta._methods_to_be_added)
|
||||
PackageMixinsMeta._methods_to_be_added.clear()
|
||||
|
||||
attr_fmt = "_InstallPhase_{0}"
|
||||
|
||||
# Copy the phases that needs it to the most derived classes
|
||||
# in order not to interfere with other packages in the hierarchy
|
||||
phases_to_be_copied = list(PackageMixinsMeta._add_method_before.keys())
|
||||
phases_to_be_copied += list(PackageMixinsMeta._add_method_after.keys())
|
||||
|
||||
for phase in phases_to_be_copied:
|
||||
|
||||
attr_name = attr_fmt.format(phase)
|
||||
|
||||
# Here we want to get the attribute directly from the class (not
|
||||
# from the instance), so that we can modify it and add the mixin
|
||||
# method to the pipeline.
|
||||
phase = getattr(cls, attr_name)
|
||||
|
||||
# Due to MRO, we may have taken a method from a parent class
|
||||
# and modifying it may influence other packages in unwanted
|
||||
# manners. Solve the problem by copying the phase into the most
|
||||
# derived class.
|
||||
setattr(cls, attr_name, phase.copy())
|
||||
|
||||
# Insert the methods in the appropriate position
|
||||
# in the installation pipeline.
|
||||
|
||||
for phase in PackageMixinsMeta._add_method_before:
|
||||
|
||||
attr_name = attr_fmt.format(phase)
|
||||
phase_obj = getattr(cls, attr_name)
|
||||
fn_list = PackageMixinsMeta._add_method_after[phase]
|
||||
|
||||
for f in fn_list:
|
||||
phase_obj.run_before.append(f)
|
||||
|
||||
# Flush the dictionary for the next class
|
||||
PackageMixinsMeta._add_method_before.clear()
|
||||
|
||||
for phase in PackageMixinsMeta._add_method_after:
|
||||
|
||||
attr_name = attr_fmt.format(phase)
|
||||
phase_obj = getattr(cls, attr_name)
|
||||
fn_list = PackageMixinsMeta._add_method_after[phase]
|
||||
|
||||
for f in fn_list:
|
||||
phase_obj.run_after.append(f)
|
||||
|
||||
# Flush the dictionary for the next class
|
||||
PackageMixinsMeta._add_method_after.clear()
|
||||
|
||||
super(PackageMixinsMeta, cls).__init__(name, bases, attr_dict)
|
||||
import spack.builder
|
||||
|
||||
|
||||
def filter_compiler_wrappers(*files, **kwargs):
|
||||
@@ -216,4 +117,4 @@ def _filter_compiler_wrappers_impl(self):
|
||||
if self.compiler.name == "nag":
|
||||
x.filter("-Wl,--enable-new-dtags", "", **filter_kwargs)
|
||||
|
||||
PackageMixinsMeta.register_method_after(_filter_compiler_wrappers_impl, after)
|
||||
spack.builder.run_after(after)(_filter_compiler_wrappers_impl)
|
||||
|
||||
@@ -120,32 +120,36 @@ def _get_method_by_spec(self, spec):
|
||||
return method
|
||||
return self.default or None
|
||||
|
||||
def __call__(self, package_self, *args, **kwargs):
|
||||
def __call__(self, package_or_builder_self, *args, **kwargs):
|
||||
"""Find the first method with a spec that matches the
|
||||
package's spec. If none is found, call the default
|
||||
or if there is none, then raise a NoSuchMethodError.
|
||||
"""
|
||||
spec_method = self._get_method_by_spec(package_self.spec)
|
||||
spec_method = self._get_method_by_spec(package_or_builder_self.spec)
|
||||
if spec_method:
|
||||
return spec_method(package_self, *args, **kwargs)
|
||||
return spec_method(package_or_builder_self, *args, **kwargs)
|
||||
# Unwrap the MRO of `package_self by hand. Note that we can't
|
||||
# use `super()` here, because using `super()` recursively
|
||||
# requires us to know the class of `package_self`, as well as
|
||||
# its superclasses for successive calls. We don't have that
|
||||
# information within `SpecMultiMethod`, because it is not
|
||||
# associated with the package class.
|
||||
for cls in inspect.getmro(package_self.__class__)[1:]:
|
||||
for cls in inspect.getmro(package_or_builder_self.__class__)[1:]:
|
||||
superself = cls.__dict__.get(self.__name__, None)
|
||||
|
||||
if isinstance(superself, SpecMultiMethod):
|
||||
# Check parent multimethod for method for spec.
|
||||
superself_method = superself._get_method_by_spec(package_self.spec)
|
||||
superself_method = superself._get_method_by_spec(package_or_builder_self.spec)
|
||||
if superself_method:
|
||||
return superself_method(package_self, *args, **kwargs)
|
||||
return superself_method(package_or_builder_self, *args, **kwargs)
|
||||
elif superself:
|
||||
return superself(package_self, *args, **kwargs)
|
||||
return superself(package_or_builder_self, *args, **kwargs)
|
||||
|
||||
raise NoSuchMethodError(
|
||||
type(package_self), self.__name__, package_self.spec, [m[0] for m in self.method_list]
|
||||
type(package_or_builder_self),
|
||||
self.__name__,
|
||||
package_or_builder_self.spec,
|
||||
[m[0] for m in self.method_list],
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import spack.util.executable
|
||||
from spack.build_systems.aspell_dict import AspellDictPackage
|
||||
from spack.build_systems.autotools import AutotoolsPackage
|
||||
from spack.build_systems.bundle import BundlePackage
|
||||
from spack.build_systems.cached_cmake import (
|
||||
CachedCMakePackage,
|
||||
cmake_cache_option,
|
||||
@@ -25,12 +26,14 @@
|
||||
)
|
||||
from spack.build_systems.cmake import CMakePackage
|
||||
from spack.build_systems.cuda import CudaPackage
|
||||
from spack.build_systems.generic import Package
|
||||
from spack.build_systems.gnu import GNUMirrorPackage
|
||||
from spack.build_systems.intel import IntelPackage
|
||||
from spack.build_systems.lua import LuaPackage
|
||||
from spack.build_systems.makefile import MakefilePackage
|
||||
from spack.build_systems.maven import MavenPackage
|
||||
from spack.build_systems.meson import MesonPackage
|
||||
from spack.build_systems.nmake import NMakePackage
|
||||
from spack.build_systems.octave import OctavePackage
|
||||
from spack.build_systems.oneapi import (
|
||||
IntelOneApiLibraryPackage,
|
||||
@@ -38,7 +41,7 @@
|
||||
IntelOneApiStaticLibraryList,
|
||||
)
|
||||
from spack.build_systems.perl import PerlPackage
|
||||
from spack.build_systems.python import PythonPackage
|
||||
from spack.build_systems.python import PythonExtension, PythonPackage
|
||||
from spack.build_systems.qmake import QMakePackage
|
||||
from spack.build_systems.r import RPackage
|
||||
from spack.build_systems.racket import RacketPackage
|
||||
@@ -50,6 +53,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.dependency import all_deptypes
|
||||
from spack.directives import *
|
||||
from spack.install_test import get_escaped_text_output
|
||||
@@ -62,17 +66,13 @@
|
||||
from spack.mixins import filter_compiler_wrappers
|
||||
from spack.multimethod import when
|
||||
from spack.package_base import (
|
||||
BundlePackage,
|
||||
DependencyConflictError,
|
||||
Package,
|
||||
build_system_flags,
|
||||
env_flags,
|
||||
flatten_dependencies,
|
||||
inject_flags,
|
||||
install_dependency_symlinks,
|
||||
on_package_attributes,
|
||||
run_after,
|
||||
run_before,
|
||||
)
|
||||
from spack.spec import InvalidSpecDetected, Spec
|
||||
from spack.util.executable import *
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
import llnl.util.filesystem as fsys
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.lang import classproperty, match_predicate, memoized, nullcontext
|
||||
from llnl.util.lang import classproperty, memoized, nullcontext
|
||||
from llnl.util.link_tree import LinkTree
|
||||
|
||||
import spack.compilers
|
||||
@@ -104,7 +104,7 @@ def deprecated_version(pkg, version):
|
||||
"""Return True if the version is deprecated, False otherwise.
|
||||
|
||||
Arguments:
|
||||
pkg (Package): The package whose version is to be checked.
|
||||
pkg (PackageBase): The package whose version is to be checked.
|
||||
version (str or spack.version.VersionBase): The version being checked
|
||||
"""
|
||||
if not isinstance(version, VersionBase):
|
||||
@@ -122,7 +122,7 @@ def preferred_version(pkg):
|
||||
Returns a sorted list of the preferred versions of the package.
|
||||
|
||||
Arguments:
|
||||
pkg (Package): The package whose versions are to be assessed.
|
||||
pkg (PackageBase): The package whose versions are to be assessed.
|
||||
"""
|
||||
# Here we sort first on the fact that a version is marked
|
||||
# as preferred in the package, then on the fact that the
|
||||
@@ -131,77 +131,6 @@ def preferred_version(pkg):
|
||||
return sorted(pkg.versions, key=key_fn).pop()
|
||||
|
||||
|
||||
class InstallPhase(object):
|
||||
"""Manages a single phase of the installation.
|
||||
|
||||
This descriptor stores at creation time the name of the method it should
|
||||
search for execution. The method is retrieved at __get__ time, so that
|
||||
it can be overridden by subclasses of whatever class declared the phases.
|
||||
|
||||
It also provides hooks to execute arbitrary callbacks before and after
|
||||
the phase.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.run_before = []
|
||||
self.run_after = []
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
# The caller is a class that is trying to customize
|
||||
# my behavior adding something
|
||||
if instance is None:
|
||||
return self
|
||||
# If instance is there the caller wants to execute the
|
||||
# install phase, thus return a properly set wrapper
|
||||
phase = getattr(instance, self.name)
|
||||
|
||||
@functools.wraps(phase)
|
||||
def phase_wrapper(spec, prefix):
|
||||
# Check instance attributes at the beginning of a phase
|
||||
self._on_phase_start(instance)
|
||||
# Execute phase pre-conditions,
|
||||
# and give them the chance to fail
|
||||
for callback in self.run_before:
|
||||
callback(instance)
|
||||
phase(spec, prefix)
|
||||
# Execute phase sanity_checks,
|
||||
# and give them the chance to fail
|
||||
for callback in self.run_after:
|
||||
callback(instance)
|
||||
# Check instance attributes at the end of a phase
|
||||
self._on_phase_exit(instance)
|
||||
|
||||
return phase_wrapper
|
||||
|
||||
def _on_phase_start(self, instance):
|
||||
# If a phase has a matching stop_before_phase attribute,
|
||||
# stop the installation process raising a StopPhase
|
||||
if getattr(instance, "stop_before_phase", None) == self.name:
|
||||
from spack.build_environment import StopPhase
|
||||
|
||||
raise StopPhase("Stopping before '{0}' phase".format(self.name))
|
||||
|
||||
def _on_phase_exit(self, instance):
|
||||
# If a phase has a matching last_phase attribute,
|
||||
# stop the installation process raising a StopPhase
|
||||
if getattr(instance, "last_phase", None) == self.name:
|
||||
from spack.build_environment import StopPhase
|
||||
|
||||
raise StopPhase("Stopping at '{0}' phase".format(self.name))
|
||||
|
||||
def copy(self):
|
||||
try:
|
||||
return copy.deepcopy(self)
|
||||
except TypeError:
|
||||
# This bug-fix was not back-ported in Python 2.6
|
||||
# http://bugs.python.org/issue1515
|
||||
other = InstallPhase(self.name)
|
||||
other.run_before.extend(self.run_before)
|
||||
other.run_after.extend(self.run_after)
|
||||
return other
|
||||
|
||||
|
||||
class WindowsRPathMeta(object):
|
||||
"""Collection of functionality surrounding Windows RPATH specific features
|
||||
|
||||
@@ -368,23 +297,18 @@ def determine_variants(cls, objs, version_str):
|
||||
|
||||
|
||||
class PackageMeta(
|
||||
spack.builder.PhaseCallbacksMeta,
|
||||
DetectablePackageMeta,
|
||||
spack.directives.DirectiveMeta,
|
||||
spack.mixins.PackageMixinsMeta,
|
||||
spack.multimethod.MultiMethodMeta,
|
||||
):
|
||||
"""
|
||||
Package metaclass for supporting directives (e.g., depends_on) and phases
|
||||
"""
|
||||
|
||||
phase_fmt = "_InstallPhase_{0}"
|
||||
|
||||
# These are accessed only through getattr, by name
|
||||
_InstallPhase_run_before = {} # type: Dict[str, List[Callable]]
|
||||
_InstallPhase_run_after = {} # type: Dict[str, List[Callable]]
|
||||
|
||||
def __new__(cls, name, bases, attr_dict):
|
||||
"""
|
||||
FIXME: REWRITE
|
||||
Instance creation is preceded by phase attribute transformations.
|
||||
|
||||
Conveniently transforms attributes to permit extensible phases by
|
||||
@@ -392,70 +316,10 @@ def __new__(cls, name, bases, attr_dict):
|
||||
InstallPhase attributes in the class that will be initialized in
|
||||
__init__.
|
||||
"""
|
||||
if "phases" in attr_dict:
|
||||
# Turn the strings in 'phases' into InstallPhase instances
|
||||
# and add them as private attributes
|
||||
_InstallPhase_phases = [PackageMeta.phase_fmt.format(x) for x in attr_dict["phases"]]
|
||||
for phase_name, callback_name in zip(_InstallPhase_phases, attr_dict["phases"]):
|
||||
attr_dict[phase_name] = InstallPhase(callback_name)
|
||||
attr_dict["_InstallPhase_phases"] = _InstallPhase_phases
|
||||
|
||||
def _flush_callbacks(check_name):
|
||||
# Name of the attribute I am going to check it exists
|
||||
check_attr = PackageMeta.phase_fmt.format(check_name)
|
||||
checks = getattr(cls, check_attr)
|
||||
if checks:
|
||||
for phase_name, funcs in checks.items():
|
||||
phase_attr = PackageMeta.phase_fmt.format(phase_name)
|
||||
try:
|
||||
# Search for the phase in the attribute dictionary
|
||||
phase = attr_dict[phase_attr]
|
||||
except KeyError:
|
||||
# If it is not there it's in the bases
|
||||
# and we added a check. We need to copy
|
||||
# and extend
|
||||
for base in bases:
|
||||
phase = getattr(base, phase_attr, None)
|
||||
if phase is not None:
|
||||
break
|
||||
|
||||
phase = attr_dict[phase_attr] = phase.copy()
|
||||
getattr(phase, check_name).extend(funcs)
|
||||
# Clear the attribute for the next class
|
||||
setattr(cls, check_attr, {})
|
||||
|
||||
_flush_callbacks("run_before")
|
||||
_flush_callbacks("run_after")
|
||||
|
||||
# Reset names for packages that inherit from another
|
||||
# package with a different name
|
||||
attr_dict["_name"] = None
|
||||
|
||||
return super(PackageMeta, cls).__new__(cls, name, bases, attr_dict)
|
||||
|
||||
@staticmethod
|
||||
def register_callback(check_type, *phases):
|
||||
def _decorator(func):
|
||||
attr_name = PackageMeta.phase_fmt.format(check_type)
|
||||
check_list = getattr(PackageMeta, attr_name)
|
||||
for item in phases:
|
||||
checks = check_list.setdefault(item, [])
|
||||
checks.append(func)
|
||||
setattr(PackageMeta, attr_name, check_list)
|
||||
return func
|
||||
|
||||
return _decorator
|
||||
|
||||
|
||||
def run_before(*phases):
|
||||
"""Registers a method of a package to be run before a given phase"""
|
||||
return PackageMeta.register_callback("run_before", *phases)
|
||||
|
||||
|
||||
def run_after(*phases):
|
||||
"""Registers a method of a package to be run after a given phase"""
|
||||
return PackageMeta.register_callback("run_after", *phases)
|
||||
|
||||
|
||||
def on_package_attributes(**attr_dict):
|
||||
"""Decorator: executes instance function only if object has attr valuses.
|
||||
@@ -475,7 +339,9 @@ def _wrapper(instance, *args, **kwargs):
|
||||
has_all_attributes = all([hasattr(instance, key) for key in attr_dict])
|
||||
if has_all_attributes:
|
||||
has_the_right_values = all(
|
||||
[getattr(instance, key) == value for key, value in attr_dict.items()]
|
||||
[
|
||||
getattr(instance, key) == value for key, value in attr_dict.items()
|
||||
] # NOQA: ignore=E501
|
||||
)
|
||||
if has_the_right_values:
|
||||
func(instance, *args, **kwargs)
|
||||
@@ -687,13 +553,6 @@ class PackageBase(six.with_metaclass(PackageMeta, WindowsRPathMeta, PackageViewM
|
||||
#: directories, sanity checks will fail.
|
||||
sanity_check_is_dir = [] # type: 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 = [] # type: List[str]
|
||||
|
||||
#: Boolean. Set to ``True`` for packages that require a manual download.
|
||||
#: This is currently used by package sanity tests and generation of a
|
||||
#: more meaningful fetch failure error.
|
||||
@@ -1038,7 +897,7 @@ def all_urls_for_version(self, version):
|
||||
version (spack.version.Version): the version for which a URL is sought
|
||||
"""
|
||||
uf = None
|
||||
if type(self).url_for_version != Package.url_for_version:
|
||||
if type(self).url_for_version != PackageBase.url_for_version:
|
||||
uf = self.url_for_version
|
||||
return self._implement_all_urls_for_version(version, uf)
|
||||
|
||||
@@ -1959,9 +1818,9 @@ def do_install(self, **kwargs):
|
||||
even with exceptions.
|
||||
restage (bool): Force spack to restage the package source.
|
||||
skip_patch (bool): Skip patch stage of build if True.
|
||||
stop_before (InstallPhase): stop execution before this
|
||||
stop_before (str): stop execution before this
|
||||
installation phase (or None)
|
||||
stop_at (InstallPhase): last installation phase to be executed
|
||||
stop_at (str): last installation phase to be executed
|
||||
(or None)
|
||||
tests (bool or list or set): False to run no tests, True to test
|
||||
all packages, or a list of package names to run tests for some
|
||||
@@ -2191,46 +2050,6 @@ def unit_test_check(self):
|
||||
"""
|
||||
return True
|
||||
|
||||
def sanity_check_prefix(self):
|
||||
"""This function checks whether install succeeded."""
|
||||
|
||||
def check_paths(path_list, filetype, predicate):
|
||||
if isinstance(path_list, six.string_types):
|
||||
path_list = [path_list]
|
||||
|
||||
for path in path_list:
|
||||
abs_path = os.path.join(self.prefix, path)
|
||||
if not predicate(abs_path):
|
||||
raise InstallError(
|
||||
"Install failed for %s. No such %s in prefix: %s"
|
||||
% (self.name, filetype, path)
|
||||
)
|
||||
|
||||
check_paths(self.sanity_check_is_file, "file", os.path.isfile)
|
||||
check_paths(self.sanity_check_is_dir, "directory", os.path.isdir)
|
||||
|
||||
ignore_file = match_predicate(spack.store.layout.hidden_file_regexes)
|
||||
if all(map(ignore_file, os.listdir(self.prefix))):
|
||||
raise InstallError("Install failed for %s. Nothing was installed!" % self.name)
|
||||
|
||||
def apply_macos_rpath_fixups(self):
|
||||
"""On Darwin, make installed libraries more easily relocatable.
|
||||
|
||||
Some build systems (handrolled, autotools, makefiles) can set their own
|
||||
rpaths that are duplicated by spack's compiler wrapper. This fixup
|
||||
interrogates, and postprocesses if necessary, all libraries installed
|
||||
by the code.
|
||||
|
||||
It should be added as a @run_after to packaging systems (or individual
|
||||
packages) that do not install relocatable libraries by default.
|
||||
"""
|
||||
if "platform=darwin" not in self.spec:
|
||||
return
|
||||
|
||||
from spack.relocate import fixup_macos_rpaths
|
||||
|
||||
fixup_macos_rpaths(self.spec)
|
||||
|
||||
@property
|
||||
def build_log_path(self):
|
||||
"""
|
||||
@@ -2268,19 +2087,6 @@ def build_system_flags(cls, name, flags):
|
||||
"""
|
||||
return None, None, flags
|
||||
|
||||
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.
|
||||
"""
|
||||
pass
|
||||
|
||||
def setup_run_environment(self, env):
|
||||
"""Sets up the run environment for a package.
|
||||
|
||||
@@ -2291,37 +2097,6 @@ def setup_run_environment(self, env):
|
||||
"""
|
||||
pass
|
||||
|
||||
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``
|
||||
"""
|
||||
pass
|
||||
|
||||
def setup_dependent_run_environment(self, env, dependent_spec):
|
||||
"""Sets up the run environment of packages that depend on this one.
|
||||
|
||||
@@ -2508,7 +2283,7 @@ def uninstall_by_spec(spec, force=False, deprecator=None):
|
||||
def do_uninstall(self, force=False):
|
||||
"""Uninstall this package by spec."""
|
||||
# delegate to instance-less method.
|
||||
Package.uninstall_by_spec(self.spec, force)
|
||||
PackageBase.uninstall_by_spec(self.spec, force)
|
||||
|
||||
def do_deprecate(self, deprecator, link_fn):
|
||||
"""Deprecate this package in favor of deprecator spec"""
|
||||
@@ -2560,7 +2335,7 @@ def do_deprecate(self, deprecator, link_fn):
|
||||
deprecated.package.do_deprecate(deprecator, link_fn)
|
||||
|
||||
# Now that we've handled metadata, uninstall and replace with link
|
||||
Package.uninstall_by_spec(spec, force=True, deprecator=deprecator)
|
||||
PackageBase.uninstall_by_spec(spec, force=True, deprecator=deprecator)
|
||||
link_fn(deprecator.prefix, spec.prefix)
|
||||
|
||||
def _check_extendable(self):
|
||||
@@ -2811,21 +2586,25 @@ def rpath_args(self):
|
||||
"""
|
||||
return " ".join("-Wl,-rpath,%s" % p for p in self.rpath)
|
||||
|
||||
def _run_test_callbacks(self, method_names, callback_type="install"):
|
||||
@property
|
||||
def builder(self):
|
||||
return spack.builder.create(self)
|
||||
|
||||
@staticmethod
|
||||
def run_test_callbacks(builder, method_names, callback_type="install"):
|
||||
"""Tries to call all of the listed methods, returning immediately
|
||||
if the list is None."""
|
||||
if method_names is None:
|
||||
if not builder.pkg.run_tests or method_names is None:
|
||||
return
|
||||
|
||||
fail_fast = spack.config.get("config:fail_fast", False)
|
||||
|
||||
with self._setup_test(verbose=False, externals=False) as logger:
|
||||
with builder.pkg._setup_test(verbose=False, externals=False) as logger:
|
||||
# Report running each of the methods in the build log
|
||||
print_test_message(logger, "Running {0}-time tests".format(callback_type), True)
|
||||
|
||||
for name in method_names:
|
||||
try:
|
||||
fn = getattr(self, name)
|
||||
fn = getattr(builder, name)
|
||||
|
||||
msg = ("RUN-TESTS: {0}-time tests [{1}]".format(callback_type, name),)
|
||||
print_test_message(logger, msg, True)
|
||||
@@ -2835,27 +2614,13 @@ def _run_test_callbacks(self, method_names, callback_type="install"):
|
||||
msg = ("RUN-TESTS: method not implemented [{0}]".format(name),)
|
||||
print_test_message(logger, msg, True)
|
||||
|
||||
self.test_failures.append((e, msg))
|
||||
builder.pkg.test_failures.append((e, msg))
|
||||
if fail_fast:
|
||||
break
|
||||
|
||||
# Raise any collected failures here
|
||||
if self.test_failures:
|
||||
raise TestFailure(self.test_failures)
|
||||
|
||||
@on_package_attributes(run_tests=True)
|
||||
def _run_default_build_time_test_callbacks(self):
|
||||
"""Tries to call all the methods that are listed in the attribute
|
||||
``build_time_test_callbacks`` if ``self.run_tests is True``.
|
||||
"""
|
||||
self._run_test_callbacks(self.build_time_test_callbacks, "build")
|
||||
|
||||
@on_package_attributes(run_tests=True)
|
||||
def _run_default_install_time_test_callbacks(self):
|
||||
"""Tries to call all the methods that are listed in the attribute
|
||||
``install_time_test_callbacks`` if ``self.run_tests is True``.
|
||||
"""
|
||||
self._run_test_callbacks(self.install_time_test_callbacks, "install")
|
||||
if builder.pkg.test_failures:
|
||||
raise TestFailure(builder.pkg.test_failures)
|
||||
|
||||
|
||||
def has_test_method(pkg):
|
||||
@@ -2979,37 +2744,6 @@ def test_process(pkg, kwargs):
|
||||
build_system_flags = PackageBase.build_system_flags
|
||||
|
||||
|
||||
class BundlePackage(PackageBase):
|
||||
"""General purpose bundle, or no-code, package class."""
|
||||
|
||||
#: There are no phases by default but the property is required to support
|
||||
#: post-install hooks (e.g., for module generation).
|
||||
phases = [] # type: List[str]
|
||||
#: This attribute is used in UI queries that require to know which
|
||||
#: build-system class we are using
|
||||
build_system_class = "BundlePackage"
|
||||
|
||||
#: Bundle packages do not have associated source or binary code.
|
||||
has_code = False
|
||||
|
||||
|
||||
class Package(PackageBase):
|
||||
"""General purpose class with a single ``install``
|
||||
phase that needs to be coded by packagers.
|
||||
"""
|
||||
|
||||
#: The one and only phase
|
||||
phases = ["install"]
|
||||
#: This attribute is used in UI queries that require to know which
|
||||
#: build-system class we are using
|
||||
build_system_class = "Package"
|
||||
# This will be used as a registration decorator in user
|
||||
# packages, if need be
|
||||
run_after("install")(PackageBase.sanity_check_prefix)
|
||||
# On macOS, force rpaths for shared library IDs and remove duplicate rpaths
|
||||
run_after("install")(PackageBase.apply_macos_rpath_fixups)
|
||||
|
||||
|
||||
def install_dependency_symlinks(pkg, spec, prefix):
|
||||
"""
|
||||
Execute a dummy install and flatten dependencies.
|
||||
|
||||
@@ -352,7 +352,7 @@ def patch_for_package(self, sha256, pkg):
|
||||
|
||||
Arguments:
|
||||
sha256 (str): sha256 hash to look up
|
||||
pkg (spack.package_base.Package): Package object to get patch for.
|
||||
pkg (spack.package_base.PackageBase): Package object to get patch for.
|
||||
|
||||
We build patch objects lazily because building them requires that
|
||||
we have information about the package's location in its repo.
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
# This package has a GitHub patch URL without full_index=1
|
||||
(["invalid-github-patch-url"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]),
|
||||
# This package has a stand-alone 'test' method in build-time callbacks
|
||||
(["test-build-callbacks"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]),
|
||||
(["fail-test-audit"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]),
|
||||
# This package has no issues
|
||||
(["mpileaks"], None),
|
||||
# This package has a conflict with a trigger which cannot constrain the constraint
|
||||
|
||||
@@ -13,10 +13,11 @@
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
import spack.build_systems.autotools
|
||||
import spack.build_systems.cmake
|
||||
import spack.environment
|
||||
import spack.platforms
|
||||
import spack.repo
|
||||
from spack.build_environment import ChildError, get_std_cmake_args, setup_package
|
||||
from spack.build_environment import ChildError, setup_package
|
||||
from spack.spec import Spec
|
||||
from spack.util.executable import which
|
||||
|
||||
@@ -144,7 +145,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.libtool_archive_file)
|
||||
assert not os.path.exists(s.package.builder.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
|
||||
@@ -155,11 +156,11 @@ 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(s.package, "install_libtool_archives", True)
|
||||
monkeypatch.setattr(type(s.package.builder), "install_libtool_archives", True)
|
||||
s.package.do_install(explicit=True)
|
||||
|
||||
# Assert libtool archives are installed
|
||||
assert os.path.exists(s.package.libtool_archive_file)
|
||||
assert os.path.exists(s.package.builder.libtool_archive_file)
|
||||
|
||||
def test_autotools_gnuconfig_replacement(self, mutable_database):
|
||||
"""
|
||||
@@ -253,22 +254,23 @@ class TestCMakePackage(object):
|
||||
def test_cmake_std_args(self):
|
||||
# Call the function on a CMakePackage instance
|
||||
s = Spec("cmake-client").concretized()
|
||||
assert s.package.std_cmake_args == get_std_cmake_args(s.package)
|
||||
expected = spack.build_systems.cmake.CMakeBuilder.std_args(s.package)
|
||||
assert s.package.builder.std_cmake_args == expected
|
||||
|
||||
# Call it on another kind of package
|
||||
s = Spec("mpich").concretized()
|
||||
assert get_std_cmake_args(s.package)
|
||||
assert spack.build_systems.cmake.CMakeBuilder.std_args(s.package)
|
||||
|
||||
def test_cmake_bad_generator(self):
|
||||
def test_cmake_bad_generator(self, monkeypatch):
|
||||
s = Spec("cmake-client").concretized()
|
||||
s.package.generator = "Yellow Sticky Notes"
|
||||
monkeypatch.setattr(type(s.package), "generator", "Yellow Sticky Notes", raising=False)
|
||||
with pytest.raises(spack.package_base.InstallError):
|
||||
get_std_cmake_args(s.package)
|
||||
s.package.builder.std_cmake_args
|
||||
|
||||
def test_cmake_secondary_generator(config, mock_packages):
|
||||
s = Spec("cmake-client").concretized()
|
||||
s.package.generator = "CodeBlocks - Unix Makefiles"
|
||||
assert get_std_cmake_args(s.package)
|
||||
assert s.package.builder.std_cmake_args
|
||||
|
||||
def test_define(self):
|
||||
s = Spec("cmake-client").concretized()
|
||||
@@ -361,7 +363,7 @@ def test_autotools_args_from_conditional_variant(config, mock_packages):
|
||||
is not met. When this is the case, the variant is not set in the spec."""
|
||||
s = Spec("autotools-conditional-variants-test").concretized()
|
||||
assert "example" not in s.variants
|
||||
assert len(s.package._activate_or_not("example", "enable", "disable")) == 0
|
||||
assert len(s.package.builder._activate_or_not("example", "enable", "disable")) == 0
|
||||
|
||||
|
||||
def test_autoreconf_search_path_args_multiple(config, mock_packages, tmpdir):
|
||||
|
||||
123
lib/spack/spack/test/builder.py
Normal file
123
lib/spack/spack/test/builder.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# Copyright 2013-2022 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 os.path
|
||||
|
||||
import pytest
|
||||
|
||||
import spack.paths
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def builder_test_repository():
|
||||
builder_test_path = os.path.join(spack.paths.repos_path, "builder.test")
|
||||
with spack.repo.use_repositories(builder_test_path) as mock_repo:
|
||||
yield mock_repo
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"spec_str,expected_values",
|
||||
[
|
||||
(
|
||||
"callbacks@2.0",
|
||||
[
|
||||
("BEFORE_INSTALL_1_CALLED", "1"),
|
||||
("BEFORE_INSTALL_2_CALLED", "1"),
|
||||
("CALLBACKS_INSTALL_CALLED", "1"),
|
||||
("AFTER_INSTALL_1_CALLED", "1"),
|
||||
("TEST_VALUE", "3"),
|
||||
("INSTALL_VALUE", "CALLBACKS"),
|
||||
],
|
||||
),
|
||||
# The last callback is conditional on "@1.0", check it's being executed
|
||||
(
|
||||
"callbacks@1.0",
|
||||
[
|
||||
("BEFORE_INSTALL_1_CALLED", "1"),
|
||||
("BEFORE_INSTALL_2_CALLED", "1"),
|
||||
("CALLBACKS_INSTALL_CALLED", "1"),
|
||||
("AFTER_INSTALL_1_CALLED", "1"),
|
||||
("AFTER_INSTALL_2_CALLED", "1"),
|
||||
("TEST_VALUE", "4"),
|
||||
("INSTALL_VALUE", "CALLBACKS"),
|
||||
],
|
||||
),
|
||||
# The package below adds to "callbacks" using inheritance, test that using super()
|
||||
# works with builder hierarchies
|
||||
(
|
||||
"inheritance@1.0",
|
||||
[
|
||||
("DERIVED_BEFORE_INSTALL_CALLED", "1"),
|
||||
("BEFORE_INSTALL_1_CALLED", "1"),
|
||||
("BEFORE_INSTALL_2_CALLED", "1"),
|
||||
("CALLBACKS_INSTALL_CALLED", "1"),
|
||||
("INHERITANCE_INSTALL_CALLED", "1"),
|
||||
("AFTER_INSTALL_1_CALLED", "1"),
|
||||
("AFTER_INSTALL_2_CALLED", "1"),
|
||||
("TEST_VALUE", "4"),
|
||||
("INSTALL_VALUE", "INHERITANCE"),
|
||||
],
|
||||
),
|
||||
# Generate custom phases using a GenericBuilder
|
||||
(
|
||||
"custom-phases",
|
||||
[
|
||||
("CONFIGURE_CALLED", "1"),
|
||||
("INSTALL_CALLED", "1"),
|
||||
("LAST_PHASE", "INSTALL"),
|
||||
],
|
||||
),
|
||||
# Old-style package, with phase defined in base builder
|
||||
(
|
||||
"old-style-autotools@1.0",
|
||||
[
|
||||
("AFTER_AUTORECONF_1_CALLED", "1"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"old-style-autotools@2.0",
|
||||
[
|
||||
("AFTER_AUTORECONF_2_CALLED", "1"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"old-style-custom-phases",
|
||||
[
|
||||
("AFTER_CONFIGURE_CALLED", "1"),
|
||||
("TEST_VALUE", "0"),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("builder_test_repository", "config")
|
||||
@pytest.mark.disable_clean_stage_check
|
||||
def test_callbacks_and_installation_procedure(spec_str, expected_values, working_env):
|
||||
"""Test the correct execution of callbacks and installation procedures for packages."""
|
||||
s = spack.spec.Spec(spec_str).concretized()
|
||||
builder = spack.builder.create(s.package)
|
||||
for phase_fn in builder:
|
||||
phase_fn.execute()
|
||||
|
||||
# Check calls have produced the expected side effects
|
||||
for var_name, expected in expected_values:
|
||||
assert os.environ[var_name] == expected, os.environ
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("builder_test_repository", "config")
|
||||
@pytest.mark.parametrize(
|
||||
"spec_str,method_name,expected",
|
||||
[
|
||||
# Call a function defined on the package, which calls the same function defined
|
||||
# on the super(builder)
|
||||
("old-style-autotools", "configure_args", ["--with-foo"]),
|
||||
# Call a function defined on the package, which calls the same function defined on the
|
||||
# super(pkg), which calls the same function defined in the super(builder)
|
||||
("old-style-derived", "configure_args", ["--with-bar", "--with-foo"]),
|
||||
],
|
||||
)
|
||||
def test_old_style_compatibility_with_super(spec_str, method_name, expected):
|
||||
s = spack.spec.Spec(spec_str).concretized()
|
||||
builder = spack.builder.create(s.package)
|
||||
value = getattr(builder, method_name)()
|
||||
assert value == expected
|
||||
@@ -43,7 +43,7 @@ def test_it_just_runs(pkg):
|
||||
|
||||
|
||||
def test_info_noversion(mock_packages, print_buffer):
|
||||
"""Check that a mock package with no versions or variants outputs None."""
|
||||
"""Check that a mock package with no versions outputs None."""
|
||||
info("noversion")
|
||||
|
||||
line_iter = iter(print_buffer)
|
||||
@@ -52,7 +52,7 @@ def test_info_noversion(mock_packages, print_buffer):
|
||||
has = [desc in line for desc in ["Preferred", "Safe", "Deprecated"]]
|
||||
if not any(has):
|
||||
continue
|
||||
elif "Variants" not in line:
|
||||
else:
|
||||
continue
|
||||
|
||||
assert "None" in next(line_iter).strip()
|
||||
|
||||
@@ -1087,7 +1087,7 @@ def test_install_empty_env(
|
||||
("test-install-callbacks", "undefined-install-test"),
|
||||
],
|
||||
)
|
||||
def test_install_callbacks_fail(install_mockery, mock_fetch, name, method):
|
||||
def test_installation_fail_tests(install_mockery, mock_fetch, name, method):
|
||||
output = install("--test=root", "--no-cache", name, fail_on_error=False)
|
||||
|
||||
assert output.count(method) == 2
|
||||
|
||||
@@ -237,8 +237,7 @@ def test_test_list_all(mock_packages):
|
||||
"simple-standalone-test",
|
||||
"test-error",
|
||||
"test-fail",
|
||||
"test-build-callbacks",
|
||||
"test-install-callbacks",
|
||||
"fail-test-audit",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ def test_concretize(self, spec):
|
||||
check_concretize(spec)
|
||||
|
||||
def test_concretize_mention_build_dep(self):
|
||||
spec = check_concretize("cmake-client ^cmake@3.4.3")
|
||||
spec = check_concretize("cmake-client ^cmake@3.21.3")
|
||||
|
||||
# Check parent's perspective of child
|
||||
to_dependencies = spec.edges_to_dependencies(name="cmake")
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
import spack.build_systems.generic
|
||||
import spack.config
|
||||
import spack.repo
|
||||
import spack.util.spack_yaml as syaml
|
||||
@@ -102,7 +103,7 @@ def fake_installs(monkeypatch, tmpdir):
|
||||
stage_path = str(tmpdir.ensure("fake-stage", dir=True))
|
||||
universal_unused_stage = spack.stage.DIYStage(stage_path)
|
||||
monkeypatch.setattr(
|
||||
spack.package_base.Package, "_make_stage", MakeStage(universal_unused_stage)
|
||||
spack.build_systems.generic.Package, "_make_stage", MakeStage(universal_unused_stage)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import spack.config
|
||||
import spack.environment as ev
|
||||
import spack.main
|
||||
import spack.package_base
|
||||
import spack.paths
|
||||
import spack.repo
|
||||
import spack.schema.compilers
|
||||
@@ -1170,13 +1171,13 @@ def test_license_dir_config(mutable_config, mock_packages):
|
||||
"""Ensure license directory is customizable"""
|
||||
expected_dir = spack.paths.default_license_dir
|
||||
assert spack.config.get("config:license_dir") == expected_dir
|
||||
assert spack.package.Package.global_license_dir == expected_dir
|
||||
assert spack.package_base.PackageBase.global_license_dir == expected_dir
|
||||
assert spack.repo.path.get_pkg_class("a").global_license_dir == expected_dir
|
||||
|
||||
rel_path = os.path.join(os.path.sep, "foo", "bar", "baz")
|
||||
spack.config.set("config:license_dir", rel_path)
|
||||
assert spack.config.get("config:license_dir") == rel_path
|
||||
assert spack.package.Package.global_license_dir == rel_path
|
||||
assert spack.package_base.PackageBase.global_license_dir == rel_path
|
||||
assert spack.repo.path.get_pkg_class("a").global_license_dir == rel_path
|
||||
|
||||
|
||||
|
||||
@@ -103,6 +103,19 @@ def test_ascii_graph_mpileaks(config, mock_packages, monkeypatch):
|
||||
| o libdwarf
|
||||
|/
|
||||
o libelf
|
||||
"""
|
||||
or graph_str
|
||||
== r"""o mpileaks
|
||||
|\
|
||||
o | callpath
|
||||
|\|
|
||||
| o mpich
|
||||
|
|
||||
o dyninst
|
||||
|\
|
||||
o | libdwarf
|
||||
|/
|
||||
o libelf
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
@@ -144,13 +144,12 @@ def __getattr__(self, attr):
|
||||
return getattr(self.wrapped_stage, attr)
|
||||
|
||||
|
||||
def test_partial_install_delete_prefix_and_stage(install_mockery, mock_fetch):
|
||||
def test_partial_install_delete_prefix_and_stage(install_mockery, mock_fetch, working_env):
|
||||
s = Spec("canfail").concretized()
|
||||
|
||||
instance_rm_prefix = s.package.remove_prefix
|
||||
|
||||
try:
|
||||
s.package.succeed = False
|
||||
s.package.remove_prefix = mock_remove_prefix
|
||||
with pytest.raises(MockInstallError):
|
||||
s.package.do_install()
|
||||
@@ -161,7 +160,7 @@ def test_partial_install_delete_prefix_and_stage(install_mockery, mock_fetch):
|
||||
# must clear failure markings for the package before re-installing it
|
||||
spack.store.db.clear_failure(s, True)
|
||||
|
||||
s.package.succeed = True
|
||||
s.package.set_install_succeed()
|
||||
s.package.stage = MockStage(s.package.stage)
|
||||
|
||||
s.package.do_install(restage=True)
|
||||
@@ -174,18 +173,20 @@ def test_partial_install_delete_prefix_and_stage(install_mockery, mock_fetch):
|
||||
|
||||
|
||||
@pytest.mark.disable_clean_stage_check
|
||||
def test_failing_overwrite_install_should_keep_previous_installation(mock_fetch, install_mockery):
|
||||
def test_failing_overwrite_install_should_keep_previous_installation(
|
||||
mock_fetch, install_mockery, working_env
|
||||
):
|
||||
"""
|
||||
Make sure that whenever `spack install --overwrite` fails, spack restores
|
||||
the original install prefix instead of cleaning it.
|
||||
"""
|
||||
# Do a successful install
|
||||
s = Spec("canfail").concretized()
|
||||
s.package.succeed = True
|
||||
s.package.set_install_succeed()
|
||||
|
||||
# Do a failing overwrite install
|
||||
s.package.do_install()
|
||||
s.package.succeed = False
|
||||
s.package.set_install_fail()
|
||||
kwargs = {"overwrite": [s.dag_hash()]}
|
||||
|
||||
with pytest.raises(Exception):
|
||||
@@ -238,13 +239,11 @@ def test_install_dependency_symlinks_pkg(install_mockery, mock_fetch, mutable_mo
|
||||
|
||||
def test_install_times(install_mockery, mock_fetch, mutable_mock_repo):
|
||||
"""Test install times added."""
|
||||
spec = Spec("dev-build-test-install-phases")
|
||||
spec.concretize()
|
||||
pkg = spec.package
|
||||
pkg.do_install()
|
||||
spec = Spec("dev-build-test-install-phases").concretized()
|
||||
spec.package.do_install()
|
||||
|
||||
# Ensure dependency directory exists after the installation.
|
||||
install_times = os.path.join(pkg.prefix, ".spack", "install_times.json")
|
||||
install_times = os.path.join(spec.package.prefix, ".spack", "install_times.json")
|
||||
assert os.path.isfile(install_times)
|
||||
|
||||
# Ensure the phases are included
|
||||
@@ -346,12 +345,11 @@ def test_installed_upstream(install_upstream, mock_fetch):
|
||||
|
||||
|
||||
@pytest.mark.disable_clean_stage_check
|
||||
def test_partial_install_keep_prefix(install_mockery, mock_fetch, monkeypatch):
|
||||
def test_partial_install_keep_prefix(install_mockery, mock_fetch, monkeypatch, working_env):
|
||||
s = Spec("canfail").concretized()
|
||||
|
||||
# If remove_prefix is called at any point in this test, that is an error
|
||||
s.package.succeed = False # make the build fail
|
||||
monkeypatch.setattr(spack.package_base.Package, "remove_prefix", mock_remove_prefix)
|
||||
monkeypatch.setattr(spack.package_base.PackageBase, "remove_prefix", mock_remove_prefix)
|
||||
with pytest.raises(spack.build_environment.ChildError):
|
||||
s.package.do_install(keep_prefix=True)
|
||||
assert os.path.exists(s.package.prefix)
|
||||
@@ -359,7 +357,7 @@ def test_partial_install_keep_prefix(install_mockery, mock_fetch, monkeypatch):
|
||||
# must clear failure markings for the package before re-installing it
|
||||
spack.store.db.clear_failure(s, True)
|
||||
|
||||
s.package.succeed = True # make the build succeed
|
||||
s.package.set_install_succeed()
|
||||
s.package.stage = MockStage(s.package.stage)
|
||||
s.package.do_install(keep_prefix=True)
|
||||
assert s.package.spec.installed
|
||||
@@ -368,14 +366,14 @@ def test_partial_install_keep_prefix(install_mockery, mock_fetch, monkeypatch):
|
||||
|
||||
def test_second_install_no_overwrite_first(install_mockery, mock_fetch, monkeypatch):
|
||||
s = Spec("canfail").concretized()
|
||||
monkeypatch.setattr(spack.package_base.Package, "remove_prefix", mock_remove_prefix)
|
||||
monkeypatch.setattr(spack.package_base.PackageBase, "remove_prefix", mock_remove_prefix)
|
||||
|
||||
s.package.succeed = True
|
||||
s.package.set_install_succeed()
|
||||
s.package.do_install()
|
||||
assert s.package.spec.installed
|
||||
|
||||
# If Package.install is called after this point, it will fail
|
||||
s.package.succeed = False
|
||||
s.package.set_install_fail()
|
||||
s.package.do_install()
|
||||
|
||||
|
||||
@@ -589,7 +587,9 @@ def _install(src, dest):
|
||||
source = spec.package.stage.source_path
|
||||
config = os.path.join(source, "config.log")
|
||||
fs.touchp(config)
|
||||
spec.package.archive_files = ["missing", "..", config]
|
||||
monkeypatch.setattr(
|
||||
type(spec.package), "archive_files", ["missing", "..", config], raising=False
|
||||
)
|
||||
|
||||
spack.installer.log(spec.package)
|
||||
|
||||
|
||||
@@ -113,14 +113,14 @@ def test_absolute_import_spack_packages_as_python_modules(mock_packages):
|
||||
assert hasattr(spack.pkg.builtin.mock, "mpileaks")
|
||||
assert hasattr(spack.pkg.builtin.mock.mpileaks, "Mpileaks")
|
||||
assert isinstance(spack.pkg.builtin.mock.mpileaks.Mpileaks, spack.package_base.PackageMeta)
|
||||
assert issubclass(spack.pkg.builtin.mock.mpileaks.Mpileaks, spack.package_base.Package)
|
||||
assert issubclass(spack.pkg.builtin.mock.mpileaks.Mpileaks, spack.package_base.PackageBase)
|
||||
|
||||
|
||||
def test_relative_import_spack_packages_as_python_modules(mock_packages):
|
||||
from spack.pkg.builtin.mock.mpileaks import Mpileaks
|
||||
|
||||
assert isinstance(Mpileaks, spack.package_base.PackageMeta)
|
||||
assert issubclass(Mpileaks, spack.package_base.Package)
|
||||
assert issubclass(Mpileaks, spack.package_base.PackageBase)
|
||||
|
||||
|
||||
def test_all_virtual_packages_have_default_providers():
|
||||
|
||||
@@ -64,7 +64,7 @@ def __init__(self, spec):
|
||||
# list of URL attributes and metadata attributes
|
||||
# these will be removed from packages.
|
||||
self.metadata_attrs = [s.url_attr for s in spack.fetch_strategy.all_strategies]
|
||||
self.metadata_attrs += spack.package_base.Package.metadata_attrs
|
||||
self.metadata_attrs += spack.package_base.PackageBase.metadata_attrs
|
||||
|
||||
self.spec = spec
|
||||
self.in_classdef = False # used to avoid nested classdefs
|
||||
@@ -158,6 +158,7 @@ def __init__(self, spec):
|
||||
|
||||
def visit_FunctionDef(self, func):
|
||||
conditions = []
|
||||
|
||||
for dec in func.decorator_list:
|
||||
if isinstance(dec, ast.Call) and dec.func.id == "when":
|
||||
try:
|
||||
|
||||
@@ -784,7 +784,7 @@ def find_versions_of_archive(
|
||||
list_depth (int): max depth to follow links on list_url pages.
|
||||
Defaults to 0.
|
||||
concurrency (int): maximum number of concurrent requests
|
||||
reference_package (spack.package_base.Package or None): a spack package
|
||||
reference_package (spack.package_base.PackageBase or None): a spack package
|
||||
used as a reference for url detection. Uses the url_for_version
|
||||
method on the package to produce reference urls which, if found,
|
||||
are preferred.
|
||||
|
||||
@@ -96,7 +96,7 @@ def validate_or_raise(self, vspec, pkg_cls=None):
|
||||
|
||||
Args:
|
||||
vspec (Variant): instance to be validated
|
||||
pkg_cls (spack.package_base.Package): the package class
|
||||
pkg_cls (spack.package_base.PackageBase): the package class
|
||||
that required the validation, if available
|
||||
|
||||
Raises:
|
||||
|
||||
Reference in New Issue
Block a user