diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py index 0e811e5ee7..85a0a4e2f7 100644 --- a/lib/spack/spack/__init__.py +++ b/lib/spack/spack/__init__.py @@ -176,10 +176,14 @@ # TODO: it's not clear where all the stuff that needs to be included in packages # should live. This file is overloaded for spack core vs. for packages. # -__all__ = ['Package', 'CMakePackage', \ - 'Version', 'when', 'ver'] +__all__ = ['Package', + 'CMakePackage', + 'AutotoolsPackage', + 'Version', + 'when', + 'ver'] from spack.package import Package, ExtensionConflictError -from spack.package import CMakePackage +from spack.package import CMakePackage, AutotoolsPackage from spack.version import Version, ver from spack.multimethod import when diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index 4194f30827..dd5637b9bc 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -38,8 +38,8 @@ import string import textwrap import time -import inspect import functools +import inspect from StringIO import StringIO from urlparse import urlparse @@ -74,7 +74,7 @@ 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 + 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. @@ -116,31 +116,63 @@ class PackageMeta(type): """ phase_fmt = '_InstallPhase_{0}' - def __init__(cls, name, bases, attr_dict): - super(PackageMeta, cls).__init__(name, bases, attr_dict) - # Parse if phases is in attr dict, then set + _InstallPhase_sanity_checks = {} + _InstallPhase_preconditions = {} + + def __new__(meta, name, bases, attr_dict): + # Check if phases is in attr dict, then set # install phases wrappers if 'phases' in attr_dict: - cls.phases = [PackageMeta.phase_fmt.format(name) for name in attr_dict['phases']] - for phase_name, callback_name in zip(cls.phases, attr_dict['phases']): - setattr(cls, phase_name, InstallPhase(callback_name)) + phases = [PackageMeta.phase_fmt.format(x) for x in attr_dict['phases']] + for phase_name, callback_name in zip(phases, attr_dict['phases']): + attr_dict[phase_name] = InstallPhase(callback_name) + attr_dict['phases'] = phases - def _transform_checks(check_name): + def _append_checks(check_name): + # Name of the attribute I am going to check it exists attr_name = PackageMeta.phase_fmt.format(check_name) - checks = getattr(cls, attr_name, None) + checks = getattr(meta, attr_name) if checks: for phase_name, funcs in checks.items(): - phase = getattr(cls, PackageMeta.phase_fmt.format(phase_name)) + phase = attr_dict.get(PackageMeta.phase_fmt.format(phase_name)) getattr(phase, check_name).extend(funcs) - # TODO : this should delete the attribute, as it is just a placeholder - # TODO : to know what to do at class definition time. Clearing it is fine - # TODO : too, but it just leaves an empty dictionary in place - setattr(cls, attr_name, {}) + # 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) + sanity_checks = getattr(meta, attr_name) + for item in args: + checks = sanity_checks.setdefault(item, []) + checks.append(func) + setattr(meta, attr_name, sanity_checks) + return func + return _register_sanity_checks + + @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 # Preconditions - _transform_checks('preconditions') + _append_checks('preconditions') # Sanity checks - _transform_checks('sanity_checks') + _append_checks('sanity_checks') + return super(PackageMeta, meta).__new__(meta, name, bases, attr_dict) class PackageBase(object): @@ -993,7 +1025,7 @@ def do_install(self, # Ensure package is not already installed # FIXME : skip condition : if any is True skip the installation - if 'install' in self.phases and spack.install_layout.check_installed(self.spec): + if spack.install_layout.check_installed(self.spec): tty.msg("%s is already installed in %s" % (self.name, self.prefix)) rec = spack.installed_db.get_record(self.spec) if (not rec.explicit) and explicit: @@ -1499,26 +1531,6 @@ def rpath_args(self): """ return " ".join("-Wl,-rpath,%s" % p for p in self.rpath) - @classmethod - def _register_checks(cls, check_type, *args): - def _register_sanity_checks(func): - attr_name = PackageMeta.phase_fmt.format(check_type) - sanity_checks = getattr(cls, attr_name, {}) - for item in args: - checks = sanity_checks.setdefault(item, []) - checks.append(func) - setattr(cls, attr_name, sanity_checks) - return func - return _register_sanity_checks - - @classmethod - def precondition(cls, *args): - return cls._register_checks('preconditions', *args) - - @classmethod - def sanity_check(cls, *args): - return cls._register_checks('sanity_checks', *args) - class Package(PackageBase): phases = ['install', 'log'] @@ -1527,6 +1539,36 @@ class Package(PackageBase): PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix) +class AutotoolsPackage(PackageBase): + phases = ['autoreconf', 'configure', 'build', 'install', 'log'] + + def autoreconf(self, spec, prefix): + """Not needed usually, configure should be already there""" + pass + + @PackageBase.sanity_check('autoreconf') + def is_configure_or_die(self): + if not os.path.exists('configure'): + raise RuntimeError('configure script not found in {0}'.format(os.getcwd())) + + def configure_args(self): + return list() + + def configure(self, spec, prefix): + options = ['--prefix={0}'.format(prefix)] + self.configure_args() + inspect.getmodule(self).configure(*options) + + def build(self, spec, prefix): + inspect.getmodule(self).make() + + def install(self, spec, prefix): + inspect.getmodule(self).make('install') + + # This will be used as a registration decorator in user + # packages, if need be + PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix) + + def install_dependency_symlinks(pkg, spec, prefix): """Execute a dummy install and flatten dependencies""" flatten_dependencies(spec, prefix) @@ -1637,7 +1679,7 @@ def _hms(seconds): class CMakePackage(PackageBase): - phases = ['setup', 'configure', 'build', 'install', 'provenance'] + phases = ['configure', 'build', 'install', 'provenance'] def make_make(self): import multiprocessing @@ -1657,7 +1699,7 @@ def configure_args(self): def configure_env(self): """Returns package-specific environment under which the configure command should be run.""" - # FIXME : Why not EnvironmentModules + # FIXME : Why not EnvironmentModules and the hooks that PackageBase already provides ? return dict() def spack_transitive_include_path(self): @@ -1720,7 +1762,6 @@ def cmdlist(str): fout.write('\nproc = subprocess.Popen(cmd, env=env)\nproc.wait()\n') set_executable(setup_fname) - def configure(self, spec, prefix): cmake = which('cmake') with working_dir(self.build_directory, create=True): diff --git a/var/spack/repos/builtin/packages/swiftsim/package.py b/var/spack/repos/builtin/packages/swiftsim/package.py index 42e8fb466a..7c3204f96b 100644 --- a/var/spack/repos/builtin/packages/swiftsim/package.py +++ b/var/spack/repos/builtin/packages/swiftsim/package.py @@ -28,7 +28,7 @@ import llnl.util.tty as tty -class Swiftsim(Package): +class Swiftsim(AutotoolsPackage): """ SPH With Inter-dependent Fine-grained Tasking (SWIFT) provides astrophysicists with a state of the art framework to perform @@ -59,19 +59,15 @@ def setup_environment(self, spack_env, run_env): tty.warn('This is needed to clone SWIFT repository') spack_env.set('GIT_SSL_NO_VERIFY', 1) - def install(self, spec, prefix): - # Generate configure from configure.ac - # and Makefile.am + def autoreconf(self, spec, prefix): libtoolize() aclocal() autoconf() autogen = Executable('./autogen.sh') autogen() - # Configure and install - options = ['--prefix=%s' % prefix, - '--enable-mpi' if '+mpi' in spec else '--disable-mpi', - '--enable-optimization'] - configure(*options) - make() - make("install") + def config_args(self): + return ['--prefix=%s' % prefix, + '--enable-mpi' if '+mpi' in spec else '--disable-mpi', + '--with-metis={0}'.format(self.spec['metis'].prefix), + '--enable-optimization'] diff --git a/var/spack/repos/builtin/packages/szip/package.py b/var/spack/repos/builtin/packages/szip/package.py index ec6d8239a9..91934f7d03 100644 --- a/var/spack/repos/builtin/packages/szip/package.py +++ b/var/spack/repos/builtin/packages/szip/package.py @@ -24,26 +24,22 @@ ############################################################################## from spack import * -class Szip(Package): - """Szip is an implementation of the extended-Rice lossless compression algorithm. - It provides lossless compression of scientific data, and is provided with HDF - software products.""" + +class Szip(AutotoolsPackage): + """Szip is an implementation of the extended-Rice lossless + compression algorithm. + + It provides lossless compression of scientific data, and is + provided with HDF software products. + """ homepage = "https://www.hdfgroup.org/doc_resource/SZIP/" - url = "http://www.hdfgroup.org/ftp/lib-external/szip/2.1/src/szip-2.1.tar.gz" + url = "http://www.hdfgroup.org/ftp/lib-external/szip/2.1/src/szip-2.1.tar.gz" version('2.1', '902f831bcefb69c6b635374424acbead') - @Package.sanity_check('install') - def always_raise(self): - raise RuntimeError('Precondition not respected') - - def install(self, spec, prefix): - configure('--prefix=%s' % prefix, - '--enable-production', - '--enable-shared', - '--enable-static', - '--enable-encoding') - - make() - make("install") + def configure_args(self): + return ['--enable-production', + '--enable-shared', + '--enable-static', + '--enable-encoding']