8.3 Dependency Generation


We gave a brief introduction to dependency generation in the Section 2.7 in Chapter 2, but it left several problems unaddressed. Therefore, this section offers some alternatives to the simple solution already described. [1] In particular, the simple approach described earlier and in the GNU make manual suffer from these failings:

[1] Much of the material in this section was invented by Tom Tromey ( tromey @cygnus.com) for the GNU automake utility and is taken from the excellent summary article by Paul Smith (the maintainer of GNU make ) from his web site http://make.paulandlesley.org.

  • It is inefficient. When make discovers that a dependency file is missing or out of date, it updates the .d file and restarts itself. Rereading the makefile can be inefficient if it performs many tasks during the reading of the makefile and the analysis of the dependency graph.

  • make generates a warning when you build a target for the first time and each time you add new source files. At these times the dependency file associated with a new source file does not yet exist, so when make attempts to read the dependency file it will produce a warning message before generating the dependency file. This is not fatal, merely irritating .

  • If you remove a source file, make stops with a fatal error during subsequent builds. In this situation, there exists a dependency file containing the removed file as a prerequisite. Since make cannot find the removed file and doesn't know how to make it, make prints the message:

     make: *** No rule to make target foo.h, needed by foo.d.  Stop. 

  • Furthermore, make cannot rebuild the dependency file because of this error. The only recourse is to remove the dependency file by hand, but since these files are often hard to find, users typically delete all the dependency files and perform a clean build. This error also occurs when files are renamed .

  • Note that this problem is most noticeable with removed or renamed header files rather than .c files. This is because .c files will be removed from the list of dependency files automatically and will not trouble the build.

8.3.1 Tromey's Way

Let's address these problems individually.

How can we avoid restarting make ?

On careful consideration, we can see that restarting make is unnecessary. If a dependency file is updated, it means that at least one of its prerequisites has changed, which means we must update the target. Knowing more than that isn't necessary in this execution of make because more dependency information won't change make 's behavior. But we want the dependency file updated so that the next run of make will have complete dependency information.

Since we don't need the dependency file in this execution of make , we could generate the file at the same time as we update the target. We can do this by rewriting the compilation rule to also update the dependency file.

 # $(call make-depend,source-file,object-file,depend-file) define make-depend   $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M   \   $(SED) 's,\($$(notdir )\) *:,$$(dir ) : ,' > .tmp   $(MV) .tmp  endef %.o: %.c         $(call make-depend,$<,$@,$(subst .o,.d,$@))         $(COMPILE.c) -o $@ $< 

We implement the dependency generation feature with the function make-depend that accepts the source, object, and dependency filenames. This provides maximum flexibility if we need to reuse the function later in a different context. When we modify our compilation rule this way, we must delete the %.d: %.c pattern rule we wrote to avoid generating the dependency files twice.

Now, the object file and dependency file are logically linked: if one exists the other must exist. Therefore, we don't really care if a dependency file is missing. If it is, the object file is also missing and both will be updated by the next build. So we can now ignore any warnings that result from missing .d files.

In Section 3.7.2 in Chapter 3, I introduced an alternate form of include directive, -include (or sinclude ), that ignores errors and does not generate warnings:

 ifneq "$(MAKECMDGOALS)" "clean"   -include $(dependencies) endif 

This solves the second problem, that of an annoying message when a dependency file does not yet exist.

Finally, we can avoid the warning when missing prerequisites are discovered with a little trickery . The trick is to create a target for the missing file that has no prerequisites and no commands. For example, suppose our dependency file generator has created this dependency:

 target.o target.d: header.h 

Now suppose that, due to code refactoring, header.h no longer exists. The next time we run the makefile we'll get the error:

 make: *** No rule to make target header.h, needed by target.d.  Stop. 

But if we add a target with no command for header.h to the dependency file, the error does not occur:

 target.o target.d: header.h header.h: 

