Highley Recommended, Inc.

Table of Contents

Project Structure

The file system structure of source code can have big impacts on the performance and build process implementations. Consistency in structure helps in the manufacturing and release of software developed across multiple teams and large development efforts. Our suggested structure looks like this picture, where the blue indicates dynamically created build derived object locations.

Project Structure Picture

The above picture was taken from a short pitch on source code and build process relationships. Source Code and Build Processes

Make Process

Make utilities are designed to work on an ordered set of steps. The best way to create a makefile, if you are starting from scratch, is to start with what you want to create first. That is to say the end result should be the name of your first target. The dependencies for this target should be what is needed before this target can do its work. Then the target rules are what is needed to translate the dependency into the target or result for that step. Each target should ideally do just one step so that all dependencies are defined and make can do the minimal amount of work or rework.

To summarize in coding terms, start with the application and work backwards until you end up with the source files. Make utilities are usually associated with software development, but can be used to perform any order set of steps where each step is translating something from a given format into another format.

The make interpreter in general processes all includes first, then expands macros and finally executes the target rules. First target is the default target and dependencies define the order of target execution.

Prerequisites For Building the Application Software

The application software and all the vendor tools for building the software have been put under ClearCase Configuration Management control or some other suitable solution to insure you can control and recreate your build environment at any point in time.

Building the Application Software

We use the GNU make program for doing software builds. We use make to build all of our C and C++ code and wrap the Java ANT build scripts with make to establish the build environment. We build on Linux and Windows platforms. We develop in a ClearCase configuration managed environment, but the build process, with the exception of the automatic build version identification is not tied to ClearCase.

Steps For Doing a Build

  1. If not familiar with build process; type 'make help' in directory where makefile is located.
  2. Other targets in the make files are clean and distclean. The clean target will remove all intermediate derived objects. The distclean target has a dependency for clean and will remove the end targets as well.

Build Flavors

To do a build with debugging information and generated dependency files, type:


The DEBUG macro defaults to "-g" which will be added to the MY_CFLAGS macro.

To do a release build with optimization and no debug information generated, type:

make DEBUG=

The DEBUG macro will be emptied to "" which will be added to the MY_CFLAGS macro.

To speed up the build turn off dependency file generation, which requires an extra compiler preprocessor pass or an entire compile if using the Windows compiler.:


The DEPFILES macro will be emptied to "".

To speed up the build even more, invoke a parallel build, using 2 times the number of CPUs for the number of parallel builds, which seems to be optimum for all platforms and compilers:

make -j 16 DEPFILES=

By the way this is where we parted ways with the clearmake program as it does not throttle across sub make processes and we locked up our build systems. Clearmake also does not work well with Java and some C++ compilers. Parallel building does make the reading of the tea leaves, build log file a bit more interesting if you are having build issues. Parallel building requires any order dependencies to be defined. These makefiles will still work with clearmake; in general no longer use clearmake.

Makefile Examples

Below are makefile examples from a customer project. Windows system platform building an embedded Wind River based application. Cygwin programs; sh, cp, and rm were used to allow a generic build process to be created.

Top Makefile

Library Rules

Application Rules

Test Executable Rules

Directory Traversal Rules

Make Process Macros

Make Process Help

Version Identification

Changing Makefiles

In general, the types of changes that will be required for makefile maintenance are the addition of new directories to make macros and directory traversal makefiles (if used) and new subsystem makefiles, where software or build targets are added. Another make maintenance point is when compilers are upgraded or new tools are added to the software manufacturing process. We for the most part do not use directory makefiles, we use a directory list macro in the common macro file which minimizes the spread of maintenance activity.

To create a makefile for a new build first copy the leaf makefile and modify the TOP macro and change if necessary the include for the rules file. Then modify the mkcommondefs.mk file and add to the correct directory list macro the new location to build. If you are using directory traversal makefiles, then you would need to modify the appropriate makefile to add the new location to build.

If the new make process involves building library software, then include mkcommonrules.mk file. If the new make process is for building an application program then include the mkcommonbinrules.mk file. Finally if the new make process is for a simple test program include the mkcommonexerules.mk file.

To debug make build process issues you may need to drop in a new default rule, the first rule in the makefile, to dump out make macros. For example:

        $(ECHO) "|$(P1)|$(P2)|"

Note: the vertical bars around the parameters which can help you locate non terminal printable characters, pesky devils or spaces in the wrong place.

