HPC Tutorials has a whole chapter on Make and another on CMake; use them as references if you have syntax questions or would like to go deeper.

This video gives a quick overview of how to use Make and CMake:

GNU Make

GNU Make is a filesystem-based tool used to help build or make things. A makefile is a set of rules. Each rule specifies a target, its prerequisites, and the recipes, which are the instructions on how to build the target.

target: prereq1 prereq2
    recipe

Makefiles are typically named “Makefile” (no extension). If a makefile is named something other than Makefile, use the -f flag to identify it, e.g.: make -f Linux-clang.make.

Makefiles require tabs. You cannot use leading spaces instead; you may need to configure your editor not to expand tabs when working with Makefiles. It will error which often looks like this:

Makefile:31 *** missing separator.  Stop.

When make builds a target, it will recursively build prerequisites. If a prerequisite is changed and make is called again, it will try to only build the pieces that have been affected by the changes.

A particular target can be built by running make <target> (e.g. make all). If make is invoked without a target, it will try to build the first rule that does not begin with a period.

Here’s a Makefile that could work for phase 2, assuming that your WaveOrthotope class is in a header and that you’re also building the original wavesolve from phase 1:

.PHONY: all

all: wavesolve wavesolve_serial

wavesolve: wavesolve.cpp WaveOrthotope.hpp
wavesolve_serial: wavesolve_serial.cpp WaveOrthotope.hpp

Implicit Rules

Make has a catalogue of implicit rules; here is one for C++ files:

n.o is made automatically from n.cc, n.cpp, or n.C with a recipe of the form ‘$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c’. We encourage you to use the suffix ‘.cc’ for C++ source files instead of ‘.C’.

This is how the previous example avoided writing recipes. The $(CXX), $(CPPFLAGS), and $(CXXFLAGS) parts are how CMake does variables. By default CXX is set to g++ while CPPFLAGS and CXXFLAGS are empty.

We can hook into these variables present in the implicit rules, for example to set the C++ version, by putting this at the top of the file:

CXXFLAGS += "-std=c++14"

The += means that the right-hand-side will be appended to the variable instead of setting it.

Food for thought: what if my compiler uses a different option for that same thing (e.g., -std=c++17 or maybe /Ostd)?

Linking

GNU Make also has variables for hooking into the linker:

  • LDFLAGS: specifies the search paths for the linker (e.g. -L/apps/libevent/2.0.22/lib)
  • LIBS: Specifies libraries to link (e.g. -levent)

As an example, if you want to link against /my/libs/foo.so, you could use LDFLAGS += "-L/my/libs" and LIBS += "-lfoo".

Cleaning Up

It is good practice to add a clean PHONY target to a Makefile which removes files generated by the build system. For instance, executables, libraries, and object files should be removed. For the sample makefile for phase 2, it might look like this:

.PHONY: clean
clean:
    $(RM) wavesolve
    $(RM) wavesolve_serial

CMake

Note: In order to use cmake on the super computer, first run module load cmake.

CMake manages the build process in an operating system and compiler independent manner–it aims to run anywhere, including Windows, unlike GNU Make which typically runs only on Linux-like systems.

This means that CMake allows developers to build software independent of platform and compiler. For example, on Windows, the Intel C++ compiler uses /Qstd=c++17 to specify the C++17 standard, while on Linux, the synonymous flag is --std=c++17. CMake abstracts away these differences so that developers can avoid the headache of keeping track of such differences.

CMake solves these problems by declaring features and properties in a named way and then using generators to create the correct flags. For example:

CMakeLists.txt

cmake_minimum_required(VERSION 3.19)
project(Example CXX) # takes a list of languages

add_executable(Example example.cc)

Building a CMake Project

Here is an example directory. CMake is designed to do out-of-tree builds. In some CMake projects, you can’t even do it as part of the subtree and you need to do it elsewhere, such as /tmp. This keeps the area containing the source code nice and clean.

$ tree .
.
| -- CMakeLists.txt
` -- example.cc

$ mkdir build
$ cd build
$ cmake ..
-- The CXX compiler identification is GNU 13.2.0
...
-- Build files have been written to: /tmp/build

By default, this will create a Makefile in Linux (in addition to supporting files):

$ tree -L 2 -C ..

.
|-- build
|   |-- CMakeCache.txt
|   |-- CMakeFiles
|   |-- cmake_install.cmake
|   `-- MakeFile
|-- CMakeLists.txt
`-- example.cc

$ make
Scanning dependencies of target Example
[  50%] Building CXX object
CMakeFiles/Example.dir/example.cc.o
[100%] Linking CXX executable Example
[100%] Built target Example

$ ./Example
Hello, World

Cleaning Up

CMake doesn’t have an equivalent of make clean, so you should remove everything from the build directory and run cmake afresh whenever you update CMakeLists.txt. If you fail to do so, the updates you made to CMakeLists.txt may not be fully reflected in your build configuration.