Example #1
0
def database(tmpdir_factory, mock_packages, config):
    """Creates a mock database with some packages installed note that
    the ref count for dyninst here will be 3, as it's recycled
    across each install.
    """
    # save the real store
    real_store = spack.store.store

    # Make a fake install directory
    install_path = tmpdir_factory.mktemp('install_for_database')

    # Make fake store (database and install layout)
    tmp_store = spack.store.Store(str(install_path))
    spack.store.store = tmp_store

    _populate(tmp_store.db)

    yield tmp_store.db

    with tmp_store.db.write_transaction():
        for spec in tmp_store.db.query():
            if spec.package.installed:
                PackageBase.uninstall_by_spec(spec, force=True)
            else:
                tmp_store.db.remove(spec)

    install_path.remove(rec=1)
    spack.store.store = real_store
Example #2
0
def mutable_database(database):
    """For tests that need to modify the database instance."""
    yield database
    with database.write_transaction():
        for spec in spack.store.db.query():
            PackageBase.uninstall_by_spec(spec, force=True)
    _populate(database)
Example #3
0
def mutable_database(database):
    """For tests that need to modify the database instance."""
    yield database
    with database.write_transaction():
        for spec in spack.store.db.query():
            PackageBase.uninstall_by_spec(spec, force=True)
    _populate(database)
Example #4
0
def database(tmpdir_factory, mock_packages, config):
    """Creates a mock database with some packages installed note that
    the ref count for dyninst here will be 3, as it's recycled
    across each install.
    """
    # save the real store
    real_store = spack.store.store

    # Make a fake install directory
    install_path = tmpdir_factory.mktemp('install_for_database')

    # Make fake store (database and install layout)
    tmp_store = spack.store.Store(str(install_path))
    spack.store.store = tmp_store

    _populate(tmp_store.db)

    yield tmp_store.db

    with tmp_store.db.write_transaction():
        for spec in tmp_store.db.query():
            if spec.package.installed:
                PackageBase.uninstall_by_spec(spec, force=True)
            else:
                tmp_store.db.remove(spec)

    install_path.remove(rec=1)
    spack.store.store = real_store
Example #5
0
def test_uninstall_by_spec_errors(mutable_database):
    """Test exceptional cases with the uninstall command."""

    # Try to uninstall a spec that has not been installed
    spec = Spec('dependent-install')
    spec.concretize()
    with pytest.raises(InstallError, match="is not installed"):
        PackageBase.uninstall_by_spec(spec)

    # Try an unforced uninstall of a spec with dependencies
    rec = mutable_database.get_record('mpich')
    with pytest.raises(PackageStillNeededError, match="Cannot uninstall"):
        PackageBase.uninstall_by_spec(rec.spec)
Example #6
0
class RPackage(PackageBase):
    """Specialized class for packages that are built using R

    This class provides a single phase that can be overridden:

    * install

    It has sensible defaults and for many packages the only thing
    necessary will be to add dependencies
    """
    phases = ['install']

    # To be used in UI queries that require to know which
    # build-system class we are using
    build_system_class = 'RPackage'

    extends('r')

    def install(self, spec, prefix):
        """Install the R package"""
        inspect.getmodule(self).R(
            'CMD', 'INSTALL',
            '--library={0}'.format(self.module.r_lib_dir),
            self.stage.source_path)

    # Check that self.prefix is there after installation
    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
Example #7
0
class MakefilePackage(PackageBase):
    """Specialized class for packages that are built using editable Makefiles

    This class provides three phases that can be overridden:

    * edit
    * build
    * install

    It is necessary to override the 'edit' phase, while 'build' and 'install'
    have sensible defaults.
    """
    phases = ['edit', 'build', 'install']
    # To be used in UI queries that require to know which
    # build-system class we are using
    build_system_class = 'MakefilePackage'

    build_targets = []
    install_targets = ['install']

    def build_directory(self):
        """Directory where the main Makefile is located"""
        return self.stage.source_path

    def edit(self, spec, prefix):
        """This phase cannot be defaulted for obvious reasons..."""
        tty.msg('Using default implementation: skipping edit phase.')

    def build(self, spec, prefix):
        """Make the build targets"""
        with working_dir(self.build_directory()):
            inspect.getmodule(self).make(*self.build_targets)

    def install(self, spec, prefix):
        """Make the install targets"""
        with working_dir(self.build_directory()):
            inspect.getmodule(self).make(*self.install_targets)

    # Check that self.prefix is there after installation
    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
