.. _sec:makefiles:
*********
Makefiles
*********
We'll look at `GNU Make `_ to
manage building projects that are split across multiple files. Make
allows us to specify rules for how to compile and link the code.
.. note::
A popular alternative to GNU Make is `CMake `_.
We'll use this to manage our :ref:`planets + multiple files `
example.
A first ``GNUmakefile``
=======================
Here's a basic makefile, which we'll name ``GNUmakefile``:
.. literalinclude:: ../../examples/multiple_files/GNUmakefile
:language: make
:caption: ``GNUmakefile``
There are a few different types of lines, but the most important are
the rules that are of the form::
target: dependencies ...
commands
...
.. note::
make uses a tab character to indent the commands, not spaces.
For example, the rule:
.. code:: make
planet_sort_split.o: planet_sort_split.cpp planet.H
g++ -c planet_sort_split.cpp
says that ``planet_sort_split.o`` depends on the files
``planet_sort_split.cpp`` and ``planet.H``. And the rule to make it
is ``g++ -c planet_sort_split.cpp``. By specifying the dependencies,
``make`` knows whether it needs to recompile a file by looking at
whether its dependencies changed.
It is common to put a target called ``ALL`` at the top, since by
default ``make`` will try to build the first target it encounters.
We build the project by doing:
.. prompt:: bash
make
.. admonition:: try it...
If you type ``make`` again, it doesn't do anything, since none of the source
files changed, so it does not need to update the build.
If you modify one file, e.g.,
.. prompt:: bash
touch planet.cpp
and then do make again:
.. prompt:: bash
make
Then it will only execute the rules that depend on the file that
changed.
Generalizing our ``GNUmakefile``
================================
Our first makefile required us to write a rule for every ``.cpp`` file
explicitly. Instead, we can use `pattern rules
`_
and `wildcards
`_
to automate a lot of the logic.
Here's the improved ``GNUmakefile``:
.. literalinclude:: ../../examples/multiple_files/GNUmakefile.new
:language: make
:caption: new ``GNUmakefile``
The first thing we do is find all of the sources and headers:
.. code:: make
SOURCES := $(wildcard *.cpp)
HEADERS := $(wildcard *.H)
The ``:=`` operator evaluates these expressions immediately when
``make`` encounters them and stores the results in the variables
``SOURCES`` and ``HEADERS``. To reference the list of files in
these variables, we use ``${}``, e.g., ``${SOURCES}``.
The next trick is that we create a list of object files by automatically
replacing the ``.cpp`` extension with a ``.o`` extension via the ``make``
expression:
.. code:: make
OBJECTS := $(SOURCES:.cpp=.o)
Finally, we have a generic rule to compile a C++ file:
.. code:: make
%.o : %.cpp ${HEADERS}
g++ -c $<
Note that we make every header file a dependency for every object
file, whether or not it is included in the source file. This is not
necessary, but simplifies how we specify the dependencies.
We use a make `automatic variable
`_,
``$<`` to indicate the first name in the dependencies.
We use another automatic variable in the final rule, ``$@`` to indicate
the name of the target, to be used as the executable name:
.. code:: make
planet_sort_split: ${OBJECTS}
g++ -o $@ ${OBJECTS}
Going further
=============
There are more things we can do to generalize our makefile.
* We can use `g++ to automatically determine dependencies `_. Also see the `GNU make automatic prerequisites `_.
* We can have the makefile create code at build time, for instance, writing
a function that contains the current git hash of the code
* We can different levels of build: optimized and debug
.. https://slashvar.github.io/2017/02/13/using-gnu-make.html
.. http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/
.. https://stackoverflow.com/questions/66118766/what-is-include-in-gnu-make-and-how-it-works
.. admonition:: try it...
Modify the ``GNUmakefile`` to add the warning flags we discussed earlier to
the compilation of each source.
.. tip::
A common target in makefiles is ``clean``, which will delete the intermediate
``*.o`` files and the executable.
.. tip::
Here's a `GNUmakefile `_
I use with some of the in-class examples to build several executables in the same directory.