This is because, if header.h does not exist, it will simply be considered out of date and any targets that use it as a prerequisite will be updated. So the dependency file will be regenerated without header.h because it is no longer referenced. If header.h does exist, make considers it up to date and continues. So, all we need to do is ensure that every prerequisite has an associated empty rule. You may recall that we first encountered this kind of rule in Section 2.1.2 in Chapter 2. Here is a version of make-depend that adds the new targets:

 # $(call make-depend,source-file,object-file,depend-file) define make-depend   $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M          \   $(SED) 's,\($$(notdir )\) *:,$$(dir ) : ,' > .tmp   $(SED) -e 's/#.*//'                                       \          -e 's/^[^:]*: *//'                                 \          -e 's/ *\$$$$//'                                  \          -e '/^$$$$/ d'                                     \          -e 's/$$$$/ :/' .tmp >> .tmp   $(MV) .tmp  endef 

We execute a new sed command on the dependency file to generate the additional rules. This chunk of sed code performs five transformations:

  1. Deletes comments

  2. Deletes the target file(s) and subsequent spaces

  3. Deletes trailing spaces

  4. Deletes blank lines

  5. Adds a colon to the end of every line

(GNU sed is able to read from a file and append to it in a single command line, saving us from having to use a second temporary file. This feature may not work on other systems.) The new sed command will take input that looks like:

 # any comments target.o target.d: prereq1 prereq2 prereq3 \      prereq4 

and transform it into:

 prereq1 prereq2 prereq3: prereq4: 

So make-depend appends this new output to the original dependency file. This solves the "No rule to make target" error.

8.3.2 makedepend Programs

Up to now we have been content to use the -M option provided by most compilers, but what if this option doesn't exist? Alternatively, are there better options than our simple -M ?

These days most C compilers have some support for generating make dependencies from the source, but not long ago this wasn't true. In the early days of the X Window System project, they implemented a tool, makedepend , that computes the dependencies from a set of C or C++ sources. This tool is freely available over the Internet. Using makedepend is a little awkward because it is written to append its output to the makefile , which we do not want to do. The output of makedepend assumes the object files reside in the same directory as the source. This means that, again, our sed expression must change:

 # $(call make-depend,source-file,object-file,depend-file) define make-depend   $(MAKEDEPEND) -f- $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH)   \   $(SED) 's,^.*/\([^/]*\.o\) *:,$(dir ) : ,' > .tmp   $(SED) -e 's/#.*//'                                         \          -e 's/^[^:]*: *//'                                   \          -e 's/ *\$$$$//'                                    \          -e '/^$$$$/ d'                                       \          -e 's/$$$$/ :/' .tmp >> .tmp   $(MV) .tmp  endef 

The -f- option tells makedepend to write its dependency information to the standard output.

An alternative to using makedepend or your native compiler is to use gcc . It sports a bewildering set of options for generating dependency information. The ones that seem most apropos for our current requirements are:

 ifneq "$(MAKECMDGOALS)" "clean"   -include $(dependencies) endif # $(call make-depend,source-file,object-file,depend-file) define make-depend   $(GCC) -MM            \          -MF          \          -MP            \          -MT          \          $(CFLAGS)      \          $(CPPFLAGS)    \          $(TARGET_ARCH) \           endef %.o: %.c         $(call make-depend,$<,$@,$(subst .o,.d,$@))         $(COMPILE.c) $(OUTPUT_OPTION) $< 

The -MM option causes gcc to omit "system" headers from the prerequisites list. This is useful because these files rarely, if ever, change and, as the build system gets more complex, reducing the clutter helps. Originally, this may have been done for performance reasons. With today's processors, the performance difference is barely measurable.

The -MF option specifies the dependency filename. This will be the object filename with the .d suffix substituted for .o . There is another gcc option, -MD or -MMD , that automatically generates the output filename using a similar substitution. Ideally we would prefer to use this option, but the substitution fails to include the proper relative path to the object file directory and instead places the .d file in the current directory. So, we are forced to do the job ourselves using -MF .

The -MP option instructs gcc to include phony targets for each prerequisite. This completely eliminates the messy five-part sed expression in our make-depend function. It seems that the automake developers who invented the phony target technique caused this option to be added to gcc .

Finally, the -MT option specifies the string to use for the target in the dependency file. Again, without this option, gcc fails to include the relative path to the object file output directory.

By using gcc , we can reduce the four commands previously required for dependency generation to a single command. Even when proprietary compilers are used it may be possible to use gcc for dependency management.

Managing Projects with GNU make
Managing Projects with GNU Make (Nutshell Handbooks)
ISBN: 0596006101
EAN: 2147483647
Year: 2003
Pages: 131

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net