Another quick debugging technique when locality of problem origin is hard to determine is to compare check sums of derived objects to determine which ones changed between two builds. You can not use this method with a debug build as random data and storage initialization may be done. You may also have bad tool implementations which add useless noise and dates to the build objects.

All application makefiles include the mkcommondefs.mk file which contains definitions for all commonly used macros, SUFFIX rules and implicit targets. Guard against adding -D compiler macros to the common makefile if they are not used everywhere. That is an example of coding obscurity which is commonly done. The file mkcommondefs.mk should not include any other files unless for example you have multiple sites that share your build process and not all of them are configured exactly the same. In that case you might need to include, at the end, a file with a minimal list of make macro overrides for site specific variances. This file should have no tab characters as it has no target rules. In general makefiles should only have tabs at the start of target rule lines.

When editing makefiles, do not remove the tab character at the start of the target rules lines or add spaces before tab characters; both can be issues. Lines that have nothing but blanks will look like a blank line but are not to a make program; targets are ended by a true blank line. Spaces after the continuation character at the end of the line are also sources of problems. A good practice is to turn on, in your editor, the display of blanks, EOL, and other normally non displayable characters as a final review of your makefile changes.

Watch out for name collisions. If you have a directory name that is in the VPATH and it matches a target or file name it can cause your build to break. Directories in general are always up to date so when they appear as a target they do not execute by default. To work around this issue and others (like targets named clean or all), you need to define the non real targets as phony targets. It is also best to locate the .PHONY target after all other targets so that it will work properly after makefile macros are expanded if any of the targets are defined in a make macro like $(DIRS).

.PHONY: all $(DIRS) clean distclean help

When you need more than one thing to happen within a shell invocation use the shell command line separator character, a semicolon (or ampersand if you must use cmd.exe), to separate the commands and have them on one line. Make hands off each line to a new shell invocation which is another cause for a broken build when doing parallel builds if you have not followed this practice. If you put them on separate lines within a target, the change directory would happen in one shell invocation and the make would happen in a separate shell. An example is:

cd directory; "$(MAKE)"

If you want to only execute the second command if the first command completes successfully use a double ampersand character to separate the commands. If you want the second command to execute if the first command fails use a double vertical bar to separate the commands. An example is:

cd directory && "$(MAKE)"

We are using the Cygwin shell program to have a more general and capable build process when building on Windows. When passing commands to the shell you need to consider how they are being processed. A few examples should be helpful. When is something an environment variable and when is it a parameter? In the example below PATH is an environment variable established before the command is executed with the command line PARAMETER. In this way, you are able to define the execution environment before executing commands which makes a build process more controlled and reliable.


Be aware of what the make utility parser does in its processing and what shell programs do in their processing. For example, how many parameters are being sent to a command?

command $(P1) $(P2) = string2 "$(P3) = string3" \"$(P4) = string4\" \
We count nine parameters. We get this by knowing that spaces are
delimiters to the shell and that the make interpreter will consume
quotation marks unless they are protected by a preceding back slash
character. The last parameter is an example of preventing the make
interpreter from expanding the macro P5 so the command will receive the
string $(P5) in this case.

Makefile Glossary

Specifics About Makefile Examples

The makefile examples generally follow the GNU make conventions of make macro variable names and uses. Some make process implementers like to have one makefile that does everything. We prefer to have a makefile at each source location. We are trading off some minor, additional make file maintenance over additional developer support. With this implementation a developer can compile a single file. Although this may not be as important with the increased use of newer IDE's like Eclipse and Visual Studio.

SET_E - We use the Bourne shell underneath the make process and we set the shell e flag based on the make flags. Normally we want a make build process to quit as soon as possible if an error occurs, unless the make invocation is done with -i and or -k option, where we are debugging a build issue and we want to not stop so we can see all errors.

SET_E      = case '$(MFLAGS)' in *[ik]*) set +e;; *) set -e;; esac

We use MY_CXX, MY_CC, and MY_LD for compiler and linker commands. This extension allows us to change compilers on the command line. The reason for having a separate macro is that once it is defined on the command line, the macro is immutable, yet we would like to possibly extend it by adding the path to the compile so that it is well defined. We may also need to prepend to the user's environment PATH variable to setup a correct execution environment.

Since we are now building multi architecture builds now and using multiple compiler tool chains, we are now creating an INFO log line for every invocation of the compiler which has the host, product name, compiler name, compiler version, and integer mode.

