Chapter 4. The “Global” Build

Table of Contents

Follow the Rules
Required Targets
File and Directory Names
Installation Hierarchy
configure.pl
Build Configuration File
Dependency Management
The *-config Scripts and the *.m4 Macro Files
A Typical -config Script
A Typical .m4 File
The Developer Installation
Future Goals

In this chapter, we present the design of the so-called “global” build. We cover the high-level aspects of the build system that ties together all the other build systems. In a sense, this is the one build system to rule them all.

Follow the Rules

In order for the global build to work, the modules it wraps must follow certain rules. If a module does not comply with all the rules, there is no guarantee that it will be able to compile under all circumstances. In other words, “rogue” modules that only implement a few pieces of the puzzle quickly become the weak link in the chain, and module build system authors who want to live outside the rule set complicate matters for everyone else.

Because the VR Juggler build system is a complicated dance, there are many rules that must be followed. For example, certain targets must be defined so that recursive make(1) calls can proceed through the entire source tree. These targets include 'release', 'install-debug', 'links', and 'buildworld'. Other rules include restrictions on the names of files or directories, use of platform-specific conventions, the presence of a working -config script (see the section called “The *-config Scripts and the *.m4 Macro Files”), and provisions for the detection of a usable installation. The full list of rules are provided in the following subsections.

Required Targets

There are a number of targets required by the global build. Some of these were listed above. The following sub-subsections give a complete list of all targets that must be implemented by a module's glue makefile. The targets are grouped by the task they perform.

Build Targets

The build targets have the job of building source code. What this means is up to the individual module. It may, for example, include the generation of source code using tools such as an IDL compiler, an XSLT processor, or a Java compiler. The targets need not do anything if, for whatever reason, the generic concept of building source code does not apply.

build

This target builds everything. It executes the first phase of the 'world' target (i.e., only the build phase, not the install phase). Since it builds both debugging and optimized versions of a module without installing, it is useful for testing changes to the library code to ensure that it works in both the debugging and optimized cases. A profiled version of the module may also be built if the module build system supports that and the compiler can build such a version.

buildworld

This target is the same as 'build'.

debug

Build only the debugging version of the module. If the target platform supports both types, static and dynamic versions are compiled. In other words, the module is built so that debugging symbols are turned on. It is the combination of 'dbg' and 'dbg-dso' (see below). This is the default target and is what gets built if running make(1) with no arguments.

optim

Build only the optimized version of the library binaries (both static and dynamic). This is built with no debugging symbols at all. It is the combination of 'opt' and 'opt-dso'.

profiled

Build only the profiled version of the library binaries (both static and dynamic). This capability is dependent on the compiler being used. Not all compilers support the process of generating profiled code, so this target may have no effect. Profiled libraries are built with debugging symbols. This target is the combination of 'prof' and 'prof-dso'.

dbg

Build only the static debugging version of the libraries. This does the same thing as 'debug' but does not compile the dynamic libraries.

dbg-dso

Build only the dynamic debugging version of the libraries. This does the same thing as 'debug' but does not compile the static libraries.

opt

Build only the static optimized version of the libraries. This does the same thing as 'optim' but does not compile the dynamic libraries.

opt-dso

Build only the dynamic optimized version of the libraries. This does the same thing as 'optim' but does not compile the static libraries.

prof

Build only the static profiled version of the libraries. This does the same thing as 'profiled' but does not compile the dynamic libraries.

prof-dso

Build only the dynamic profiled version of the libraries. This does the same thing as 'profiled' but does not compile the static libraries.

Installation Targets

The installation targets set in motion the process of installing a module. As with build targets, what this means may vary from module to module. Each module is responsible for ensuring that its installation hierarchy exists before trying to copy files.

install

This is the complement to 'build' (described in the section called “Build Targets”), and in most cases, it is assumed that the build was performed before an installation is attempted. This target executes the second phase of the 'world' target. It performs a complete installation of debugging and optimized versions of a module. Installation of a profiled build will be performed if a profiled version was generated. Further, both the dynamic and static versions of a module will be installed if the target platform supports both. (This is of course assuming that the module builds one or more libraries.)

installworld

This target is the same as 'install'.

install-debug

Install only the debugging version of the module. If the module includes one or more libraries, both static and dynamic versions of the libraries are installed.

install-optim

Install only the optimized version of the module. If the module includes one or more libraries, both static and dynamic versions of the libraries are installed.

install-profiled

Install only the profiled version of the module. This may have no effect if the module build system does not support building profiled code or if the compiler cannot generate profiled code. If the module includes one or more libraries, both static and dynamic versions of the libraries are installed.

Multi-Step Targets

There are a few multi-step targets required by the global build. Essentially, these targets perform builds and installations, though they do not necessarily build and installed exactly the same thing. They are intended to be used for making releases or for users who simply want a one-step build/install of a module.

world

Clean up the build environment and then build and install everything using the default ABI and ISA. This is a simple target for those who just want to build and install the module as simply as possible. “Everything” in this case is the following:

  • Debugging, optimized, and profiled versions of the library binaries

  • Shared and static versions of the library binaries (if both are supported on the target platform)

  • Header files

  • Sample applications, test code, user tools, etc.

  • Data files (sample config files, model files, whatever)

world-all-abi

This is the same as the 'world' target except that it builds and installs all possible ABI and ISA combinations for the target platform. On IRIX, for example, this means that all combinations of N32, 64, mips3, and mips4 (debugging and optimized versions) are built and installed. Most platforms currently support only one ABI/ISA combination thus making this target the same as 'world'.

Note

As of this writing, the global build does not have this target. Some modules in the Juggler Project still do not support building multiple ABIs.