Example #8
0
class CMakePackage(PackageBase):
    """Specialized class for packages that are built using cmake

    This class provides three phases that can be overridden:
    - cmake
    - build
    - install

    They all have sensible defaults and for many packages the only thing
    necessary will be to override `cmake_args`
    """
    phases = ['cmake', 'build', 'install']
    # To be used in UI queries that require to know which
    # build-system class we are using
    build_system_class = 'CMakePackage'

    def build_type(self):
        """Override to provide the correct build_type in case a complex
        logic is needed
        """
        return 'RelWithDebInfo'

    def root_cmakelists_dir(self):
        """Directory where to find the root CMakeLists.txt"""
        return self.stage.source_path

    @property
    def std_cmake_args(self):
        """Standard cmake arguments provided as a property for
        convenience of package writers
        """
        # standard CMake arguments
        return CMakePackage._std_args(self)

    @staticmethod
    def _std_args(pkg):
        """Computes the standard cmake arguments for a generic package"""
        try:
            build_type = pkg.build_type()
        except AttributeError:
            build_type = 'RelWithDebInfo'

        args = [
            '-DCMAKE_INSTALL_PREFIX:PATH={0}'.format(pkg.prefix),
            '-DCMAKE_BUILD_TYPE:STRING={0}'.format(build_type),
            '-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON'
        ]
        if platform.mac_ver()[0]:
            args.append('-DCMAKE_FIND_FRAMEWORK:STRING=LAST')

        # Set up CMake rpath
        args.append('-DCMAKE_INSTALL_RPATH_USE_LINK_PATH:BOOL=FALSE')
        rpaths = ':'.join(spack.build_environment.get_rpaths(pkg))
        args.append('-DCMAKE_INSTALL_RPATH:STRING={0}'.format(rpaths))
        return args

    def build_directory(self):
        """Override to provide another place to build the package"""
        return join_path(self.stage.source_path, 'spack-build')

    def cmake_args(self):
        """Method to be overridden. Should return an iterable containing
        all the arguments that must be passed to configure, except:
        - CMAKE_INSTALL_PREFIX
        - CMAKE_BUILD_TYPE
        """
        return []

    def cmake(self, spec, prefix):
        """Run cmake in the build directory"""
        options = [self.root_cmakelists_dir()] + self.std_cmake_args + \
            self.cmake_args()
        create = not os.path.exists(self.build_directory())
        with working_dir(self.build_directory(), create=create):
            inspect.getmodule(self).cmake(*options)

    def build(self, spec, prefix):
        """The usual `make` after cmake"""
        with working_dir(self.build_directory()):
            inspect.getmodule(self).make()

    def install(self, spec, prefix):
        """...and the final `make install` after cmake"""
        with working_dir(self.build_directory()):
            inspect.getmodule(self).make('install')

    @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

    def check(self):
        """Default test : search the Makefile for the target `test`
        and run them if found.
        """
        with working_dir(self.build_directory()):
            self._if_make_target_execute('test')

    # Check that self.prefix is there after installation
    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