MY_CXXFLAGS & MY_CFLAGS - are the rolled up compiler flags macros. They are completely separate from CXXFLAGS and CFLAGS. For linking we have the MY_LDXFLAGS macro which is completely separate from the LDXFLAGS macro. For C++ we have so far only needed to separate out the GNU compiler template depths parameter. In addition to the compiler flags macros we use MY_DFLAGS for defining defines. By defining the following macros on the make command line, additional compiler or link flags can be added to the build; CFLAGS, CXXFLAGS, DFLAGS, or LDXFLAGS. Example below does a non debug C++ compile without preprocessor dependency file generation, -E flag tells the compiler to output the preprocessor listing into file.o, on Windows the compiler would output to STDOUT. Source code in this example would have been file.cpp:

make DEBUG= DEPFILES= CXXFLAGS=-E linux64_rh6.3_gcc4.4.6_shared/file.o
# Users can specify compile options on command-line via 'CXXFLAGS="..."'
            -c \
            $(CDEBUG) \
            $(OPTIMIZED) \
            $(MY_DFLAGS) \
            $(DFLAGS) \
            $(INCS) \
            INFO=$(QINFO) \
            PATH=$(MY_PATH) $(MY_CXX) $(MY_CXXFLAGS) \
                $(CXXFLAGS) $(C_OUT) $<
LINK_TYPE           = shared
MKDIR               = /bin/mkdir -p
OBJ_EXT             = o
OSTYPE              = linux
ifdef DEBUG
    CDEBUG          = -g
    LIBDBG          = _$(LINK_TYPE)_d
    OPTIMIZED       =
    CDEBUG          =
    LIBDBG          = _$(LINK_TYPE)
    OPTIMIZED       = -O3
# Create library object files, $(OBJS), rule.
$(OBJDIR)/%.$(OBJ_EXT): %.cpp
        $(SET_E); $(MKDIR) $(OBJDIR); $(COMPILE)

LINK - is a gremlin macro that ultimately gets defined by the value of the LINK_TYPE macro; static, shared, dll. Default for LINK_TYPE is shared. So a shared library link expands to the following:

# Where are we in the file system?
LD_CMD      = $^ or @$^
LOCDIR      = $(shell $(PWD))
# Which sub make (leaf directory) are we doing?
PROJECT     = $(shell $(BASENAME) $(LOCDIR))
LIBPATH     = $(TOP)/cpp/lib/$(OBJDIR)
              PATH=$(PATH) \
              $(MY_LD) \
              $(LD_OUT) \
              $(LIB_NAME) \
              $(MY_LDXFLAGS) \
              $(LDXFLAGS) \
              $(LDBFLAGS) \
        $(SET_E); $(MKDIR) $(LIBPATH); $(RM) $@; $(LINK)

If you have library source code spread by functionality that you want to compile and then roll up into one library, these make macros are useful. Given a list of source directories, the object files will be determined based on the source code found in those directories.