release

This target is similar to 'world' except that the installation tree is suitable for redistribution. Extra files such as the change logs, the release notes, and the license files are installed. In addition, the tree is stamped with a build time to help track possible differences between two releases of the same version. (This has only occurred for one VR Juggler beta release, but it seems like a good idea to have the build time included with a distribution.)

release-all-abi

This is the same as the 'release' target except that it builds and installs all possible ABI and ISA combinations for the target platform. On IRIX, for example, this means that all combinations of N32, 64, mips3, and mips4 (debugging and optimized versions) are built and installed. Most platforms currently support only one ABI/ISA combination thus making this target the same as 'release'.

Note

As of this writing, the global build does not have this target. Some modules in the Juggler Project still do not support building multiple ABIs.

Clean-Up Targets

There are three targets used to clean up the build environment. Each cleans the tree to a different degree. Of the following three, 'clean' and 'cleandepend' remove disjoint sets of files. The 'clobber' target performs at least the tasks of 'clean' and 'cleandepend'.

clean

Clean up everything in the build environment. This uses the 'clean' target defined by Doozer++ that is automatically included by all makefiles. The cleaning process is recursive just as the build process is. Each makefile may define which files are safe for cleaning, but generally core files, compiler-generated files, and object files are the only things removed during this process.

cleandepend

Clean up the automatically generated dependency files (the .d files in each directory). This method for cleaning up deletes only these files and nothing else—ever.

clobber

Clean up (clobber) the entire build environment except what was generated by configure. This runs the above clean-up targets and removes the object directory(ies) and lib directory(ies). Its purpose is to reset the build environment to its state just prior to running configure.

Developer Targets

Finally, there are two targets that are relevant only to developers. These relate to the developer installation (see the section called “The Developer Installation”). One creates the developer installation, and the other removes it.

links

Set up the developer pseudo-installation environment.

clean-links

Remove the developer pseudo-installation environment.

File and Directory Names

There are certain naming conventions for files and directories that must be followed in order to ensure consistency among all the modules in the Juggler Project. Not all of these relate directly to the build system, but the names may be influenced by the way the build system works.

Autoconf-Generated Header File

All the modules (save one) make use of an Autoconf-generated header file that sets up #defines based on tests performed by the module's configure script. To avoid overlap or confusion, these files are named based on the module's C++ namespace. Furthermore, the header files must be generated within the module's unique header directory. For example, in Gadgeteer, the C++ namespace is gadget. Hence, the header file is gadgetDefines.h, and it is generated to the gadget directory.

The reason for the redundancy is to prevent user errors by avoiding ambiguities. Consider the following bit of code:

#include <defines.h>
#include <vpr/Sync/Mutex.h>
#include <vrj/Kernel/Kernel.h>

Now, for the sake of this example, assume that a user had both -I$VJ_BASE_DIR/include/vpr and -I$VJ_BASE_DIR/include/vrj on his or her command line. If both VPR and VR Juggler had a defines.h file, there would be no way to distinguish which is which. While this is a textbook case of operator error, the naming convention we use avoids this case entirely.

The problem arises because the generated header files do not include any other headers in the project. As a result, the generated headers are not tied as tightly to the directory structure as are the static headers. Hence, the above case is not so far-fetched. It could happen very easily with an inexperienced user.

Module Configuration Header

Each module has a single header that includes the header file generated by running configure. The idea here is to have a single point where common actions are taken based on what comes in through the generated header. For example, based on platform settings, symbol export macros are defined in the module configuration header. This single header is then included by all the other files in the project.

The naming convention for the module configuration header is the same as that of the generated header except that the word “Config” is used instead of “Defines”. The reasoning for this convention is similar to that of the generated header, but in this case, at least one other file from the project is being included. Namely, the generated header file is always included on the first (non-comment) line of this header file. We decided long ago that the name Config.h was too common and needed an extra bit of uniqueness. Again, this is done to prevent user errors.

Data Directory

In VR Juggler 2.0, the installation of multiple modules must be managed so that one module's (optional) extra data does not conflict with that of another module. All data must be installed into the directory $(prefix)/share (to use some make(1) notation). To prevent conflicts with other modules (and with other software that may already exist on the target machine), each module must name a project data directory (the variable $(projdatadir) is used to store this in the makefiles). In most cases, the unique directory should be the name of the project in lowercase letters with no spaces. For example, the directory for JCCL would be $(prefix)/share/jccl, and the directory for VR Juggler would be $(prefix)/share/vrjuggler.

Installation Hierarchy

The structure of an installation hierarchy is fairly open, but there are several basic requirements. They are as follows:

  • Headers go in $prefix/include. Ideally, a module will use a subdirectory of that for its own header files.

  • User-accessible executables/scripts go in $prefix/bin.

  • Libraries go in subdirectories of $prefix/lib (or a variant thereof depending on platform-specific conventions). More specifically, optimized libraries go in $prefix/lib/opt, debugging libraries go in $prefix/lib/debug, and profiled libraries go in $prefix/lib/profiled. There is further subdivision within those directories based on the binary format (ELF, a.out, etc.) and the instruction set architecture (i386, i686, mips4, sparc, etc.). To make things more convenient for users, symlinks to (or copies of depending on the host platform) libraries should be made in $prefix/lib. For full releases, we make symlinks to the optimized libraries. In the developer installation, we make symlinks to the debugging libraries. Typically, profiled libraries will be named differently than their non-profiled counterparts, so symlinks to those can be made along side the non-profiled versions.

  • Project data files and sample code goes in $prefix/share/<project-name>. The use of the <project-name> subdirectory is to avoid conflicts with existing software.