Makepp supports essentially all the useful features that a good traditional make program such as GNU make has. This includes parallel builds and an extensive set of text manipulation functions. But makepp has many additional features not found in GNU make.
makepp scans automatically for include files. This
obviates the need for tools like makedepend. makepp's
scanner works even if the included files don't exist yet but have to
be built. (This is true no matter where on the include path they
come from, unlike programs that depend on gcc's
-MM -MG
option.) makepp has a flexible
system for doing this which is based on examining the build command; you can adapt
it for other languages or build commands by writing a simple perl
subroutine.
makepp has a better system for handling builds involving multiple directories and multiple makefiles. The traditional technique is to have make invoke itself recursively in each directory. Depending on the build procedure, several recursive passes are sometimes needed. This makes the makefiles somewhat more complicated if they guarantee a correct build. The real problem is that unless dependencies are trivial (e.g., just one library file), it is almost impossible to express accurately dependencies of targets in one makefile in terms of targets from the other makefile. Unix make isn't smart enough to realize that a target in one makefile depends on a file that is a target in a lower-level makefile; it can't take build commands from the lower-level makefile while it is trying to build the target in the upper-level makefile. So the usual solution is to build everything that can be built with the lower-level makefiles, hoping that that's adequate to build everything that's needed for the upper-level makefile.
Makepp loads all the makefiles in at once, so it has no problem dealing with situations where a file in one makefile depends on a file produced by a different makefile. Makepp cd's automatically to the directory containing the makefile before executing a command from a makefile, so each makefile may be written independently without knowledge of the top-level build directory.
Makefiles can use wildcards reliably. Wild cards match either files that exist, or files that makepp knows how to build, so even for a program with dozens of modules, your entire makefile could simply read something like this:
CXX = g++ CXXFLAGS = -g %.o : %.c $(CXX) $(CXXFLAGS) -c $(input) -o $(output) my_program: *.o $(CXX) $(inputs) -o $(output)
and this will work even if none of the .o
files have
been built yet.
makepp keeps track of the build commands, so that if compilation options change, files are automatically rebuilt. This is important to guarantee correct builds. (This idea was taken from Bob Sidebothem's "cons" utility, which was described in the Perl Journal in 1998 and is available from CPAN.)
To illustrate why this is important, consider the following structure definition:
class ABC { int x; #ifndef SPECIAL_OPTION int y; #endif int z; };
Now suppose you decide to turn on the SPECIAL_OPTION
option by adding -DSPECIAL_OPTION
to the command line.
A recompilation of everything is needed, but a traditional unix make
will not detect this. After a very frustrating debugging session,
you will discover that all that needs to be done is to rebuild
everything, and then you will switch to using makepp instead
of a traditional make. At least, that's what I did.
As another example, suppose that you are working on a project which is pretty well debugged, so it's usually compiled with C<-O2>. Now you run into a bug which you need to look at in the debugger. Code compiled with optimization is difficult to examine in the debugger, so you want to recompile your code so that you can look at it. If your makefile is set up to store the compiler options in the usual variables, you can just do this:
makepp CFLAGS=-g CXXFLAGS=-g
and makepp will know that the command line has changed for all the modules. Then when you've found your bug, just type
makepp
and it will be recompiled with optimization. You don't need to
type make clean
when you change build options.
Some makefiles (e.g., those for the linux kernel) go to incredible lengths to force recompilation when the compile command changes. With makepp, it's taken care of automatically--you don't have to do anything.
By default, makepp doesn't merely ensure that all targets are newer than all dependencies; if you replace a dependency with an older file, makepp knows that it has to rebuild the target, simply because the input file has changed. This is another important feature to guarantee correct builds which was taken from the "cons" utility.
Many modifications to source files do not actually require a rebuild. For example, if you just change a comment line, or if you reindent some code, there is no particular reason to force a compilation. For C/C++ compilation, makepp determines whether a file needs recompilation by computing a cryptographic checksum of the file's contents (ignoring comments and whitespace) instead of looking at the file time.
This is particularly useful if you have include files that are generated by files that change, and yet the generated include files themselves seldom change. Suppose you have a complicated yacc grammar in your program, with a build rule like this:
y.tab.c y.tab.h: yacc -d parser.y
Ordinarily, every time you make even a tiny change to
parser.y
, every file that depends on
y.tab.h
must be rebuilt since the file time of
y.tab.h
has changed. However, most changes to
parser.y
won't actually change the contents of
y.tab.h
(except possibly a comment), so all that
recompilation is unnecessary.
Makepp can automatically incorporate files from a different directory tree (the "repository") as needed into the current build tree. (This idea was also taken from the "cons" program.) This has a several interesting uses:
% makepp CFLAGS=-O2 # Compile everything.Ooops, bug discovered.
makepp CFLAGS=-g # Recompiles everything again. gdb my_programFind the bug...
makepp CFLAGS=-O2 # Recompiles everything a third time.
With makepp, you can simply cd to an empty directory, and specify your original directory as a repository. This will create new object files in the empty directory, while leaving your old object files intact. Now you can find the bug in the directory compiled with debug, fix it in your original sources, and then go back to your original directory. Now only the few files that you changed actually need to be recompiled.
The entire procedure would look like this:
% makepp CFLAGS=-O2 # Compile everything.Ooops, bug discovered.
% mkdir debugging % cd debugging % makepp -R .. CFLAGS=-g # Compile with debugging enabled, but # put objects in debugging subdir. % gdb my_programFind and fix the bug....
% cd .. # Back to original directory. % makepp CFLAGS=-O2 # Recompiles only those files # that you changed.
This can be a tremendous savings in time if there are many modules.
.o
filesmakepp can often infer exactly
which objects are actually necessary without being explicitly
told. If you enable this feature, then if one of your source file
includes xx.h
, and there is a file called
xx.o
that makepp knows how to make, then
makepp adds xx.o
to the link command line. I
don't use non-shared libraries now in many places where I used to,
because makepp can automatically pick out the modules I
need.
makepp won't be confused by soft links to a directory or
by different relative filenames that refer to the same file. All
directory paths to a file are recognized, including
foo
, ./foo
, ../src/foo
,
/auto_mnt/somedisk/bob/src/foo
, and
/users/bob/src/foo
.
makepp can support filenames with colons or spaces or other special characters that cause trouble for the traditional make. Just surround the filename with quotes.
Makepp can use arbitrary perl subroutines for textual substitution in the makefile. If you know perl, you are not constrained at all by the set of makepp's builtin textual manipulation functions.
You can also simply write perl code in your makefile. Make variables are actually simply stored as perl variables, so you can manipulate them with the full power of the entire perl language.
By default, makepp makes a file called
.makepp_log
that contains a description of every file
that it tried to build, what rule was used to build it, what it
depended on, and (if the file was rebuilt) which dependency was
different. This can be extremely useful for debugging a
makefile--if you're wondering why makepp decided to rebuild a
file, or why it didn't, you can just look in the log file where it
explains the decisions.
Makepp supports parallel compilations, but (unlike GNU make) it won't mix output from separate processes which are running simultaneously.
Makepp supports easier-to-remember
synonymns for the cryptic make variables $@
,
$^
, and $<
.