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”.
Summary
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.
Recommended Reads
- How Does a Compiler Work? That is the Question
- Setting up Eclipse IDE for C on Ubuntu Linux Made Simple
Hi, this is Cary, your friendly tech enthusiast, educator and author. Currently working as a software architect at Highgo Software Canada. I enjoy simplifying complex concepts, diving into coding challenges, unraveling the mysteries of software. Most importantly, I like sharing and teaching others about all things tech. Find more blogs from me at highgo.ca
Pingback: Easy Config File Management with libconfig Tutorial in C - techbuddies.io