5.5. Build ToolsThe six different build tools examined in the second half of this chapter are: shell scripts and batch files, make, GNU Autotools, Ant, Jam, and SCons. These are all no-cost, open source build tools. This is not to say that there are no useful build tools from commercial vendorsthere are a dozen or so listed at http://www.softwareengineering.info/build/build_by_product.html. They're just not used as commonly as the build tools that are listed below. Section 5.1.1, earlier in this chapter, described the different stages of a build. It is worth remembering each stage as you consider different build tools: configuration, target definition, reading build files, dependency calculation, deciding what to build, command construction, and execution of commands. Some of these stages in some build tools are very small or even nonexistent. What should you look for in a build tool? In order of most important to least, my recommendations are:
Good support from a tool vendor or the user community is also extremely helpful. And don't forget that almost all tools have won an award at some time or other! 5.5.1. Shell Scripts and Batch FilesThe simplest possible build tool is a piece of paper with a list of the compilation commands to type in order. A shell script or Windows batch file is really just that same list of commands, with tests for success or failure added occasionally. This approach has some advantages. It's quick to develop, since many of the commands can be cut and pasted straight from the command line after you work out what they should be. There is little or no confusion about why a certain command was executed. For simple projects, with a few dozen small files and with straightforward build dependencies, shell scripts or batch files can be an adequate build tool. Unfortunately, there are many disadvantages to using scripts and batch files as build tools. Some of these are:
In conclusion, shell scripts and batch files are adequate build tools only for the smallest and simplest projects. If you ever expect your project to grow, then take the time to use a real build tool. 5.5.2. makemake is the original build tool and is probably still the most common one. make is generally most popular with developers of C and C++ applications, while those using other languages, particularly Java, more commonly use Ant (see Section 5.5.4, later in this chapter).
Build dependencies are specified explicitly in build files, which are conventionally named Makefile or makefile and are written in make's own programming language. The format of these makefiles is shown in Example 5-2. make maintains static definitions of dependencies in the makefiles and does not detect dependencies between files. Files are noted as changed only when their modification timestamps change or when they are absent. Example 5-2. A simple makefilemyproduct : file1.c file2.c gcc -o myproduct file1.c file2.c echo "Finished building" In the Unix project shown in Example 5-2, the target myproduct depends on file1.c and file2.c. To create this target, make will execute the lines after the dependency line with the : on it (that is, the lines that begin with gcc and echo). Note that both of the lines below the target myproduct start with a tab character, not four spaces. Let's say that file1.c is a C source file and that it contains the line: #include "file1.h"; that is, file1.c depends on file1.h. However, modifying or even deleting file1.h after myproduct has been built will never cause make to rebuild myproduct, because the dependency on file1.h is unknown. You could manually fix this problem by adding file1.h to the dependency line, changing the first line of the makefile to: myproduct : file1.c file1.h file2.c This lack of automatic dependency checking explains why most large projects that use make alone as their build tool have trouble rebuilding only those files that are affected by a change. The are numerous implementations of make for almost every platform created in the last 25 years. The original make first became widely used with System V Release 2 Unix in 1984 but had been available since 1977. Compatible versions of make since then include a distributed make called dmake (1990), gmake (1991), BSD NET2 make (1991), and SunOS make (1989). nmake, the Microsoft version of make, is probably the most divergent make; it is one of the underlying build tools that are used when Visual Studio projects are built, whether from within the GUI or by using the msdev or devenv command-line tools.[3] gmake (http://www.gnu.org/software/make) is the GNU version of make; it was written by Richard Stallman and Roland McGrath. gmake is singled out from all the other versions of make since it has been ported to so many platforms and supports a wide set of all the different features from the other versions of make. gmake has been maintained and updated by Paul Smith since Version 3.76 in 1997.
Makealike (or should it be makeoid?) programs are build tools that are based on the concepts introduced by make. Some of these are cake, cook (which is used with the CMS tool Aegis), Lucent's nmake (no relation to Microsoft's nmake), Plan 9's mk, and mms for VMS. makepp (http://makepp.sourceforge.net) is a more recent replacement for gmake, designed to address many of the problems with make that are listed later in this section, with improvements including automatic dependency checking and using file signatures instead of timestamps. bmake (http://www.crufty.net/help/sjg/bmake.html) is derived from NetBSD's make and has some useful extensions in how variables and conditional targets are treated. All these tools have been used in projects, but none have achieved wide usage. Documentation varies widely among the different versions of make. gmake is reasonably well documented (http://www.gnu.org/software/make/manual), but, as with many build tools, developers tend to write makefiles for large projects by copying fragments from existing makefiles. This is especially true if the project has a complicated or hierarchical directory structure. Beyond the gmake manual, there are two books for more information about make. The first is Managing Projects with make, by Robert Mecklenburg. The third edition covers much more than the previous two editions. The second book is more gmake-specific: Programming with GNU Software, by Mike Loukides and Andy Oram. Both books are published by O'Reilly. Of the two books mentioned, only Managing Projects with make discusses how to create makefiles that scale well as a project grows. The classic article about this issue is "Recursive Make Considered Harmful," by Peter Miller (http://www.canb.auug.org.au/~millerp/rmch/recu-make-cons-harm.html). Peter Miller is also the author of the CMS tool Aegis. His key observation is that if you have makefiles in many directories and subdirectories, then make can spend a lot of time and effort processing each makefile, reevaluating some of the same dependencies over and over, and still end up with a fragmented view of what the whole project needs. The better alternative is to use make's include directive to gather all the makefiles into a single, logical makefile and then process just that one makefile. This approach is called included or nonrecursive make. There is a good discussion of how the OpenRADIUS project implemented included makefiles at http://www.xs4all.nl/~evbergen/nonrecursive-make.html. If you are interested in practical comparisons of the two approaches, see Appendix A for some tests and their results, and some similar results by Boris Kolpackov at http://kolpackov.net/projects/build/benchmark.xhtml. The greatest strength of make is that it's everywhere, and so it's already familiar to many developers. Unfortunately, the problems with make are numerous:
Once the problems with maintaining a large number of makefiles for multiple platforms became apparent, Prof. David Wheeler's observation that "all problems in computer science can be solved by another level of indirection" came into play, and numerous makefile generators were created.[4] The idea is to take a template file that lists both the files that you want from your build tool and also your source files, and then run the makefile generator with some platform-specific arguments to generate the appropriate makefiles. Some of the better-known ones are imake and Automake. Automake is by far the most common one nowadays in open source projects and is covered further in Section 5.5.3.2, later in this chapter. imake is primarily used for building the X11 Window System and is described further in the book Software Portability with imake, by Paul DuBois (O'Reilly).
Some products that attempt to solve the problems with using make for parallel or distributed builds include Electric Make (http://www.electric-cloud.com) and distcc (see Section 5.4.1, earlier in this chapter). Electric Make is a commercial replacement for make (original make, GNU gmake, and Microsoft's nmake are all supported). For a starting price of $50,000, it monitors all the compilations and other build-related commands on multiple machines, and when it detects an incorrect dependency it reschedules the erroneous compilation for later on. Even the logfiles are rewritten to show the correct build ordering. To summarize, if your project is unlikely to grow too large, is intended for only a small number of platforms, and is written in C or C++, then make is still an appropriate build tool. If not, there are better alternatives; these are discussed in the rest of this chapter. 5.5.3. GNU AutotoolsThe GNU Autotools suite, also referred to simply as the Autotool suite, or sometimes as the GNU Build System, is probably the build suite most commonly used by open source C and C++ projects. The familiar incantation of ./configure; make; make install seems to build and install about 90% of the tarballs that you can download.
The GNU Autotools suite actually consists of three separate toolsAutoconf, Automake, and Libtoolall of which use some common configuration files. Autoconf (http://www.gnu.org/software/autoconf) creates shell scripts named configure. These scripts can be executed to find out which features required by an application are present on a particular platform. Automake (http://www.gnu.org/software/automake) uses Makefile.am files to create Makefile.in template files, which Autoconf can then use to create GNU gmake makefiles. Libtool (http://www.gnu.org/software/libtool) helps create static and shared libraries in a portable and versioned manner for programs that are written in C. The most commonly used of these three tools seems to be Autoconf, judging by the number of configure files out there. Even if a project doesn't use Autoconf, the configure file is often the place where installation begins.
5.5.3.1. AutoconfThe concept of Autoconf is that all you should have to do to install a package is run the generated configure script to discover the precise details of how various features, such as compilation and linking, are actually working on your platform. This information is then passed to the build tool and also into the program by header files and preprocessor #define definitions. This concept alone may explain the popularity of the GNU Autotools suite; other build tools tend to make assumptions about what is provided on each version of each platform, but the GNU Autotools suite confirms this information when the application is actually installed. Other factors that explain the suite's popularity are that only the basic Bourne shell, the necessary C or C++ compilers, and make are usually needed for the configure script to work. Also, the default make dist command creates a convenient tarball of all the files that you need to package a release. The configure script has a number of different arguments that you can use to enable and disable various parts of the program that you are building. The default of using no arguments Should Just Work, and if configure fails to run due to a missing dependency for the project, then the error message is usually clear enough. However, if running configure fails in any other way, then debugging the problem can be hard. Section 5.5.3.5, later in this chapter, describes some helpful approaches for debugging installations from the perspective of a user. From the perspective of a developer working with Autoconf, you create a file configure.ac to be the input for Autoconf. configure.ac (which used to be named configure.in) is written in a mixture of GNU m4 (a macro processing language) and Unix shell commands. However, you need to ship only the generated configure file, not its precursors. 5.5.3.2. AutomakeAutomake produces makefiles that will work with GNU make, and they should also work with many of the other variants of make. Automake uses the file Makefile.am to describe the desired executables, libraries, and other files for the project. The language used for Makefile.am is specific to Automake, but Automake also reads the same configure.ac configuration file that is used by Autoconf. Using the Makefile.am file, Automake produces Makefile.in files that Autoconf can use to produce makefiles. The make targets in the makefiles that are created by Automake and then Autoconf include well-known targets such as all, install, and clean. Other useful targets are uninstall, which simplistically undoes whatever install did, and check, which executes any tests defined by the package developer. The target dist creates a distribution archive file, and distcheck creates a distribution, then untars the archive into a new directory, builds the package there, installs it, and finally runs make check.
5.5.3.3. LibtoolLibtool is designed to hide the implementation details of creating libraries, especially shared libraries, on different platforms. It's fully integrated with Autoconf and Automake but can be used even without them. The contents of the desired libraries are defined in Makefile.am, just as executables were for Automake. Libtool also includes support for versioning of libraries and for tracking which versions are expected for a particular package. When it comes to finding out which libraries have already been installed and using them in other packages, pkg-config (http://pkgconfig.sourceforge.net) is a separate tool that integrates well with the GNU Autotools suite and is useful for discovering the particular arguments that are necessary to use an installed library on your platform. 5.5.3.4. An Autotools "Hello World" programNone of the half dozen or so tutorials that I found while researching this section worked exactly as written. So I've provided the precise steps that I used to create my own "Hello World" program with GNU Autotools. These instructions are for Autoconf 2.53 and Automake v1.6.3. Libtool was not used here.
5.5.3.5. Debugging GNU Autotools installsThe advice in this section refers to what you can do when ./configure; make; make install doesn't do what it should, but you have faith that compiling the package on your machine should be possible. Problems of what to do when Autoconf, Automake, and Libtool don't work when you are developing the configure file itself are a different issue. For those kinds of problems, the manuals and the mailing lists at http://lists.gnu.org/archive/html/autoconf, http://lists.gnu.org/archive/html/automake, and http://lists.gnu.org/archive/html/libtool are good places to start. Also, since the actual Autotools programs are themselves written in shell script and Perl, familiarity with using a Perl debugger may help. Some problems that you may see during installation and build include:
5.5.3.6. More AutotoolsDocumentation for the individual GNU Autotools is good enough, but the current sources of information about how to use them all together are barely adequate. There are the standard GNU manuals for each tool at http://www.gnu.org/software/autoconf/manual, http://www.gnu.org/software/automake/manual, and http://www.gnu.org/software/libtool/manual.html. There is also one book that aims to bind all three tools together, GNU Autoconf, Automake, and Libtool, by Gary Vaughan, Ben Elliston, Tom Tromey, and Ian Lance Taylor (New Riders). It's also known as the "Goat book" because of its cover picture. Even though it's rather dated now, it is still strongly recommended for anyone trying to do more than cut and paste from other projects. The contents of the Goat book are also available online at http://sources.redhat.com/autobook. Another useful (though dated) article, which includes a well-written example, is "The GNU Configure and Build System," by Ian Lance Taylor (http://www.airs.com/ian/configure). The GNU Autotools were originally developed for Unix systems and only later modified to work for Windows machines. The recommended way to use them in Windows is to install the Cygwin environment and the necessary Perl and Microsoft tools. The project at http://gnuwin32.sourceforge.net has more information on how to do this. Mac OS X is supported by GNU Autotools. Languages that are supported by the default installations of the GNU Autotools include C, C++, Fortran, Perl, Lex, Yacc, TEX, Emacs Lisp, and, to a lesser degree, Java and Python. The Autotools are a well-established way of making sure that software is portable to a very large number of platforms. Since developers can write their own m4 macros for configure.ac files, the Autotools are extensible. Indeed, there is a public archive of useful m4 macros at http://autoconf-archive.cryp.to, and the GNULIB project (http://savannah.gnu.org/cgi-bin/viewcvs/gnulib/gnulib/m4) has more examples. The Autotools have good support for localization of text strings in products, using the GNU gettext utilities (http://www.gnu.org/software/gettext). Still, opinion is divided about GNU Autotools. On one hand, thousands of people use the results of Autoconf and run configure quite happily every day. On the other hand, when an install does fail, they have little hope of understanding or fixing the problem without substantial effort. Most of the problems with GNU Autotools seem to be voiced by developers who are trying to write the makefiles. These issues generally fall into the following categories:
5.5.4. AntAnt (http://ant.apache.org) is an open source build tool, part of the Apache community of open source software projects. Ant is licensed under the Apache License. Originally designed as an extensible replacement for make but "without make's wrinkles," Ant quickly found favor as the build tool for projects written in Java. Ant comes with a large number of ready-to-use tasks. Each task allows users to define a small part of a build process in their build files. The ease with which Ant can have other tasks added to it has resulted in a build tool with a diverse set of abilities. You could go so far as to say that if you want your Java tool to be used nowadays, it has to have an Ant task to run it. All IDEs, whether open or closed, that are intended for developing Java projects now have built-in support for using Ant.
The build files for Ant are written in XML. The core of Ant itself is written in Java, as are the Ant tasks that are used in the build files. The build files have <project> XML elements, which contain <target> elements, which are the names of targets that can be passed to Ant on the command line. target elements can specify that they depend on other target elements. Each target contains one or more <task> elements, which are the elements that control what Ant actually executes during the build. Ant build files are conventionally named build.xml. For the Java project shown in Figure 5-5, a build.xml that uses the jar, javac, and the delete Ant tasks would look like that shown in Example 5-3. Figure 5-5. Directory tree of an example Java projectThe default task is named dist and it calls the run_tests target, which in turn calls the compile and compile_tests targets to compile the product and compile the tests, using the javac task. Where there is just an echo task in this example build file, you would add tasks to actually run the tests. Finally, the first target, dist, uses the jar task to create a distributable JAR archive of the product. Example 5-3. An Ant build.xml file<?xml version="1.0" encoding="ISO-8859-1"?> <project name="My Project" basedir="." default="dist"> <target name="dist" description="Jar up the project ready for distribution" depends="run_tests"> <jar destfile="dist/project.jar" basedir="build/classes"/> </target> <target name="run_tests" depends="compile, compile_tests" description="Test the project class files"> <echo>Call an Ant task to test the project</echo> </target> <target name="compile" description="Compile the source files"> <javac srcdir="src" destdir="build/classes"/> </target> <target name="compile_tests" description="Compile the test source files"> <javac srcdir="test" destdir="build/test_classes"/> </target> <target name="clean" description="Remove all generated files"> <delete dir="build/classes/org"/> <delete dir="build/test_classes/org"/> <delete dir="dist/*"/> </target> </project> The output from executing ant with no arguments (so that the default dist target is used) shows the target names and the tasks that they call. Note that while this minimal build took 15 seconds from start to finish on a rather underpowered laptop, it only took 3 seconds on a more modern desktop machine: [theo@theo-laptop example]$ ant Buildfile: build.xml compile: [javac] Compiling 2 source files to /home/theo/example/build/classes compile_tests: [javac] Compiling 1 source file to /home/theo/example/build/test_classes run_tests: [echo] Call an Ant task to test the project here. dist: [jar] Building jar: /home/theo/example/dist/project.jar BUILD SUCCESSFUL Total time: 15 seconds Since Ant is written in Java, it runs unchanged on all platforms that support Java. The common ones are Solaris, GNU/Linux, and Windows, using the downloads from Sun (http://java.sun.com). Dozens of other platforms have had other Java Virtual Machines (JVMs) written for them; there is an extensive list available at http://www.geocities.com/marcoschmidt.geo/jvm.html. All official Ant tasks are written to operate correctly regardless of the underlying platform; that this mostly works as designed is due to plenty of testing. Troublesome areas include the Cygwin environment and older versions of Windows. If you write a task yourself, be careful about portability when using native commands, especially ones like ps, the Unix command for listing processes, which seems to have different arguments wherever it is found. The documentation for Ant is generally good and certainly extensive, with standardized descriptions of what each Ant task does.[5] The Ant documentation includes FAQs, reference manuals, API manuals, Wikis, presentations, and half a dozen books at last count. The books include O'Reilly's Ant: The Definitive Guide, now in a second edition, by Steve Holzner, and Java Development with Ant (Manning Publications), by Erik Hatcher and Steve Loughran, who are two members of the Ant project. There is also a different O'Reilly Ant book only in German: Antkurz und gut, by Stefan Edlich. Be warned, though: the first editions of the O'Reilly books cover only up to Ant 1.4.
Ant has many strengths. It runs on all the platforms for which you can find a JVM, and that's every major platform I've used in the past 10 years. Ant uses XML to describe the build dependencies for a project, so you don't have to be able to write Java to use Ant. Using XML also means that the information can be easily transformedfor instance, into a graphical representation of the dependencies, as is done by Vizant (http://vizant.sourceforge.net) and AntGraph (at the time of this writing, the download site is no longer valid, but AntGraph's author can be contacted via http://www.ericburke.com). Another major strength of Ant is the large number of Ant tasks that already exist. Over 80 core tasks are shipped with Ant, and they provide support for Java compilation, filesystem commands, archiving, mail, and CVS commands. The 60 optional tasks that are also part of the Ant project provide access to other SCM tools, unit testing, and much more. There are hundreds of other public Ant tasks freely available to do everything a project could require, including source code and document generation, web page maintenance, and even automated blogging. Creating your own Ant task is not hard if you can write basic Java, since the documentation is clear and there are a plethora of available examples. You can also use the exec task to specify the precise commands to be executed as part of the build, though this tends to create platform-specific build files and makes it harder to determine whether a task succeeded or failed.
Some of Ant's weaknesses are:
Ant does not support internationalization directly, nor is any of the Ant logging output localized, but given the good support in Java for both, this should be easier to do than with most build tools. Numerous Ant-related projects have been developed; some add to the list of tasks for Ant, some providing alternate ways such as GUIs to create build files for Ant, and some reimplement Ant for other environments. Examples of each of these include:
As many other IDEs already do, Microsoft's Visual Studio and Borland's JBuilder both save their lists of the source files that are associated with a project in XML files, and these files are used by the IDE's internal build tool to build the project. XSLT scripts can transform these tool-specific XML project files into Ant or nant build files. This often makes it easier or cheaper to build projects entirely without the IDE, since you may be able to avoid installing the full and sometimes costly IDE on multiple build machines. Another popular use of Ant is as the basis for an automation environment. Automation environments are discussed in detail in Section 3.4; basically, they are applications to help you automate the checkout-build-test-deploy parts of your build process (this is known as "continuous integration" in the XP literature). Typical tasks for automated environments are downloading the source for a project using an SCM tool and then running a build and unit tests, and doing all this continually, or every hour or night. Reports of the current state of the project are created and made available, often through a web site or by email. Some automation environments that use Ant are Anthill (http://www.urbancode.com/projects/anthill), which is a commercial tool that also has a no-cost version, and CruiseControl (http://cruisecontrol.sourceforge.net), an open source automation environment. Both these and other automation environments are discussed further in Section 3.4. Another outgrowth of Ant is Maven (http://maven.apache.org), a project management tool from the Apache Project. While you can use Ant to manage other parts of your project such as releases and tracking multiple .jar file dependencies, large build.xml files can become hard to understand. Maven lets you describe your project's structure using a version of XML with programming constructs named Jelly. You can still call Ant tasks from Maven, but Maven can also download the required files for a project as necessary; Ant 1.7 will also be able to do this. Many of the Apache projects use Maven to describe their structure. The idea of having an overall structured description of a project is a good one and seems likely to become common in all automated environments. In summary, Ant has become the default build tool for Java projects: if a project is written in Java, it's most likely being built with Ant. The exceptions are those projects tied to an IDE with its own build tool, and even these IDEs now support building products by using Ant. In addition, if you want a definition of a build that can be transformed into formats suitable for use by other tools, then using XML for your build file language is very convenient. 5.5.5. JamJam (http://www.perforce.com/jam/jam.html) is an open source build tool from Perforce Software, written by Christoper Seiwald and aimed squarely at projects written in C and C++. Jam costs nothing, and is licensed in a very free manner:
That's the entirety of the license for Jam. As you might guess from it, Jam is not supported by Perforce; Perforce's business is more focused on its own SCM tool, which is discussed in Section 4.6.4.
In Jam, build dependencies are specified in build files called Jamfiles, and these Jamfiles can include each other in a hierarchical fashion (the top-level build file is named Jamrules). Example 5-4 shows a typical Jamfile. First, note the quirky but required spaces before the semicolons at the end of the lines. The top line of the Jamfile specifies the location of the Jamfile in the directory hierarchy; this one is in a directory named src. The next two lines show how there are two other Jamfiles included by this Jamfile. The line beginning with Main shows an executable named hello being defined as containing the four .c source files and needing to be linked with the libraries listed after LinkLibraries. This Jamfile will create an executable hello on any one of the many platforms supported by Jam. The appropriate file suffixes and prefix are supplied by Jam for each platform. Example 5-4. A JamfileSubDir TOP src ; SubInclude TOP src subdir1 ; SubInclude TOP src subdir2 ; Main hello : hello.c foo.c bar.c baz.c ; LinkLibraries hello : libother libcommon ; There are three internal stages of execution when Jam is run. In the first stage, all the Jamfiles are opened and (re)read, and for all the Jam targets that could be named on the command line, such as hello, a dependency tree is created. Source files are automatically scanned for other dependencies using extensible rules. Just as with make, file modification timestamps are used to decide whether files have changed, as opposed to the digest scheme used by SCons. In the second stage, Jam calculates which files need to be updated as part of building (or rebuilding) the specified targets. In the third stage, the specific Jam rules such as Main that were used in the Jamfiles are used to create the Jam variables that are later used to create the commands that are actually executed. Jam rules are written in the Jam language, and each Jam rule has an action associated with it, which is written as a snippet of shell script. Unix platforms that are supported by Jam include GNU/Linux, AIX, the BSD Unixes, HP-UX, IRIX, QNX, Solaris, and a number of other less well-known Unixes. Other platforms include Windows and Mac OS X, and also VMS, OS/2, and BeOS. Compilers that are supported by default include gcc, Microsoft Visual C++, MinGW, the Borland compiler bcc, HP-UX aCC, AIX Visual Age, Solaris CC, and IRIX MIPSpro. Other recognized tools and file formats include Lex and Yacc. Documentation for Jam is accurate, but woefully thin. What is most obviously missing is a cookbook of recipes for various kinds of projects. The next place to search for help is the Jamming mailing list (http://maillist.perforce.com/pipermail/jamming). Another surprising lack is that of a central list of bugs related to Jam (BoostJam does have such a service). Jam's strengths are many compared with make. First, the idea of a global understanding of dependencies works well; just what is needed for the target that you specify is what gets built. Jam is also fast. One commercial project is able to build all the targets that are found in a million lines of C source code in over 4,000 files in under 10 minutes on an unremarkable desktop machine. Jam is also relatively small; its source code is under 15,000 lines of C source, which makes porting it to different platforms somewhat easier than with larger build tools. Indeed, Jam has already been ported to many different platforms, probably second in number only to make. Extending Jam is relatively easy, since new or overriding rules and actions can be defined in the Jamfiles that are parsed at startup, as shown in Example 5-5. This example also shows that Jam rules and actions are easy to customize, but are often hard to understand. Example 5-5. Extending Jam# # Generate C source using a CORBA interface defined in an # IDL file. # # Argument $(1) is the name of the output file # Argument $(2) is the name of the IDL source file # Argument $(3) is an optional string argument passed to # the IDL compiler. # rule Idl { # A Jam idiom to extract the directory from the # first # argument. outdir is a local variable also available to the # action defined below. local outdir = $(<[1]:D) ; # Set a global Jam variable named IDLFLAGS IDLFLAGS on $(<) = $(3) ; # Specify that the C source depends upon the IDL file and # also on the output directory Depends $(<) : $(outdir) $(>) ; # Make sure that the output directory exists MkDir $(outdir) ; } # # Actually generate the source files using the IDL compiler # and the IDL source file. # # An action is made up of shell script commands, so doesn't # have to have the space-semicolon line endings, but semicolons # are still used to separate the shell commands. # actions Idl { cd $(outdir); idlcompiler $(IDLFLAGS) $(>); } # # An example of using the new rule in a Jamfile # to generate the file def.c from def.idl with some verbose # output # Idl build/project1/idl/def.c : def.idl : -Dverbose=true ; Jam's weaknesses are mainly in debuggability and documentation. Jam's debugging output is extremely verbose and hard to follow or restrict in any useful way. This leads to one of the common complaints about Jam, along the lines of "Jam didn't build my new library." This complaint is often due to the differences between make and Jam: if nothing depended on your new library, then Jam knew it didn't have to build it, so it didn't. In make, you probably defined every target explicitly, so some target or other probably ended up building it for you. Other weaknesses of Jam include:
BoostJam is a frontend to Jam and includes explicit support for building object files somewhere other than in the source directory. It also supports building variants (debug, optimize) and multiple targets with multiple compilers better than Jam does. BoostJam also has a better way of quoting arguments passed on the command line, much improved debugging output, and a number of other helpful additions. As of 2005, the BoostJam team is actively developing Version 2 of BoostJam. In summary, Jam is an accurate, free, and fast build tool, probably the fastest of all the build tools examined in this chapter. A wide range of different platforms and C and C++ compilers are supported by default, and adding more is possible. Weighing against these benefits is the difficulty of debugging Jamfiles, minimal documentation, and lack of any impetus from Perforce to develop Jam any further. The current developments in BoostJam, particularly Version 2, are the brightest hope for improvements to Jam. 5.5.6. SConsSCons (http://www.scons.org) is an open source build tool written by Steven Knight and others. Among its early contributors were Chad Austin, Charles Crain, Steve Leblanc, and Anthony Roach. The current licensing scheme is the MIT license.
SCons is implemented in Python (http://www.python.org), a modern, interpreted, object-oriented language freely available under the Python license on many platforms. Python is often compared to Tcl, Perl, Scheme, and Java, and is reportedly one of the easiest languages to learn.[6] For its build files, SCons uses script files that are written in Python and named SConscript.
Unix platforms that are supported by SCons include GNU/Linux, AIX, the BSD Unixes, HP-UX, IRIX, and Solaris. Other platforms include Windows and Mac OS X, and also OS/2. Compilers that are supported include gcc, Java, Microsoft Visual C++ (including the use of precompiled headers), MinGW, the Intel compiler tools, .NET tools, the Borland compiler bcc, HP-UX aCC, AIX Visual Age, Solaris CC, and IRIX MIPSpro, among others. Other recognized tools and file formats include Lex, Yacc, tar, LATEX, m4, Qt, SWIG, PostScript, PDF, and zip. Some distinguishing aspects of SCons are:
Example 5-6 shows an SConscript file. It is written in Python, which has no semicolons at the end of lines and uses the indentation of each line to identify a block of code, instead of curly braces as seen in C and Java. Comments are preceeded by the # character. A single top-level file named SConstruct can be used to tell SCons about the SConscript files, potentially one in each subdirectory. Example 5-6. An SCons SConscript file# Explicitly allow this file to use a common environment defined elsewhere. Import('env') # Define an executable named 'hello' env.Program(target = 'hello', source = ['hello.c', 'foo.c', 'bar.c', 'baz.c'], LIBS = ['other', 'common']) The line with the Program method call shows an executable named hello being defined as containing the four .c source files and needing to be linked with the libraries listed in the LIBS argument. This SConscript file will create an executable hello on all of the platforms supported by SCons. The appropriate file suffixes and prefix are supplied by SCons for each platform. The output shown in Example 5-7 is a small section of the results of using the --debug=tree command-line argument with SCons. It shows how libA.a depends on the three object files, which in turn depend on the .c three source files. Note that if this were a build on a Windows platform, then the file extensions and directory separators would be changed appropriately. Another very useful debug option is --debug=explain, which tells you why a file was recompiled. Example 5-7. SCons debug output+-dir1/libA.a +-dir1/a1.o | +-dir1/a1.c +-dir1/a2.o | +-dir1/a2.c +-dir1/a3.o +-dir1/a3.c Documentation of SCons is of good quality. There is a 5,000-line manpage installed with the package, which is also available from the SCons home page. The Wiki on the home page is well laid-out and has a number of cookbook examples about how to use SCons. Basic examples for using SCons can also be found at the end of the manpage. The mailing lists for SCons are friendly and a good source of help. No book has yet been published about SCons, but it seems to be only a matter of time before one is. Some other well-considered aspects of SCons include the fact that the environment in which the build is performed is defined independently of the user's own environment. This helps to avoid the awkward situation of leaving departed developers' machines untouched just to perform builds. Variables in the Python build files are properly scoped, which is not true of many other build tools, though of course this does mean that variables have to be explicitly imported between SConscript files. Scalability of SCons for long command lines (lengths of over 10,000 characters) has also been demonstrated, at least for Windows. Other things that make SCons easier to debug than most build tools are options to display the time spent in different major parts of a build, access to full-scale profiling data for the Python profiler pstats, and an option to start the build from within pdb, the Python debugger. SCons even has enough confidence in the correctness of its dependency checking to provide a command-line argument to explicitly build the product in a random order! This unique idea helps performance when building different versions of a product simultaneously from the same source code, since it avoids performance problems that occur when multiple processes try to access the same file at the same time. The SCons core developers have an extensive set of unit tests and system tests, and changes to SCons are controlled using the change management tool Aegis. Weaknesses and irritations of SCons seem to be relatively few. One complaint is about how long an already up-to-date build can take (up to 10 seconds when nothing actually needs to be built). The time spent checking dependencies can be reduced by using the SCons arguments --max-drift=1 and --implicit-deps-unchanged. Still, the startup time for SCons can feel slow. If you make an error in an SConscript file, the Python backtrace at the command line can be quite long, and you have to get used to the most recent part of the trace being at the bottom of the screen, not the top. The .sconsign MD5 signature files exist for each generated file, but these droppings can be made to occur in the directory where the files are built, or even in a single file, as opposed to the source directory. Internationalization is not currently supported. SCons does require Python, which may not be installed on your machine by default. However, it deliberately does not require cutting-edge versions of Python (though it is tested with them), and Python isn't hard to learn, with many good tutorials available online. Python in a Nutshell, by Alex Martelli (O'Reilly), is one good introduction to Python, as is Dive into Python, by Mark Pilgrim (Apress), which is also available at http://diveintopython.org. The Python Cookbook, by Alex Martelli and David Ascher (O'Reilly), is also handy when you want just an example of how to do something with Python. In summary, SCons is a well-designed build tool for a range of languages and platforms, and one that has been developed carefully by a knowledgeable group of developers. The choice of Python as a build tool does introduce yet another language dependency to any project, but this overhead seems to be small in practice. If you are starting a project from scratch, or your current build tool is just too much to bear any longer, SCons is the build tool to use.
5.5.7. Custom Build ToolsAs projects grow, there is often a need to generate some part of the source code automatically using generic code structures, in the same sense as C++ templates. Common areas for generated code are logging functions, class skeletons, and header files. The input specification can be a simple text file, an XML file, or a whole hierarchy of definitions in different files. A generator tool that uses these input files is invoked as part of the build process to generate the required files. Whatever mechanism is chosen, the generator is essentially a custom build tool, complete with all the potential advantages and frustrations that all the other build tools in this chapter possess. Before writing or using such a generator tool, consider all the things that you want in a regular build tool, such as accurate dependency calculation, easy debugging, and fast up-to-date builds. |