Managing Projects with GNU Make (Nutshell Handbooks)

     

An archive library, usually called simply a library or archive, is a special type of file containing other files called members . Archives are used to group related object files into more manageable units. For example, the C standard library libc.a contains low-level C functions. Libraries are very common so make has special support for creating, maintaining, and referencing them. Archives are created and modified with the ar program.

Let's look at an example. We can modify our word counting program by refactoring the reusable parts into a reusable library. Our library will consist of the two files counter.o and lexer.o . The ar command to create this library is:

$ ar rv libcounter.a counter.o lexer.o a - counter.o a - lexer.o

The options rv indicate that we want to r eplace members of the archive with the object files listed and that ar should v erbosely echo its actions. We can use the replace option even though the archive doesn't exist. The first argument after the options is the archive name followed by a list of object files. (Some versions of ar also require the "c" option, for c reate, if the archive does not exist but GNU ar does not.) The two lines following the ar command are its verbose output indicating the object files were added.

Using the replace option to ar allows us to create or update an archive incrementally:

$ ar rv libcounter.a counter.o r - counter.o $ ar rv libcounter.a lexer.o r - lexer.o

Here ar echoed each action with "r" to indicate the file was replaced in the archive.

A library can be linked into an executable in several ways. The most straightforward way is to simply list the library file on the command line. The compiler or linker will use the file suffix to determine the type of a particular file on the command line and do the Right Thing :

cc count_words.o libcounter.a /lib/libfl.a -o count_words

Here cc will recognize the two files libcounter.a and /lib/libfl.a as libraries and search them for undefined symbols. The other way to reference libraries on the command line is with the -l option:

cc count_words.o -lcounter -lfl -o count_words

As you can see, with this option you omit the prefix and suffix of the library filename. The -l option makes the command line more compact and easier to read, but it has a far more useful function. When cc sees the -l option it searches for the library in the system's standard library directories. This relieves the programmer from having to know the precise location of a library and makes the command line more portable. Also, on systems that support shared libraries (libraries with the extension .so on Unix systems), the linker will search for a shared library first, before searching for an archive library. This allows programs to benefit from shared libraries without specifically requiring them. This is the default behavior of GNU's linker/compiler. Older linker/compilers may not perform this optimization.

The search path used by the compiler can be changed by adding -L options indicating the directories to search and in what order. These directories are added before the system libraries and are used for all -l options on the command line. In fact, the last example fails to link because the current directory is not in cc 's library search path. We can fix this error by adding the current directory like this:

cc count_words.o -L. -lcounter -lfl -o count_words

Libraries add a bit of complication to the process of building a program. How can make help to simplify the situation? GNU make includes special features to support both the creation of libraries and their use in linking programs. Let's see how they work.

2.8.1 Creating and Updating Libraries

Within a makefile , a library file is specified with its name just like any other file. A simple rule to create our library is:

libcounter.a: counter.o lexer.o $(AR) $(ARFLAGS) $@ $^

This uses the built-in definition for the ar program in AR and the standard options rv in ARFLAGS . The archive output file is automatically set in $@ and the prerequisites are set in $^ .

Now, if you make libcounter.a a prerequisite of count_words make will update our library before linking the executable. Notice one small irritation, however. All members of the archive are replaced even if they have not been modified. This is a waste of time and we can do better:

libcounter.a: counter.o lexer.o $(AR) $(ARFLGS) $@ $?

If you use $? instead of $^ , make will pass only those objects files that are newer than the target to ar .

