========================================================================== Modular C++ Build System User Guide ========================================================================== # Author : Christopher Batten # Date : Sep 2008, Sep 2009 The Modular C++ Build System is a set of makefiles, scripts, and policies for managing, testing, and building large C++ projects. The fundamental idea is that we divide the large project into several smaller pieces called subprojects. Subprojects are both a physical concept (ie. how the files are named and organized) as well as a logical concept (ie. how the classes and functions are named and organized). Each subproject should encapsulate a moderate amount of related functionality; subprojects usually contain tens of classes and source files. The build process is modular since the header files, source files, make fragment, and autoconf fragment for a subproject are all contained within one subdirectory. We should be able to easily copy subprojects between projects (assuming they are using the same modular build system) and the only change we need to make is updating the list of subprojects in the top-level autoconf script. This user guide is divided into two parts: the first part is a tutorial, and the second part contains general user reference. The build system includes an integrated unit test framework (which is also an example of a subproject), so developers should also read the testing documentation in 'utst/utst-uguide.txt'. Table of Contents Part 1: Tutorial 1.1. Building a Project 1.2. Adding a Basic Subproject 1.3. Adding a More Advanced Subproject 1.4. Managing Optional Subprojects 1.5. Installing Subprojects 1.6. Including External Subprojects 1.7. Distributing Package Tarballs Part 2: Reference 2.1. Overview of Build Process 2.1. Basic Autoconf Toolflow 2.2. Managing Subprojects in configure.ac 2.3. Subproject Autoconf Fragments 2.4. Basic Make Toolflow 2.5. Subproject Makefile Fragments -------------------------------------------------------------------------- 1. Tutorial -------------------------------------------------------------------------- This part walks through using the Modular C++ Build System to create a new project, add subprojects, and then build, test, and install the project. You can follow along through the tutorial yourself by typing in the commands marked with a '%' symbol at the shell prompt. Tasks to try on your own are denoted with (*). To cut and paste commands from this tutorial into your bash shell (and make sure bash ignores the '%' character) just use an alias to "undefine" the '%' character like this: % alias %="" Before creating a new project we need to decide what to name the project. We should choose both a longer descriptive name (eg. Image Processing Tools) as well as a short abbreviated name (eg. 'ipt'). For this tutorial we will use the abbreviated name 'mcppbs-tut'. Once we know the abbreviated name for our project, we then need to download and extract the 'mcppbs' template from the public git repository. We unpack the template into a directory with the abbreviated project name, and to simplify simplify the rest of the tutorial we will also define a '$PROJROOT' environment variable which contains the absolute path to the project's top-level root directory. % TGZ=http://gitorious.org/cbatten-sw/mcppbs/archive-tarball/master % wget -O- $TGZ | tar xzf - % mv cbatten-sw-mcppbs mcppbs-tut % cd mcppbs-tut % PROJROOT=$PWD Take a look at the files in the top-level project directory. - 'README' : Project documentation - 'mcppbs-uguide.txt' : Build system documentation - 'configure.ac' : Input for autoconf tools - 'aclocal.m4' : Local macros for autoconf tools - 'Makefile.in' : Template for 'configure' script - 'config.h.in' : Generated by 'autoheader' - 'configure' : Generated by 'autoconf' - 'scripts' : Subdirectory with build system scripts - 'utst' : Subdirectory with unit testing framework After acquiring the template, we need to update the metadata in 'configure.ac' with the new project's name, maintainer's name, and a shorter abbreviated form of the project name. The abbreviated form should be all lowercase, uses a dash as a separator, and usually is the same as the project directory name. We want to change the metadata in 'configure.ac' so that it looks like this: m4_define( proj_name, [MCPPBS Tutorial]) m4_define( proj_maintainer, [Christopher Batten]) m4_define( proj_abbreviation, [mcppbs-tut]) m4_define( proj_version, [0.0]) For the tutorial we use sed to make the change, but you can also use your favorite text editor. Note that after we change 'configure.ac' we need to rerun the autoconf tools. % cd $PROJROOT % sed -i.bak \ -e 's/\( proj_name,\).*/\1 [MCPPBS Tutorial])/' \ -e 's/\( proj_maintainer,\).*/\1 [Christopher Batten])/' \ -e 's/\( proj_abbreviation,\).*/\1 [mcppbs-tut])/' \ -e 's/\( proj_version,\).*/\1 [0.0])/' configure.ac % autoconf && autoheader This might also be a good time to update the 'README' with information specific to the new project. You are encouraged to keep a pointer in the 'README' to the documentation on the build system ('mcppbs-uguide.txt') so that your end-users can learn how the build system works. -------------------------------------------------------------------------- 1.1. Building a Project -------------------------------------------------------------------------- The first thing we will try is just building the project, although the only subproject in the default template is the unit testing framework. Take a look at the unit testing subproject in the 'utst' subdirectory. You will see the following files: - 'utst-guide.txt' : Unit testing framework documentation - 'utst.h' : Top-Level header file - 'utst-*.h' : Header files - 'utst-*.inl' : Inline header/source files (see style guide) - 'utst-*.cc' : Source files - 'utst.t.cc' : Unit test source file - 'utst.ac' : Autoconf fragment - 'utst.mk.in' : Makefile fragment - 'utst.pc.in' : Metadata file for installing subproject library The build system is structured so that we do not intermingle the generated files with the source files. This makes it much easier to manage larger projects. You should _always_ create a separate build directory in which to build the project. To do a clean build you just need to delete the build directory and start over. Separate build directories also makes it trivial to work with multiple builds of the same project at the same time. This is particularly useful when building a project for multiple architectures. The following commands create a build directory, configure the project, and then builds/runs all the unit tests. % cd $PROJROOT % mkdir build % cd build % ../configure % make % make check Notice that we run the portable 'configure' shell script to customize the build system for the current platform. The 'configure' script displays information about what kind of checks it is performing. Take a look at what is in the build directory. You will see the following files: - 'Makefile' : 'configure' generated makefile - 'config.log' : Log from 'configure' script - 'config.status' : Cached version of the 'configure' script - 'utst.mk' : 'configure' generated makefile fragment - 'utst-config.h' : 'configure' generated header file - 'utst-*.d' : Auto-generated dependency file per '.cc' - 'utst-*.o' : Object file for each '.cc' - 'libutst.a' : Library for 'utst' subproject - 'utst.t.d' : Dependency file for 'utst.t.cc' unit test - 'utst.t.o' : Object file for 'utst.t.cc' unit test - 'utst-utst' : Executable for 'utst.t.cc' unit test - 'utst-utst.out' : Output from running 'utst-utst' The 'configure' script takes the 'Makefile.in' file in the project root directory and makes some substitutions for special variables denoted with the '@variable@' syntax. Look for '@srcdir@' in 'Makefile.in' and compare that line to the same line in the generated 'Makefile'. You can see that the 'configure' script has substituted the correct path to the top-level directory for the C++ project. No matter where you put the build directory, 'configure' will fill this variable in correctly so that the build system can always find the source files. Compare 'Makefile.in' and 'config.h.in' to the generated 'Makefile' and 'utst-config.h' to see what other changes the 'configure' script customized based on your specific platform. Notice that if a subproject is enabled then a C preprocessor macro will be defined (eg. 'UTST_ENABLED') so that developers can write conditional code based on which subprojects are available. Unlike many other projects, we do not use a single global 'config.h' file. Instead each subproject has its own 'config.h' file (eg. 'utst-config.h'). Although all of these header files are identical, separating them by subproject enables us to install the subproject's header files (including the appropriate 'config.h' file) and avoid filename clashes. Subprojects should only '#include' their _own_ 'config.h' file. To clean up the build directory we can use the 'clean' make target. Take a look at what is in the build directory before and after running 'make clean'. Afterwards you should just see the scripts generated by 'configure'. To remove everything so that the build directory is completely empty we can use the 'distclean' make target. % cd $PROJROOT/build % make clean -------------------------------------------------------------------------- 1.2. Adding a Basic Subproject -------------------------------------------------------------------------- Each subproject is contained in a subdirectory beneath the main project directory. The name of the subdirectory should be an abbreviated form (four to eight characters) of the full name of the subproject. For example, the unit testing subproject is contained in the 'utst' subdirectory. The subdirectory includes all of that subproject's header and source files, and these files should use the subproject's name as a filename prefix with a dash as a separator (eg. 'utst-TestLog.h'). To add a new subproject, we need to take the following steps: - Create a new subdirectory with the abbreviated subproject name - Add the subproject's source files - Add autoconf and makefile fragments - Include the subproject in the top-level 'configure.ac' We will now go through these steps for an example subproject named Simple Geometric Primitives ('sgp'). This subproject might encapsulate various classes and functions to represent and manipulate points, lines, and polygons. For this tutorial, we will just add a very simple class representing a point in a 2D plane. The first step is to create a new subdirectory. % cd $PROJROOT % mkdir sgp The next step is to write three new C++ files for our point class: 'sgp-Point.h', 'sgp-Point.cc', and 'sgp-Point.t.cc'. Notice that we use 'sgp-' as a prefix for all files in the subproject. The 'sgp-Point.t.cc' file is a unit test for the 'Point' class. For more information on the unit testing framework see the general documentation in 'utst/utst-uguide.txt'. So first we write the 'sgp-Point.h' header file. % cd $PROJROOT/sgp % cat > sgp-Point.h \ <<'END' #ifndef SGP_POINT_H #define SGP_POINT_H #include namespace sgp { class Point { public : Point(); Point( int x, int y ); int get_x() const; int get_y() const; private : int m_x, m_y; }; std::ostream& operator<<( std::ostream& os, const Point& pt ); bool operator==( const Point& pt1, const Point& pt2 ); Point operator+( const Point& pt1, const Point& pt2 ); } #endif END Notice that the 'Point' class is placed in the 'sgp' namespace and that we use a 'SGP_' prefix for preprocessor macros. Next we write the source file. % cd $PROJROOT/sgp % cat > sgp-Point.cc \ <<'END' #include "sgp-Point.h" namespace sgp { Point::Point() : m_x(0), m_y(0) { } Point::Point( int x, int y ) : m_x(x), m_y(y) { } int Point::get_x() const { return m_x; } int Point::get_y() const { return m_y; } std::ostream& operator<<( std::ostream& os, const Point& p ) { return ( os << "(" << p.get_x() << "," << p.get_y() << ")" ); } bool operator==( const Point& p1, const Point& p2 ) { return ((p1.get_x() == p2.get_x()) && (p1.get_y() == p2.get_y())); } Point operator+( const Point& p1, const Point& p2 ) { return Point( p1.get_x()+p2.get_x(), p1.get_y()+p2.get_y() ); } } END We need to define an insertion operator and an equality operator to be able to use some of the basic checks in the unit testing framework. Here is the 'sgp-Point.t.cc' file which does a simple unit test to check if the addition of two points is working. % cd $PROJROOT/sgp % cat > sgp-Point.t.cc \ <<'END' #include "utst.h" #include "sgp-Point.h" UTST_AUTO_TEST_CASE( TestAddition ) { sgp::Point pt1(1,2); sgp::Point pt2(2,3); UTST_CHECK_EQ( pt1 + pt2, sgp::Point(3,5) ); } int main( int argc, char* argv[] ) { utst::auto_command_line_driver( argc, argv ); } END We might also want to have a command-line program in our subproject. The following little example takes two points specified on the command line and outputs their sum. % cd $PROJROOT/sgp % cat > sgp-add-points.cc \ <<'END' #include "sgp-Point.h" #include int main( int argc, char* argv[] ) { if ( argc != 5 ) return 1; sgp::Point pt1(atoi(argv[1]),atoi(argv[2])); sgp::Point pt2(atoi(argv[3]),atoi(argv[4])); std::cout << pt1 << " + " << pt2 << " = " << (pt1+pt2) << std::endl; return 0; } END Now that we have added our source code and the unit test to our subproject, the next step is to add the autoconf fragment ('sgp.ac') and the makefile fragment ('sgp.mk.in'). We should always include a call to the 'MCPPBS_SUBPROJECT' macro in our autoconf fragment. This macro will expand out to various setup steps in the 'configure' script, and later in this tutorial we will see how we can use this macro to specify dependencies between subprojects. We will also see later how we can use additional autoconf macros in this fragments to check installed libraries or compiler features. % cd $PROJROOT/sgp % cat > sgp.ac \ <<'END' MCPPBS_SUBPROJECT([sgp]) END The 'MCPPBS_SUBPROJECT' macro will set various variables which will be substituted into our makefile fragment when we run 'configure'. For example, 'configure' will substitute in the appropriate compiler and linker flags so that we can properly build this subproject. The makefile fragment should ultimately set special make variables to tell the top-level makefile about these compiler and linker flags as well as the source files which make up the 'sgp' subproject. % cd $PROJROOT/sgp % cat > sgp.mk.in \ <<'END' sgp_intdeps = @sgp_intdeps@ sgp_cppflags = @sgp_cppflags@ sgp_ldflags = @sgp_ldflags@ sgp_libs = @sgp_libs@ sgp_hdrs = sgp-Point.h sgp_srcs = sgp-Point.cc sgp_test_srcs = sgp-Point.t.cc sgp_install_prog_srcs = sgp-add-points.cc END We use 'sgp_' as a prefix for all make variables in the makefile fragment. Here is a list of all the recognized make variables for a subproject named 'sproj'. - 'sproj_hdrs; : Headers (.h) - 'sproj_inls' : Inline header/source files (.inl) - 'sproj_srcs' : Source files (.cc,.c,.S) - 'sproj_test_srcs' : Sources for unit tests (.t.cc) - 'sproj_prog_srcs' : Sources for programs (.cc,.c) - 'sproj_install_prog_srcs' : Sources for programs to install (.cc,.c) Note that we can include C, C++, and assembly source files in our sources list ('sprojs_srcs'), C and C++ source files in our program sources ('sproj_proj_srcs', 'sproj_install_prog_srcs'), but only C++ test programs ('sproj_test_srcs'). Even though we can include these different type of sources everything is always compiled with the C++ compiler. Now that our 'sgp' subproject is ready to go, the last step is to include the subproject in the top-level 'configure.ac' file. If you look through the 'configure.ac' file you will see that there is already a call to the 'MCPPBS_INCLUDE_INTERNAL' macro for 'utst'. We simply need to add a new line for the new 'sgp' subproject. For the tutorial, we use sed to make this change. After modifying 'configure.ac' we need to rerun the autoconf tools. Remember that we always run 'autoconf' and 'autoheader' together. % cd $PROJROOT % sed -i.bak '/MCPPBS_INCLUDE_INTERNAL(\[utst\])/ a \ MCPPBS_INCLUDE_INTERNAL([sgp]) ' configure.ac % autoconf && autoheader There are primarily two types of subprojects: internal and external. Internal subprojects are ones where we actually include the source code for the subproject as a subdirectory. External subprojects are ones where we simply leverage a previously installed library for a subproject. We will learn more about external subprojects later in this tutorial. We are now finally ready to build the project with our newly added 'sgp' subproject. We simply go into the build directory and use 'make' to build a library ('libsgp.a') which includes all of the object files for the subproject as well as any subproject programs. Then we use 'make check' to build and run all of the unit tests. % cd $PROJROOT/build % make % make check Let's take a closer look at the output. Notice that the makefile automatically reran the 'configure' script since it was modified by 'autoconf'. The 'configure' script reports which subprojects are being included in the current project. If you look at the output from the 'configure' script you will see the following two lines: configure: configuring internal subproject utst configure: configuring internal subproject sgp The makefile has rules to build the unit test executable 'sgp-Point-utst' from the 'sgp-Point.t.cc' source file. You should see the following output from running 'sgp-Point-utst': Unit Tests : sgp-Point-utst Running test suite : default + Running test case : TestAddition The output from each unit test is saved in a '.out' file, and the output tells us that the 'TestAddition' test case was run and that there were no errors. 'make check' also displays a summary of all the unit test results (by grepping the '.out' files) producing this output: Unit Tests : utst-utst Unit Tests : sgp-Point-utst If there were any errors they would be displayed in this summary as well as in the per unit test output. (*) Run the 'sgp-add-points' command line program and verify that it works. (*) Try purposely changing the unit test so that it fails and observe how the output from 'make check' changes. (*) Try also adding a new subtraction operator for points and the associated unit test. (*) Finally, add a new class to the 'sgp' subproject called 'sgp::Rectangle' which uses two points to represent a rectangle. -------------------------------------------------------------------------- 1.3. Adding a More Advanced Subproject -------------------------------------------------------------------------- In this section we will add a second subproject called the Image Processing Library ('ipl'). This subproject might include support for various image file formats and image processing routines like color conversion or coordinate transformations. For the tutorial, we will implement a new class called 'RasterImage'. Ideally this class would take a diagram made up of many primitives from the 'sgp' subproject and rasterize them into an image. For this tutorial, we will just use a single point instead of a full diagram. This section will illustrate making one subproject depend on another subproject, and also show how to add a subproject autoconf fragment. As with the 'sgp' subproject, we first create a new subdirectory. % cd $PROJROOT % mkdir ipl We now create the header file 'ipl-RasterImage.h'. % cd $PROJROOT/ipl % cat > ipl-RasterImage.h \ <<'END' #ifndef IPL_RASTER_IMAGE_H #define IPL_RASTER_IMAGE_H #include "sgp-Point.h" #include namespace ipl { class RasterImage { public : RasterImage(); void set_diagram( const sgp::Point& pt ); sgp::Point get_diagram() const; private : sgp::Point m_pt; }; std::ostream& operator<<( std::ostream& os, const RasterImage& im ); bool operator==( const RasterImage& im1, const RasterImage& im2 ); } #endif END As before, we use the abbreviated subproject name as a namespace and as a prefix for preprocessor macros. Notice that we do not use '#include "sgp/sgp-Point.h"' but instead we just use '#include "sgp-Point.h"'. The build system makes the source files in all of the subprojects appear flat by using the '-I' command line argument to 'g++' and the 'VPATH' make variable. So you should just '#include' a subproject header file without the path. Next we create the source file 'ipl-RasterImage.cc'. % cd $PROJROOT/ipl % cat > ipl-RasterImage.cc \ <<'END' #include "ipl-RasterImage.h" namespace ipl { RasterImage::RasterImage() { } void RasterImage::set_diagram( const sgp::Point& pt ) { m_pt = pt; } sgp::Point RasterImage::get_diagram() const { return m_pt; } std::ostream& operator<<( std::ostream& os, const RasterImage& im ) { return ( os << "[ " << im.get_diagram() << "]" ); } bool operator==( const RasterImage& im1, const RasterImage& im2 ) { return ( im1.get_diagram() == im2.get_diagram() ); } } END As you can see, the 'RasterImage' is just a little wrapper around a single point. We now add a simple unit test. % cd $PROJROOT/ipl % cat > ipl-RasterImage.t.cc \ <<'END' #include "utst.h" #include "sgp-Point.h" #include "ipl-RasterImage.h" UTST_AUTO_TEST_CASE( TestBasic ) { ipl::RasterImage im1, im2; im1.set_diagram( sgp::Point(1,2) ); im2.set_diagram( sgp::Point(1,2) ); UTST_CHECK_EQ( im1, im2 ); } int main( int argc, char* argv[] ) { utst::auto_command_line_driver( argc, argv ); } END We now need to add the autoconf fragment ('ipl.ac') and the makefile fragment ('ipl.mk.in'). Our autoconf fragment will use the second argument to 'MCPPBS_SUBPROJECT' to tell the build system that the 'ipl' subproject depends on the 'sgp' subproject. The build system can then take care of making sure that we link the appropriate libraries together when building the 'ipl' subproject. The build system can also discover indirect dependencies. So if the 'sgp' subproject in turn depended on a subproject 'foo' the build system would discover this and make sure that 'ipl' will build correctly. % cd $PROJROOT/ipl % cat > ipl.ac \ <<'END' MCPPBS_SUBPROJECT([ipl],[sgp]) END Our makefile fragment looks similar to the 'sgp' subproject except that now we use 'ipl_' as a prefix for all make variables. % cd $PROJROOT/ipl % cat > ipl.mk.in \ <<'END' ipl_intdeps = @ipl_intdeps@ ipl_cppflags = @ipl_cppflags@ ipl_ldflags = @ipl_ldflags@ ipl_libs = @ipl_libs@ ipl_hdrs = ipl-RasterImage.h ipl_srcs = ipl-RasterImage.cc ipl_test_srcs = ipl-RasterImage.t.cc END Finally, we include the 'ipl' subproject in the top-level 'configure.ac', build the project, and run the unit tests. % cd $PROJROOT % sed -i.bak '/MCPPBS_INCLUDE_INTERNAL(\[sgp\])/ a \ MCPPBS_INCLUDE_INTERNAL([ipl]) \ ' configure.ac % cd build % make check We add 'ipl' after the other subprojects since the subprojects must be ordered such that subprojects only depend on those included earlier. Since 'ipl' depends on 'sgp', 'sgp' must be included before 'ipl'. Notice that this time we did not explicitly rerun the autoconf tools. The makefile detects that 'configure.ac' has changed and reruns the autoconf tools. The 'configure' script output now mentions the 'ipl' subproject. configure: configuring internal subproject utst configure: configuring internal subproject sgp configure: configuring internal subproject ipl Also notice that the makefile only builds the 'ipl' subproject. There is no need to rebuild the 'utst' nor the 'sgp' subprojects since they have not changed. The automatic dependency checking helps minimize what needs to be recompiled. Look carefully at the compiler and linker command lines which the build system is using to build the new subproject. You should see something like this when building 'ipl-RasterImage.o' g++ -Wall -g -O3 -MMD -MP -I. -I../ipl -I../sgp -c \ ../ipl/ipl-RasterImage.cc The build system have used the fact that 'ipl' depends on 'sgp' (expressed with the 'MCPPBS_SUBPROJECT' macro in 'ipl/ipl.ac') to add '-I../sgp' to the compiler command line. You should see something like this when linking the 'ipl-RasterImage-utst' unit test executable. g++ -L. -o ipl-RasterImage-utst ipl-RasterImage.t.o -lipl -lsgp -lutst Each subproject builds a library which can then be used by that subproject's executables and also by other subprojects. The build system has used correctly used 'libipl.a' and 'libsgp.a' when linking the unit test case. The build system will automatically determine the proper order of the libraries for the linker command line. (*) To understand how dependencies between subprojects work, try removing 'sgp' from the 'MCPPBS_SUBPROJECT' autoconf macro in 'ipl/ipl.ac'. If you then just rerun 'make check' it won't actually cause an error, because of some subtleties in how make manages dependencies (only the compiler and linker command lines change and make isn't smart enough to know this means we need to recompile the unit tests). So let's do a clean build by first using 'make clean' and then using 'make check'. You should get an errors indicating that ipl cannot find the appropriate header files. If you look at the compiler command line you will see that '-I../sgp' is not present. Go ahead and add 'sgp' back to the 'MCPPBS_SUBPROJECT' autoconf macro and verify that things work once again. In the last part of this section, we will illustrate how to use a subproject autoconf fragment. Let's say that the 'ipl' subproject wants to use the globally installed 'libjpeg.a' library to read and write JPEG files. If this library is not installed, then we still want to build our project; it just won't have JPEG support. The following is some example autoconf code which we can add to 'ipl.ac' to do this check. % cd $PROJROOT/ipl % cat >> ipl.ac \ <<'END' AC_CHECK_HEADERS([jpeglib.h],[have_jpeg="yes"],[have_jpeg="no"]) AS_IF([test "$have_jpeg" = "yes"],[ AC_SEARCH_LIBS([jpeg_std_error],[jpeg],[],[have_jpeg="no"]) ]) AS_IF([test "$have_jpeg" = "no"],[ AC_MSG_WARN([Could not find jpeg library]) AC_MSG_WARN([Build will not include jpeg support]) ],[ AC_DEFINE([IPL_HAVE_JPEG],,[Define if jpeg library is available]) AS_VAR_APPEND([sgp_libs],"-ljpeg ") ]) END This code first uses the 'AC_CHECK_HEADERS' macro to see whether or not the 'jpeglib.h' header file exists. If so, then we also check if the library 'libjpeg.a' exists with the 'AC_SEARCH_LIBS' macro. We need to pass a function which is in the library so that the 'configure' script can try to link this function against the library. Although we could just use 'main', using a function which is specific to that library is more robust. If the header and the library exist, then we use the 'AC_DEFINE' macro to define the C preprocessor macro 'IPL_HAVE_JPEG' and we also append the library to 'sgp_libs' which will eventually be substituted into our makefile fragment. Notice that as with other macros, we use the abbreviated subproject name as a prefix. Most of the built-in checks (eg. 'AC_CHECK_HEADERS', 'AC_SEARCH_LIBS') are cached so that it is perfectly fine to include the same check in different subproject autoconf fragments. Note that autoconf macros use the compiler set with 'AC_PROG_CXX' to do these checks, so if you are using a cross compiler and have installed the desired libraries in that cross-compiler's sysroot, then the checks should work. Now that we have written the check, we also need to update our code so that our subproject can work with our without the JPEG library. We do this by including 'ipl-config.h' (which is generated by the 'configure' script) and then writing conditional code based on whether or not the 'IPL_HAVE_JPEG' macro is defined. For illustrative purposes we just print out a message in 'ipl-RasterImage.h' based on this macro. We want to add this to the top of 'ipl-RasterImage.h': #include "ipl-config.h" #ifdef IPL_HAVE_JPEG #warning "Building with the jpeg library" #else #warning "Building without the jpeg library" #endif For the tutorial, we use sed to update the 'ipl-RasterImage.h' file: % cd $PROJROOT/ipl % sed -i.bak '/#include "sgp-Point.h"/ a \ #include "ipl-config.h" \ #ifdef IPL_HAVE_JPEG \ #warning "Building with the jpeg library" \ #else \ #warning "Building without the jpeg library" \ #endif \ ' ipl-RasterImage.h So let's test our changes out by rebuilding the project. % cd $PROJROOT/build % make check Notice that the makefile realizes that 'ipl/ipl.ac' has changed and reruns the autoconf tools before rebuilding the project. If the JPEG library is available on your system then the 'configure' script should generate this output: configure: configuring internal subproject ipl ... checking jpeglib.h usability... yes checking jpeglib.h presence... yes checking for jpeglib.h... yes checking for library containing jpeg_std_error... -ljpeg If the JPEG library is not available on your system then you will see this output: configure: configuring default subproject : ipl ... checking jpeglib.h usability... no checking jpeglib.h presence... no checking for jpeglib.h... no configure: WARNING: Could not find jpeg library configure: WARNING: Build will not include jpeg support Make sure you see the correct output from preprocessing 'ipl-RasterImage.h' indicating whether or not the library is available. Take a look in 'ipl-config.h' and find the 'IPL_HAVE_JPEG' preprocessor macro and see if it is defined or not. In addition to checking for external libraries, a subproject's autoconf fragment can be useful for checking if the compiler supports a certain standard library or language feature. For example, the following fragment checks to see if the STL vector class is available and if so it defines the IPL_HAVE_STL_VECTOR preprocessor macro. We could potentially add code to our subproject to use the STL 'vector' if it is available or use some other data structure if it is not available. % cd $PROJROOT/ipl % cat >> ipl.ac \ <<'END' AC_CACHE_CHECK( [whether compiler supports std::vector], [ac_cv_have_std_vector], [ AC_LANG([C++]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM( [[ #include ]],[[ std::vector vec; ]])], [ac_cv_have_std_vector="yes"], [ac_cv_have_std_vector="no"]) ]) AS_IF([test "$ac_cv_have_std_vector" = "yes"],[ AC_DEFINE([IPL_HAVE_STD_VECTOR],,[Define if std::vector is available]) ]) END This autoconf check essentially creates a small little test program which just instantiates a 'vector' object and then tries to compile the test program to see if the compiler supports the 'vector' class. See the autoconf documentation for more information on the 'AC_COMPILE_IFELSE' and the 'AC_LANG_PROGRAM' macros. Notice that entire check is wrapped in 'AC_CACHE_CHECK' so that we only do the check once even if the same code is used in two subprojects. Rerun the autoconf tools and take a look at the output from 'configure' and the contents of 'ipl-config.h' % cd $PROJROOT/build % make check -------------------------------------------------------------------------- 1.4. Managing Optional Subprojects -------------------------------------------------------------------------- Making a subproject optional is useful when the subproject includes early development code or when the subproject provides auxiliary functionality. To mark a subproject as optional, we just need to add a '*' to the appropriate subproject name in 'configure.ac'. For example, to make both the 'sgp' and the 'ipl' subprojects optional we would change the 'configure.ac' to look like this: MCPPBS_INCLUDE_INTERNAL([sgp*]) MCPPBS_INCLUDE_INTERNAL([ipl*]) Let's make this change and then rebuild the project: % cd $PROJROOT % sed -i.bak \ -e 's/MCPPBS_INCLUDE_INTERNAL(\[sgp/&*/' \ -e 's/MCPPBS_INCLUDE_INTERNAL(\[ipl/&*/' configure.ac % cd build % make check You will notice that only the 'utst' subproject is included in the build. You can see this by looking at the output from the 'configure' script and at which unit tests are actually run. You can include the optional subprojects with command line arguments to the 'configure' script. (*) Try 'configure --help' to see a list of valid command line arguments, including the arguments which enable optional subprojects. For example, here is how we might enable both optional subprojects. % cd $PROJROOT/build % ../configure --with-sgp --with-ipl % make check We can also enable all optional subprojects with '--with-optional-subprojects'. The build system will use the dependency information given with the 'MCPPBS_SUBPROJECT' autoconf macro to enable dependencies as necessary. For example, if we just use '--with-ipl' the build system will also enable the 'sgp' subproject since 'ipl' depends on 'sgp'. % cd $PROJROOT/build % ../configure --with-ipl % make check You can change this behavior by including an asterisk suffix in the list of dependencies give with 'MCPPBS_SUBPROJECT'. You should only do this if your subproject can correctly build and function without the dependency. The user would then need to explicitly enable the corresponding subproject if desired. Before moving on we make all the subprojects required again. % cd $PROJROOT % sed -i.bak \ -e 's/\(MCPPBS_INCLUDE_INTERNAL(\[sgp\)\*/\1/' \ -e 's/\(MCPPBS_INCLUDE_INTERNAL(\[ipl\)\*/\1/' configure.ac % autoconf && autoheader % cd build % ../configure % make check -------------------------------------------------------------------------- 1.5. Installing Subprojects -------------------------------------------------------------------------- The 'install' make target installs your subprojects' programs and libraries. By default, all of the enabled subprojects are installed into '/usr/local'. In the following example we use the '--prefix' command line argument to the 'configure' script to install the project into an alternative directory. % cd $PROJROOT/build % ../configure --prefix=$PWD/install % make % make install Take a look at the files in the 'install' directory. The build system has installed the 'sgp-add-points' program into the 'install/bin' directory. Sometimes we want to install the libraries and header files for some of our subprojects so that other projects can leverage them. To do this we use the 'MCPPBS_INSTALL_LIBS' macro in the top-level 'configure.ac' file. As an example we will install the libraries for the 'utst', 'sgp', and 'ipl' subprojects. Before installing a subproject as a library we must create a 'subproject.pc.in' file in the subproject subdirectory. This file is a package config metadata file which helps users of our library know how to correctly configure their compiler and linker command lines. See the 'pkg-config' man page for more information, and 'utst.pc.in' for an example. For now we simply copy the 'utst.pc.in' file and modify it appropriately. % cd $PROJROOT/sgp % cp $PROJROOT/utst/utst.pc.in sgp.pc.in % sed -i.bak \ -e 's/utst/sgp/g' \ -e 's/\(Description :\).*/\1 Simple geometric primitives/' sgp.pc.in % cd $PROJROOT/ipl % cp $PROJROOT/utst/utst.pc.in ipl.pc.in % sed -i.bak \ -e 's/utst/ipl/g' \ -e 's/\(Description :\).*/\1 Image processing library/' ipl.pc.in You can customize these package config metadata files as needed to make sure that the installed libraries are easily leveraged by other projects. We can now use the 'MCPPBS_INSTALL_LIBS' macro in our 'configure.ac' to tell the build system that we would like to install libraries for all three subprojects. % cd $PROJROOT % sed -i.bak '/MCPPBS_INCLUDE_INTERNAL(\[ipl\])/ a \ MCPPBS_INSTALL_LIBS([utst,sgp,ipl]) \ ' configure.ac % autoconf && autoheader Now let's reinstall our project and take a look at what is installed into the install directory. % cd $PROJROOT/build % make % make install The build system has installed three libraries into 'install/lib' and the associated header files into subdirectories of 'install/include'. Also notice the package config metadata files have been installed into 'install/share/pkgconfig'. Often we would like to install our project into a common directory shared by many other projects (eg. '/usr/local'). The problem with using the above approach is that it can be very difficult to uninstall an old version of the project or upgrade to a new version of the project. So instead we can use the program 'stow' to stage the installation in a separate location and then create symlinks from the shared location. Here is an example of stowing the project. % cd $PROJROOT/build % rm -rf install % ../configure --prefix=$PWD/install % make % make install prefix=$PWD/install/pkgs/mcppbs-tut % cd install/pkgs % stow mcppbs-tut Take another look in the 'install' directory. You should see symlinks from the header and library files in 'install/include' and 'install/lib' back into the 'install/pkgs/mcppbs-tut' directory. If we want to uninstall a package we can just use the '--delete' option to 'stow'. % cd $PROJROOT/build/install/pkgs % stow --delete mcppbs-tut The build system provides support for automatically using 'stow' for installation. We can enable this feature with the '--enable-stow' command line option to the configure script. This option tells the makefile to create the staging area and to run stow appropriately. % cd $PROJROOT/build % rm -rf install % ../configure --enable-stow --prefix=$PWD/install % make % make install Compare the result to what we saw earlier. Notice that the 'pkgs/mcppbs-tut' subdirectory includes the version number as a suffix. The version number comes from 'configure.ac' or from the 'scripts/vcs-version.sh' script. The build system also has support for a default stow prefix specified with the $STOW_PREFIX environment variable. If this variable is set there is no longer any need to specify the prefix explicitly when running configure. % cd $PROJROOT/build % rm -rf install % export STOW_PREFIX=$PWD/install % ../configure --enable-stow % make % make install By default 'pkg-config' only looks in some standard system directories for its metadata files. So even though we have installed the package config metadata files, the actual 'pkg-config' program does not yet know it should look in this newly created install directory. We can set the 'PKG_CONFIG_PATH' environment variable to tell 'pkg-config' where to look for its metadata files. You might want to set your 'PKG_CONFIG_PATH' as a function of your 'STOW_PREFIX' in your startup scripts. % cd $PROJROOT/build % export PKG_CONFIG_PATH="$PWD/install/share/pkgconfig:$PKG_CONFIG_PATH" % pkg-config --exists utst sgp ipl; echo $? -------------------------------------------------------------------------- 1.6. Including External Subprojects -------------------------------------------------------------------------- Once we have installed a set of subprojects, other projects can easily include them with the 'MCPPBS_INCLUDE_EXTERNAL' autoconf macro in their top-level 'configure.ac'. There is a tradeoff between copying the source code of a subproject into your subproject and making it an internal subproject versus linking against and external subproject. Internal subprojects allow a developer to customize the subproject's source code and insulate the developer from changes to the original source code, but they also require developers to always compile these subproject's and to manually merge in updates from the original source. By using an external subprojects, a developer can avoid constantly rebuilding the subproject and automatically leverages any updates, but if those updates break an interface the developers will have to immediately update their own code. Of course, external subprojects also create an external dependency - we have to build and install these subprojects before we can build a project which depends on them. To illustrate external subprojects we will modify our project to use the just installed versions of the 'utst' and 'sgp' subprojects. First we update our 'configure.ac'. % cd $PROJROOT % sed -i.bak \ -e 's/\(AC_CONFIG_SRCDIR\).*/\1([ipl\/ipl.ac])/' \ -e 's/\(MCPPBS_INCLUDE\)_INTERNAL(\[utst\])/\1_EXTERNAL([utst])/' \ -e 's/\(MCPPBS_INCLUDE\)_INTERNAL(\[sgp\])/\1_EXTERNAL([sgp])/' \ -e 's/MCPPBS_INSTALL_LIBS.*//' configure.ac % autoconf && autoheader Notice that we need to change 'AC_CONFIG_SRCDIR' since our project no longer includes the 'utst/utst.h' source file. Now we rebuild the project in a new build directory. % cd $PROJROOT % mkdir build-extsprojs % cd build-extsprojs % ../configure % make % make check The 'configure' script will report that it has found the external 'utst' and 'sgp' subprojects, and that it is configuring the internal 'ipl' subproject. checking for external subproject utst... yes checking for external subproject sgp... yes configure: configuring internal subproject ipl If you take a close look at the compiler and linker command lines you can see that things have been configured such that the project is now linking against the libraries we installed in the previous section. Remember that 'pkg-config' is only able to find these libraries because we set the 'PKG_CONFIG_PATH' environment variable appropriately. -------------------------------------------------------------------------- 1.7. Distributing Package Tarballs -------------------------------------------------------------------------- The make 'dist' target will create a package tarball. A package tarball is simply a single compressed file which contains the build system and all of the project's source code. This tarball contain all of the subprojects included in the top-level 'configure.ac' regardless of whether or not they are enabled. The 'distcheck' make target will create a tarball, then try and "check" that this tarball is complete by decompressing it and running 'make', 'make check', and 'make distclean'. Here is an example: % cd $PROJROOT/build % make dist % make distcheck Notice that the tarball includes the version number as a suffix. If your project is under version control then you should set the version number to '?' in your top-level 'configure.ac'. This tells the build system to use the 'scripts/vcs-version.sh' script to determine the version number using your version control system. Currently only git is supported. We are now done with the tutorial, so we can remove the tutorial's working directory. % cd $PROJROOT/.. % rm -rf $PROJROOT -------------------------------------------------------------------------- 2. Reference -------------------------------------------------------------------------- In this part we provide more detailed reference on the various parts of the build system including how the Autoconf and Make toolflow works as well as some more details on subprojects. As mentioned above, subprojects are both a physical concept (ie. how the files are named and organized) as well as a logical concept (ie. how the classes and functions are named and organized). Each subproject is contained in a subdirectory beneath the main project directory. The name of the subdirectory should be an abbreviated form (four to eight characters) of the full name of the subproject. The subdirectory includes all of that subproject's header and source files, and these files should use the subproject's name as a filename prefix with a dash as a separator (eg. 'utst-TestLog.h'). Subprojects should create a top-level header file (eg. 'utst.h') which uses the '#include' preprocessor directive to include all of the header files in that subproject. Developers are strongly encouraged to include general user documentation in the spirit of 'utst-uguide.txt' to help users of the subproject. Every subproject should also include two files which tells the build system about the subproject: an autoconf fragment tells the top-level autoconf script how to configure the subproject (eg. 'utst/utst.ac') and a makefile fragment tells the top-level makefile how to build the subproject (eg. 'utst/utst.mk.in'). The build system is similar in spirit to the non-recursive makefile approach advocated by Peter Miller in his paper titled "Recursive Make Considered Harmful". In addition to the physical aspects of the subproject, there are also policies regarding the logical aspects. All functions and classes in the subproject should be in a C++ namespace having the same name as the abbreviated subproject name. For example, all of the functions and classes in the unit testing framework are in the 'utst' namespace. Preprocessor macros should include a prefix which also corresponds to the uppercase form of the abbreviated subproject name (eg. the 'UTST_TEST_CASE' preprocessor macro). If there are two parts to a subproject's name they should be separated with a dash (eg. 'foo-bar') for physical purposes (ie. the subdirectory name and the file name prefix) and separated with an underscore (eg. 'foo_bar') for logical purpose (ie. the name of the subproject's C++ namespace and the preprocessor macro prefix). -------------------------------------------------------------------------- 2.1. Overview of Build Process -------------------------------------------------------------------------- The basic build process involves two steps. First, we run a portable shell script named 'configure' which examines the end-user's build platform and customizes the build system so that it will work correctly. Then we use the 'make' command to build the project. The build system is made to work in any directory, and developers are strongly discouraged from building their project directly in the source directory. A separate build directory keeps the source separate from the generated scripts, object files, and binaries. The basic build process is shown below assuming we start in the root directory of the project. > mkdir build > cd build > ../configure > make -------------------------------------------------------------------------- 2.2. Basic Autoconf Toolflow -------------------------------------------------------------------------- Designing C++ projects such that they are portable across various platforms can be particularly challenging. The Modular C++ Build System leverages the GNU Autoconf tools to help configure the process for the desired build platform. The basic toolflow for using the GNU Autoconf tools is shown below: (configure.ac)-. .->[autoheader]->(config.h.in)--. .->(config.h) +-+ \ | (aclocal.m4)--' '-->[autoconf]------------>[(configure)]-+->(Makefile) / (Makefile.in) -----------------------------------' In this figure, files are denoted with parentheses '()' and tools are denoted with brackets '[]'. The process begins with the 'configure.ac' script which is "written" in the m4 macro language. This script contains macros which tell the build system about the project and also specify various checks to perform. For example, it might specify that we should check for a specific library or check to see if the compiler supports a specific language feature. Based on these checks, the build system can change our makefile or conditionally compile various portions of our project so that we can correctly build it on different platforms. The 'aclocal.m4' file contains additional macro functions that can be used with the standard autoconf macros in 'configure.ac'. The 'configure.ac' script is used as input to the 'autoconf' command which generates a portable shell script named 'configure'. The 'configure' shell script is executed by the end-user to customize the build system for their specific platform. The 'configure' script is annotated with '[()]' since it is a file which is generated by 'autoconf', but then it is executed as a tool to customize the build system for the current platform. The 'configure' script takes as input the 'Makefile.in' and the 'config.h.in' files and does variable substitutions to generate the 'Makefile' and the 'config.h' files. For example, if a specific library is installed we might add that library to the linker command line in the 'Makefile', or if a specific language feature is available then we might define a preprocessor macro in 'config.h'. Once the 'Makefile' and 'config.h' are generated we can use 'make' to build the project. The 'autoheader' command automatically generates 'config.h.in' by scanning 'configure.ac' for certain macros. The details are not too important, except that developers should always run 'autoconf' and 'autoheader' at the same time so that the various scripts stay synchronized and up-to-date. To regenerate the 'configure' script and 'config.h.in' developers can just execute the following command in the top-level project directory. > autoconf && autoheader Given the basic GNU Autoconf toolflow, the following commands will create a new build directory and configure the build system (assuming we start in the top-level project directory). > mkdir build > cd build > ../configure Note that most of the time we don't actually need to rerun 'autoconf' and 'autoheader'; we just execute the portable 'configure' shell script to do the configuration. We only need to rerun 'autoconf' and 'autoheader' when we change 'configure.ac'. So to simplify the build process for the end-users we should always include the 'configure' script in the distribution. We should also note that the Modular C++ Build System actually doesn't use a single 'config.h' header file. Instead 'config.h.in' is used to generate a unique configuration header file for each subproject (eg. 'utst-config.h'). Per subproject configuration header files avoid filename conflicts when we install and then use subproject libraries. New projects need to update 'configure.ac' with the project's name, version number, maintainer's name, and the shorter abbreviated project name. They then need to rerun 'autoconf' and 'autoheader'. Here is an example of what this metadata looks like in 'configure.ac': m4_define( proj_name, [Image Processing Tools]) m4_define( proj_maintainer, [Christopher Batten]) m4_define( proj_abbreviation, [ipt]) The version metadata deserves special mention. Usually we are working on a project which is checked into some kind of version control system (VCS). In this case we don't really want to hard-code a version number into the 'configure.ac' file since it will quickly be out of date with respect to the VCS. So instead we usually mark the version as '?' in the 'configure.ac' file and just use the standard VCS files to track revisions. When we make a distribution tarball though, the source code is exported from the VCS and we will loose version information. So the build system includes a script called 'vcs-version.sh' in the 'scripts' subdirectory which creates a version string suitable from the VCS. We can then use this string in the distribution. The makefile's 'dist' target will execute 'vcs-version.sh' and substitute the version string into the tarball name, the README, and the 'configure.ac' script so that users of the tarball will know what version the source code is from. This is just a very simplified description of the autoconf toolflow. See the GNU Autoconf documentation ('http://www.gnu.org/software/autoconf') for more details on using these tools. -------------------------------------------------------------------------- 2.3. Managing Subprojects in 'configure.ac' -------------------------------------------------------------------------- The Modular C++ Build System includes a couple changes to this basic GNU Autoconf toolflow. Several new macros are defined in 'aclocal.m4' which can be used in 'configure.ac'. The following macros initialize the build system and check for some standard tools used by the build system. - 'MCPPBS_INIT' : Initialize build system - 'MCPPBS_PROG_INSTALL' : Check for install script, stow-based install - 'MCPPBS_PROG_RUN' : Check for isa sim for non-native builds The 'configure.ac' should also include subprojects with either the 'MCPPBS_INCLUDE_INTERNAL' or the 'MCPPBS_INCLUDE_EXTERNAL' autoconf macros. An internal subproject means that the source code for that subproject is included as a subdirectory of the project, while an external subproject means that this project will simply be linking against an externally installed subproject library. The following is an example using these macros for a project with four subprojects: MCPPBS_INCLUDE_EXTERNAL([utst]) MCPPBS_INCLUDE_EXTERNAL([sproj-a]) MCPPBS_INCLUDE_INTERNAL([sproj-b]) MCPPBS_INCLUDE_INTERNAL([sproj-c*]) The subprojects should be ordered such that subprojects only depend on those included earlier. In this example, 'sproj-b' might use some of the classes in 'sproj-a' so we need to make sure that we include 'sproj-a' first and then include 'sproj-b'. For this example, we assume that both 'sproj-b' and 'sproj-c' depend on 'sproj-a'. All projects need to include the 'utst' subproject. If you do not want to use the 'utst' unit testing framework then you will need to explicitly modify the 'Makefile.in'. The '*' suffix (eg. 'sproj-c*') denotes an optional subproject. Optional subprojects are not included in the build by default. Making a subproject optional is useful when the subproject includes early development code or when the subproject provides auxiliary functionality. We can enable optional subprojects with command line arguments to the 'configure' script. To see a list of possible command line arguments use '--help'. To configure the example project with 'sproj-c' enabled we can use the following command (assuming we are in the build directory): > ../configure --with-sproj-b To enable all optional subprojects we can use the '--with-optional-subprojects' command line argument. The final 'MCPPBS_INSTALL_LIBS' autoconf macro tells the build system which (if any) subproject libraries you would like to install. Subproject executables are always installed (see below), but the _library_ for a subproject is only installed if it is on the list specified with 'MCPPBS_INSTALL_LIBS'. The build system uses the 'pkg-config' tool and its associated package config metadata files for both installing subproject libraries and using external subprojects. So any subproject on this list must have a corresponding package config metadata file ('utst/utst.pc.in'). You can use special autoconf variables in this metadata file which the configure script will then fill in when you build your project. -------------------------------------------------------------------------- 2.4. Subproject Autoconf Fragments -------------------------------------------------------------------------- The main problem with the build system as described so far, is that if a subproject requires platform dependent checks then we would need to add those checks to the top-level 'configure.ac'. For example, assume we have a subproject which makes use of an external library. We would need to modify 'configure.ac' to check if that library is available and if not cause an error so that the end-user can figure out how to proceed. But if we modify 'configure.ac' then the subproject is no longer modular in the sense that we cannot just copy it into another project; we also have to copy some code from 'configure.ac' into the new project. The Modular C++ Build System addresses this problem by allowing subprojects to have their own local autoconf fragment. The fragment filename should be the abbreviated subproject name with the '.ac' extension (eg. 'utst/utst.ac'). This script can contain standard autoconf macros such as those used in 'configure.ac'. Even if a subproject does not need any custom configuration, it still must include an empty autoconf fragment. The top-level 'configure.ac' script will go through and include the autoconf fragments in each enabled subproject. Each subproject's autoconf fragment should always begin with a call to the 'MCPPBS_SUBPROJECT' autoconf macro with the subproject's name as the fist argument. This macro takes care of setting up the configure information for the subproject. You can also optionally specify a second argument which lists the internal and external subproject dependencies for this subproject. The following is an example: MCPPBS_SUBPROJECT([sproj-c],[sproj-a,sproj-b]) This tells the build system that 'sproj-c' depends on 'sproj-a' and 'sproj-b'. The build system can handle indirect dependencies (eg. if 'sproj-a' depends on other unlisted subprojects) so you should only list the true direct dependencies. You can mark some of these dependencies with an '*' to indicate that this is an optional dependency. The build system will not complain if an optional dependency is not present, but will complain if a required dependency is not included. A common use for the rest of the autoconf fragment is to check for an external library. You can use the 'AC_CHECK_HEADERS' and the 'AC_SEARCH_LIBS' macros to see whether or not an external library is installed. Sometimes libraries and the associated header files are installed in non-standard locations. It is easy to tell the build system to add a path to the linker and the compiler command lines. Simply set the 'LDFLAGS' and the 'CPPFLAGS' environment variables when configuring the project. Here is an example which will allow the build system to find a library if it is installed in '/opt/local': > ../configure LDFLAGS='-L/opt/local/lib' \ CPPFLAGS='-I/opt/local/include' In addition to checking for external libraries, we can also use the autoconf fragment to check for various language features. Often for these types of checks we can use a macro someone else has written and added to the Autoconf Macro Library ('http://autoconf-archive.cryp.to'). For example, if we wanted to check if the build platform supported the GCC ABI name mangling extension we could copy the 'AX_CXX_GCC_ABI_DEMANGLE' macro into our autoconf fragment. A developer can add whatever they want to their autoconf fragment, but they should probably prefix any macro names with their abbreviated subproject name to avoid name collisions. For more complicated checks which might need to be performed by multiple subprojects, it is suggested that the autoconf fragment use a shell variable to see if the check has already been done (possibly by a different subproject) to speedup the configuration process. -------------------------------------------------------------------------- 2.5. Basic Make Toolflow -------------------------------------------------------------------------- To drive the actual build process the Modular C++ Build System leverages the ubiquitous 'make' tool. To use 'make', a developer first writes a 'Makefile' which contains a set of rules. Each rule specifies a target file, a set of prerequisite files, and a command to generate the target file from the prerequisites. The 'make' tool reads in a makefile (usually named 'Makefile') and executes the appropriate commands to build, test, and install the project. When a developer changes a subset of the source files, 'make' determines which target files are out-of-date based on the timestamps of the target and prerequisite files. 'make' then runs the minimum number of commands to regenerate all out-of-date targets, and thus efficiently rebuilds the project. A makefile can also have non-file targets which act as aliases for a set of files to build or commands to run. The Modular C++ Build System requires the GNU version of make since it uses several GNU make extensions. See the GNU Make documentation ('http://www.gnu.org/software/make') for more details on the 'make' tool. Obviously writing a makefile from scratch for every new project is undesirable. The Modular C++ Build System provides a top-level makefile with most of the common make targets; the developer just needs to include a list of the project's source files. So the following commands will configure and build the project (assuming we start in the root directory of the project). > mkdir build > cd build > ../configure > make The default make target is to build all of the project's libraries as well as all of the program executables. To efficiently build C++ projects, it is important to track all of the dependencies between header and source files. Each header file might depend on several other header files by using the '#include' preprocessor directive, and then these header files might include even more header files. Ideally, we want to accurately capture these dependencies in our makefile so that make will just rebuild what is necessary, but this can be quite tedious. By default, the Modular C++ Build System includes support for automatic dependency generation. The makefile uses the '-MMD -MP' command line options to 'gcc' so that whenever 'gcc' builds an object file it also generates a dependency makefile fragment (with the '.d' extension). This file captures all of the prerequisites required to build that object file, and it is included by the top-level makefile so that the build system automatically tracks all source file dependencies. The top-level makefile also includes support for detecting when the 'configure' shell script is out-of-date and automatically rerunning the autoconf tools. As mentioned before, we organize our project into one subdirectory per subproject. The makefile uses the '-I' command line argument to 'gcc' and the 'VPATH' make variable so that all of the source files appear "flat". In other words, source and header files within a subproject do not need to (and probably should not) use paths to another subproject's header files. They can just use the basename. For example, let's assume subproject 'A' has a source file 'foo.h' which uses the '#include' directive to include source file 'bar.h' in subproject 'B'. Then 'foo.h' should not use '#include "B/bar.h"' but instead should simply use '#include "bar.h"'. We can also specify a desired target to build on the 'make' command line. For example, to build the unit test framework's tutorial program we use: > make utst-tutorial The top-level makefile also includes several standard "non-file" targets. Some of them are listed below: - 'default' : build all libraries and programs - 'check' : build and run all unit tests - 'install' : install headers, project library, and some programs - 'clean' : remove all generated content (except autoconf files) - 'dist' : make a source tarball - 'distcheck' : make a source tarball, untar it, check it, clean it - 'distclean' : remove everything So for example, to build and run all of the unit tests we use: > make check By default, the build system installs the project into '/usr/local'. If the abbreviated project name is 'foo' and the install directory is '/usr/local', then the binaries are installed into '/usr/local/bin', the header files are installed into '/usr/local/include/foo', and a single library for the entire project (with the name 'libfoo.a') is installed into '/usr/local/lib'. To install the project somewhere else, a developer needs to use the 'configure' script's '--prefix' command line argument as follows: > ../configure --prefix=$HOME/install > make > make install There are two additional installation make targets which install a subset of the project: - 'install-libs' : Install header & libraries for some subprojects - 'install-exes' : Install just the executable binaries Sometimes files are installed one place but accessed from a different directory through symbolic links. For example, the GNU Stow package management system stages installs and uses symbolic links to help keep installed packages organized. For these type of installs, we need to set make's 'prefix' variable when we actually do the install. Set 'configure --prefix' to where the files will be accessed, and set 'make install prefix=' to where the files should actually be copied. > ../configure --prefix="access-dir" > make > make install prefix="staged-dir" The build system includes support for automatically using stow-based installation. To enable this feature use the '--enable-stow' command line option to the configure script. > ../configure --enable-stow --prefix="access-dir" > make > make install The configure script will check the $STOW_PREFIX environment variable and if it is set then it will use the corresponding path as the default prefix for stow-based installation. This allows a user to specify this variable once and then have all packages use stow to install in the same place. > export STOW_PREFIX="access-dir" > ../configure --enable-stow > make > make install -------------------------------------------------------------------------- 2.6. Subproject Makefile Fragments -------------------------------------------------------------------------- The problem with a single top-level makefile is that it requires each subproject to change the common makefile. To address this, the Modular C++ Build System allows each subproject to have its own local makefile fragment. The fragment filename should be the abbreviated subproject name with the '.mk.in' extension (eg. 'utst/utst.mk.in'). The makefile fragment should define the following make variables (assuming 'sproj' is the name of the subproject): - 'sproj_intdeps' : Internal subproject dependencies - 'sproj_cppflags' : Header include path (-I) - 'sproj_ldflags' : Library search paths (-l) - 'sproj_libs' : Libs required to build subproject (-l) - 'sproj_hdrs' : Header files (.h) - 'sproj_inls' : Inline header/source files (.inl) - 'sproj_srcs' : Source files (.cc,.c,.S) - 'sproj_test_srcs' : Sources for unit tests (.t.cc) - 'sproj_prog_srcs' : Sources for programs (.cc,.c) - 'sproj_install_prog_srcs' : Sources for programs to install (.cc,.c) The 'MCPPBS_SUBPROJECT' autoconf macro in 'subproject.ac' will automatically figure out what compiler and linker flags are required to build this subproject. So if you use the following in your make fragment, then the 'configure' script will fill in these four values. sproj_intdeps = @sproj_intdeps@ sproj_cppflags = @sproj_cppflags@ sproj_ldflags = @sproj_ldflags@ sproj_libs = @sproj_libs@ The top-level makefile adds additional targets and rules for each subproject. For example, the makefile builds a library for each subproject (eg. 'libsproj.a'). The top-level makefile also adds a target which will build and run all of the unit tests for a single project. For example, the following will run all of the unit tests for the 'foo' subproject: > make check-foo Developers can add whatever they want to the makefile fragment but they should probably prefix any macro names with their abbreviated subproject name to avoid name collisions.