summaryrefslogtreecommitdiff
path: root/lib/spack/docs/build_systems/custompackage.rst
blob: 5175a0df4df7032ea368e0d7df3adb35d20a5ac5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
.. Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
   Spack Project Developers. See the top-level COPYRIGHT file for details.

   SPDX-License-Identifier: (Apache-2.0 OR MIT)

.. _custompackage:

--------------------
Custom Build Systems
--------------------

While the built-in build systems should meet your needs for the
vast majority of packages, some packages provide custom build scripts.
This guide is intended for the following use cases:

* Packaging software with its own custom build system
* Adding support for new build systems

If you want to add support for a new build system, a good place to
start is to look at the definitions of other build systems. This guide
focuses mostly on how Spack's build systems work.

In this guide, we will be using the
`perl <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/perl/package.py>`_ and
`cmake <https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/cmake/package.py>`_
packages as examples. ``perl``'s build system is a hand-written
``Configure`` shell script, while ``cmake`` bootstraps itself during
installation. Both of these packages require custom build systems.

^^^^^^^^^^
Base class
^^^^^^^^^^

If your package does not belong to any of the built-in build
systems that Spack already supports, you should inherit from the
``Package`` base class. ``Package`` is a simple base class with a
single phase: ``install``. If your package is simple, you may be able
to simply write an ``install`` method that gets the job done. However,
if your package is more complex and installation involves multiple
steps, you should add separate phases as mentioned in the next section.

If you are creating a new build system base class, you should inherit
from ``PackageBase``. This is the superclass for all build systems in
Spack.

^^^^^^
Phases
^^^^^^

The most important concept in Spack's build system support is the idea
of phases. Each build system defines a set of phases that are necessary
to install the package. They usually follow some sort of "configure",
"build", "install" guideline, but any of those phases may be missing
or combined with another phase.

If you look at the ``perl`` package, you'll see:

.. code-block:: python

   phases = ["configure", "build", "install"]

Similarly, ``cmake`` defines:

.. code-block:: python

   phases = ["bootstrap", "build", "install"]

If we look at the ``cmake`` example, this tells Spack's ``PackageBase``
class to run the ``bootstrap``, ``build``, and ``install`` functions
in that order. It is now up to you to define these methods.

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Phase and phase_args functions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If we look at ``perl``, we see that it defines a ``configure`` method:

.. code-block:: python

   def configure(self, spec, prefix):
       configure = Executable("./Configure")
       configure(*self.configure_args())

There is also a corresponding ``configure_args`` function that handles
all of the arguments to pass to ``Configure``, just like in
``AutotoolsPackage``. Comparatively, the ``build`` and ``install``
phases are pretty simple:

.. code-block:: python

   def build(self, spec, prefix):
       make()

   def install(self, spec, prefix):
       make("install")

The ``cmake`` package looks very similar, but with a ``bootstrap``
function instead of ``configure``:

.. code-block:: python

   def bootstrap(self, spec, prefix):
       bootstrap = Executable("./bootstrap")
       bootstrap(*self.bootstrap_args())

   def build(self, spec, prefix):
       make()

   def install(self, spec, prefix):
       make("install")

Again, there is a ``boostrap_args`` function that determines the
correct bootstrap flags to use.

^^^^^^^^^^^^^^^^^^^^
run_before/run_after
^^^^^^^^^^^^^^^^^^^^

Occasionally, you may want to run extra steps either before or after
a given phase. This applies not just to custom build systems, but to
existing build systems as well. You may need to patch a file that is
generated by configure, or install extra files in addition to what
``make install`` copies to the installation prefix. This is where
``@run_before`` and ``@run_after`` come in.

These Python decorators allow you to write functions that are called
before or after a particular phase. For example, in ``perl``, we see:

.. code-block:: python

   @run_after("install")
   def install_cpanm(self):
       spec = self.spec

       if spec.satisfies("+cpanm"):
           with working_dir(join_path("cpanm", "cpanm")):
               perl = spec["perl"].command
               perl("Makefile.PL")
               make()
               make("install")

This extra step automatically installs ``cpanm`` in addition to the
base Perl installation.

^^^^^^^^^^^^^^^^^^^^^
on_package_attributes
^^^^^^^^^^^^^^^^^^^^^

The ``run_before``/``run_after`` logic discussed above becomes
particularly powerful when combined with the ``@on_package_attributes``
decorator. This decorator allows you to conditionally run certain
functions depending on the attributes of that package. The most
common example is conditional testing. Many unit tests are prone to
failure, even when there is nothing wrong with the installation.
Unfortunately, non-portable unit tests and tests that are
"supposed to fail" are more common than we would like. Instead of
always running unit tests on installation, Spack lets users
conditionally run tests with the ``--test=root`` flag.

If we wanted to define a function that would conditionally run
if and only if this flag is set, we would use the following line:

.. code-block:: python

   @on_package_attributes(run_tests=True)

^^^^^^^
Testing
^^^^^^^

Let's put everything together and add unit tests to be optionally run
during the installation of our package.
In the ``perl`` package, we can see:

.. code-block:: python

   @run_after("build")
   @on_package_attributes(run_tests=True)
   def test(self):
       make("test")

As you can guess, this runs ``make test`` *after* building the package,
if and only if testing is requested. Again, this is not specific to
custom build systems, it can be added to existing build systems as well.

.. warning::

   The order of decorators matters. The following ordering:

   .. code-block:: python

      @run_after("install")
      @on_package_attributes(run_tests=True)

   works as expected. However, if you reverse the ordering:

   .. code-block:: python

      @on_package_attributes(run_tests=True)
      @run_after("install")

   the tests will always be run regardless of whether or not
   ``--test=root`` is requested. See https://github.com/spack/spack/issues/3833
   for more information

Ideally, every package in Spack will have some sort of test to ensure
that it was built correctly. It is up to the package authors to make
sure this happens. If you are adding a package for some software and
the developers list commands to test the installation, please add these
tests to your ``package.py``.

For more information on other forms of package testing, refer to
:ref:`Checking an installation <checking_an_installation>`.