13.2. MakefilesSometime during your life with Linux you will probably have to deal with make, even if you don't plan to do any programming. It's possible you'll want to patch and rebuild the kernel, and that involves running make. If you're lucky, you won't have to muck with the makefiles — but we've tried to direct this book toward unlucky people as well. So in this section, we'll explain enough of the subtle syntax of make so that you're not intimidated by a makefile. For some of our examples, we'll draw on the current makefile for the Linux kernel. It exploits a lot of extensions in the powerful GNU version of make, so we'll describe some of those as well as the standard make features. A good introduction to make is provided in Managing Projects with make by Andrew Oram and Steve Talbott (O'Reilly). GNU extensions are well documented by the GNU make manual. Most users see make as a way to build object files and libraries from sources and to build executables from object files. More conceptually, make is a general-purpose program that builds targets from dependencies. The target can be a program executable, a PostScript document, or whatever. The prerequisites can be C code, a TeX text file, and so on. While you can write simple shell scripts to execute gcc commands that build an executable program, make is special in that it knows which targets need to be rebuilt and which don't. An object file needs to be recompiled only if its corresponding source has changed. For example, say you have a program that consists of three C source files. If you were to build the executable using the command: papaya$ gcc -o foo foo.c bar.c baz.c each time you changed any of the source files, all three would be recompiled and relinked into the executable. If you changed only one source file, this is a real waste of time (especially if the program in question is much larger than a handful of sources). What you really want to do is recompile only the one source file that changed into an object file and relink all the object files in the program to form the executable. make can automate this process for you. 13.2.1. What make DoesThe basic goal of make is to let you build a file in small steps. If a lot of source files make up the final executable, you can change one and rebuild the executable without having to recompile everything. In order to give you this flexibility, make records what files you need to do your build. Here's a trivial makefile. Call it makefile or Makefile and keep it in the same directory as the source files: edimh: main.o edit.o gcc -o edimh main.o edit.o main.o: main.c gcc -c main.c edit.o: edit.c gcc -c edit.c This file builds a program named edimh from two source files named main.c and edit.c. You aren't restricted to C programming in a makefile; the commands could be anything. Three entries appear in the file. Each contains a dependency line that shows how a file is built. Thus the first line says that edimh (the name before the colon) is built from the two object files main.o and edit.o (the names after the colon). This line tells make that it should execute the following gcc line whenever one of those object files changes. The lines containing commands have to begin with tabs (not spaces). The command: papaya$ make edimh executes the gcc line if there isn't currently any file named edimh. However, the gcc line also executes if edimh exists, but one of the object files is newer. Here, edimh is called a target. The files after the colon are called either dependencies or prerequisites. The next two entries perform the same service for the object files. main.o is built if it doesn't exist or if the associated source file main.c is newer. edit.o is built from edit.c. How does make know if a file is new? It looks at the timestamp, which the filesystem associates with every file. You can see timestamps by issuing the ls -l command. Since the timestamp is accurate to one second, it reliably tells make whether you've edited a source file since the latest compilation or have compiled an object file since the executable was last built. Let's try out the makefile and see what it does: papaya$ make edimh gcc -c main.c gcc -c edit.c gcc -o edimh main.o edit.o If we edit main.c and reissue the command, it rebuilds only the necessary files, saving us some time: papaya$ make edimh gcc -c main.c gcc -o edimh main.o edit.o It doesn't matter what order the three entries are within the makefile. make figures out which files depend on which and executes all the commands in the right order. Putting the entry for edimh first is convenient because that becomes the file built by default. In other words, typing make is the same as typing make edimh. Here's a more extensive makefile. See if you can figure out what it does: install: all mv edimh /usr/local mv readimh /usr/local all: edimh readimh readimh: read.o edit.o gcc -o readimh main.o read.o edimh: main.o edit.o gcc -o edimh main.o edit.o main.o: main.c gcc -c main.c edit.o: edit.c gcc -c edit.c read.o: read.c gcc -c read.c First we see the target install. This is never going to generate a file; it's called a phony target because it exists just so that you can execute the commands listed under it. But before install runs, all has to run because install depends on all. (Remember, the order of the entries in the file doesn't matter.) So make turns to the all target. There are no commands under it (this is perfectly legal), but it depends on edimh and readimh. These are real files; each is an executable program. So make keeps tracing back through the list of dependencies until it arrives at the .c files, which don't depend on anything else. Then it painstakingly rebuilds each target. Here is a sample run (you may need root privilege to install the files in the /usr/local directory): papaya$ make install gcc -c main.c gcc -c edit.c gcc -o edimh main.o edit.o gcc -c read.c gcc -o readimh main.o read.o mv edimh /usr/local mv readimh /usr/local This run of make does a complete build and install. First it builds the files needed to create edimh. Then it builds the additional object file it needs to create readmh. With those two executables created, the all target is satisfied. Now make can go on to build the install target, which means moving the two executables to their final home. Many makefiles, including the ones that build Linux, contain a variety of phony targets to do routine activities. For instance, the makefile for the Linux kernel includes commands to remove temporary files: clean: archclean rm -f kernel/ksyms.lst rm -f core `find . -name '*.[oas]' -print` . . . and to create a list of object files and the header files they depend on (this is a complicated but important task; if a header file changes, you want to make sure the files that refer to it are recompiled): depend dep: touch tools/version.h for i in init/*.c;do echo -n "init/";$(CPP) -M $$i;done > .tmpdep . . . Some of these shell commands get pretty complicated; we'll look at makefile commands later in this chapter, in Section 13.2.5. 13.2.2. Some Syntax RulesThe hardest thing about maintaining makefiles, at least if you're new to them, is getting the syntax right. OK, let's be straight about it, make syntax is really stupid. If you use spaces where you're supposed to use tabs or vice versa, your makefile blows up. And the error messages are really confusing. Always put a tab — not spaces — at the beginning of a command. And don't use a tab before any other line. You can place a hash sign (#) anywhere on a line to start a comment. Everything after the hash sign is ignored. If you put a backslash at the end of a line, it continues on the next line. That works for long commands and other types of makefile lines, too. Now let's look at some of the powerful features of make, which form a kind of programming language of their own. 13.2.3. MacrosWhen people use a filename or other string more than once in a makefile, they tend to assign it to a macro. That's simply a string that make expands to another string. For instance, you could change the beginning of our trivial makefile to read: OBJECTS = main.o edit.o edimh: $(OBJECTS) gcc -o edimh $(OBJECTS) When make runs, it simply plugs in main.o edit.o wherever you specify $(OBJECTS). If you have to add another object file to the project, just specify it on the first line of the file. The dependency line and command will then be updated correspondingly. Don't forget the parentheses when you refer to $(OBJECTS). Macros may resemble shell variables like $HOME and $PATH, but they're not the same. One macro can be defined in terms of another macro, so you could say something like: ROOT = /usr/local HEADERS = $(ROOT)/include SOURCES = $(ROOT)/src In this case, HEADERS evaluates to the directory /usr/local/include and SOURCES to /usr/local/src. If you are installing this package on your system and don't want it to be in /usr/local, just choose another name and change the line that defines ROOT. By the way, you don't have to use uppercase names for macros, but that's a universal convention. An extension in GNU make allows you to add to the definition of a macro. This uses a := string in place of an equals sign: DRIVERS = drivers/block/block.a ifdef CONFIG_SCSI DRIVERS := $(DRIVERS) drivers/scsi/scsi.a endif The first line is a normal macro definition, setting the DRIVERS macro to the filename drivers/block/block.a. The next definition adds the filename drivers/scsi/scsi.a. But it takes effect only if the macro CONFIG_SCSI is defined. The full definition in that case becomes: drivers/block/block.a drivers/scsi/scsi.a So how do you define CONFIG_SCSI? You could put it in the makefile, assigning any string you want: CONFIG_SCSI = yes But you'll probably find it easier to define it on the make command line. Here's how to do it: papaya$ make CONFIG_SCSI=yes target_name One subtlety of using macros is that you can leave them undefined. If no one defines them, a null string is substituted (that is, you end up with nothing where the macro is supposed to be). But this also gives you the option of defining the macro as an environment variable. For instance, if you don't define CONFIG_SCSI in the makefile, you could put this in your .bashrc file, for use with the bash shell: export CONFIG_SCSI=yes Or put this in .cshrc if you use csh or tcsh: setenv CONFIG_SCSI yes All your builds will then have CONFIG_SCSI defined. 13.2.4. Suffix Rules and Pattern RulesFor something as routine as building an object file from a source file, you don't want to specify every single dependency in your makefile. And you don't have to. Unix compilers enforce a simple standard (compile a file ending in the suffix .c to create a file ending in the suffix .o), and make provides a feature called suffix rules to cover all such files. Here's a simple suffix rule to compile a C source file, which you could put in your makefile: .c.o: gcc -c $(CFLAGS) $< The .c.o: line means "use a .c dependency to build a .o file." CFLAGS is a macro into which you can plug any compiler options you want: -g for debugging, for instance, or -O for optimization. The string $< is a cryptic way of saying "the dependency." So the name of your .c file is plugged in when make executes this command. Here's a sample run using this suffix rule. The command line passes both the -g option and the -O option: papaya$ make CFLAGS="-O -g" edit.o gcc -c -O -g edit.c You actually don't have to specify this suffix rule in your makefile because something very similar is already built into make. It even uses CFLAGS, so you can determine the options used for compiling just by setting that variable. The makefile used to build the Linux kernel currently contains the following definition, a whole slew of gcc options: CFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -pipe While we're discussing compiler flags, one set is seen so often that it's worth a special mention. This is the -D option, which is used to define symbols in the source code. Since all kinds of commonly used symbols appear in #ifdefs, you may need to pass lots of such options to your makefile, such as -DDEBUG or -DBSD. If you do this on the make command line, be sure to put quotation marks or apostrophes around the whole set. This is because you want the shell to pass the set to your makefile as one argument: papaya$ make CFLAGS="-DDEBUG -DBSD" ... GNU make offers something called pattern rules, which are even better than suffix rules. A pattern rule uses a percent sign to mean "any string." So C source files would be compiled using a rule, as in the following: %.o: %.c gcc -c -o $@ $(CFLAGS) $< Here the output file %.o comes first, and the dependency %.c comes after a colon. In short, a pattern rule is just like a regular dependency line, but it contains percent signs instead of exact filenames. We see the $< string to refer to the dependency, but we also see $@, which refers to the output file. So the name of the .o file is plugged in there. Both of these are built-in macros; make defines them every time it executes an entry. Another common built-in macro is $*, which refers to the name of the dependency stripped of the suffix. So if the dependency is edit.c, the string $*.s would evaluate to edit.s (an assembly-language source file). Here's something useful you can do with a pattern rule that you can't do with a suffix rule: you add the string _dbg to the name of the output file so that later you can tell that you compiled it with debugging information: %_dbg.o: %.c gcc -c -g -o $@ $(CFLAGS) $< DEBUG_OBJECTS = main_dbg.o edit_dbg.o edimh_dbg: $(DEBUG_OBJECTS) gcc -o $@ $(DEBUG_OBJECTS) Now you can build all your objects in two different ways: one with debugging information and one without. They'll have different filenames, so you can keep them in one directory: papaya$ make edimh_dbg gcc -c -g -o main_dbg.o main.c gcc -c -g -o edit_dbg.o edit.c gcc -o edimh_dbg main_dbg.o edit_dbg.o 13.2.5. Multiple CommandsAny shell commands can be executed in a makefile. But things can get kind of complicated because make executes each command in a separate shell. So this would not work: target: cd obj HOST_DIR=/home/e mv *.o $HOST_DIR Neither the cd command nor the definition of the variable HOST_DIR has any effect on subsequent commands. You have to string everything together into one command. The shell uses a semicolon as a separator between commands, so you can combine them all on one line, as in: target: cd obj ; HOST_DIR=/home/e ; mv *.o $$HOST_DIR One more change: to define and use a shell variable within the command, you have to double the dollar sign. This lets make know that you mean it to be a shell variable, not a macro. You may find the file easier to read if you break the semicolon-separated commands onto multiple lines, using backslashes so that make considers them to be on one line: target: cd obj ; \ HOST_DIR=/home/e ; \ mv *.o $$HOST_DIR Sometimes makefiles contain their own make commands; this is called recursive make. It looks like this: linuxsubdirs: dummy set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i; done The macro $(MAKE) invokes make. There are a few reasons for nesting makes. One reason, which applies to this example, is to perform builds in multiple directories (each of these other directories has to contain its own makefile). Another reason is to define macros on the command line, so you can do builds with a variety of macro definitions. GNU make offers another powerful interface to the shell as an extension. You can issue a shell command and assign its output to a macro. A couple of examples can be found in the Linux kernel makefile, but we'll just show a simple example here: HOST_NAME = $(shell uname -n) This assigns the name of your network node — the output of the uname -n command — to the macro HOST_NAME. make offers a couple of conventions you may occasionally want to use. One is to put an at sign before a command, which keeps make from echoing the command when it's executed: @if [ -x /bin/dnsdomainname ]; then \ echo #define LINUX_COMPILE_DOMAIN \"`dnsdomainname`\"; \ else \ echo #define LINUX_COMPILE_DOMAIN \"`domainname`\"; \ fi >> tools/version.h Another convention is to put a hyphen before a command, which tells make to keep going even if the command fails. This may be useful if you want to continue after an mv or cp command fails: - mv edimh /usr/local - mv readimh /usr/local 13.2.6. Including Other makefilesLarge projects tend to break parts of their makefiles into separate files. This makes it easy for different makefiles in different directories to share things, particularly macro definitions. The line: include filename reads in the contents of filename. You can see this in the Linux kernel makefile, for instance: include .depend If you look in the file .depend, you'll find a bunch of makefile entries: these lines declare that object files depend on particular header files. (By the way, .depend might not exist yet; it has to be created by another entry in the makefile.) Sometimes include lines refer to macros instead of filenames, as in: include ${INC_FILE} In this case, INC_FILE must be defined either as an environment variable or as a macro. Doing things this way gives you more control over which file is used. 13.2.7. Interpreting make MessagesThe error messages from make can be quite cryptic, so we'd like to give you some help in interpreting them. The following explanations cover the most common messages.
13.2.8. Autoconf, Automake, and Other Makefile ToolsWriting makefiles for a larger project usually is a boring and time-consuming task, especially if the programs are expected to be compiled on multiple platforms. From the GNU project come two tools called Autoconf and Automake that have a steep learning curve but, once mastered, greatly simplify the task of creating portable makefiles. In addition, libtool helps a lot to create shared libraries in a portable manner. You can probably find these tools on your distribution CD, or you can download them from ftp://ftp.gnu.org/gnu/. From a user's point of view, using Autoconf involves running a program configure, which should have been shipped in the source package you are trying to build. This program analyzes your system and configures the makefiles of the package to be suitable for your system and setup. A good thing to try before running the configure script for real is to issue the command: owl$ ./configure --help This shows all command-line switches that the configure program understands. Many packages allow different setups — e.g., different modules to be compiled in — and you can select these with configure options. From a programmer's point of view, you don't write makefiles, but rather files called makefile.in. These can contain place holders that will be replaced with actual values when the user runs the configure program, generating the makefiles that make then runs. In addition, you need to write a file called configure.in that describes your project and what to check for on the target system. The Autoconf tool then generates the configure program from this configure.in file. Writing the configure.in file is unfortunately way too involved to be described here, but the Autoconf package contains documentation to get you started. Writing the makefile.in files is still a cumbersome and lengthy task, but even this can be mostly automated by using the Automake package. Using this package, you do not write the makefile.in files, but rather the makefile.am files, which have a much simpler syntax and are much less verbose. By running the automake tool, these makefile.am files are converted to the makefile.in, which you include when you distribute your source code and which are later converted into the makefiles themselves when the package is configured for the user's system. How to write makefile.am files is beyond the scope of this book as well. Again, please check the documentation of the package to get started. These days, most open-source packages use the libtool/automake/autoconf combo for generating the makefiles, but this does not mean that this rather complicated and involved method is the only one available. Other makefile-generating tools exist as well, such as the imake tool used to configure the X Window System. Another tool that is not as powerful as the Autoconf suite (even though it still lets you do most things you would want to do when it comes to makefile generation) but extremely easy to use (it can even generate its own description files for you from scratch) is the qmake tool that ships together with the C++ GUI library Qt (downloadable from http://www.trolltech.com). Copyright © 2003 O'Reilly & Associates. All rights reserved. |
|