summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@googlemail.com>2017-01-25 16:57:01 +0100
committerTodd Gamblin <tgamblin@llnl.gov>2017-01-25 08:57:01 -0700
commitfc866ae0fe960abf723d5f89c2ae6c6baaa3ce5e (patch)
treeebcb8358d513dfce29c349493a2d735bb85a501f /lib
parent90d47a3eadb5f9b3f90a790c276f8ca5a90c81a1 (diff)
downloadspack-fc866ae0fe960abf723d5f89c2ae6c6baaa3ce5e.tar.gz
spack-fc866ae0fe960abf723d5f89c2ae6c6baaa3ce5e.tar.bz2
spack-fc866ae0fe960abf723d5f89c2ae6c6baaa3ce5e.tar.xz
spack-fc866ae0fe960abf723d5f89c2ae6c6baaa3ce5e.zip
build systems: simpler, clearer decorators: run_after, run_before (#2860)
* PackageMeta: `run_before` is an alias of `precondition`, `run_after` an alias of `sanity_check` * PackageMeta: removed `precondition` and `sanity_check` * PackageMeta: decorators are now free-standing * package: modified/added docstrings. Fixed the semantics of `on_package_attributes`. * package: added unit test assertion as side effects of install * build_systems: factored build-time test running into base class * r: updated decorators in package.py * docs: updated decorator names
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/packaging_guide.rst8
-rw-r--r--lib/spack/spack/__init__.py16
-rw-r--r--lib/spack/spack/build_systems/autotools.py24
-rw-r--r--lib/spack/spack/build_systems/cmake.py22
-rw-r--r--lib/spack/spack/build_systems/makefile.py4
-rw-r--r--lib/spack/spack/build_systems/python.py4
-rw-r--r--lib/spack/spack/build_systems/r.py4
-rw-r--r--lib/spack/spack/package.py158
8 files changed, 126 insertions, 114 deletions
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst
index f3927a0709..9b08a7d498 100644
--- a/lib/spack/docs/packaging_guide.rst
+++ b/lib/spack/docs/packaging_guide.rst
@@ -2775,17 +2775,17 @@ any base class listed in :ref:`installation_procedure`.
.. code-block:: python
- @MakefilePackage.sanity_check('build')
- @MakefilePackage.on_package_attributes(run_tests=True)
+ @run_after('build')
+ @on_package_attributes(run_tests=True)
def check_build(self):
# Custom implementation goes here
pass
-The first decorator ``MakefilePackage.sanity_check('build')`` schedules this
+The first decorator ``run_after('build')`` schedules this
function to be invoked after the ``build`` phase has been executed, while the
second one makes the invocation conditional on the fact that ``self.run_tests == True``.
It is also possible to schedule a function to be invoked *before* a given phase
-using the ``MakefilePackage.precondition`` decorator.
+using the ``run_before`` decorator.
.. note::
diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 0b1934112e..6a28fbb2b0 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -156,14 +156,24 @@ dirty = _config.get('dirty', False)
#-----------------------------------------------------------------------------
__all__ = []
-from spack.package import Package
+from spack.package import Package, run_before, run_after, on_package_attributes
from spack.build_systems.makefile import MakefilePackage
from spack.build_systems.autotools import AutotoolsPackage
from spack.build_systems.cmake import CMakePackage
from spack.build_systems.python import PythonPackage
from spack.build_systems.r import RPackage
-__all__ += ['Package', 'CMakePackage', 'AutotoolsPackage', 'MakefilePackage',
- 'PythonPackage', 'RPackage']
+
+__all__ += [
+ 'run_before',
+ 'run_after',
+ 'on_package_attributes',
+ 'Package',
+ 'CMakePackage',
+ 'AutotoolsPackage',
+ 'MakefilePackage',
+ 'PythonPackage',
+ 'RPackage'
+]
from spack.version import Version, ver
__all__ += ['Version', 'ver']
diff --git a/lib/spack/spack/build_systems/autotools.py b/lib/spack/spack/build_systems/autotools.py
index 37c780b360..d08ea02428 100644
--- a/lib/spack/spack/build_systems/autotools.py
+++ b/lib/spack/spack/build_systems/autotools.py
@@ -30,9 +30,8 @@ import shutil
from subprocess import PIPE
from subprocess import check_call
-import llnl.util.tty as tty
from llnl.util.filesystem import working_dir
-from spack.package import PackageBase
+from spack.package import PackageBase, run_after
class AutotoolsPackage(PackageBase):
@@ -80,6 +79,8 @@ class AutotoolsPackage(PackageBase):
#: phase
install_targets = ['install']
+ build_time_test_callbacks = ['check']
+
def _do_patch_config_guess(self):
"""Some packages ship with an older config.guess and need to have
this updated when installed on a newer architecture."""
@@ -168,7 +169,7 @@ class AutotoolsPackage(PackageBase):
"""Not needed usually, configure should be already there"""
pass
- @PackageBase.sanity_check('autoreconf')
+ @run_after('autoreconf')
def is_configure_or_die(self):
"""Checks the presence of a `configure` file after the
:py:meth:`.autoreconf` phase.
@@ -211,20 +212,7 @@ class AutotoolsPackage(PackageBase):
with working_dir(self.build_directory()):
inspect.getmodule(self).make(*self.install_targets)
- @PackageBase.sanity_check('build')
- @PackageBase.on_package_attributes(run_tests=True)
- def _run_default_function(self):
- """This function is run after build if ``self.run_tests == True``
-
- It will search for a method named :py:meth:`.check` and run it. A
- sensible default is provided in the base class.
- """
- try:
- fn = getattr(self, 'check')
- tty.msg('Trying default sanity checks [check]')
- fn()
- except AttributeError:
- tty.msg('Skipping default sanity checks [method `check` not implemented]') # NOQA: ignore=E501
+ run_after('build')(PackageBase._run_default_build_time_test_callbacks)
def check(self):
"""Searches the Makefile for targets ``test`` and ``check``
@@ -235,4 +223,4 @@ class AutotoolsPackage(PackageBase):
self._if_make_target_execute('check')
# Check that self.prefix is there after installation
- PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
+ run_after('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/build_systems/cmake.py b/lib/spack/spack/build_systems/cmake.py
index a5e23e54f4..823ef502c4 100644
--- a/lib/spack/spack/build_systems/cmake.py
+++ b/lib/spack/spack/build_systems/cmake.py
@@ -26,11 +26,10 @@
import inspect
import platform
-import llnl.util.tty as tty
import spack.build_environment
from llnl.util.filesystem import working_dir, join_path
from spack.directives import depends_on
-from spack.package import PackageBase
+from spack.package import PackageBase, run_after
class CMakePackage(PackageBase):
@@ -72,6 +71,8 @@ class CMakePackage(PackageBase):
build_targets = []
install_targets = ['install']
+ build_time_test_callbacks = ['check']
+
depends_on('cmake', type='build')
def build_type(self):
@@ -155,20 +156,7 @@ class CMakePackage(PackageBase):
with working_dir(self.build_directory()):
inspect.getmodule(self).make(*self.install_targets)
- @PackageBase.sanity_check('build')
- @PackageBase.on_package_attributes(run_tests=True)
- def _run_default_function(self):
- """This function is run after build if ``self.run_tests == True``
-
- It will search for a method named ``check`` and run it. A sensible
- default is provided in the base class.
- """
- try:
- fn = getattr(self, 'check')
- tty.msg('Trying default build sanity checks [check]')
- fn()
- except AttributeError:
- tty.msg('Skipping default build sanity checks [method `check` not implemented]') # NOQA: ignore=E501
+ run_after('build')(PackageBase._run_default_build_time_test_callbacks)
def check(self):
"""Searches the CMake-generated Makefile for the target ``test``
@@ -178,4 +166,4 @@ class CMakePackage(PackageBase):
self._if_make_target_execute('test')
# Check that self.prefix is there after installation
- PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
+ run_after('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/build_systems/makefile.py b/lib/spack/spack/build_systems/makefile.py
index e8fa86264b..f07bcd62ab 100644
--- a/lib/spack/spack/build_systems/makefile.py
+++ b/lib/spack/spack/build_systems/makefile.py
@@ -27,7 +27,7 @@ import inspect
import llnl.util.tty as tty
from llnl.util.filesystem import working_dir
-from spack.package import PackageBase
+from spack.package import PackageBase, run_after
class MakefilePackage(PackageBase):
@@ -100,4 +100,4 @@ class MakefilePackage(PackageBase):
inspect.getmodule(self).make(*self.install_targets)
# Check that self.prefix is there after installation
- PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
+ run_after('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/build_systems/python.py b/lib/spack/spack/build_systems/python.py
index d21c291ae6..5e7c1c356d 100644
--- a/lib/spack/spack/build_systems/python.py
+++ b/lib/spack/spack/build_systems/python.py
@@ -26,7 +26,7 @@
import inspect
from spack.directives import extends
-from spack.package import PackageBase
+from spack.package import PackageBase, run_after
from llnl.util.filesystem import working_dir
@@ -306,4 +306,4 @@ class PythonPackage(PackageBase):
return []
# Check that self.prefix is there after installation
- PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
+ run_after('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/build_systems/r.py b/lib/spack/spack/build_systems/r.py
index a4f7359ec8..cde3dc9fdd 100644
--- a/lib/spack/spack/build_systems/r.py
+++ b/lib/spack/spack/build_systems/r.py
@@ -26,7 +26,7 @@
import inspect
from spack.directives import extends
-from spack.package import PackageBase
+from spack.package import PackageBase, run_after
class RPackage(PackageBase):
@@ -55,4 +55,4 @@ class RPackage(PackageBase):
self.stage.source_path)
# Check that self.prefix is there after installation
- PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
+ run_after('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 24ff82fa38..0841ddcd61 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -78,13 +78,14 @@ class InstallPhase(object):
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 prerequisite and sanity checks.
+ It also provides hooks to execute arbitrary callbacks before and after
+ the phase.
"""
def __init__(self, name):
self.name = name
- self.preconditions = []
- self.sanity_checks = []
+ self.run_before = []
+ self.run_after = []
def __get__(self, instance, owner):
# The caller is a class that is trying to customize
@@ -101,14 +102,13 @@ class InstallPhase(object):
self._on_phase_start(instance)
# Execute phase pre-conditions,
# and give them the chance to fail
- for check in self.preconditions:
- # Do something sensible at some point
- check(instance)
+ for callback in self.run_before:
+ callback(instance)
phase(spec, prefix)
# Execute phase sanity_checks,
# and give them the chance to fail
- for check in self.sanity_checks:
- check(instance)
+ 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
@@ -129,8 +129,8 @@ class InstallPhase(object):
# This bug-fix was not back-ported in Python 2.6
# http://bugs.python.org/issue1515
other = InstallPhase(self.name)
- other.preconditions.extend(self.preconditions)
- other.sanity_checks.extend(self.sanity_checks)
+ other.run_before.extend(self.run_before)
+ other.run_after.extend(self.run_after)
return other
@@ -142,22 +142,23 @@ class PackageMeta(spack.directives.DirectiveMetaMixin):
"""
phase_fmt = '_InstallPhase_{0}'
- _InstallPhase_sanity_checks = {}
- _InstallPhase_preconditions = {}
+ _InstallPhase_run_before = {}
+ _InstallPhase_run_after = {}
+
+ def __new__(mcs, name, bases, attr_dict):
- def __new__(meta, name, bases, attr_dict):
- # Check if phases is in attr dict, then set
- # install phases wrappers
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']] # NOQA: ignore=E501
for phase_name, callback_name in zip(_InstallPhase_phases, attr_dict['phases']): # NOQA: ignore=E501
attr_dict[phase_name] = InstallPhase(callback_name)
attr_dict['_InstallPhase_phases'] = _InstallPhase_phases
- def _append_checks(check_name):
+ def _flush_callbacks(check_name):
# Name of the attribute I am going to check it exists
attr_name = PackageMeta.phase_fmt.format(check_name)
- checks = getattr(meta, attr_name)
+ checks = getattr(mcs, attr_name)
if checks:
for phase_name, funcs in checks.items():
try:
@@ -180,57 +181,61 @@ class PackageMeta(spack.directives.DirectiveMetaMixin):
PackageMeta.phase_fmt.format(phase_name)]
getattr(phase, check_name).extend(funcs)
# Clear the attribute for the next class
- setattr(meta, attr_name, {})
-
- @classmethod
- def _register_checks(cls, check_type, *args):
- def _register_sanity_checks(func):
- attr_name = PackageMeta.phase_fmt.format(check_type)
- check_list = getattr(meta, attr_name)
- for item in args:
- checks = check_list.setdefault(item, [])
- checks.append(func)
- setattr(meta, attr_name, check_list)
- return func
- return _register_sanity_checks
-
- @staticmethod
- def on_package_attributes(**attrs):
- def _execute_under_condition(func):
- @functools.wraps(func)
- def _wrapper(instance):
- # If all the attributes have the value we require, then
- # execute
- if all([getattr(instance, key, None) == value for key, value in attrs.items()]): # NOQA: ignore=E501
- func(instance)
- return _wrapper
- return _execute_under_condition
-
- @classmethod
- def precondition(cls, *args):
- return cls._register_checks('preconditions', *args)
-
- @classmethod
- def sanity_check(cls, *args):
- return cls._register_checks('sanity_checks', *args)
-
- if all([not hasattr(x, '_register_checks') for x in bases]):
- attr_dict['_register_checks'] = _register_checks
-
- if all([not hasattr(x, 'sanity_check') for x in bases]):
- attr_dict['sanity_check'] = sanity_check
-
- if all([not hasattr(x, 'precondition') for x in bases]):
- attr_dict['precondition'] = precondition
-
- if all([not hasattr(x, 'on_package_attributes') for x in bases]):
- attr_dict['on_package_attributes'] = on_package_attributes
+ setattr(mcs, attr_name, {})
# Preconditions
- _append_checks('preconditions')
+ _flush_callbacks('run_before')
# Sanity checks
- _append_checks('sanity_checks')
- return super(PackageMeta, meta).__new__(meta, name, bases, attr_dict)
+ _flush_callbacks('run_after')
+ return super(PackageMeta, mcs).__new__(mcs, 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):
+ """Executes the decorated method only if at the moment of calling
+ the instance has attributes that are equal to certain values.
+
+ :param dict attr_dict: dictionary mapping attribute names to their
+ required values
+ """
+ def _execute_under_condition(func):
+
+ @functools.wraps(func)
+ def _wrapper(instance, *args, **kwargs):
+ # If all the attributes have the value we require, then execute
+ 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()] # NOQA: ignore=E501
+ )
+ if has_the_right_values:
+ func(instance, *args, **kwargs)
+ return _wrapper
+
+ return _execute_under_condition
class PackageBase(object):
@@ -1704,6 +1709,27 @@ class PackageBase(object):
"""
return " ".join("-Wl,-rpath,%s" % p for p in self.rpath)
+ build_time_test_callbacks = None
+
+ @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``.
+
+ If ``build_time_test_callbacks is None`` returns immediately.
+ """
+ if self.build_time_test_callbacks is None:
+ return
+
+ for name in self.build_time_test_callbacks:
+ try:
+ fn = getattr(self, name)
+ tty.msg('RUN-TESTS: build-time tests [{0}]'.format(name))
+ fn()
+ except AttributeError:
+ msg = 'RUN-TESTS: method not implemented [{0}]'
+ tty.warn(msg.format(name))
+
class Package(PackageBase):
"""General purpose class with a single ``install``
@@ -1716,7 +1742,7 @@ class Package(PackageBase):
build_system_class = 'Package'
# This will be used as a registration decorator in user
# packages, if need be
- PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
+ run_after('install')(PackageBase.sanity_check_prefix)
def install_dependency_symlinks(pkg, spec, prefix):