Reading Assignment: All of Programming Ch37.1 Build Tool: make
Useful Resources:
For large projects, we typically want to recompile only the files that have actually changed, or those that depend on a file that has change(e.g. those that include
a header file which has changed). We could try to track this manually and only recompile those files. That’s why we introduce make
to manage the task for us.
The input to make is a Makefile
, which contians one or more rules that specify how to produce a target from its prerequisites. For, example:
1 | rectangle: rectangle.c |
In this example, rectangle
in the first line is the target specification, rectangle.c
is the lists of prerequisites, in this case, this file is the only one prerequisite, if you have many, it should be separated with a single space. In the second line, there is the command required to rebuild that target from the prerequisites. The commands may appear over multiple lines, however, each line must begin with a TAB
character.
When you run make
, you can specify a particular target to build, it will automatically build the first target if you have many. To build the target, make
will first check if is up-to-date. Then make
checks if the target needs to be (re)built. If any dependency is new that the target file, then the target is out-of-date, and must be rebuilt.
Variables
You could put the compiler options in a variable, and use that variable in each of the relevant rules.
1 | CFLAGS=-std=gnu99 -pedantic -Wall |
Clean
It is a target intended to remove the compiled program, all object files, all editor backups, and any other files that you might consider to be clustery.
Usage:
- force the entire program to be rebuilt
- clean up the directory
We might add a clean
target to our Makefile
as follows:
1 | .PHONY: clean |
Note that the .PHONY
: clean
tells make
that clean
is a phony target, which means we don’t actually expect it to create a file called clean
, nor would we want to consider it up to date and skip its commands if a file called “clean” already existed. If we want other phony targets, we would list them all as if they were prerequisites of the .PHONY
target.
Generic Rules
In make
, we can write generic ruls. We can build (something).o
from (something).c
.
1 | CFLAGS=-std=gnu99 -pedantic -Wall |
It specifies how to make a file ending with .o
from a file of the same name, except with .c
instead of .o
. We have to use the special built-in variable $<
, which make
will set to the name of the first prerequisite of the rule.
However, there is a significant problem here. We have made it so that our object files no longer depend on the relevant header files. If we were to change a header file, then make
might not rebuild all of the relevant object files. We could make every object file depend on every header file, however, it will rebuild everything we need to when we change a header file, because we would rebuild every object file, even if we only need to rebuild a few.
We can fix this by adding extra dependency information to our Makefile
:
1 | # This fixed the problem |
Managing all of the dependency information by hand would be tedious and error-prone, and we also have to figure out every file which is transitively included by each source file, and keep the information up to date as the code changes. Therefore, makedepend
which will eidt Makefile
to put all of this information at the end comes. See man makedepend
page for more details.
Built-in Generic Rules
Some generic rules are so common that they are built into make
, and we do not even have to write them. We can see all of the rules(including both those that are built-in and those that are specified by the Makefile
) by using make -p
. Doing so also builds the default target as usual. If we want to avoid building anything, we can do make -p -f /dev/null
to use the special file /dev/null
as our Makefile
.
Built-in Functions
We can use some of make
‘s built-in functions to automatically compute the set of .c
files in the current directory and then to generate the list of target object files from that list. The syntax of function calls in make
is $(functionName, arge1, arg2, arg3)
. We can use the $(wildcard pattern)
function to generate the list of .c
files in the current directory: SRC = $(wildcard *.c)
. Then we can use the $(patsubst pattern, replacement *.c)
. Then we can use the $(patsubst pattern, replacement, text)
function to repalce the .c
endings with .o
endings: OBJS = $(patsubst %.c, %.o, $(SRCS))
. Once we have done this, we can use $(SRCS)
and $(OBJS)
in our Makefile
.
1 | # Multiple .o file(Using the pattern substitution) |
Parallelizing Compilation
If you give make
the -j
option, it requsts that it run as many tasks in parallel as it can. On large projects, this many make a significant difference in how long a build takes.
1 | -j8 # runs up to 8 tasks in parallel |
…And Much More
In fact, you can use make
for pretty much any task that you can describe in terms of creating targets from the prereuisites that they depend on. For most such tasks, you can put the parallelization capabilities of make
to good use to speed up the task.