Makefile

From LQWiki
Jump to navigation Jump to search

A makefile is most often used as a build configuration file for a software project (or some part of a project's source code tree). It is used by the make program. The make program helps compile source code faster by not recompiling things that don't need to be recompiled.

Why use a makefile?

Let's say you are developing a program. To compile, you might run the gcc compiler on your source code. Let's say you have several files. Typing gcc for every source file you have would be very cumbersome. So you make a handy shell script, and now you can compile all your files with just one command. However, what if you had a huge amount of source code? Compiling all that source code would take a while, and it would be quite frustrating if you had to compile every single file for hours just because you made a minor change in one very small file. This is why the makefile and the make program were made.

The make program and the corresponding makefile solve the compilation problem by keeping track of what files were changed. Every file has a timestamp on it indicating when it was last changed. make examines the timestamps on the source code files and compares it with the compiled code. If a source code file was modified after the compiled file was modified, then make updates the compiled file by recompiling it - and only it. If the source code file was modified before the compiled file was, then nothing is done - which saves time.

How to write makefiles

Makefiles define a set of rules to determine which files to compare to others, and how the files should be updated.

Rules are comprised of two things: targets and dependencies. A target is the outcome - an executable file, an object file, a PDF document - whatever the outcome of running a certain program will be. A dependency is what needs to be used in order to reach the target - for example, to get an executable file, you need to have a source file - this is the dependency.

After the rule, the command below it specifies what needs to be done in order to get from the dependency to the target - say, run gcc, run tex.

The rule-command pair is written as follows:

target: dependencies
     command

(NB there is a TAB character before the command, not a space!)

So, a very simple rule-command pair will consist of

say_hello_world: say_hello_world.c say_hello_world.h
     gcc say_hello_world.c say_hello_world.h -o say_hello_world

If either say_hello_world.c or say_hello_world.h are changed, the command gcc say_hello_world.c say_hello_world.h will be run. But if neither were modified, the command would not be run. This is the key to makefiles - only what needs to be done is done.

Dependencies can be recursive. Consider this example. myprog depends on main.c and lib.o. But lib.o depends on lib.c. So, we would write our makefile as follows

myprog: main.c lib.o
   gcc main.c lib.o -o myprog

lib.o: lib.c
   gcc -c lib.c

Now, if main.c was not modified, but lib.c was, make will check the dependencies to myprog first, find that main.c is unchanged, then would check lib.o. Then, it finds a rule for lib.o and checks its dependencies, find lib.c change, run gcc -c lib.c first to get lib.o up to date, then rerun gcc main.c lib.o -o myprog to get myprog up to date.

When make is run on a makefile, the rules for either the first target it encounters, or a target called all are carried out first. This helps you shape the dependencies in your makefile (known as a dependency tree).

Specifying dependencies

Dependencies are specified in a file known as a makefile. You can call the makefile anything you like, but the make program will, however, will automatically select a file called Makefile or makefile (or some other platform-specific specification such as BSDmakefile).

For convenience, you should choose one of the defaults, which allows you to simply type

$ make

however, other files can be specified with the -f option to make. There may be special uses to using makefiles with different names.

Example of a makefile

#This specifies that the final target is the 'prog' program
all: prog

#This line means that the prog program depends 
#on the object file prog.o - in other words, 
#if prog.o is newer than prog, run gcc again to make prog.  
prog: prog.o 
    gcc -g prog.o -o prog
#But before we can try and make prog.o, we 
#should check if there are any dependencies on prog.o. 
#Indeed there is, and so the action is similar to the one before.
prog.o: prog.c 
    gcc -g -c prog.c

Make's actions are not C-specific, however. Make is much more abstract, which allows you to create Makefiles for Java programs, for example

JavaProg.class: JavaProg.java
    javac JavaProg.java

Any file that needs to be updated from another file with a command can benefit from a makefile. For example, you might create a piece of documentation using the SGML DocBook DTD, and then use a makefile to automatically build HTML, LaTeX, plain text, or whatever other kind of output from your sgml file you desire.

The rules in a makefile are applied when make finds the file in the current directory. When make is run without arguments and it finds a makefile, it follows the rules in the Makefile to make the 'all' target, prog. prog is made from prog.o. prog.o is made from prog.c.

More sophisticated targets will invoke simpler targets, thus meeting whatever dependencies a target declares. For example, the "all" target may declare that it needs the "foo" target, and "foo" requires "foo.o", which in turn leads to "foo.c" being compiled into "foo.o" by the C compiler (such as gcc).

Makefiles can become very complex for larger projects. Often the GNU build tools (autoconf,automake,libtool) are used to help with this. Alternatives to make include Apache Ant (which is Java-based) and Jam.

Wildcards

Wildcards can be used to simplify a lot of repetitive work. For instance, instead of having a large project with hundreds of object files, each with individual rules, you can specify one rule to catch all object files. Use of wildcards has some portability issues.

%.o : %.c++
	g++ -c $< -o $@

That rule will automatically compile ANY required .o file if a .c++ file exists.