Example #9
0
class PythonPackage(PackageBase):
    """Specialized class for packages that are built using Python
    setup.py files

    This class provides the following phases that can be overridden:

    * build
    * build_py
    * build_ext
    * build_clib
    * build_scripts
    * clean
    * install
    * install_lib
    * install_headers
    * install_scripts
    * install_data
    * sdist
    * register
    * bdist
    * bdist_dumb
    * bdist_rpm
    * bdist_wininst
    * upload
    * check

    These are all standard setup.py commands and can be found by running:

    .. code-block:: console

       $ python setup.py --help-commands

    By default, only the 'build' and 'install' phases are run, but if you
    need to run more phases, simply modify your ``phases`` list like so:

    .. code-block:: python

       phases = ['build_ext', 'install', 'bdist']

    Each phase provides a function <phase> that runs:

    .. code-block:: console

       $ python --no-user-cfg setup.py <phase>

    Each phase also has a <phase_args> function that can pass arguments to
    this call. All of these functions are empty except for the ``install_args``
    function, which passes ``--prefix=/path/to/installation/directory``.

    If you need to run a phase which is not a standard setup.py command,
    you'll need to define a function for it like so:

    .. code-block:: python

       def configure(self, spec, prefix):
           self.setup_py('configure')
    """
    # 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 = 'PythonPackage'

    extends('python')

    def setup_file(self, spec, prefix):
        """Returns the name of the setup file to use."""
        return 'setup.py'

    def build_directory(self):
        """The directory containing the ``setup.py`` file."""
        return self.stage.source_path

    def python(self, *args):
        inspect.getmodule(self).python(*args)

    def setup_py(self, *args):
        setup = self.setup_file(self.spec, self.prefix)

        with working_dir(self.build_directory()):
            self.python(setup, '--no-user-cfg', *args)

    # The following phases and their descriptions come from:
    #   $ python setup.py --help-commands
    # Only standard commands are included here, but some packages
    # define extra commands as well

    def build(self, spec, prefix):
        """Build everything needed to install."""
        args = self.build_args(spec, prefix)

        self.setup_py('build', *args)

    def build_args(self, spec, prefix):
        """Arguments to pass to build."""
        return []

    def build_py(self, spec, prefix):
        '''"Build" pure Python modules (copy to build directory).'''
        args = self.build_py_args(spec, prefix)

        self.setup_py('build_py', *args)

    def build_py_args(self, spec, prefix):
        """Arguments to pass to build_py."""
        return []

    def build_ext(self, spec, prefix):
        """Build C/C++ extensions (compile/link to build directory)."""
        args = self.build_ext_args(spec, prefix)

        self.setup_py('build_ext', *args)

    def build_ext_args(self, spec, prefix):
        """Arguments to pass to build_ext."""
        return []

    def build_clib(self, spec, prefix):
        """Build C/C++ libraries used by Python extensions."""
        args = self.build_clib_args(spec, prefix)

        self.setup_py('build_clib', *args)

    def build_clib_args(self, spec, prefix):
        """Arguments to pass to build_clib."""
        return []

    def build_scripts(self, spec, prefix):
        '''"Build" scripts (copy and fixup #! line).'''
        args = self.build_scripts_args(spec, prefix)

        self.setup_py('build_scripts', *args)

    def clean(self, spec, prefix):
        """Clean up temporary files from 'build' command."""
        args = self.clean_args(spec, prefix)

        self.setup_py('clean', *args)

    def clean_args(self, spec, prefix):
        """Arguments to pass to clean."""
        return []

    def install(self, spec, prefix):
        """Install everything from build directory."""
        args = self.install_args(spec, prefix)

        self.setup_py('install', *args)

    def install_args(self, spec, prefix):
        """Arguments to pass to install."""
        return ['--prefix={0}'.format(prefix)]

    def install_lib(self, spec, prefix):
        """Install all Python modules (extensions and pure Python)."""
        args = self.install_lib_args(spec, prefix)

        self.setup_py('install_lib', *args)

    def install_lib_args(self, spec, prefix):
        """Arguments to pass to install_lib."""
        return []

    def install_headers(self, spec, prefix):
        """Install C/C++ header files."""
        args = self.install_headers_args(spec, prefix)

        self.setup_py('install_headers', *args)

    def install_headers_args(self, spec, prefix):
        """Arguments to pass to install_headers."""
        return []

    def install_scripts(self, spec, prefix):
        """Install scripts (Python or otherwise)."""
        args = self.install_scripts_args(spec, prefix)

        self.setup_py('install_scripts', *args)

    def install_scripts_args(self, spec, prefix):
        """Arguments to pass to install_scripts."""
        return []

    def install_data(self, spec, prefix):
        """Install data files."""
        args = self.install_data_args(spec, prefix)

        self.setup_py('install_data', *args)

    def install_data_args(self, spec, prefix):
        """Arguments to pass to install_data."""
        return []

    def sdist(self, spec, prefix):
        """Create a source distribution (tarball, zip file, etc.)."""
        args = self.sdist_args(spec, prefix)

        self.setup_py('sdist', *args)

    def sdist_args(self, spec, prefix):
        """Arguments to pass to sdist."""
        return []

    def register(self, spec, prefix):
        """Register the distribution with the Python package index."""
        args = self.register_args(spec, prefix)

        self.setup_py('register', *args)

    def register_args(self, spec, prefix):
        """Arguments to pass to register."""
        return []

    def bdist(self, spec, prefix):
        """Create a built (binary) distribution."""
        args = self.bdist_args(spec, prefix)

        self.setup_py('bdist', *args)

    def bdist_args(self, spec, prefix):
        """Arguments to pass to bdist."""
        return []

    def bdist_dumb(self, spec, prefix):
        '''Create a "dumb" built distribution.'''
        args = self.bdist_dumb_args(spec, prefix)

        self.setup_py('bdist_dumb', *args)

    def bdist_dumb_args(self, spec, prefix):
        """Arguments to pass to bdist_dumb."""
        return []

    def bdist_rpm(self, spec, prefix):
        """Create an RPM distribution."""
        args = self.bdist_rpm(spec, prefix)

        self.setup_py('bdist_rpm', *args)

    def bdist_rpm_args(self, spec, prefix):
        """Arguments to pass to bdist_rpm."""
        return []

    def bdist_wininst(self, spec, prefix):
        """Create an executable installer for MS Windows."""
        args = self.bdist_wininst_args(spec, prefix)

        self.setup_py('bdist_wininst', *args)

    def bdist_wininst_args(self, spec, prefix):
        """Arguments to pass to bdist_wininst."""
        return []

    def upload(self, spec, prefix):
        """Upload binary package to PyPI."""
        args = self.upload_args(spec, prefix)

        self.setup_py('upload', *args)

    def upload_args(self, spec, prefix):
        """Arguments to pass to upload."""
        return []

    def check(self, spec, prefix):
        """Perform some checks on the package."""
        args = self.check_args(spec, prefix)

        self.setup_py('check', *args)

    def check_args(self, spec, prefix):
        """Arguments to pass to check."""
        return []

    # Check that self.prefix is there after installation
    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
