Skip to content

make

The program make and Makefiles are a simple way to organize code compilation. This tutorial offers a very basic idea of what is possible using make.

Prerequisites

For this tutorial, please run

1
2
module load powertools
getexample makefile_example2

This will create a directory makefile_example2 with a main program hello.c, a function hellofunc.c, and an include file hello.h.

Compiling by hand

To compile this code, you would use the following command:

1
gcc -o hello hello.c hellofunc.c -I.

This command compiles the two C files, and names the executable hello. With the -I. flag, gcc will look in the current directory for the include file hello.h. For future steps, please remove the executable with rm hello.

With only two C files, it is easy to compile with the above approach, but with more files, it is harder to keep track of everything. In addition, if you are only making changes to one C file, the above approach recompiles all of C files every time which is time-consuming and inefficient.

Transferring to a Makefile

A Makefile will be helpful for such cases. A simple Makefile is included in the getexample, so before we start, please move it to makefile.bak with mv makefile makefile.bak.

Now, in the makefile_example2 directory, create a file called makefile which has the following two lines:

makefile
1
2
hello: hello.c hellofunc.c
    gcc -o hello hello.c hellofunc.c -I.

Here, the tab must actually be a tab character, not spaces.

Now, type make on the terminal and check if the executable is created. The make command will execute the compile command as you have written it in the Makefile.

Note that invoking make with no arguments executes the first rule in the file. Furthermore, by putting the list of files on which the command depends on the first line after the ':', make knows that the rule hello needs to be executed if any of those files change.

Useful Makefile variables

Can we make it a little bit more efficient? Let's modify makefile like so:

makefile
1
2
3
4
CC=gcc
CFLAGS=-I.
hello: hello.o hellofunc.o
    $(CC) -o hello hello.o hellofunc.o

In this Makefile, we define the variables CC and CFLAGS, which are special macros communicating to make how we want to compile the files hello.c and hellofunc.c. In particular, CC is for the C compiler, and CFLAGS is the list of flags to pass to C compiler.

By putting the object files (hello.o and hellofunc.o) in the dependency list and in the rule, make will automatically compile the .c files individually into object files, and then build the executable hello. If your project is small (just a few files), this form of Makefile is enough.

Setting up dependencies

However, there is a problem. This Makefile misses the include files. For example, if you made a change to hello.h, make would not recompile the .c files, even though the change in hello.h may affect them. In order to fix this problem, we need to tell make that all .c files depend on certain .h files. This can be done by writing a simple rule and adding it to the Makefile.

makefile
1
2
3
4
5
6
7
8
9
CC=gcc
CFLAGS=-I.
DEPS = hello.h

%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

hello: hello.o hellofunc.o
    $(CC) -o hello hello.o hellofunc.o

This addition first creates the macro DEPS (the macro name can be anything), which is the set of .h files on which the .c files depend.

Then we define a rule for all .o files. The rule says that each .o file depends on

  • the .c file with the same name, and
  • the .h files which are included in DEPS.

Next, the rule says that to generate the .o file, make needs to compile the .c file using the compiler defined in CC. The components are described as follows:

  • the -c flag says to generate the object file,
  • the -o $@ says to put the output of the compilation in the file named on the left side of the : (in this case, the .o file),
  • the $< is the first item in the dependencies list (in this case, the .c file),
  • and the CFLAGS macro is defined on the second line.

Generalizing

To simplify the final rule, you can use special macros $@ and $^, which are the left and right sides of the :, respectively. This also generalizes the rule to work for multiple files at once.

In the example below, all of the include files should be listed as part of the macro DEPS, and all of the object files should be listed as part of the macro OBJ.

makefile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CC=gcc
CFLAGS=-I.
DEPS = hello.h
OBJ = hello.o hellofunc.o

%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

hello: $(OBJ)
    $(CC) -o $@ $^ $(CFLAGS)

Further resources

Now you have a good sense of the Makefile. For more information on Makefiles and the make function, check out the GNU Make Manual.

You can download some other Makefile examples on the HPCC using getexample.