In this blog post I will try to explain to you the anatomy of a typical make file, as most of the new comers to Unix/Linux arena often find it difficult comprehend the philosophy behind the make & they often do get imitated by its weird looking syntax and structure. We’ll first describe why a make is needed and then I will highlight the basic structure and syntax of a make file and will then we will wrap up the post by presenting some examples to make your concepts clear.
The case for make
Make is a tool that is used to control the build process of your software.
Make automates what is built, how it gets built, when it gets built etc. A makefile is a place where you store all of your commands to build the software.
Similar concept exists in the Java world in shape of Ant and more recently Maven that even provide more functionality and flexibility to compile, test & even deploy your applications through simple xml files.
Suppose you have multiple source files that require long & complex compiler invocations, “make” simplifies this by storing these commands in a single makefile so that they can be managed from a single location. You can also specify which files & directories you want create and delete based upon certain circumstances. Like for example you want all your library files to be compiled into the lib folder of the installation directory. “Make” also makes it easier for other people who want to compile and execute your application; in place of creating huge documentation on how to build your software you can just tell them to run make & it would take care of the rest.
Finally as the final argument in favor of make I think it really speeds up the build process of your application. Make is intelligent enough to recognize which files have changed and thus only builds those files whose components have changed. Plus makefiles also constitute a database of dependency information allowing you to automatically verify that all the files for the build are present.
Anatomy of a MakeFile
A make file is a simple text file containing rules that tell make what to build and how to build it. A makefile consists of number of rules, at its basic the general structure of make looks likes this:-
# Comments use the hash symbol
Target : dependency [dependency] [ … ]
Target is what we are trying to build a binary or an object file. Dependency is a list of one or more files required for the compilation, commands are the steps either compiler invocations or shell scripts required to create the target.
The spaces before commands are TABS & should be used as such, empty spaces would not suffice in their place & you would get the missing separator error. This is the most common problem people new to make get stucked with.
Example of a Make File
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
rm edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o
Two long lines are split into two by using backslash-newline. This make file will create an executable “edit”.
To compile the program simply just type make on the command prompt.
This make file has 10 rules. The first target edit is the default target which is the binary we are trying to create. Edit has eight dependencies from main.o to utils.o, all of these files must exist to create the edit binary. The next line is the command that make executes to actually create the binary. Here make would actually build the executable from all these object files that are named .o.
When make encounters a dependency and it does not exists make executes the command to build it & if the dependency has further dependencies make tries to find them & if they does not exist make tries to build them using the command specified and so likewise make is able to traverse through all of the dependencies if present and build the software smoothly.
Clean target as you can see is the simple command that removes all the object files and as well as the final binary using the rm command.
A phony target is one that is not really the name of a file. It is just a name for some commands to be executed when you make an explicit request. There are two reasons to use a phony target: to avoid a conflict with a file of the same name, and to improve performance.
Like for example there is a file named clean it would be disregarded by make. Phony targets are defined like this in a make file for example
.PHONY : clean zip
rm abc.o xyz
zip : clean
zip abc.zip *.c *.h Makefile
There are a lot of command line arguments that make can take the following is a short list of useful command options.
-f file Read the file name file instead of standard names
-n Display the commands make would executes without actually executing them
-s Execute silently ie not printing the commands make executes
-w print directory names when make changes directory
-r disable all of makes built-in rules.
-d print lots of debugging information
-k keep executing even if one target fails to build.
-jN Run N commands in parallel, where n is an non-zero integer.
More exhaustive list can be found at http://www.gnu.org/software/make/manual/make.html#Options-Summary
To simplify editing and maintenance make allows us to use variables. There are four kinds of variables
- User defined variables
- Environment variables
- Automatic variables and
- Pre-defined variables
We define variables using the general form:
VARNAME = value [ … ]
It is a convention to use upper case letters for variables names, to obtain a variables value we just parenthesize the variable and add a dollar sign as a prefix.
There are two kinds of variables in “make” simply expanded variables and recursively expanded variables. Recursively expanded variables as the name suggest expands variables within variables until there are no further variables. For example
HOME_DIR = /home/asad
DEV_DIR = $(HOME-DIR)/development
PROJ_DIR = $(HOME-DIR)$(DEV-DIR)/project
SRC_DIR = $(HOME-DIR)$(DEV-DIR)$(PROJ_DIR)/src
If you use SRC_DIR variable somewhere all of the variables would get expanded.
Simply expanded variables are scanned once where they are defined and all embedded variable references are immediately resolved. Syntax is slightly different
CC := gcc
CC += -g
This would result in gcc –g when resolved.
If you consider using recursively expanded variables for this for example
CC = gcc
CC = $(CC) – g
We would get stuck in an infinite loop as $(CC) would keep on getting resolved to the same expression $(CC) & make would throw an error.
Every environment variable that make sees when it starts up is transformed into a make variable with the same name and value. However, an explicit assignment in the makefile, or with a command argument, overrides the environment. $(HOME) variable for example can be used in make to refer to the users home directory.
Automatic variables are those variables whose value is evaluated each time a rule is executed, based on target and dependencies of that rule. Automatic variables are used to create pattern rules, patterns are generic instructions on how to compile an arbitrary .c file to its corresponding .o file.
Here is a partial list of automatic variables
$@ The file name of the target of the rule.
$< The name of the first prerequisite/dependency.
$? The names of all the prerequisites that are newer than the target, with spaces between them.
$* The basename (or stem) of a filename.
$^ Expands to a space-delimited list of all of a rules depedencies.
$(@D) The directory part of targets path name, if named target is in the sub-directory.
$(@F) The file part of targets path name, if named target is in the sub-directory.
For an exhaustive list please visit
CFLAGS := -g
CC := gcc
$(CC) $CFLAGS –c $* .c
$(CC) $(CFLAGS) $< -o $@
for each filename ending in .c make would create an object file ending with .o, while executing $< would be replace with lock.c similarly target name $@ will be replaced by lock.
These are the variables that as the name suggests are predefined by make. A few of them are listed below.
AR Archive-maintaining program; default `ar’.
AS Program for compiling assembly files; default `as’.
CC Program for compiling C programs; default `cc’.
CO Program for checking out files from RCS; default `co’.
CXX Program for compiling C++ programs; default `g++’.
CO Program for extracting a file from RCS; default `co’.
CPP Program for running the C preprocessor, with results to standard output; default `$(CC) -E’.
RM Command to remove a file; default `rm -f’.
ARFLAGS Flags to give the archive-maintaining program; default `rv’.
ASFLAGS Extra flags to give to the assembler (when explicitly invoked on a `.s’ or `.S’ file).
CFLAGS Extra flags to give to the C compiler.
For an exhaustive list please see
Conditional expressions can be applied to a make file so that parts of make file execute or not based on certain conditions. Generic syntax is as follows
The following example of a conditional tells make to use one set of libraries if the CC variable is `gcc’, and a different set of libraries otherwise. It works by controlling which of two command lines will be used as the command for a rule. The result is that `CC=gcc’ as an argument to make changes not only which compiler is used but also which libraries are linked.
libs_for_gcc = -lgnu
$(CC) -o foo $(objects) $(libs_for_gcc)
$(CC) -o foo $(objects) $(normal_libs)
This conditional uses three directives: one ifeq, one else and one endif.
In the last example I would like to show you how to create install, dist and uninstall targets that you can most probably use while distributing your applications.
install : hello
install $< $(HOME)
.PHONY : dist unistall
$(RM) hello *.o core
tar czvf hello.tar.gz hello.c Makefile
and you can use make install, make dist & make uninstall commands for respective functions.
With this I think we should wrap up our tutorial on make. This is not a fully exhaustive tutorial but it would place you on the right grounds & will get you started with make hopefully. If you have any questions please don’t hesitate to ask and If you want to know more about specific details & I don’t impress you much you can always access the online documentation at http://www.gnu.org/software/make/manual/.