Friday, 12 October 2007

make and Makefile in Linux

Well, now that you're learned how to use lots of source files, objects, and libraries to build large programs, how are you ever going to manage them all? How will you remember all the commands and options you'll need to remember to build it? Fortunately, you don't have to. DJGPP (and most development systems) include a program that manages your projects for you. If you are using RHIDE, this is the project definition, which is managed through RHIDE's menus (and is documented in RHIDE's documentation). There is also a stand-alone program called make that keeps track of how to make your program from the sources (hence the name). The file that contains the instructions for make is called a makefile (and is usually called makefile).

The syntax of a makefile is deceptively simple (GNU make includes a lot of extensions, functions, and tricks that can make makefiles almost as complex as your source). You can have two kinds of commands (besides comments) - variable definitions and rules. A variable definition can be a simple thing, like this:

OBJECTS = main.o foo.o bar.o math3d.o graphics.o
They can get pretty complicated also (see the make reference manual) but for now we'll stick to the simple cases.

The more interesting command is a rule, which tells make what it's making, what it's made from, and how to make it. Consider this example:

main.o : main.c
gcc -c main.c

Note that the line with gcc is prefixed by a tab - a real tab character, not eight spaces! Many DOS editors will insert eight spaces when you hit the TAB key, instead of just inserting a tab. If you get a "missing separator" error, that's what happened to you.

Anyway, in this example rule there are three parts: The target, the dependencies, and the commands (well, command). The target is the thing that we want to make. In this case, we want to make the file main.o. Next, after the target (and the colon, which separates targets from dependencies), we list the depenencies - the files that the target depends on. In this case, main.o needs to be rebuilt whenever main.c changes, so we list main.c as a dependency. The last part of the rule is the command that make should run in order to make the target from the depenencies. In this case, we list the gcc command to compile main.c into main.o. Note that you may list as many commands as required, as long as you don't leave a blank line and all commands start with a tab.

Makefiles may (and often do) contain many variables and rules. Here's a Makefile we'd use for our sample program:

main.exe : main.o foo.o
gcc main.o foo.o -o main.exe

main.o : main.c
gcc -c main.c

foo.o : foo.c
gcc -c foo.c

To use this makefile, you run the make program, like this:

C:\SRC> make

Pretty simple, yes? The make program knows to look for a file called Makefile or makefile. If you used a different name, run it like this: "make -f makefile.djg".

Note that make always starts with the first rule it sees, in this case the one for main.exe, and tries to build that first. As it find dependencies, it checks to see if those need to be updated. Based on the makefile above, make will run these three commands the first time you call it:

gcc -c main.c
gcc -c foo.c
gcc main.o foo.o -o main.exe

However, if you then edit foo.c and run make again, it will only run these commands:

gcc -c foo.c
gcc main.o foo.o -o main.exe

It doesn't recompile main.c because it knows that it doesn't have to. In a big project, that could save you a lot of time.

Our example makefile didn't use any variables. Let's include some, to see if it help us out:

CC = gcc
CFLAGS = -g -O2
OBJECTS = main.o foo.o

main.exe : $(OBJECTS)
$(CC) $(CFLAGS) $(OBJECTS) -o main.exe

main.o : main.c
$(CC) $(CFLAGS) -c main.c

foo.o : foo.c
$(CC) $(CFLAGS) -c foo.c

This makefile looks a lot like the old makefile, except that a lot of the commands have been replaced with variable substitutions. What make does is replace the variables with their variables in the target, dependency, and command sections of the rules. That lets you specify some things in one place to make it easier to maintain. In our example, we use $(CC) to specify the compiler, so we could set it to something else if we wanted to without having to change the whole makefile.

Here's another trick that GNU make can let you do. In the above makefile, we had to include the rule for compiling sources into objects twice - once for each source file. That could get tiresome when we have dozens of sources, so let's define a pattern instead. This pattern will be used whenever make needs to compile any source:

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

Here, we have used the percent (%) character to denote that part of the target and dependency that matches whatever the pattern is used for, and the $< is a special variable (imaging it like $(<)) that means "whatever the depencies are". Another useful variable is $@, which means "the target". Our Makefile now looks like this:

CC = gcc
CFLAGS = -g -O2
OBJECTS = main.o foo.o

main.exe : $(OBJECTS)
$(CC) $(CFLAGS) $(OBJECTS) -o main.exe

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

Now, if we need to add more source files, we only have to update the line that defines the OBJECTS variable!

Note that make is pretty smart about a lot of things already, like how to build object files. In our above example, we could have left out the last rule completely! The fact that we chose CC and CFLAGS as names was no coincidence, since those are the variables that make's built-in rules use. To see a list of all the built-in rules, consult the make documentation or run "make -p"

The reference manual for make (run "info make") contains many more examples and nifty tricks for building makefiles, but this section covered the bare minimum that you'll need to know to manage your projects with make.

No comments:

My photo
London, United Kingdom
twitter.com/zhengxin

Facebook & Twitter