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
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)
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)
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)
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)
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)
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)
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)
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)