documentation: build-system phases + build-time tests (#2780)
* documentation: reworked packaging guide to add build-system phases * documentation: improvements to AutotoolsPackage autodocs * build_systems: updated autodocs * run-tests: added a few information on how to run tests fixes #2606 fixes#2605 * documentation: fixed items brought up by @davydden * typos in docs * consistent use of 'build system' (i.e. removed 'build-system' from docs) * added a note on possible default implementations for build-time tests * documentation: fixed items brought up by @citibeth * added note to explain the difference between build system and language used in a package * capitalized bullet items * added link to API docs * documentation: fixed multiple cross-references after rebase * documentation: fixed minor issues raised by @tgamblin * documentation: added entry in table for the `PythonPackage` class * docs: fixed issues brought up by @citybeth in the second review
This commit is contained in:
parent
72f2f845e7
commit
a8e1d78881
@ -1999,41 +1999,122 @@ the Python extensions provided by them: once for ``+python`` and once
|
|||||||
for ``~python``. Other than using a little extra disk space, that
|
for ``~python``. Other than using a little extra disk space, that
|
||||||
solution has no serious problems.
|
solution has no serious problems.
|
||||||
|
|
||||||
-----------------------------------
|
.. _installation_procedure:
|
||||||
Implementing the ``install`` method
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
The last element of a package is its ``install()`` method. This is
|
---------------------------------------
|
||||||
|
Implementing the installation procedure
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
The last element of a package is its **installation procedure**. This is
|
||||||
where the real work of installation happens, and it's the main part of
|
where the real work of installation happens, and it's the main part of
|
||||||
the package you'll need to customize for each piece of software.
|
the package you'll need to customize for each piece of software.
|
||||||
|
|
||||||
.. code-block:: python
|
Defining an installation procedure means overriding a set of methods or attributes
|
||||||
|
that will be called at some point during the installation of the package.
|
||||||
|
The package base class, usually specialized for a given build system, determines the
|
||||||
|
actual set of entities available for overriding.
|
||||||
|
The classes that are currently provided by Spack are:
|
||||||
|
|
||||||
|
+------------------------------------+----------------------------------+
|
||||||
|
| | **Base class purpose** |
|
||||||
|
+====================================+==================================+
|
||||||
|
| :py:class:`.Package` | General base class not |
|
||||||
|
| | specialized for any build system |
|
||||||
|
+------------------------------------+----------------------------------+
|
||||||
|
| :py:class:`.MakefilePackage` | Specialized class for packages |
|
||||||
|
| | built invoking |
|
||||||
|
| | hand-written Makefiles |
|
||||||
|
+------------------------------------+----------------------------------+
|
||||||
|
| :py:class:`.AutotoolsPackage` | Specialized class for packages |
|
||||||
|
| | built using GNU Autotools |
|
||||||
|
+------------------------------------+----------------------------------+
|
||||||
|
| :py:class:`.CMakePackage` | Specialized class for packages |
|
||||||
|
| | built using CMake |
|
||||||
|
+------------------------------------+----------------------------------+
|
||||||
|
| :py:class:`.RPackage` | Specialized class for |
|
||||||
|
| | :py:class:`.R` extensions |
|
||||||
|
+------------------------------------+----------------------------------+
|
||||||
|
| :py:class:`.PythonPackage` | Specialized class for |
|
||||||
|
| | :py:class:`.Python` extensions |
|
||||||
|
+------------------------------------+----------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Choice of the appropriate base class for a package
|
||||||
|
In most cases packagers don't have to worry about the selection of the right base class
|
||||||
|
for a package, as ``spack create`` will make the appropriate choice on their behalf. In those
|
||||||
|
rare cases where manual intervention is needed we need to stress that a
|
||||||
|
package base class depends on the *build system* being used, not the language of the package.
|
||||||
|
For example, a Python extension installed with CMake would ``extends('python')`` and
|
||||||
|
subclass from :py:class:`.CMakePackage`.
|
||||||
|
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Installation pipeline
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When a user runs ``spack install``, Spack:
|
||||||
|
|
||||||
|
1. Fetches an archive for the correct version of the software.
|
||||||
|
2. Expands the archive.
|
||||||
|
3. Sets the current working directory to the root directory of the expanded archive.
|
||||||
|
|
||||||
|
Then, depending on the base class of the package under consideration, it will execute
|
||||||
|
a certain number of **phases** that reflect the way a package of that type is usually built.
|
||||||
|
The name and order in which the phases will be executed can be obtained either reading the API
|
||||||
|
docs at :py:mod:`~.spack.build_systems`, or using the ``spack info`` command:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
:emphasize-lines: 13,14
|
||||||
|
|
||||||
|
$ spack info m4
|
||||||
|
AutotoolsPackage: m4
|
||||||
|
Homepage: https://www.gnu.org/software/m4/m4.html
|
||||||
|
|
||||||
|
Safe versions:
|
||||||
|
1.4.17 ftp://ftp.gnu.org/gnu/m4/m4-1.4.17.tar.gz
|
||||||
|
|
||||||
|
Variants:
|
||||||
|
Name Default Description
|
||||||
|
|
||||||
|
sigsegv on Build the libsigsegv dependency
|
||||||
|
|
||||||
|
Installation Phases:
|
||||||
|
autoreconf configure build install
|
||||||
|
|
||||||
|
Build Dependencies:
|
||||||
|
libsigsegv
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
Typically, phases have default implementations that fit most of the common cases:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../../lib/spack/spack/build_systems/autotools.py
|
||||||
|
:pyobject: AutotoolsPackage.configure
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
def install(self, spec prefix):
|
It is thus just sufficient for a packager to override a few
|
||||||
configure('--prefix={0}'.format(prefix))
|
build system specific helper methods or attributes to provide, for instance,
|
||||||
|
configure arguments:
|
||||||
|
|
||||||
make()
|
.. literalinclude:: ../../../var/spack/repos/builtin/packages/m4/package.py
|
||||||
make('install')
|
:pyobject: M4.configure_args
|
||||||
|
:linenos:
|
||||||
|
|
||||||
``install`` takes a ``spec``: a description of how the package should
|
.. note::
|
||||||
be built, and a ``prefix``: the path to the directory where the
|
Each specific build system has a list of attributes that can be overridden to
|
||||||
software should be installed.
|
fine-tune the installation of a package without overriding an entire phase. To
|
||||||
|
have more information on them the place to go is the API docs of the :py:mod:`~.spack.build_systems`
|
||||||
|
module.
|
||||||
|
|
||||||
Spack provides wrapper functions for ``configure`` and ``make`` so
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
that you can call them in a similar way to how you'd call a shell
|
Overriding an entire phase
|
||||||
command. In reality, these are Python functions. Spack provides
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
these functions to make writing packages more natural. See the section
|
|
||||||
on :ref:`shell wrappers <shell-wrappers>`.
|
|
||||||
|
|
||||||
Now that the metadata is out of the way, we can move on to the
|
In extreme cases it may be necessary to override an entire phase. Regardless
|
||||||
``install()`` method. When a user runs ``spack install``, Spack
|
of the build system, the signature is the same. For example, the signature
|
||||||
fetches an archive for the correct version of the software, expands
|
for the install phase is:
|
||||||
the archive, and sets the current working directory to the root
|
|
||||||
directory of the expanded archive. It then instantiates a package
|
|
||||||
object and calls the ``install()`` method.
|
|
||||||
|
|
||||||
The ``install()`` signature looks like this:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@ -2041,8 +2122,6 @@ The ``install()`` signature looks like this:
|
|||||||
def install(self, spec, prefix):
|
def install(self, spec, prefix):
|
||||||
...
|
...
|
||||||
|
|
||||||
The parameters are as follows:
|
|
||||||
|
|
||||||
``self``
|
``self``
|
||||||
For those not used to Python instance methods, this is the
|
For those not used to Python instance methods, this is the
|
||||||
package itself. In this case it's an instance of ``Foo``, which
|
package itself. In this case it's an instance of ``Foo``, which
|
||||||
@ -2059,19 +2138,15 @@ The parameters are as follows:
|
|||||||
targets into. It acts like a string, but it's actually its own
|
targets into. It acts like a string, but it's actually its own
|
||||||
special type, :py:class:`Prefix <spack.util.prefix.Prefix>`.
|
special type, :py:class:`Prefix <spack.util.prefix.Prefix>`.
|
||||||
|
|
||||||
``spec`` and ``prefix`` are passed to ``install`` for convenience.
|
The arguments ``spec`` and ``prefix`` are passed only for convenience, as they always
|
||||||
``spec`` is also available as an attribute on the package
|
correspond to ``self.spec`` and ``self.spec.prefix`` respectively.
|
||||||
(``self.spec``), and ``prefix`` is actually an attribute of ``spec``
|
|
||||||
(``spec.prefix``).
|
|
||||||
|
|
||||||
As mentioned in :ref:`install-environment`, you will usually not need
|
As mentioned in :ref:`install-environment`, you will usually not need to refer
|
||||||
to refer to dependencies explicitly in your package file, as the
|
to dependencies explicitly in your package file, as the compiler wrappers take care of most of
|
||||||
compiler wrappers take care of most of the heavy lifting here. There
|
the heavy lifting here. There will be times, though, when you need to refer to
|
||||||
will be times, though, when you need to refer to the install locations
|
the install locations of dependencies, or when you need to do something different
|
||||||
of dependencies, or when you need to do something different depending
|
depending on the version, compiler, dependencies, etc. that your package is
|
||||||
on the version, compiler, dependencies, etc. that your package is
|
built with. These parameters give you access to this type of information.
|
||||||
built with. These parameters give you access to this type of
|
|
||||||
information.
|
|
||||||
|
|
||||||
.. _install-environment:
|
.. _install-environment:
|
||||||
|
|
||||||
@ -2629,9 +2704,9 @@ build system.
|
|||||||
|
|
||||||
.. _sanity-checks:
|
.. _sanity-checks:
|
||||||
|
|
||||||
-------------------------------
|
------------------------
|
||||||
Sanity checking an installation
|
Checking an installation
|
||||||
-------------------------------
|
------------------------
|
||||||
|
|
||||||
By default, Spack assumes that a build has failed if nothing is
|
By default, Spack assumes that a build has failed if nothing is
|
||||||
written to the install prefix, and that it has succeeded if anything
|
written to the install prefix, and that it has succeeded if anything
|
||||||
@ -2650,16 +2725,18 @@ Consider a simple autotools build like this:
|
|||||||
If you are using using standard autotools or CMake, ``configure`` and
|
If you are using using standard autotools or CMake, ``configure`` and
|
||||||
``make`` will not write anything to the install prefix. Only ``make
|
``make`` will not write anything to the install prefix. Only ``make
|
||||||
install`` writes the files, and only once the build is already
|
install`` writes the files, and only once the build is already
|
||||||
complete. Not all builds are like this. Many builds of scientific
|
complete.
|
||||||
software modify the install prefix *before* ``make install``. Builds
|
|
||||||
like this can falsely report that they were successfully installed if
|
|
||||||
an error occurs before the install is complete but after files have
|
|
||||||
been written to the ``prefix``.
|
|
||||||
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
``sanity_check_is_file`` and ``sanity_check_is_dir``
|
``sanity_check_is_file`` and ``sanity_check_is_dir``
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Unfortunately, many builds of scientific
|
||||||
|
software modify the install prefix *before* ``make install``. Builds
|
||||||
|
like this can falsely report that they were successfully installed if
|
||||||
|
an error occurs before the install is complete but after files have
|
||||||
|
been written to the ``prefix``.
|
||||||
|
|
||||||
You can optionally specify *sanity checks* to deal with this problem.
|
You can optionally specify *sanity checks* to deal with this problem.
|
||||||
Add properties like this to your package:
|
Add properties like this to your package:
|
||||||
|
|
||||||
@ -2683,6 +2760,48 @@ the build will fail and the install prefix will be removed. If they
|
|||||||
succeed, Spack considers the build successful and keeps the prefix in
|
succeed, Spack considers the build successful and keeps the prefix in
|
||||||
place.
|
place.
|
||||||
|
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
Build-time tests
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Sometimes packages finish to build "correctly" and issues with their run-time
|
||||||
|
behavior are discovered only at a later stage, maybe after a full software stack
|
||||||
|
relying on them has already been built. To avoid situations of that kind it's possible
|
||||||
|
to write build-time tests that will be executed only if the option ``--run-tests``
|
||||||
|
of ``spack install`` has been activated.
|
||||||
|
|
||||||
|
The proper way to write these tests is relying on two decorators that come with
|
||||||
|
any base class listed in :ref:`installation_procedure`.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@MakefilePackage.sanity_check('build')
|
||||||
|
@MakefilePackage.on_package_attributes(run_tests=True)
|
||||||
|
def check_build(self):
|
||||||
|
# Custom implementation goes here
|
||||||
|
pass
|
||||||
|
|
||||||
|
The first decorator ``MakefilePackage.sanity_check('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.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Default implementations for build-time tests
|
||||||
|
|
||||||
|
Packages that are built using specific build systems may already have a
|
||||||
|
default implementation for build-time tests. For instance :py:class:`~.AutotoolsPackage`
|
||||||
|
based packages will try to invoke ``make test`` and ``make check`` if
|
||||||
|
Spack is asked to run tests.
|
||||||
|
More information on each class is available in the the :py:mod:`~.spack.build_systems`
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The API for adding tests is not yet considered stable and may change drastically in future releases.
|
||||||
|
|
||||||
.. _file-manipulation:
|
.. _file-manipulation:
|
||||||
|
|
||||||
---------------------------
|
---------------------------
|
||||||
|
@ -36,31 +36,51 @@
|
|||||||
|
|
||||||
|
|
||||||
class AutotoolsPackage(PackageBase):
|
class AutotoolsPackage(PackageBase):
|
||||||
"""Specialized class for packages that are built using GNU Autotools
|
"""Specialized class for packages built using GNU Autotools.
|
||||||
|
|
||||||
This class provides four phases that can be overridden:
|
This class provides four phases that can be overridden:
|
||||||
|
|
||||||
* autoreconf
|
1. :py:meth:`~.AutotoolsPackage.autoreconf`
|
||||||
* configure
|
2. :py:meth:`~.AutotoolsPackage.configure`
|
||||||
* build
|
3. :py:meth:`~.AutotoolsPackage.build`
|
||||||
* install
|
4. :py:meth:`~.AutotoolsPackage.install`
|
||||||
|
|
||||||
They all have sensible defaults and for many packages the only thing
|
They all have sensible defaults and for many packages the only thing
|
||||||
necessary will be to override ``configure_args``
|
necessary will be to override the helper method :py:meth:`.configure_args`.
|
||||||
|
For a finer tuning you may also override:
|
||||||
|
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
|
| **Method** | **Purpose** |
|
||||||
|
+===============================================+====================+
|
||||||
|
| :py:attr:`~.AutotoolsPackage.build_targets` | Specify ``make`` |
|
||||||
|
| | targets for the |
|
||||||
|
| | build phase |
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
|
| :py:attr:`~.AutotoolsPackage.install_targets` | Specify ``make`` |
|
||||||
|
| | targets for the |
|
||||||
|
| | install phase |
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
|
| :py:meth:`~.AutotoolsPackage.check` | Run build time |
|
||||||
|
| | tests if required |
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
|
|
||||||
Additionally, you may specify make targets for build and install
|
|
||||||
phases by overriding ``build_targets`` and ``install_targets``
|
|
||||||
"""
|
"""
|
||||||
|
#: Phases of a GNU Autotools package
|
||||||
phases = ['autoreconf', 'configure', 'build', 'install']
|
phases = ['autoreconf', 'configure', 'build', 'install']
|
||||||
# To be used in UI queries that require to know which
|
#: This attribute is used in UI queries that need to know the build
|
||||||
# build-system class we are using
|
#: system base class
|
||||||
build_system_class = 'AutotoolsPackage'
|
build_system_class = 'AutotoolsPackage'
|
||||||
|
#: Whether or not to update ``config.guess`` on old architectures
|
||||||
patch_config_guess = True
|
patch_config_guess = True
|
||||||
|
|
||||||
|
#: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.build`
|
||||||
|
#: phase
|
||||||
build_targets = []
|
build_targets = []
|
||||||
|
#: Targets for ``make`` during the :py:meth:`~.AutotoolsPackage.install`
|
||||||
|
#: phase
|
||||||
install_targets = ['install']
|
install_targets = ['install']
|
||||||
|
|
||||||
def do_patch_config_guess(self):
|
def _do_patch_config_guess(self):
|
||||||
"""Some packages ship with an older config.guess and need to have
|
"""Some packages ship with an older config.guess and need to have
|
||||||
this updated when installed on a newer architecture."""
|
this updated when installed on a newer architecture."""
|
||||||
|
|
||||||
@ -86,7 +106,7 @@ def do_patch_config_guess(self):
|
|||||||
check_call([my_config_guess], stdout=PIPE, stderr=PIPE)
|
check_call([my_config_guess], stdout=PIPE, stderr=PIPE)
|
||||||
# The package's config.guess already runs OK, so just use it
|
# The package's config.guess already runs OK, so just use it
|
||||||
return True
|
return True
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
@ -104,7 +124,7 @@ def do_patch_config_guess(self):
|
|||||||
check_call([config_guess], stdout=PIPE, stderr=PIPE)
|
check_call([config_guess], stdout=PIPE, stderr=PIPE)
|
||||||
shutil.copyfile(config_guess, my_config_guess)
|
shutil.copyfile(config_guess, my_config_guess)
|
||||||
return True
|
return True
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Look for the system's config.guess
|
# Look for the system's config.guess
|
||||||
@ -121,7 +141,7 @@ def do_patch_config_guess(self):
|
|||||||
check_call([config_guess], stdout=PIPE, stderr=PIPE)
|
check_call([config_guess], stdout=PIPE, stderr=PIPE)
|
||||||
shutil.copyfile(config_guess, my_config_guess)
|
shutil.copyfile(config_guess, my_config_guess)
|
||||||
return True
|
return True
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@ -131,11 +151,17 @@ def build_directory(self):
|
|||||||
return self.stage.source_path
|
return self.stage.source_path
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
"""Perform any required patches."""
|
"""Patches config.guess if
|
||||||
|
:py:attr:``~.AutotoolsPackage.patch_config_guess`` is True
|
||||||
|
|
||||||
|
:raise RuntimeError: if something goes wrong when patching
|
||||||
|
``config.guess``
|
||||||
|
"""
|
||||||
|
|
||||||
if self.patch_config_guess and self.spec.satisfies(
|
if self.patch_config_guess and self.spec.satisfies(
|
||||||
'arch=linux-rhel7-ppc64le'):
|
'arch=linux-rhel7-ppc64le'
|
||||||
if not self.do_patch_config_guess():
|
):
|
||||||
|
if not self._do_patch_config_guess():
|
||||||
raise RuntimeError('Failed to find suitable config.guess')
|
raise RuntimeError('Failed to find suitable config.guess')
|
||||||
|
|
||||||
def autoreconf(self, spec, prefix):
|
def autoreconf(self, spec, prefix):
|
||||||
@ -144,22 +170,27 @@ def autoreconf(self, spec, prefix):
|
|||||||
|
|
||||||
@PackageBase.sanity_check('autoreconf')
|
@PackageBase.sanity_check('autoreconf')
|
||||||
def is_configure_or_die(self):
|
def is_configure_or_die(self):
|
||||||
"""Checks the presence of a ``configure`` file after the
|
"""Checks the presence of a `configure` file after the
|
||||||
autoreconf phase"""
|
:py:meth:`.autoreconf` phase.
|
||||||
|
|
||||||
|
:raise RuntimeError: if the ``configure`` script does not exist.
|
||||||
|
"""
|
||||||
with working_dir(self.build_directory()):
|
with working_dir(self.build_directory()):
|
||||||
if not os.path.exists('configure'):
|
if not os.path.exists('configure'):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
'configure script not found in {0}'.format(os.getcwd()))
|
'configure script not found in {0}'.format(os.getcwd()))
|
||||||
|
|
||||||
def configure_args(self):
|
def configure_args(self):
|
||||||
"""Method to be overridden. Should return an iterable containing
|
"""Produces a list containing all the arguments that must be passed to
|
||||||
all the arguments that must be passed to configure, except ``--prefix``
|
configure, except ``--prefix`` which will be pre-pended to the list.
|
||||||
|
|
||||||
|
:return: list of arguments for configure
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def configure(self, spec, prefix):
|
def configure(self, spec, prefix):
|
||||||
"""Runs configure with the arguments specified in ``configure_args``
|
"""Runs configure with the arguments specified in :py:meth:`.configure_args`
|
||||||
and an appropriately set prefix
|
and an appropriately set prefix.
|
||||||
"""
|
"""
|
||||||
options = ['--prefix={0}'.format(prefix)] + self.configure_args()
|
options = ['--prefix={0}'.format(prefix)] + self.configure_args()
|
||||||
|
|
||||||
@ -167,12 +198,16 @@ def configure(self, spec, prefix):
|
|||||||
inspect.getmodule(self).configure(*options)
|
inspect.getmodule(self).configure(*options)
|
||||||
|
|
||||||
def build(self, spec, prefix):
|
def build(self, spec, prefix):
|
||||||
"""Make the build targets"""
|
"""Makes the build targets specified by
|
||||||
|
:py:attr:``~.AutotoolsPackage.build_targets``
|
||||||
|
"""
|
||||||
with working_dir(self.build_directory()):
|
with working_dir(self.build_directory()):
|
||||||
inspect.getmodule(self).make(*self.build_targets)
|
inspect.getmodule(self).make(*self.build_targets)
|
||||||
|
|
||||||
def install(self, spec, prefix):
|
def install(self, spec, prefix):
|
||||||
"""Make the install targets"""
|
"""Makes the install targets specified by
|
||||||
|
:py:attr:``~.AutotoolsPackage.install_targets``
|
||||||
|
"""
|
||||||
with working_dir(self.build_directory()):
|
with working_dir(self.build_directory()):
|
||||||
inspect.getmodule(self).make(*self.install_targets)
|
inspect.getmodule(self).make(*self.install_targets)
|
||||||
|
|
||||||
@ -181,8 +216,8 @@ def install(self, spec, prefix):
|
|||||||
def _run_default_function(self):
|
def _run_default_function(self):
|
||||||
"""This function is run after build if ``self.run_tests == True``
|
"""This function is run after build if ``self.run_tests == True``
|
||||||
|
|
||||||
It will search for a method named ``check`` and run it. A sensible
|
It will search for a method named :py:meth:`.check` and run it. A
|
||||||
default is provided in the base class.
|
sensible default is provided in the base class.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
fn = getattr(self, 'check')
|
fn = getattr(self, 'check')
|
||||||
@ -192,8 +227,8 @@ def _run_default_function(self):
|
|||||||
tty.msg('Skipping default sanity checks [method `check` not implemented]') # NOQA: ignore=E501
|
tty.msg('Skipping default sanity checks [method `check` not implemented]') # NOQA: ignore=E501
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
"""Default test: search the Makefile for targets ``test`` and ``check``
|
"""Searches the Makefile for targets ``test`` and ``check``
|
||||||
and run them if found.
|
and runs them if found.
|
||||||
"""
|
"""
|
||||||
with working_dir(self.build_directory()):
|
with working_dir(self.build_directory()):
|
||||||
self._if_make_target_execute('test')
|
self._if_make_target_execute('test')
|
||||||
|
@ -34,23 +34,39 @@
|
|||||||
|
|
||||||
|
|
||||||
class CMakePackage(PackageBase):
|
class CMakePackage(PackageBase):
|
||||||
"""Specialized class for packages that are built using CMake
|
"""Specialized class for packages built using CMake
|
||||||
|
|
||||||
This class provides three phases that can be overridden:
|
This class provides three phases that can be overridden:
|
||||||
|
|
||||||
* cmake
|
1. :py:meth:`~.CMakePackage.cmake`
|
||||||
* build
|
2. :py:meth:`~.CMakePackage.build`
|
||||||
* install
|
3. :py:meth:`~.CMakePackage.install`
|
||||||
|
|
||||||
They all have sensible defaults and for many packages the only thing
|
They all have sensible defaults and for many packages the only thing
|
||||||
necessary will be to override ``cmake_args``
|
necessary will be to override :py:meth:`~.CMakePackage.cmake_args`.
|
||||||
|
For a finer tuning you may also override:
|
||||||
|
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
|
| **Method** | **Purpose** |
|
||||||
|
+===============================================+====================+
|
||||||
|
| :py:meth:`~.CMakePackage.build_type` | Specify the value |
|
||||||
|
| | for the |
|
||||||
|
| | CMAKE_BUILD_TYPE |
|
||||||
|
| | variable |
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
|
| :py:meth:`~.CMakePackage.root_cmakelists_dir` | Location of the |
|
||||||
|
| | root CMakeLists.txt|
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
|
| :py:meth:`~.CMakePackage.build_directory` | Directory where to |
|
||||||
|
| | build the package |
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
|
|
||||||
|
|
||||||
Additionally, you may specify make targets for build and install
|
|
||||||
phases by overriding ``build_targets`` and ``install_targets``
|
|
||||||
"""
|
"""
|
||||||
|
#: Phases of a CMake package
|
||||||
phases = ['cmake', 'build', 'install']
|
phases = ['cmake', 'build', 'install']
|
||||||
# To be used in UI queries that require to know which
|
#: This attribute is used in UI queries that need to know the build
|
||||||
# build-system class we are using
|
#: system base class
|
||||||
build_system_class = 'CMakePackage'
|
build_system_class = 'CMakePackage'
|
||||||
|
|
||||||
build_targets = []
|
build_targets = []
|
||||||
@ -59,19 +75,25 @@ class CMakePackage(PackageBase):
|
|||||||
depends_on('cmake', type='build')
|
depends_on('cmake', type='build')
|
||||||
|
|
||||||
def build_type(self):
|
def build_type(self):
|
||||||
"""Override to provide the correct build_type in case a complex
|
"""Returns the correct value for the ``CMAKE_BUILD_TYPE`` variable
|
||||||
logic is needed
|
|
||||||
|
:return: value for ``CMAKE_BUILD_TYPE``
|
||||||
"""
|
"""
|
||||||
return 'RelWithDebInfo'
|
return 'RelWithDebInfo'
|
||||||
|
|
||||||
def root_cmakelists_dir(self):
|
def root_cmakelists_dir(self):
|
||||||
"""Directory where to find the root CMakeLists.txt"""
|
"""Returns the location of the root CMakeLists.txt
|
||||||
|
|
||||||
|
:return: directory containing the root CMakeLists.txt
|
||||||
|
"""
|
||||||
return self.stage.source_path
|
return self.stage.source_path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def std_cmake_args(self):
|
def std_cmake_args(self):
|
||||||
"""Standard cmake arguments provided as a property for
|
"""Standard cmake arguments provided as a property for
|
||||||
convenience of package writers
|
convenience of package writers
|
||||||
|
|
||||||
|
:return: standard cmake arguments
|
||||||
"""
|
"""
|
||||||
# standard CMake arguments
|
# standard CMake arguments
|
||||||
return CMakePackage._std_args(self)
|
return CMakePackage._std_args(self)
|
||||||
@ -97,20 +119,27 @@ def _std_args(pkg):
|
|||||||
return args
|
return args
|
||||||
|
|
||||||
def build_directory(self):
|
def build_directory(self):
|
||||||
"""Override to provide another place to build the package"""
|
"""Returns the directory to use when building the package
|
||||||
|
|
||||||
|
:return: directory where to build the package
|
||||||
|
"""
|
||||||
return join_path(self.stage.source_path, 'spack-build')
|
return join_path(self.stage.source_path, 'spack-build')
|
||||||
|
|
||||||
def cmake_args(self):
|
def cmake_args(self):
|
||||||
"""Method to be overridden. Should return an iterable containing
|
"""Produces a list containing all the arguments that must be passed to
|
||||||
all the arguments that must be passed to configure, except:
|
cmake, except:
|
||||||
|
|
||||||
* CMAKE_INSTALL_PREFIX
|
* CMAKE_INSTALL_PREFIX
|
||||||
* CMAKE_BUILD_TYPE
|
* CMAKE_BUILD_TYPE
|
||||||
|
|
||||||
|
which will be set automatically.
|
||||||
|
|
||||||
|
:return: list of arguments for cmake
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def cmake(self, spec, prefix):
|
def cmake(self, spec, prefix):
|
||||||
"""Run cmake in the build directory"""
|
"""Runs ``cmake`` in the build directory"""
|
||||||
options = [self.root_cmakelists_dir()] + self.std_cmake_args + \
|
options = [self.root_cmakelists_dir()] + self.std_cmake_args + \
|
||||||
self.cmake_args()
|
self.cmake_args()
|
||||||
with working_dir(self.build_directory(), create=True):
|
with working_dir(self.build_directory(), create=True):
|
||||||
@ -142,8 +171,8 @@ def _run_default_function(self):
|
|||||||
tty.msg('Skipping default build sanity checks [method `check` not implemented]') # NOQA: ignore=E501
|
tty.msg('Skipping default build sanity checks [method `check` not implemented]') # NOQA: ignore=E501
|
||||||
|
|
||||||
def check(self):
|
def check(self):
|
||||||
"""Default test: search the Makefile for the target ``test``
|
"""Searches the CMake-generated Makefile for the target ``test``
|
||||||
and run them if found.
|
and runs it if found.
|
||||||
"""
|
"""
|
||||||
with working_dir(self.build_directory()):
|
with working_dir(self.build_directory()):
|
||||||
self._if_make_target_execute('test')
|
self._if_make_target_execute('test')
|
||||||
|
@ -35,36 +35,67 @@ class MakefilePackage(PackageBase):
|
|||||||
|
|
||||||
This class provides three phases that can be overridden:
|
This class provides three phases that can be overridden:
|
||||||
|
|
||||||
* edit
|
1. :py:meth:`~.MakefilePackage.edit`
|
||||||
* build
|
2. :py:meth:`~.MakefilePackage.build`
|
||||||
* install
|
3. :py:meth:`~.MakefilePackage.install`
|
||||||
|
|
||||||
It is necessary to override the 'edit' phase, while 'build' and 'install'
|
It is usually necessary to override the :py:meth:`~.MakefilePackage.edit`
|
||||||
have sensible defaults.
|
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`` |
|
||||||
|
| | targets for the |
|
||||||
|
| | build phase |
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
|
| :py:attr:`~.MakefilePackage.install_targets` | Specify ``make`` |
|
||||||
|
| | targets for the |
|
||||||
|
| | install phase |
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
|
| :py:meth:`~.MakefilePackage.build_directory` | Directory where the|
|
||||||
|
| | Makefile is located|
|
||||||
|
+-----------------------------------------------+--------------------+
|
||||||
"""
|
"""
|
||||||
|
#: Phases of a package that is built with an hand-written Makefile
|
||||||
phases = ['edit', 'build', 'install']
|
phases = ['edit', 'build', 'install']
|
||||||
# To be used in UI queries that require to know which
|
#: This attribute is used in UI queries that need to know the build
|
||||||
# build-system class we are using
|
#: system base class
|
||||||
build_system_class = 'MakefilePackage'
|
build_system_class = 'MakefilePackage'
|
||||||
|
|
||||||
|
#: Targets for ``make`` during the :py:meth:`~.MakefilePackage.build`
|
||||||
|
#: phase
|
||||||
build_targets = []
|
build_targets = []
|
||||||
|
#: Targets for ``make`` during the :py:meth:`~.MakefilePackage.install`
|
||||||
|
#: phase
|
||||||
install_targets = ['install']
|
install_targets = ['install']
|
||||||
|
|
||||||
def build_directory(self):
|
def build_directory(self):
|
||||||
"""Directory where the main Makefile is located"""
|
"""Returns the directory containing the main Makefile
|
||||||
|
|
||||||
|
:return: build directory
|
||||||
|
"""
|
||||||
return self.stage.source_path
|
return self.stage.source_path
|
||||||
|
|
||||||
def edit(self, spec, prefix):
|
def edit(self, spec, prefix):
|
||||||
"""This phase cannot be defaulted for obvious reasons..."""
|
"""Edits the Makefile before calling make. This phase cannot
|
||||||
|
be defaulted.
|
||||||
|
"""
|
||||||
tty.msg('Using default implementation: skipping edit phase.')
|
tty.msg('Using default implementation: skipping edit phase.')
|
||||||
|
|
||||||
def build(self, spec, prefix):
|
def build(self, spec, prefix):
|
||||||
"""Make the build targets"""
|
"""Calls make, passing :py:attr:`~.MakefilePackage.build_targets`
|
||||||
|
as targets.
|
||||||
|
"""
|
||||||
with working_dir(self.build_directory()):
|
with working_dir(self.build_directory()):
|
||||||
inspect.getmodule(self).make(*self.build_targets)
|
inspect.getmodule(self).make(*self.build_targets)
|
||||||
|
|
||||||
def install(self, spec, prefix):
|
def install(self, spec, prefix):
|
||||||
"""Make the install targets"""
|
"""Calls make, passing :py:attr:`~.MakefilePackage.install_targets`
|
||||||
|
as targets.
|
||||||
|
"""
|
||||||
with working_dir(self.build_directory()):
|
with working_dir(self.build_directory()):
|
||||||
inspect.getmodule(self).make(*self.install_targets)
|
inspect.getmodule(self).make(*self.install_targets)
|
||||||
|
|
||||||
|
@ -34,21 +34,21 @@ class RPackage(PackageBase):
|
|||||||
|
|
||||||
This class provides a single phase that can be overridden:
|
This class provides a single phase that can be overridden:
|
||||||
|
|
||||||
* install
|
1. :py:meth:`~.RPackage.install`
|
||||||
|
|
||||||
It has sensible defaults and for many packages the only thing
|
It has sensible defaults, and for many packages the only thing
|
||||||
necessary will be to add dependencies
|
necessary will be to add dependencies
|
||||||
"""
|
"""
|
||||||
phases = ['install']
|
phases = ['install']
|
||||||
|
|
||||||
# To be used in UI queries that require to know which
|
#: This attribute is used in UI queries that need to know the build
|
||||||
# build-system class we are using
|
#: system base class
|
||||||
build_system_class = 'RPackage'
|
build_system_class = 'RPackage'
|
||||||
|
|
||||||
extends('r')
|
extends('r')
|
||||||
|
|
||||||
def install(self, spec, prefix):
|
def install(self, spec, prefix):
|
||||||
"""Install the R package"""
|
"""Installs an R package."""
|
||||||
inspect.getmodule(self).R(
|
inspect.getmodule(self).R(
|
||||||
'CMD', 'INSTALL',
|
'CMD', 'INSTALL',
|
||||||
'--library={0}'.format(self.module.r_lib_dir),
|
'--library={0}'.format(self.module.r_lib_dir),
|
||||||
|
@ -1706,9 +1706,13 @@ def rpath_args(self):
|
|||||||
|
|
||||||
|
|
||||||
class Package(PackageBase):
|
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']
|
phases = ['install']
|
||||||
# To be used in UI queries that require to know which
|
#: This attribute is used in UI queries that require to know which
|
||||||
# build-system class we are using
|
#: build-system class we are using
|
||||||
build_system_class = 'Package'
|
build_system_class = 'Package'
|
||||||
# This will be used as a registration decorator in user
|
# This will be used as a registration decorator in user
|
||||||
# packages, if need be
|
# packages, if need be
|
||||||
|
Loading…
Reference in New Issue
Block a user