Can we do better still? Maybe, but maybe not. make has support for updating individual files within an archive, executing one ar command for each object file member, but before we go delving into those details there are several points worth noting about this style of building libraries. One of the primary goals of make is to use the processor efficiently by updating only those files that are out of date. Unfortunately, the style of invoking ar once for each out-of-date member quickly bogs down. If the archive contains more than a few dozen files, the expense of invoking ar for each update begins to outweigh the " elegance " factor of using the syntax we are about to introduce. By using the simple method above and invoking ar in an explicit rule, we can execute ar once for all files and save ourselves many fork/ exec calls. In addition, on many systems using the r to ar is very inefficient. On my 1.9 GHz Pentium 4, building a large archive from scratch (with 14,216 members totaling 55 MB) takes 4 minutes 24 seconds. However, updating a single object file with ar r on the resulting archive takes 28 seconds. So building the archive from scratch is faster if I need to replace more than 10 files (out of 14,216!). In such situations it is probably more prudent to perform a single update of the archive with all modified object files using the $? automatic variable. For smaller libraries and faster processors there is no performance reason to prefer the simple approach above to the more elegant one below. In those situations, using the special library support that follows is a fine approach.

In GNU make , a member of an archive can be referenced using the notation:

libgraphics.a(bitblt.o): bitblt.o $(AR) $(ARFLAGS) $@ $<

Here the library name is libgraphics.a and the member name is bitblt.o (for bit block transfer ). The syntax lib name .a ( module .o ) refers to the module contained within the library. The prerequisite for this target is simply the object file itself and the command adds the object file to the archive. The automatic variable $< is used in the command to get only the first prerequisite. In fact, there is a built-in pattern rule that does exactly this.

When we put this all together, our makefile looks like this:

VPATH = src include CPPFLAGS = -I include count_words: libcounter.a /lib/libfl.a libcounter.a: libcounter.a(lexer.o) libcounter.a(counter.o) libcounter.a(lexer.o): lexer.o $(AR) $(ARFLAGS) $@ $< libcounter.a(counter.o): counter.o $(AR) $(ARFLAGS) $@ $< count_words.o: counter.h counter.o: counter.h lexer.h lexer.o: lexer.h

When executed, make produces this output:

$ make gcc -I include -c -o count_words.o src/count_words.c flex -t src/lexer.l> lexer.c gcc -I include -c -o lexer.o lexer.c ar rv libcounter.a lexer.o ar: creating libcounter.a a - lexer.o gcc -I include -c -o counter.o src/counter.c ar rv libcounter.a counter.o a - counter.o gcc count_words.o libcounter.a /lib/libfl.a -o count_words rm lexer.c

Notice the archive updating rule. The automatic variable $@ is expanded to the library name even though the target in the makefile is libcounter.a(lexer.o) .

Finally, it should be mentioned that an archive library contains an index of the symbols it contains. Newer archive programs such as GNU ar manage this index automatically when a new module is added to the archive. However, many older versions of ar do not. To create or update the index of an archive another program ranlib is used. On these systems, the built-in implicit rule for updating archives is insufficient. For these systems, a rule such as:

libcounter.a: libcounter.a(lexer.o) libcounter.a(counter.o) $(RANLIB) $@

must be used. Or if you choose to use the alternate approach for large archives:

libcounter.a: counter.o lexer.o $(RM) $@ $(AR) $(ARFLGS) $@ $^ $(RANLIB) $@

Of course, this syntax for managing the members of an archive can be used with the built-in implicit rules as well. GNU make comes with a built-in rule for updating an archive. When we use this rule, our makefile becomes:

VPATH = src include CPPFLAGS = -I include count_words: libcounter.a -lfl libcounter.a: libcounter.a(lexer.o) libcounter.a(counter.o) count_words.o: counter.h counter.o: counter.h lexer.h lexer.o: lexer.h

2.8.2 Using Libraries as Prerequisites

When libraries appear as prerequisites, they can be referenced using either a standard filename or with the -l syntax. When filename syntax is used:

xpong: $(OBJECTS) /lib/X11/libX11.a /lib/X11/libXaw.a $(LINK) $^ -o $@

the linker will simply read the library files listed on the command line and process them normally. When the -l syntax is used, the prerequisites aren't proper files at all:

xpong: $(OBJECTS) -lX11 -lXaw $(LINK) $^ -o $@