Example #10
0
class AutotoolsPackage(PackageBase):
    """Specialized class for packages that are built using GNU Autotools

    This class provides four phases that can be overridden:
    - autoreconf
    - configure
    - build
    - install

    They all have sensible defaults and for many packages the only thing
    necessary will be to override `configure_args`
    """
    phases = ['autoreconf', 'configure', 'build', 'install']
    # To be used in UI queries that require to know which
    # build-system class we are using
    build_system_class = 'AutotoolsPackage'

    def autoreconf(self, spec, prefix):
        """Not needed usually, configure should be already there"""
        pass

    @PackageBase.sanity_check('autoreconf')
    def is_configure_or_die(self):
        """Checks the presence of a `configure` file after the
        autoreconf phase"""
        if not os.path.exists('configure'):
            raise RuntimeError('configure script not found in {0}'.format(
                os.getcwd()))

    def configure_args(self):
        """Method to be overridden. Should return an iterable containing
        all the arguments that must be passed to configure, except --prefix
        """
        return []

    def configure(self, spec, prefix):
        """Runs configure with the arguments specified in `configure_args`
        and an appropriately set prefix
        """
        options = ['--prefix={0}'.format(prefix)] + self.configure_args()
        inspect.getmodule(self).configure(*options)

    def build(self, spec, prefix):
        """The usual `make` after configure"""
        inspect.getmodule(self).make()

    def install(self, spec, prefix):
        """...and the final `make install` after configure"""
        inspect.getmodule(self).make('install')

    @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 sanity checks [check]')
            fn()
        except AttributeError:
            tty.msg(
                'Skipping default sanity checks [method `check` not implemented]'
            )  # NOQA: ignore=E501

    def check(self):
        """Default test : search the Makefile for targets `test` and `check`
        and run them if found.
        """
        self._if_make_target_execute('test')
        self._if_make_target_execute('check')

    # Check that self.prefix is there after installation
    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
