Working with several directories

If your program grows to a substantial size, or if it uses libraries that need to be built but should be kept separate, it is quite likely that you have split up your sources into several directories. One of the main motivations for writing makepp was to make dealing with several directories much easier than with the standard make utility. If you're familiar with the standard unix make, you'll notice that with makepp, you don't have to mess around with ugly complexities like recursive invocations of make.

With makepp, you simply put a separate makefile in each directory that builds the relevant files in that directory. When a makefile refers to files whose build commands are in different makefiles, makepp automatically finds the appropriate build rules in the other makefiles. All actions in each makefile are executed with the current directory set to be the directory containing the makefile, so each makefile can be written independently of all the others. No makefile has to know anything about the other makefiles; it does not even have to tell makepp to load the rules from those other makefiles.

When you've written your makefiles, cd to the directory that contains your main program, and type makepp just like you usually would. Makepp will load in the makefile from that directory. It will notice that this makefile refers to files in other directories, and it will examine those other directories to see if there is a makefile in them. In this way, all relevant makefiles will be loaded.

As a simple example, suppose your top level directory contains the following makefile:

# Top level makefile:

CXX	:= c++
CXXFLAGS := -O2
my_program: main.o goodies/libgoodies.so
	$(CXX) $(inputs) -o $(output)

%.o: %.cxx
	$(CXX) $(CXXFLAGS) -c $(input) -o $(output)

You would need to write a makefile in the directory goodies which builds libgoodies.so, like this:

# goodies/Makefile

CXX	:= c++
CXXFLAGS := -O2

MODULES = candy.o chips.o licorice.o cookies.o popcorn.o spinach.o

libgoodies.so: $(MODULES)
	$(CXX) -shared $(inputs) -o $(output)
			# Note that the command is written assuming that
			# the current directory is the subdirectory
			# "goodies", not the top level subdirectory.
			# Makepp cds into this directory before executing
			# any commands from this makefile.

%.o: %.cxx
	$(CXX) $(CXXFLAGS) -fpic -c $(input) -o $(output)

And that's all you need to do.


Any variables which you specify on the command line override the definition of the variable in all makefiles. Thus, for example, if you type makepp CXXFLAGS="-g", all modules will be recompiled for debug because the definition of CXXFLAGS in both makefiles is overridden.

The directories containing other sources need not be subdirectories of the top-level directory (as they are in this example). They can be anywhere in the file system; makepp will automatically load a makefile from any directory that contains a file which is a dependency of some target it is trying to build. It will also load a makefile from any directory that is scanned by a wildcard.

Automatic loading works if files built by your makefile all reside in the same directory as the makefile itself. If you write your makefile so that its rules produce files in a different directory than the makefile itself, then you might have to tell makepp where to look for the makefiles, since it doesn't have any way of guessing. You can do this using the load_makefile statement in your makefile. For more information about this and other issues related to multi-directory builds, see the section on directories in the reference manual.

One caveat: if you reference the variable $(MAKE) in your makefile, makepp automatically goes into backward compatibility mode and turns off automatic loading.

Template or boilerplate files

Makepp has several other features which make life slightly easier for programmers who have to maintain a program spanning several directories. In the above examples, you'll notice that the definitions of the variables CXX and CXXFLAGS have to be repeated in each makefile. It can be a nuisance to reenter the same information into every makefile, and it could be a problem if you ever decide to change it--you may have to modify dozens of different makefiles.

What you can do instead is to put all of the information that's common to each makefile into a separate file, located perhaps at the top of the directory tree. Common information usually includes variable definitions, and sometimes also pattern rules. (In the above example, however, the pattern rules are not the same in both makefiles.) Let's suppose you've called this file standard_defs.mk. Then each makefile simply needs to contain a statement like this:

include standard_defs.mk

When makepp sees this statement, it inserts the contents of the file into the makefile at that point. The include statement first looks for the file in the current directory, then in the parent of the current directory, and so on up to the top level of the file system, so you don't actually need to specify ../standard_defs.mk or ../../../../standard_defs.mk.

So we could rewrite the above makefiles to look like this. standard_defs.mk would exist in the top level directory, and it might contain the following definitions:

# standard_defs.mk
CXX	:= c++
CXXFLAGS := -O2

#
# We've also included a pattern rule that might be useful in one or more
# subdirectories.  This pattern rule is for C compilation for putting
# things into a shared library (that's what the -fpic option is for).
#
%.o: %.cxx
	$(CXX) $(CXXFLAGS) -fpic -c $(input) -o $(output)

Note that since the included file is actually inserted into each makefile, rules in the included file are applied with the default directory set to the directory containing the makefile that included the file, not the directory containing the include file.

The top level Makefile might look like this:

# Top level makefile
include standard_defs.mk

my_program: main.o goodies/libgoodies.so
	$(CXX) $(inputs) -o $(output)

#
# Note that this pattern rule overrides the one found in standard_defs.mk,
# because makepp sees it later.  This pattern rule is for compilation for
# a module that doesn't belong in a shared library.
#
%.o: %.cxx
	$(CXX) $(CXXFLAGS) $(input) -o $(output)

And the subdirectory's makefile might look like this:

# goodies/Makefile
include standard_defs.mk

MODULES = candy.o chips.o licorice.o cookies.o popcorn.o spinach.o

libgoodies.so: $(MODULES)
	$(CXX) -shared $(inputs) -o $(output)

# We don't need the pattern rule for compilation of .cxx to .o files, because
# it's contained in standard_defs.mk.

The -F compilation option

If you run makepp from within an editor such as emacs, and you are editing sources from several different directories, you may find that the default directory for makepp differs depending on which file you were most recently editing. As a result, makepp may not load the correct makefile.

What you can do to ensure that makepp always loads the correct makefile(s), no matter what directory happens to be your current directory, is to use the -F command line option, like this:

makepp -F ~/src/my_program

Makepp will first cd to the directory ~/src/my_program before it attempts to load a makefile.


Tutorial index | Next (wildcards) | Previous (phony targets)
Last modified: Mon Feb 19 15:51:33 PST 2001