When the -l form is used in a prerequisite, make will search for the library (preferring a shared library) and substitute its value, as an absolute path, into the $^ and $? variables . One great advantage of the second form is that it allows you to use the search and shared library preference feature even when the system's linker cannot perform these duties . Another advantage is that you can customize make 's search path so it can find your application's libraries as well as system libraries. In this case, the first form would ignore the shared library and use the archive library since that is what was specified on the link line. In the second form, make knows that shared libraries are preferred and will search first for a shared version of X11 before settling for the archive version. The pattern for recognizing libraries from the -l format is stored in .LIBPATTERNS and can be customized for other library filename formats.

Unfortunately, there is a small wrinkle. If a makefile specifies a library file target, it cannot use the -l option for that file in a prerequisite. For instance, the following makefile :

count_words: count_words.o -lcounter -lfl $(CC) $^ -o $@ libcounter.a: libcounter.a(lexer.o) libcounter.a(counter.o)

fails with the error:

No rule to make target `-lcounter', needed by `count_words'

It appears that this error occurs because make does not expand -lcounter to libcounter.a and search for a target, but instead does a straight library search. So for libraries built within the makefile , the filename form must be used.

Getting complex programs to link without error can be somewhat of a black art. The linker will search libraries in the order in which they are listed on the command line. So if library A includes an undefined symbol, say open , that is defined in library B , the link command line must list A before B (that is, A requires B ). Otherwise, when the linker reads A and sees the undefined symbol open , it's too late to go back to B . The linker doesn't ever go back. As you can see, the order of libraries on the command line is of fundamental importance.

When the prerequisites of a target are saved in the $^ and $? variables, their order is preserved. So using $^ as in the previous example expands to the same files in the same order as the prerequisites list. This is true even when the prerequisites are split across multiple rules. In that case, the prerequisites of each rule are appended to the target prerequisite list in the order they are seen.

A closely related problem is mutual reference between libraries, often referred to as circular references or circularities . Suppose a change is made and library B now references a symbol defined in library A . We know A must come before B , but now B must come before A . Hmm, a problem. The solution is to reference A both before and after B : -l A - l B - l A . In large, complex programs, libraries often need to be repeated in this way, sometimes more than twice.

This situation poses a minor problem for make because the automatic variables normally discard duplicates. For example, suppose we need to repeat a library prerequisite to satisfy a library circularity:

xpong: xpong.o libui.a libdynamics.a libui.a -lX11 $(CC) $^ -o $@

This prerequisite list will be processed into the following link command:

gcc xpong.o libui.a libdynamics.a /usr/lib/X11R6/libX11.a -o xpong

Oops. To overcome this behavior of $^ an additional variable is available in make , $+ . This variable is identical to $^ with the exception that duplicate prerequisites are preserved. Using $+ :

xpong: xpong.o libui.a libdynamics.a libui.a -lX11 $(CC) $+ -o $@

This prerequisite list will be processed into the following link command:

gcc xpong.o libui.a libdynamics.a libui.a /usr/lib/X11R6/libX11.a -o xpong

2.8.3 Double- Colon Rules

Double-colon rules are an obscure feature that allows the same target to be updated with different commands depending on which set of prerequisites are newer than the target. Normally, when a target appears more than once all the prerequisites are appended in a long list with only one command script to perform the update. With double-colon rules, however, each occurrence of the target is considered a completely separate entity and is handled individually. This means that for a particular target, all the rules must be of the same type, either they are all double-colon rules or all single-colon rules.

Realistic, useful examples of this feature are difficult to come by (which is why it is an obscure feature), but here is an artificial example:

file-list:: generate-list-script chmod +x $< generate-list-script $(files) > file-list file-list:: $(files) generate-list-script $(files) > file-list

We can regenerate the file-list target two ways. If the generating script has been updated, we make the script executable, then run it. If the source files have changed, we simply run the script. Although a bit far- fetched , this gives you a feel for how the feature might be used.

We've covered most of the features of make rules and, along with variables and commands, this is the essence of make . We've focused largely on the specific syntax and behavior of the features without going much into how to apply them in more complex situations. That is the subject of Part II. For now, we will continue our discussion with variables and then commands.

Категории