The recursion performed by the glue makefile would have nothing
to do were it not for the component makefiles in each subdirectory.
The job of these files is to provide the list of source files that
must be compiled. When the recursion comes to a given subdirectory, a
target ('dbg', 'opt', etc.) is
executed. Based on that target, the compiler argument list is
constructed, and the source files are compiled iteratively (or in
parallel depending on arguments given to
make(1)).
In most cases, the targets are defined in Doozer++ makefile stubs such as
dpp.obj.mk. It is up to the build system author to ensure that the
right makefile stubs are used together. Doozer++ 1.5 provides only a
few such stubs, and they all cooperate well. For example,
dpp.libs.mk, dpp.libs-basic.mk, and dpp.simple-app.mk all work seamlessly with
dpp.obj.mk, dpp.subdir.mk, and dpp.obj-subdir.mk.
In the following sub-subsections, we examine the basic structure of a component makefile. This is almost a line-by-line examination of a given makefile. We concentrate our discussion on the compilation of C/C++ code since that makes up the majority of the Juggler Project. Java and IDL are handled somewhat differently, though the basic ideas are the same. The major difference is that no dependency files are generated for Java or IDL using Doozer++ 1.5.
The default target should be defined before doing anything
else. This prevents any external files that are included from
inadvertently defining a target that would be implicitly defined
as the default target. In most cases, the default target will be
'all' or 'debug'.
The next step should be to include
make.defs.mk to get all of its variable
definitions. Some of these may be needed later in the execution of
the including file, so it should be included as early as possible.
It should always be included using the following syntax for the
path:
include @topdir@/make.defs.mk
This will ensure that the path is specified correctly when
the actual makefile is generated. Once
make.defs.mk is included,
$(MKPATH) can be used for including files in
the Doozer++/mk directory of the source
tree.
The first set of assignments is for preset variables (that is, those that are substituted by configure when the makefile is generated) and for variables that are closely related to the preset variety. In general, many of these variables will have values that are specific to the current makefile. In other words, they cannot be shared via a common file that would be included by all makefiles.
Depending on which makefile is being examined, any extra “miscellaneous” variables needed are also set here. For the most part, everything set here is local to the current file.
There are several significant variables that must be set. They are as follows:
The directory where headers are installed, if there
any headers to install. The installed files is contingent
upon the $(INSTALL_FILES) variable (see
below).
The directory containing the source code to be
compiled. Normally, this is assigned a value based on the
@srcdir@ substitution variable.
The path to the installer program. This must be included in every component makefile because it may be the install-sh script distributed with Autoconf. In that case, the path will be relative, and it will vary with each level of nesting in the source tree.
There are also some optional variables that may be necessary depending on the circumstances. They are:
The subdirectory of $(OBJDIR)
(defined by Doozer++) where the object files compiled by this
component makefile go. In most cases, it is helpful to use
this variable, especially when building multiple
libraries.
This is the list of all subdirectories of the current
directory where compiling needs to be done. It is used in
conjunction with
dpp.subdir.mk or
dpp.obj-subdir.mk to perform recursive calls to
make(1). These recursive calls to
make(1) are handled wherever other
subdirectories exist—not just in the top-level glue
makefile.
The files to install. If it is not set, it defaults to
$(srcdir)/*.h. It may be extended
(using the += operator) or overridden based on specific
needs. Extensions must be performed after including the
Doozer++ .mk file(s), however. See
below for more details on including these files.
Next, we provide the list of source files that will be
compiled. This is also used by makedepend(1)
(or other dependency-generating tool) to get dependencies. For C++
code, the list of sources must be assigned to the
$(SRCS) variable. These file names should not
have any path prepended to them. When the dependencies (the
so-called “.d files”) are
generated, it knows which directory contains the files to parse
from the $(srcdir) variable.
In some cases, some files may be conditionally compiled depending on the results of running the configure script. To add these source files to $(SRCS), insert something similar to the following before making the list of object files:
ifeq (@SOME_VAR@, "YES") SRCS+= extra_src.cpp endif
Once all the variables are defined, this is a good time to
include any of the .mk files that this file
will need. The path to these files can be extracted from the
variable $(MKPATH) that is defined via
make.defs.mk. Typically, these will be the
files needed for compiling, generating dependencies and
installing.
Some makefiles may need targets for any source files that
are not compiled into the module's library but are used, for
example, as test code. This is one example of a time when it may
be convenient to compile code in the current directory rather than
in a common object file directory. Generally, these targets will
be similar to the library targets; however, dependencies will not
be generated for these files. The Doozer++ dependency-generation code only looks at
$(SRCS). A local target for generating
dependencies could be defined if so desired.
After including one of the Doozer++ compiler makefile stubs, a makefile can add to the
list of files and/or directories removed when the
'clean' and 'clobber'
targets are run. To extend these targets, add to (+= operator) one
or more of the following variables:
The list of files removed by the
'clean' target.
The list of directories removed by the
'clean' target.
The list of files removed by the
'clobber' target.
The list of directories removed by the
'clobber' target.
Most Makefile.in files end with an area
where all the source dependencies are loaded via the GNU make
include mechanism. In general, it appears as follows:
-include $(DEPEND_FILES)
The variable $(DEPEND_FILES) is
constructed by Doozer++ based on the list of C/C++ sources (see the section called “Listing Source Files” above). The use of
-include versus include
silences warnings when the dependency files have not been created
yet.
Some makefiles wrap the line using GNU make conditional constructs. In particular, most component makefiles actually use the following line:
ifndef DO_CLEANDEPEND -include $(DEPEND_FILES) endif
This is a workaround for a deficiency in GNU make. Namely,
the target that is being executed cannot be tested. As a result,
cleaning up dependencies with the 'cleandepend'
target is not straightforward. Doozer++ defines that target, and
when it is executed, it defines the variable
$(DO_CLEANDEPEND). If, at some point, GNU make
had features to test the target being executed, this workaround
(and many, many others) could be removed.
Extension to a given component makefile is almost always done
by adding more source files to the $(SRCS)
variable assignment. Whether this is done using conditional
constructs or extending the always-build list depends on the
circumstances. Other extensions may come in the form of new files
that may be generated from an IDL file or an extension to the list
of files to be installed.