# Advanced Build Systems / cmake

Today's assignment: <https://classroom.github.com/a/2XbwIK5D>

## Introduction

The `make` tool that you've got introduced to is most definitely a legacy tool.
It primarily focuses on one single thing, that is, given proper dependency
information, it will rebuild all the needed parts, to update the target(s)
requested.

It handles some more basics of adapting/customizing the build process to fit a
particular environment, but it's quite lacking as a complete build system.

Historically, in the open source software world, the
[autotools](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html)
were developed as a layer on top of `make` to fix many of its shortcomings. If
you install software from source you may well come across it (on the user side)
-- here's an
[example](https://github.com/UNH-HPC-2026/iam851/raw/refs/heads/main/hello-0.02.tar.gz).

```sh
[kai@macbook class.wiki (master)]$ tar zxvf hello-0.02.tar.gz
[kai@macbook class.wiki (master)]$ cd hello-0.02
[kai@macbook hello-0.02 (master)]$ ./configure
[...]
[kai@macbook hello-0.02 (master)]$ make
/Applications/Xcode.app/Contents/Developer/usr/bin/make  all-am
  CC       hello.o
  CC       greeting.o
  CC       factorial.o
  CCLD     hello
```

On the other hand, modern IDEs like Microsoft Visual Studio or Apple's Xcode
typically provide their own ways of building projects. These are much more
advanced, but often proprietary and not portable.

## cmake

[cmake](http://cmake.org) is an alternative to the autotools, which has been
gaining in popularity. Its goals are rather similar to the autotools, but the
underlying technology is quite different.

The main goal is to enable the developer to provide code that will work on
different platforms without having to customize the build and related tasks for
each system. The autotools are built on top of traditional Makefiles, while
cmake is more general and has a number of choices for its backend -- one can
just use regular Makefiles again, but one can also use IDEs like Microsoft
Visual Studio or Apple's XCode.

The autotools are built using existing languages (shell, m4, perl in the
background), while cmake has its own DSL (domain specific language) to describe
what to build and how.

### Building an executable

We will now build a simple project (the `hello` project yet again) using cmake. We'll do the basics here, but
you it'll be helpful to look at the
[cmake tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html)
for going into more depth.

First of all, before we start using a new way of building code, let's clean
house.

#### Your turn

We're still working on the same code example (last time -- I promise :wink:). I copied my version of the code, built by a make, into this class' assignment repo. Since the `Makefile` file is committed to git, it'll be there in the history forever, if one ever
wanted to revisit it. But to move on, let's remove it, and also remove any
object files, executables, etc., you may still have lying around.

Removing them can be done in your IDE / git GUI frontend, or if you like to do
things on the command line:

```sh
vscode ➜ /workspaces/class-5 (work) $ git rm Makefile 
rm 'Makefile'
vscode ➜ /workspaces/class-5 (work) $ rm *.o hello
vscode ➜ /workspaces/class-5 (work) $ ls
factorial.c  greeting.c  hello.c  hello.h  README.md
vscode ➜ /workspaces/class-5 (work) $ git commit -m "Remove Makefile and built files"
```

#### Adding `CMakeLists.txt`

Instead of a `Makefile`, cmake uses another file that provides info on what to
build, it's called `CMakeLists.txt`.

```
cmake_minimum_required(VERSION 3.16)
project(hello LANGUAGES C)

# Set C standard
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)

# Create the executable
add_executable(hello
  hello.c
  greeting.c
  factorial.c
)
```

This is mostly boilerplate code. Usually, I just copy an existing `CMakeLists.txt` file from another project and modify it as needed. This time, I actually asked my editor's AI to create one for me, and the above is what I got. It's pretty much the same I had usually created myself, except that it added some lines to make sure we're using C99 as the C standard, which is a good thing.

So let's go through it line by line.

The first line states the minimum required cmake version. Here I
version 3.16, so that if someone uses, e.g., an ancient cmake 2.8, they'll get a
useful error rather than all kinds of things going wrong.

The project is then given a name (which for now isn't really being used).

As I said above, it added some lines to set the C standard to C99. This is
generally a good idea, as different compilers have different default standards, and having different users compile the same code with different standards can lead to subtle inconsistencies.

Finally, the last lines do the real work. This command says that you want to add an executable to the project.
That executable is named `hello`, and it's to be built from the three `.c`
sources stated.

### Out-of-tree builds

cmake supports "out-of-(source-)tree" builds. They're optional, but you should definitely
get in the habit of using them. That is, instead of putting all kinds of object
files, executables, etc in the same directory as your sources, you can have all
that stuff happen in a separate directory.

#### Your turn

- Create the `CMakeLists.txt` file with the contents shown above, and use it to
  build the code. It should go as shown below. Note that I'm first creating a
  separate build directory, and then call cmake and do the build from there,
  which is much preferred over building the code directly in the same directory
  as the sources. Once you got it working, commit your work. You do not want to
  commit the actual build files in the `build/` directory, though, just the
  `CMakeLists.txt` (and other changes you made to the code, if any.)

```sh
vscode ➜ /workspaces/class-5 (work) $ mkdir build
vscode ➜ /workspaces/class-5 (work) $ cd build
vscode ➜ /workspaces/class-5/build (work) $ cmake -S ..
-- The C compiler identification is GNU 13.3.0
-- The CXX compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.5s)
-- Generating done (0.0s)
-- Build files have been written to: /workspaces/class-5/build
vscode ➜ /workspaces/class-5/build (work) $ make
[ 25%] Building C object CMakeFiles/hello.dir/hello.c.o
[ 50%] Building C object CMakeFiles/hello.dir/greeting.c.o
[ 75%] Building C object CMakeFiles/hello.dir/factorial.c.o
[100%] Linking C executable hello
[100%] Built target hello
vscode ➜ /workspaces/class-5/build (work) $ ./hello 
Hi there, class!
10 factorial is 3628800
```

[As may have happened before, as we're using more tools, I'm kinda just hoping
that they are present on your system. But actually, these days, even for Linux,
most people using Linux aren't programmers, so often those development tools
aren't installed by default. On Ubuntu you may see the helpful message `To
install cmake, use the command "sudo apt install cmake"`. On Mac OS, one can
certainly find all these packages in many variants on the web, but I recommend
using either <https://macports.com> or <https://brew.sh> as package managers -- once
those are set up, installing a package is typically as simple as `sudo port
install cmake`.]

So what happens is that you create and change into a separate directory called
`build/` here. You then run `cmake -S ..` (or just `cmake ..`), which invokes `cmake` and tells it to
find the sources into the parent directory `..`. What cmake does is,
essentially, to create a custom Makefile, so you don't have to write it
yourself. Then you just call `make` and that'll build your code. From this point
on, as you develop / debug your code, you only need to call `make` again. As you
can see, it figured out what compiler to use, and it also handles dependencies
on header files automatically.

Another useful feature of `cmake` is that adds a `clean` rule automatically.
(When using plain Makefiles, people often add one manually). It cleans up things
-- more specifically, it removes generated files, so one doesn't have to do
`rm *.o hello` or something like that by hand.

`cmake` by default doesn't actually show the details of the commands that it
runs, avoiding clutter. But sometimes, one might actually want to know what
exactly is going on (in particular, when something is going wrong). In that
case, `make VERBOSE=1` is useful.

Actually, `cmake` supports many different build systems as backends. If you want
to be generic, you might want to get in the habit of saying "`cmake --build .`" instead of just `make`. This will use whatever build system cmake generated files for. On Linux, that's usually Makefiles, but on Windows, it might be Visual Studio project files, etc.

### Building a library

While not exactly called for here, in this simple example code, I'd like to
introduce the concept of (software) libraries. In a nutshell, a library is a
collection of various routines that are packaged together and where one can
easily pull out those that one wants to use (and one doesn't have to do it
manually -- the linker will do it). A very commonly used example is the C
standard library, which contains functions like `printf`, `fopen`, and hundreds
others. Whenever you use `printf` in your program, the compiler/linker will just
pull out the actual `printf` code from the library and link it in.

There are numerous third-party libraries, and since it's generally a good idea
to avoid reinventing the wheel, we'll use some as we go. Sometimes building
libraries is also a good way of organizing your own code. Let's pretend the
`factorial()` and `greeting()` functions are actually useful and you want to
package them in a library, called `libstuff`. This is how to change your
`CMakeLists.txt`:

```
cmake_minimum_required(VERSION 3.16)
project(hello LANGUAGES C)

# Set C standard
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)

# It's not a real meaningful library, so I'm not giving it a real
# meaningful name ("stuff") either...
add_library(stuff greeting.c factorial.c)

add_executable(hello hello.c)
target_link_libraries(hello PRIVATE stuff)
```

`add_library` is very similar to `add_executable`, it's just that it creates
library rather than an executable from the provided sources. In order to use a
library (`hello` needs it), one uses `target_link_libraries`.

#### Your turn

Make the changes above to build a library containing the `greeting()` and
`factorial()` functions. Explain the commands that are run. Make sure everything
still works.

### IDE support for cmake

Most modern IDEs have support for cmake projects. For example, in VSCode, if you
have the CMake Tools extension installed, you can just open the folder
containing the `CMakeLists.txt` file, and it will automatically detect it as a
cmake project. You can then configure, build, and run your project directly from
within VSCode, without having to use the command line.

### Header files

Unfortunately, given C/C++'s somewhat clunky handling of header files, cmake still needs help to properly associate the header files with a library it builds and provides to other targets. In the example above, the header file `hello.h` is just located in the current directory, so it'll be found without further intervention (because of the `#include "hello.h"` syntax, rather than `#include <hello.h>`. But for the record, one should associate the library with the header files that it needs using

```
 It's not a real meaningful library, so I'm not giving it a real
# meaningful name ("stuff") either...
add_library(stuff greeting.c factorial.c)
# the header files need to be made available to users of the library
# In this simple layout, they are in the same directory as the sources
target_include_directories(stuff PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
```

Things get yet more complicated if one wants to build and install a library for other, separate, codes to use. But that's beyond the scope of this class. [See here for more, in particular the `BUILD_INTERFACE` vs `INSTALL_INTERFACE`](https://cmake.org/cmake/help/latest/command/target_include_directories.html).

## Homework

- *(by Thu)* If you don't already know them, figure out the scoring rules for the game of
  ten-pin bowling (spares, strikes, etc).

- If you haven't done so yet, update your wiki page from class 1 with some more formatting / picture or other [advanced formatting](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting).

- I told you that cmake will automatically take care of header file
  dependencies, even if the header files (like `hello.h`) are never even
  mentioned in `CMakeLists.txt`. Come up with a plan to verify that this is in
  fact the case, and try it.

- (A bit of a challenge) Figure out how to build a C++ program. You might want to use the C++ version of the `hello` program from class 3/4, or if you have anything more interesting, feel free to use that. Do this work in a new subdirectory of your assignment repository, e.g., `cxx/`. Create a proper `CMakeLists.txt` file to build it. You can look into cmake's
  `add_subdirectory()` to set it up in a way where both the C and the C++ code get built as part of a single project.

- To submit the homework, as before, push (sync) it back to github, and add
  comments to do Feedback PR (pull request).

- For more info on pull requests, this is a nice write-up:
  <https://medium.com/@urna.hybesis/pull-request-workflow-with-git-6-steps-guide-3858e30b5fa4>

