--- title: Create a `Makefile` author: Franklin Bristow --- Create a `Makefile` =================== ::: outcomes * [X] Create a `Makefile` to accomplish a task (building software, running commands). ::: Dependency management in Python is straightforward with `pip`. While people do distribute Python programs to other people to install on their computers, most of the time we're not *compiling* Python programs. We're going to switch now to taking a look at building software (compiling source code into a runnable program), specifically in the context of building C programs. There are many approaches to building C programs, and we've seen at least one way to do that in this course: ```bash clang my-app.c -o my-app # compile my-app.c ``` This is fine when our program is exactly one file big, but as you design and grow your software, you're very quickly going to find that single-file programs become unwieldy. ::: aside I'm not saying you *can't* have a single file that's got thousands of lines of code, but I am saying that it's a "bad idea"™. ::: In practice you're probably going to be [decomposing] your application and problem into smaller functional units (you're going to have your `main` function in one file, your `gui` code in a bunch of files in a separate folder, your data structures in individual files, ...). You certainly *can* still compile these one at a time, but managing the dependencies between different units quickly gets painful (and tedious): ```bash clang list.c -c -o list.o clang set.c -c -o set.o clang my-app.c list.o set.o -o my-app ### Did I remember to re-compile everything? ``` [decomposing]: https://en.wikipedia.org/wiki/Decomposition_(computer_science) What even is `make`? -------------------- [`make`] is a program that can help you with situations like this: You tell it what things you're trying to compile, how to compile those things, and what each unit depends on, and `make` will figure out the correct order of commands to run to make sure that you're able to get your program out at the end. ::: aside `make` is going through the process of building a [dependency graph]: a set of nodes and directed edges $G = (V, E)$, where the nodes are the compiled outputs and the instructions for creating them, and the directed edges are the way that one compiled output depends on the existence of another compiled output. In the example we had above (`list.o`, `set.o`, and `my-app`), the dependency graph would look something like this: ![A dependency graph for `list.o`, `set.o`, and `my-app`.](dependency-graph.svg) [dependency graph]: https://en.wikipedia.org/wiki/Dependency_graph ::: We fortunately don't have to draw a graph for `make` to create this graph. Instead, we list out "targets" (the things we're trying to build), their dependencies (what this unit depends on), and the commands `make` should use to create that target. This all goes into a special file named `Makefile`. [`make`]: https://en.wikipedia.org/wiki/Make_(software) Running `make` -------------- Re-download or copy `hello.tar.gz`: https://home.cs.umanitoba.ca/~fbristow/hello.tar.gz If you're downloading this file again, you should extract it: ```bash tar -xf hello.tar.gz ``` The folder `hello` has a file named `Makefile`. You can run the command `make` in this directory to build the targets: ```bash cd hello make ``` `make` works by looking for a file in the current working directory that has a special name, [usually `Makefile`] with an upper-case `M`. By default, `make` will find the first "target" in the file (the first thing that appears on the left side of a colon `:`). In `hello`, the first line in the `Makefile` that matches this looks like: ```makefile all: HelloWorld.class hello ccrash ``` Our first "target" here is listing out all of the things we want to build in this `Makefile` as a "dependency". This target is often called `all`, but this is also a "de facto" standard, you can also call this first target `cats` and `make` will behave the same way. Assuming that everything's working, you should see output similar to the following: ```bash [you@bird hello]> make javac HelloWorld.java clang -Wall -Wpedantic -Wextra -Werror -g hello.c -o hello clang -Wall -Wpedantic -Wextra -Werror -g ccrash.c -o ccrash ``` This `Makefile` has a `clean` target, this is another "de facto" standard for a target name that doesn't create anything new but deletes outputs from compilers: ```bash make clean ``` You can also ask `make` to build a specific target by specifying the target name after `make`: ```bash make hello ``` [usually `Makefile`]: https://www.gnu.org/software/make/manual/html_node/Makefile-Names.html Format of a `Makefile` ---------------------- Let's take a look at what `Makefile`s look like in more detail. The `Makefile` in `hello` is slightly more complicated and we'll see all of it soon, but the simplest format for a `Makefile` is straightforward: ```makefile target: dependencies commands ``` This is a **rule**, and the rule consists of a **target**, the target's **dependencies**, and a **sequence of 0 or more commands** that can be used to transform the dependencies into the target. Whatever your stance is on [tabs vs spaces], `make` doesn't care what you think: the whitespace before the `commands` **must** be a Tab (it cannot be spaces). Thankfully most text editors are aware of this, and when you create or open a file named `Makefile`, they will very helpfully put tabs in instead of spaces. ::: example Here's the `Makefile` for our imaginary program above: ```makefile set.o: set.c # set.o depends on set.c clang set.c -c -o set.o list.o: list.c clang list.c -c -o list.o my-app: my-app.c list.o set.o clang my-app.c list.o set.o -o my-app ``` ::: Here are some general [rules of thumb]: * The dependencies listed on the right side of the `:` should generally include your source code (`.c`, `.java`, `.py`). * The targets listed on the left side of the `:` are always going to be what your compiler produces as output (`.class`, `.o`). * The commands should take as input the dependencies, and produce as output the target. [tabs vs spaces]: https://softwareengineering.stackexchange.com/questions/57/tabs-versus-spaces-what-is-the-proper-indentation-character-for-everything-in-e [rules of thumb]: https://en.wikipedia.org/wiki/Rule_of_thumb ### Variables Similar to shell scripts, `Makefile`s can also use [variables]. We're going to look at a specific kind of variable that can be used in rules to help make sure we don't have to name targets and dependencies in our list of commands: [automatic variables]. Here are some of the automatic variables you can use in commands to generate a target from its dependencies: +--------------------+-------------------------------------+ | Automatic variable | Description | +====================+=====================================+ | `$@` | The target's file name. | +--------------------+-------------------------------------+ | `$<` | The name of the *first* dependency. | +--------------------+-------------------------------------+ | `$^` | The name of *all* dependencies. | +--------------------+-------------------------------------+ ::: example Let's convert the `Makefile` above for our imaginary program to use these variables: ```makefile set.o: set.c clang $< -c -o $@ list.o: list.c clang $< -c -o $@ my-app: my-app.c list.o set.o clang $^ -o $@ ``` ::: While this isn't necessarily any easier to read, it does make sure that you're including both the dependencies and the targets in the commands used to transform the dependencies *into* the targets. ::: challenge Convert the `Makefile` from `hello` to use these [automatic variables]. There's only one target specified (`HelloWorld.class`), so that's the only one to change. ::: [variables]: https://www.gnu.org/software/make/manual/html_node/Using-Variables.html [automatic variables]: https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html Using GNU Make to build C programs ---------------------------------- You might have noticed that the `Makefile` in `hello` looks, uh, a little weird: * It clearly compiles some C programs when you run `make`. * The names of the C programs are listed as a dependency in the `all` target. * There are no `clang` commands in this `Makefile` at all. What gives? [GNU Make] knows a lot about C programs (and other languages) in the form of [implicit rules]. One of the implicit rules that GNU Make has is for C programs: ```makefile LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) %: %.c $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@ ``` There's a bunch of unusual looking stuff here, but it's straightforward: * `LINK.c` is a variable definition, and it's a string that gets built up by evaluating many other variables (`CC`, `CFLAGS`, `CPPFLAGS`, ...). * `%: %.c` is a target/dependency pair. The `%` is a *wildcard*, matching anything on the left as a target that has a matching dependency on the right with an extension of `.c` (this is what matches `hello` and `hello.c` in our `Makefile`). * The command to build the target is a bunch of other variables, some of which we've seen (`$^` and `$@`). Let's look at these side-by-side: :::::: columns ::: {.column width=45%} ### The implicit rules and variables in GNU Make ```makefile LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) %: %.c $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@ ``` ::: ::: {.column width=45%} ### Our `Makefile` ```makefile CC = clang CFLAGS = -Wall -Wpedantic -Wextra -Werror -g all: hello ``` ::: :::::: While these two rule sets aren't literally part of the same file, you can imagine that the rules and variables on the left (the stuff that's defined in GNU Make) come first, and the rules and variables on the right (the stuff that's defined in our `Makefile`) come after: ```makefile LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) %: %.c $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@ CC = clang CFLAGS = -Wall -Wpedantic -Wextra -Werror -g all: hello ``` Variables that don't have values will eventually turn into empty strings, and variables that have values (even if they're declared and defined later in the file) will be replace with the values that they're given. Let's step through this: 1. The first rule that has an actual target is our `all` target, so that's the first thing that's evaluated. `all` depends on `hello`. 2. `hello` then matches `%: %c`; it matches the wildcard as a target on the left specifically because there is a file named `hello.c` in this directory (the dependency is satisfied). 3. The command for transforming `hello.c` (the dependency) into `hello` (the target) consists almost entirely of variables (everything *except* `-o`), but `$(LINK.c)` uses `$(CC)` (which we defined as `clang`) and `$(CFLAGS)` (which we defined as `-Wall...`), and uses the special variable `$^` (all dependencies). 4. The command turns into: ```bash clang -Wall -Wpedantic -Wextra -Werror -g hello.c -o hello ``` (the extra spaces between `-g` and `hello.c` are the spaces in the variable `LINK.c` between the other variables, the spaces between `hello.c` and `-o` are from the empty variables between `$^` and `-o`). Neat :camera:. [GNU Make]: https://www.gnu.org/software/make/ [implicit rules]: https://www.gnu.org/software/make/manual/make.html#Catalogue-of-Rules Using `make` to build... stuff ------------------------------ `make` is great at building code, but `make` isn't just about building code, it's about identifying **targets** and satisfying their **dependencies** by running **commands**. Let's walk through an example of using `make` to do *other* stuff, like converting files from one format to another. Gosh. That sounds like something we've done before... Right, let's use `make` to convert files from Markdown to other formats like `pdf` or `html`. Here's a Markdown-formatted document that you can place into your current working directory: ```markdown Hello! ====== I'm a Markdown formatted file. We can write about * Numbers and formulas $\frac{1}{2}$, * Code: `System.out.println` * Or *whatever*. ``` Copy this into a file named `hello.md` in a directory *somewhere*. Now create a new file named `Makefile`; let's step through writing it together: 1. We need to have a target for `all`, and we want that rule to be first. So let's add an `all` target. We'll focus for now on converting our Markdown file to HTML. ```makefile all: hello.html ``` 2. Now we need to add a target for `hello.html` --- `make` doesn't know how to convert `.md` to `.html` in the same way it does `.c` to a compiled program, so let's do that. We want to add a target that matches specifically `hello.html` (we'll get fancier later, let's keep it simple for now). `hello.html` depends on `hello.md` (we're transforming `hello.md` into `hello.html`). ```makefile all: hello.html hello.html: hello.md ``` 3. Once we've got a target, we need to tell `make` how to do that transformation. Beneath our target for `hello.html`, let's add a command to convert it: ```makefile all: hello.html hello.html: hello.md pandoc hello.md --standalone -o hello.html ``` Now run `make` in your directory, and `make` should transform `hello.md` into `hello.html` using `pandoc`. Nice :tada:! Now the important bit: make changes to `hello.md` and re-run `make`. `make` will detect changes in the file (the modified date in the dependency is newer than the created date on the target) and will automatically figure out which dependencies need to be rebuilt. Neat :camera:. Let's use what we've learned to try and turn this into something that can do what we did with shell scripting: 1. Let's switch our command to use the built-in variables for the names of targets `$@` and dependencies `$<`: ```makefile all: hello.html hello.html: hello.md pandoc $< --standalone -o $@ ``` 2. We don't want to give specific names for targets (or dependencies), so let's use wildcards: ```makefile all: hello.html %.html: %.md pandoc $< --standalone -o $@ ``` 3. We don't want to list out the targets for `all`, we can use the [wildcard function]: ```makefile MD = $(wildcard *.md) all: $(MD:md=html) # use the value of MD, but replace `md` with `html` %.html: %.md pandoc $< --standalone -o $@ ``` Now you can add more Markdown files to this directory with any name and the files will be converted to HTML. ::: challenge Look further at having this work for nested directories (you'll have to spend some more time looking at wildcards), and have this work for multiple formats of output (either by making new targets with new extensions, or by [accepting variable arguments to `make`]). [accepting variable arguments to `make`]: https://www.gnu.org/software/make/manual/html_node/Overriding.html ::: [wildcard function]: https://www.gnu.org/software/make/manual/html_node/Wildcard-Function.html Running `make` concurrently --------------------------- One final and minor thing to add is that `make` can try to build targets that are unrelated *concurrently*. Your computer has a processor with more than one core, and each core can be used by `make` to build unrelated dependencies for targets. Here's the dependency graph we had from above: ![A dependency graph for `list.o`, `set.o`, and `my-app`.](dependency-graph.svg) When `make` builds `set.o` and `list.o`, it can build them literally at the same time because they don't have common dependencies. We can tell `make` to run on multiple CPUs using the option `-j` for **j**obs. ::: example You can pass `-j` to `make` for *any* `Makefile`, and if `make` identifies targets it can build independently of other targets, it will do that! ```bash make -j10 # run 10 jobs concurrently (if possible) ``` This is helpful when you're building things that aren't trivial, or if you're using `make` to transform files from one format to another where that transformation is expensive (e.g., converting audio or video from one format to another, converting images between formats). ::: warning Don't set `-j` higher than the number of CPUs you have installed on the system. A command you can use to find out how many CPUs you have installed on a Linux system is `nproc`, and you can use that in conjunction with `make`: ```bash make -j$(nproc) ``` ::: ::: Further reading --------------- There's a lot here, and we're just barely scratching the surface. You can find more information about `make` in a few different places: * If you really just want a place where you can quickly find, copy, and paste a `Makefile` that will work for the situation that you want, or if you're looking for a more in depth tutorial on how to build a `Makefile`, look no further than . * The [GNU Make documentation] is... comprehensive, it's about 1MB (or one million characters) of documentation about how to use `make`. It's a lot, but if you have a question, you can almost certainly find the answer there. [GNU Make documentation]: https://www.gnu.org/software/make/manual/