What is a C Makefile?
A Makefile is a text file that guides the build tool
make on how to compile code. It contains a series of rules and instructions that define how to compile source codes to final executable file. For example, which compiler to use for compilation? (we commonly use gcc to build a C program or g++ to build a C++ program on Ubuntu Linux), what compiler flags to use?, For C language projects, a Makefile typically includes a list of source files, compiler and linker commands, and the steps to produce the final executable.
On a Unix-based operating system like Ubuntu, we normally build a C program with the
make command, which reads the Makefile and compile the final software executable with the specified compiler,
In this section, I will briefly describe the syntax of a basic Makefile to get you familiar with the basics. Below is an example of a simple Makefile:
MYPROG=myprogram CC=gcc CFLAGS=-g -O0 -Wall MYOBJS= myfuncs.o myprogram.o all: $(MYPROG) $(MYPROG): $(MYOBJS) $(CC) $(CFLAGS) $(MYOBJS) -o $(MYPROG) myprogram.o: $(CC) $(CFLAGS) -c myprogram.c myfuncs.o: $(CC) $(CFLAGS) -c myfuncs.c clean: rm -rf $(MYPROG) $(MYOBJS)
C Makefile Basics: Variable Definitions
You can define a variable in Makefile using
[name]=[value] syntax. These variables can be referenced using $(name) syntax. The above example defines the following variables.
MYPROG=myprogram CC=gcc CFLAGS=-g -O0 -Wall MYOBJS= myfuncs.o myprogram.o
The Make Target
When we issue the
make command, we have an option to specify a target. For example:
# specify "myprogram" as the target and execute it $ make myprogram # specify "clean" as the target and execute it $ make clean
Without specifying a target in the
make command, the
all target is selected by default:
# target "all" is selected by default $ make
It is your responsibility to make sure the Makefile contains the definitions of targets. If a target is not defined, the
make command will give you “no rule to make target” error.
Define Make Target in C Makefile
A target can be specified like this:
# Define a build target with this syntax: [name of target]: [dependencies] [put your build commands here]
The Makefile example on the top defines 5 build targets:
all: $(MYPROG) $(MYPROG): $(MYOBJS) $(CC) $(CFLAGS) $(MYOBJS) -o $(MYPROG) myprogram.o: $(CC) $(CFLAGS) -c myprogram.c myfuncs.o: $(CC) $(CFLAGS) -c myfuncs.c clean: rm -rf $(MYPROG) $(MYOBJS)
When user issues the “
make” command without selecting a target (defaults to “all”), the Makefile execution can be understood as:
- Target “
all” has a dependency on $(MYPROG), which translates to “myprogram”. This means that “myprogram” runs first because “all” depends on it.
- Target “
myprogram” has dependency on $(MYOBJS), which translates to “myfuncs.o myprogram.o”. This means that “myfuncs.o” target will run first, and then “myprogram.o” runs second because “myprogram” depends on them.
- Target “
myfuncs.o” has no dependencies, and it needs to run the command: “$(CC) $(CFLAGS) -c myfuncs.c”, which translates to “gcc -g -O0 -Wall -c myfuncs.c” to produce an object file call “myfuncs.o”.
- Target “
myprogram.o” has no dependencies, and it needs to run the command: “$(CC) $(CFLAGS) -c myprogram.c”, which translates to “gcc -g -O0 -Wall -c myprogram.c” to produce an object file call “myprogram.o”.
- Once “myfuncs.o” and “myprogram.o” have completed, we are back at “
myprogram” target, and it needs to run the command “$(CC) $(CFLAGS) $(MYOBJS) -o $(MYPROG)”, which translates to “gcc -g -O0 -Wall myfuncs.o myprogram.o -o myprogram” to produce the final executable called “myprogram”.
Likewise, when user issues the “
make clean” command, the “clean” target gets invoked and run the command “rm -rf myprogram myfuncs.o myprogram.o”.
We have gone through the basic fundamentals of a Makefile that you most likely will write one to build your C program on Ubuntu. In large C software project, especially open source projects, makefile structure tends to be very complex and large. In such cases, a Makefile is automatically generated by
autotools based on User’s environment and selection of components to build, but yet they still follow the same fundamentals mentioned in this section.