FIND_SRCS     = $(filter-out $(EXCLUDES), \
                $(wildcard $(DIR)/*.cpp))
FIND_OBJS     = $(filter-out $(EXCLUDES), \
                $(patsubst $(DIR)/%.cpp, \
                $(DIR)/$(OBJDIR)/%.$(OBJ_EXT), \

LIBX - roll up macro for the list of application libraries. The list is in UNIX/Linux form, -L ../dir1/dir2 -lname, which we transform for the Windows platform, as libpath:name.lib, using the substitution below. Platform libraries should never be added into this macro. Library order in general matters. The best way to order libraries is to put the libraries with the most undefined references first in the list and work down to the ones that do not reference other libraries. It can be a real pain when you run into the situation of libraries with circular references or old Windows static linked builds where you need to use the nodefaultlib link option.

LIBS     = $(patsubst -L%,-libpath:%,$(LIBSTMP1))
LIBSTMP1 = $(patsubst -l%,%.$(LIB_SUFFIX),$(LIBX))

In the macros file there is a script that translates the LIBX linker list syntax into a make dependency list, LIB_DEPS. This macro is used as a dependency to force relinking of an application if a library is newer than the application.

# Convert LIBX linker library list into make dependency list
#Start with:
#  -L/usr/local/lib -ltom -ldick -lharry -L/usr/lib -ljane -ldoe
#Convert to:
#  /usr/local/lib/libtom.so /usr/local/lib/libdick.so
#  /usr/local/lib/libharry.so /usr/lib/libjane.so /usr/lib/libdoe.so
# If any member of the list does not exist, return nothing so the linker
# can tell you it can not find a file. Before doing this make failed
# indicating it could not find the target instead of a missing
# dependency which was very hard to debug, make version 3.81.
LIB_DEPS                = $(shell \
    for i in $(LIBX); \
    do \
        tmp=`$(ECHO) $$i | \
            $(SED) -r -n -e 's|^([ ]*)(-L)([^ ]+)|\3|p'`; \
        if [ "$$tmp" != '' ]; then \
            pat=$$tmp; \
            continue; \
        fi; \
        tmpout="$$tmpout `$(ECHO) $$i | \
            $(SED) -r \
                -e 's|(-l)([^ ]+)|'$$pat/$(LIB_PREFIX)'\2'.$(LIB_SUFFIX)'|' \
                -e 's|^([a-zA-Z])(:)(.*)|/cygdrive/\1\3|'`"; \
    done; \
    for i in $$tmpout; \
    do \
        if [ -f $$i ]; then \
            out="$$out $$i"; \
        fi; \
    done; \
    $(ECHO) $$out)

$(BIN) : $(OBJS) $(LIB_DEPS)

CXXPATH - is used in the discovery process of the compiler location we are using for the Linux platform. Since we have relocated it we combine the location with the compiler file to create an absolute reference to the compiler. Note: we look at /usr/local/gcc first in case we have the need to override the platform installed compiler. For a bug fix, for example.

CXXPATH             = $(shell PATH=/usr/local/gcc/bin:$(PATH) \
                      $(DIRNAME) "`$(WHICH) $(CXXTEMP)`")

MY_PATH - This allows us a way of testing new compiler versions on the side and working out any issues before putting them into production. Windows applications are not able to use Cygwin paths so the make process has to construct multiple paths at times. The Windows compiler is not well behaved in that it randomly outputs absolute drive letter paths when we use relative paths. The compiler also emits error output to STDOUT instead of STDERR which can require additional output filtering. Note java_fix, in the path. If you want to run the Java compiler on Linux in a ClearCase database you will need to use an interposer. After Java version 1.4_05 the JVM launch code started using the Linux unique /proc/self function to try and determine its file system location which of course fails in a ClearCase database as it gets a pointer to the cleartext container instead of a file system structure location. The interposer function returns a file not found error which causes the JVM to use the previous method of determining the file system location. We have had to make changes to this fix for different versions of Java where we needed to change symbolic links to the fixup_script which we wrap around the Java and ANT executables to fix the execution issue.

# Build up execution environment PATH variable. Java 7 update 15 no
# longer needs a wrapper script to run within ClearCase. Starting
# with Java 4 update 5 through at least Java 7 update needed a
# wrapper script to insert an interposer on Linux platforms to fix
# the issue of calling the Linux unique /proc/self command that
# returns a symlink to the calling process and then assuming it
# gives a valid location in the file system.
ifeq "$(findstring jdk6_,$(JAVA_HOME))" "jdk6_"
    MY_PATH        := $(TOOLS)/$(OSTYPE)/java_fix:$(JAVA_HOME)/bin:$(ANT_HOME)/bin:$(PATH)
    MY_PATH        := $(JAVA_HOME)/bin:$(ANT_HOME)/bin:$(PATH)
ifneq "$(findstring $(CCPATH),$(PATH))" "$(CCPATH)"
    MY_PATH        := $(CCPATH):$(JAVA_HOME)/bin:$(ANT_HOME)/bin:$(PATH)
ifneq "$(findstring $(CXXPATH),$(PATH))" "$(CXXPATH)"
    MY_PATH        := $(CXXPATH):$(JAVA_HOME)/bin:$(ANT_HOME)/bin:$(PATH)

For Windows there are three macro blocks in the mkcommondefs.mk file that define the three different compiler configurations that are supported by this build process. We configure for Studio version 10.0 or Studio version 8.0. By default we build 64 bit integer mode using Studio 10.0 and allow for a user override to build in 32 bit mode. To build with Studio 8.0 define MC_STUDIO_VER=8.0.

ifeq "$(MS_STUDIO_VER)" "10.0"
    ADDRMODE            = 64
    COMP_INCS           = \
                          -I"$(WVSPATH)/VC/include" \
                          -I"$(WVSPATH)/VC/atlmfc/include" \
    MY_DFLAGS          += \
                          -D_CRT_NONSTDC_NO_WARNINGS \
                          -D_CRT_SECURE_NO_WARNINGS \
    PLATFORM_OPTS       = -nologo -W3 -EHsc -GR -Gy -GS \
                          -Zc:forScope $(COMP_INCS)
    # Create Cygwin; CVSPATH, CSDKDIR; and Windows; WVSPATH, WSDKDIR
    # style paths. Get the Visual Studio installation pointer
    # environment variable. Truncate the path to the root of the
    # installation location.
    WVSPATH            := $(shell $(ECHO) "$$VS100COMNTOOLS" | \
                          $(SED) -re 's|^(/cygdrive/)([a-zA-Z])(.*)|\2:\3|' \
                                  -e 's|[\\/]Common7[\\/]Tools[\\/]||' \
                                  -e 's|\\|/|g')
    CVSPATH            := $(shell $(ECHO) "$(WVSPATH)" | \
                          $(SED) -re 's|^([a-zA-Z])(:)(.*)|/cygdrive/\1\3|')
    # Retrieve from the registry the Windows SDK installation directory.
    WSDKDIR            := $(shell reg query \
        'HKLM\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v7.0A' \
        /v InstallationFolder 2>NULL) | \
        $(SED) -re 's|\r$$||g' \
                -e '/^$$/d' \
                -e '/HKEY/d' \
                -e 's|^(.*)([A-Za-z]:.*)$$|\2|' \
                -e 's|\\|/|g' \
                -e 's|/$$||')
    ifeq "$(WSDKDIR)" ""
        $(error Not able to configure for Windows SDK install location.)
        CSDKDIR        := $(shell $(ECHO) "$(WSDKDIR)" | \
                          $(SED) -re 's|^([a-zA-Z])(:)(.*)|/cygdrive/\1\3|')
    ifeq "$(ADDRMODE)" "64" # 64 bit address mode
    AR                  = $(MY_LD) -lib -nologo -machine:X64
    JAVA_HOME           = $(TOOLS)/$(OSTYPE)/jdk7_$(ADDRMODE)
    MT                  = "$(CSDKDIR)/bin/x64/mt$(EXE_SUFFIX)"
    MY_CC               = "$(CVSPATH)/VC/bin/x86_amd64/cl$(EXE_SUFFIX)"
    MY_CXX              = $(MY_CC)
    MY_DFLAGS          += -D_WIN64
    MY_LD               = "$(CVSPATH)/VC/bin/x86_amd64/link$(EXE_SUFFIX)"
    MY_PATH            := "$(CVSPATH)/Common7/IDE:$(CVSPATH)/VC/bin/x86_amd64:$(CVSPATH)/VC/bin:$(CSDKDIR)/bin/x64:$(JAVA_HOME)/bin:$(ANT_HOME)/bin:$(PATH)"
    OBJDIR              = $(OSTYPE)$(ADDRMODE)_vs$(MS_STUDIO_VER)$(LIBDBG)
    OPTIMIZED          += -favor:INTEL64
    PLATFORM_LIBS       = -nologo $(SUB_SYSTEM) $(MLINK) \
                          $(PROC_ARCH) user32.lib advapi32.lib \
                          -libpath:"$(WVSPATH)/VC/lib/amd64" \
                          -libpath:"$(WVSPATH)/VC/atlmfc/lib/amd64" \
    PROC_ARCH           = -machine:X64
    RC                  = "$(CSDKDIR)/bin/x64/rc$(EXE_SUFFIX)" -r

Parallel builds, in addition to order dependencies, may require other constraints. In the case of the Windows compiler, when you are doing a .Net build, parallel building may need to be turned off as the compiler preprocessor is generating source code which will get compiled after the generation. If you wrap Java ANT builds you will also need to turn off parallel building. Below is an example makefile fragment where the application is built on Windows and other operating systems but needs to be handled differently for the Windows platform. This is a complicated example where the leaf make changes many compiler flags and in some cases changes depend on what version of the Windows compiler is being used.

ifeq "$(OSTYPE)" "win"
  # change many options that are (incompatible with option "/clr")
  #  and add option "/clr" (for logging via "EventLog" class)
  MY_CXXFLAGS   += -clr
  ifdef DEBUG
      CDEBUG    := $(subst -MTd,-MDd,$(CDEBUG))
      CDEBUG    := $(patsubst -RTC1,,$(CDEBUG))
      CDEBUG    := $(subst -MT,-MD,$(CDEBUG))

Sometimes you may need to discover your build process as in the case of generated source code along with make leaf files. The below make macros generate a list of directory locations where makefiles are found. Note: the Cygwin find command can be slow in some cases so the macro below uses a cmd script that is equivalent and much faster.

ifeq "$(OSTYPE)" "win"
DTOP                   := $(subst /,\\,$(TOP))
DIRS                   := $(shell $(CMD) /c dir /B /S $(DTOP)\\cpp\\src\\msg | \
                          $(GREP) makefile | $(SED) -e 's|\\|/|g' \
                          -e 's|^.*/src/|$(TOP)/cpp/src/|' | while read one; \
                          do $(ECHO) `$(DIRNAME) $$one`; done)
DIRS                    = $(shell $(FIND) $(TOP)/cpp/src/msg \
                          -maxdepth 2 -type f \
                          -name makefile | while read one; \
                          do echo `$(DIRNAME) $$one`; done)

Another build issue is when you get too many files in one build location and you exceed the command line length for a build platform. The make macros below creates a linker command file with the list of all the object files to be linked into a library.

ifeq "$(OSTYPE)" "win"
$(LIBRARY): $(OBJDIR)/Obj_File_List
        $(SET_E); $(MKDIR) $(LIBPATH); $(RM) $@; $(LINK)

$(OBJDIR)/Obj_File_List: $(OBJS)
        $(RM) $@; \
        $(CMD) /c dir /B \*.cpp | \
            $(SED) -e 's,^,$(OBJDIR)/,' -e 's,.cpp$$,.$(OBJ_EXT),' >$@
        $(SET_E); $(MKDIR) $(LIBPATH); $(RM) $@; $(LINK)

LINK_SHARED             = \
                          PATH=$(PATH) \
                          LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) \
                          $(MY_LD) \
                          $(LD_OUT) \
                          $(LIB_NAME) \
                          $(MY_LDXFLAGS) \
                          $(LDXFLAGS) \
                          $(LDBFLAGS) \

Running valgrind heap analyzer (massif) in deployment

See the Valgrind Manual for information on options and usage. Valgrind Manual

Example of Using the Clang/LLVM Tool Set

We have extended the make build process to allow dropping in another compiler tool chain to support code analysis. We have a working prototype of the Clang/LLVM tool set built for Red Hat 6.3 64 bit integer mode.

Examples for different types of analysis. Note for static analysis the compiler does not compile the code but runs the preprocessor and outputs the static analysis information. You must build for each type of analysis. The memory analysis is not currently referencing back to the source code; this is being investigated.

There are many more fine grained analysis options that can be done. See Users Manual

Currently there is no video player for this video file on our Red Hat 6.3 platform. Video Showing How to Use Clang Analysis

Make/Ant Process

Make Wrapper for Ant Build

To do a Java build we wrap the ANT process with make to set up the execution environment for the build. The following is a synopsis of the make portion which is invoking ANT and building the all target defined in the build.xml file. We pass in the debug option for stack trace on error which is always on since for Java there is no execution impact. We pass in our build version identification which gets put in the jar manifest file. Note: since PATH has relative paths to compilers, the makefile and the Ant build.xml file need to be located at the same source tree depth for the paths to be valid between the two script files.

ANT                 = \
                      JINFO=$(QJINFO) \
                      ANT_ARGS="$(ANT_ARGS)" \
                      ANT_HOME=$(ANT_HOME) \
                      ANT_OPTS="$(MY_ANT_OPTS)" \
                      JAVA_HOME=$(JAVA_HOME) \
                      PATH=$(MY_PATH) ant --noconfig
ANT_HOME            = $(TOOLS)/apache-ant
JDEBUG              = true
MY_ANT_OPTS        += $(ANT_OPTS)

Example makefile wrapper.


Ant Build XML

Ant build scripts that have good construction should have the following targets; init, build, package, docs, all, and clean.

We use Eclipse and ANT based findbugs to help improve the Java code. We also use Junit for unit testing.

Maintenance of ANT scripts can be made easier and less error prone by doing some simple implementation details; like keep paths and files as separate variables. That way you can have separate class paths for building and execution where the locations are generally different. The Java world likes to embed the version in the file name for the jar file. So by keeping the file name separate from the path there is only one location to edit for version changes. If the build process is complicated then implementing a properties or global initialization target can centralize what is likely to change for COTS dependencies and other data that needs to be used in multiple ANT script locations. Some examples of doing this are the below references.



Example of a complete Ant build.xml file.

build.xml file

utility build.xml file

Other Build Process Documentation

GNU Make Manual Version 3.81
GNU Make Manual
Apache Ant User Manual

Remember all build processes are a compromise. You end up putting in requests for developers that are not always necessary. You work around tool problems. You accommodate other process issues.