Example #11
0
class AutotoolsPackage(PackageBase):
    """Specialized class for packages that are built using GNU Autotools

    This class provides four phases that can be overridden:

    * autoreconf
    * configure
    * build
    * install

    They all have sensible defaults and for many packages the only thing
    necessary will be to override ``configure_args``

    Additionally, you may specify make targets for build and install
    phases by overriding ``build_targets`` and ``install_targets``
    """
    phases = ['autoreconf', 'configure', 'build', 'install']
    # To be used in UI queries that require to know which
    # build-system class we are using
    build_system_class = 'AutotoolsPackage'
    patch_config_guess = True

    build_targets = []
    install_targets = ['install']

    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."""

        my_config_guess = None
        config_guess = None
        if os.path.exists('config.guess'):
            # First search the top-level source directory
            my_config_guess = 'config.guess'
        else:
            # Then search in all sub directories.
            # We would like to use AC_CONFIG_AUX_DIR, but not all packages
            # ship with their configure.in or configure.ac.
            d = '.'
            dirs = [os.path.join(d, o) for o in os.listdir(d)
                    if os.path.isdir(os.path.join(d, o))]
            for dirname in dirs:
                path = os.path.join(dirname, 'config.guess')
                if os.path.exists(path):
                    my_config_guess = path

        if my_config_guess is not None:
            try:
                check_call([my_config_guess], stdout=PIPE, stderr=PIPE)
                # The package's config.guess already runs OK, so just use it
                return True
            except:
                pass
        else:
            return True

        # Look for a spack-installed automake package
        if 'automake' in self.spec:
            automake_path = os.path.join(self.spec['automake'].prefix, 'share',
                                         'automake-' +
                                         str(self.spec['automake'].version))
            path = os.path.join(automake_path, 'config.guess')
            if os.path.exists(path):
                config_guess = path
        if config_guess is not None:
            try:
                check_call([config_guess], stdout=PIPE, stderr=PIPE)
                shutil.copyfile(config_guess, my_config_guess)
                return True
            except:
                pass

        # Look for the system's config.guess
        if os.path.exists('/usr/share'):
            automake_dir = [s for s in os.listdir('/usr/share') if
                            "automake" in s]
            if automake_dir:
                automake_path = os.path.join('/usr/share', automake_dir[0])
                path = os.path.join(automake_path, 'config.guess')
                if os.path.exists(path):
                    config_guess = path
        if config_guess is not None:
            try:
                check_call([config_guess], stdout=PIPE, stderr=PIPE)
                shutil.copyfile(config_guess, my_config_guess)
                return True
            except:
                pass

        return False

    def build_directory(self):
        """Override to provide another place to build the package"""
        return self.stage.source_path

    def patch(self):
        """Perform any required patches."""

        if self.patch_config_guess and self.spec.satisfies(
                'arch=linux-rhel7-ppc64le'):
            if not self.do_patch_config_guess():
                raise RuntimeError('Failed to find suitable config.guess')

    def autoreconf(self, spec, prefix):
        """Not needed usually, configure should be already there"""
        pass

    @PackageBase.sanity_check('autoreconf')
    def is_configure_or_die(self):
        """Checks the presence of a ``configure`` file after the
        autoreconf phase"""
        with working_dir(self.build_directory()):
            if not os.path.exists('configure'):
                raise RuntimeError(
                    'configure script not found in {0}'.format(os.getcwd()))

    def configure_args(self):
        """Method to be overridden. Should return an iterable containing
        all the arguments that must be passed to configure, except ``--prefix``
        """
        return []

    def configure(self, spec, prefix):
        """Runs configure with the arguments specified in ``configure_args``
        and an appropriately set prefix
        """
        options = ['--prefix={0}'.format(prefix)] + self.configure_args()

        with working_dir(self.build_directory()):
            inspect.getmodule(self).configure(*options)

    def build(self, spec, prefix):
        """Make the build targets"""
        with working_dir(self.build_directory()):
            inspect.getmodule(self).make(*self.build_targets)

    def install(self, spec, prefix):
        """Make the install targets"""
        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 sanity checks [check]')
            fn()
        except AttributeError:
            tty.msg('Skipping default sanity checks [method `check` not implemented]')  # NOQA: ignore=E501

    def check(self):
        """Default test: search the Makefile for targets ``test`` and ``check``
        and run them if found.
        """
        with working_dir(self.build_directory()):
            self._if_make_target_execute('test')
            self._if_make_target_execute('check')

    # Check that self.prefix is there after installation
    PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)