Individual Component Makefiles

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.

Basic Structure

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.

Default Target

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'.

Include make.defs.mk

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.

Preset and “Local” Variables

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:

includedir

The directory where headers are installed, if there any headers to install. The installed files is contingent upon the $(INSTALL_FILES) variable (see below).

srcdir

The directory containing the source code to be compiled. Normally, this is assigned a value based on the @srcdir@ substitution variable.

INSTALL

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:

SUBOBJDIR

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.

SUBDIR

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.

INSTALL_FILES

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.

Listing Source 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

Include All Necessary Doozer++ .mk Files

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.

Local Targets

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.

Clean-Up Information

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:

CLEAN_FILES

The list of files removed by the 'clean' target.

CLEAN_DIRS

The list of directories removed by the 'clean' target.

CLOBBER_FILES

The list of files removed by the 'clobber' target.

CLOBBER_DIRS

The list of directories removed by the 'clobber' target.

Dependencies

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

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.