Linux Build System

Revision as of 18:56, 27 June 2011 by HE-NICK (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search


He source.png Documentation on this page is intended for use by customers who have access to HeroEngine source code under their license agreement.

The HeroEngine Linux build system is organized in a similar manner to the Win32 build system. It is comprised of a master build script, solution makefiles, and project makefiles. The build system supports building all of server components of HeroEngine at once, building individual solutions, or building individual projects. The Linux build system supports both debug and release targets, and each can be configured with target-specific compiler and linker options.

Build & Installation

Building the Linux Environment

First, follow the instructions to set up a Linux Server Development Environment.

Second, get a server ready to run the Linux version of HeroEngine by following these instructions:

HeroEngine Server: CentOS 5.3

For development purposes, the same server may be used to build and run the Linux server processes.

Building HeroEngine

Performing a full build with the default configuration is accomplished by performing the following steps. Note that all instructions assume a current working directory of the HeroEngine branch root, denoted with "$".

1. Navigate to $/tools/compiler/linux

[localhost] HERoot > cd $/tools/compiler/linux

2. Issue one of the following 'make' commands to begin the build process:

[localhost] linux > make            - builds the default target (debug) of HeroEngine
[localhost] linux > make debug      - builds the debug target of HeroEngine
[localhost] linux > make release    - builds the release target of HeroEngine

Note: at any time, you may type 'make list' in a Linux build directory to list available targets. These targets include individual solution or project targets; and also include a corresponding set of 'clean' targets that remove object and/or dependency files generated by the build process.

Performing a 'make list' in $/tools/compiler/linux, for example, would yield the following results:

[localhost] HERoot > cd $/tools/compiler/linux
[localhost] linux > make list

 TARGET can be:
 debug            : build all libraries and binaries using debug configuration
 release          : build all libraries and binaries using release configuration
 [solution name]  : build debug version of...
 cleandebug       : cleans all debug objects, libraries and binaries
 cleanrelease     : cleans all release objects, libraries and binaries
 cleandepsdebug   : clean debug auto-dependencies
 cleandepsrelease : clean release auto-dependencies

3. After the build completes (each solution is built in sequence), the appropriate binaries and libraries will be available in the following directories:

For the debug target:

For the release target:

Once complete, the build of HeroEngine yields output whose final few lines appear similar to the following:

g++ -c -ffunction-sections -fno-const-strings -Wno-deprecated -g -w  -I. -I../../ext/deja_insight
-I../../ext/granny -I../../ext/NxTetra -I../../ext/PhysX_2.8.1/cooking/include -I../../ext/PhysX_2.8.1
/Foundation/include -I../../ext/PhysX_2.8.1/physics/include -I../../ext/PhysX_2.8.1/PhysXLoader/include
-I../../include -I../../include/common/binding -I../../include/common/FileInterface -I../../include/common
/MemoryServices -I../../include/common/ResourceInterface -I../../include/common/snippets -I../../src/common
/AutoPhysX -I../../src/common/comlayer -I../../src/common/heightmap -I../../src/common/NxuStream2 -I../..
/src/common/snippets -I../../src/common/grimp -I../../src/common/psk -I../../src/common/TinyXML -I../..
/src/common/MeshEZM -I../../ext/speedtreert -I../../include/SpeedTree -I../../src/SpeedTree/NxCharacter 
-DNX_DISABLE_HARDWARE -D_DEBUG -DSPEEDTREE_EXPORTS ../../../common/linux_compat/linux_compat.cpp -o ./build
g++ -o bin/debug/ ./build/debug/SpeedTree/binding.o ./build/debug/SpeedTree/dlmalloc.o ./build
/debug/SpeedTree/GlobalNewDelete.o ./build/debug/SpeedTree/htmltable.o ./build/debug/SpeedTree/memalloc.o 
./build/debug/SpeedTree/ObjectPool.o ./build/debug/SpeedTree/SpeedTree.o ./build/debug/SpeedTree
/linux_compat.o -L. -L./lib/debug -L./bin -L../../ext/Boost/lib -L../../ext/speedtreert -L../../ext/CryptLib
/lib/linux -L../../ext/deja_insight/lib/linux -L../../ext/PhysX_2.8.1/lib/linux -L../../lib/linux -L../lib
/linux -lspeedtree -Wl --gc-sections --export-dynamic  -shared
make[2]: Leaving directory `$/he_plugins/compiler/linux'
make[1]: Leaving directory `$/he_plugins/compiler/linux'

[localhost] linux >

Building Projects & Solutions

Building the individual projects within each solution can be accomplished by following the procedure listed below:

1. Navigate to one of HeroEngine's solution build directories (depending on the project you wish to build):

[localhost] HERoot > cd $/firestorm/compiler/linux
[localhost] HERoot > cd $/gauntlet/compiler/linux
[localhost] HERoot > cd $/hjservers/compiler/linux
[localhost] HERoot > cd $/he_plugins/compiler/linux

2. Issue a 'make [projectname]' command. Project names can be listed - as mentioned previously - by issuing a 'make list' in one of the solution build directories.

[localhost] HERoot > cd $/firestorm/compiler/linux
[localhost] linux > make MemoryServices

Note: to build the entire solution, simply type 'make', 'make debug' or 'make release' from within a solution build directory.

3. After performing a solution or project build, resultant binaries and libraries will be available (as before) in the appropriate ./bin/[targetname] and ./lib/[targetname] directories.

Installing HeroEngine

After the completion of a full build (i.e. one initiated by a 'make debug'/'make release' from $/tools/compiler/linux), you'll find a tarball (.tar.gz file) containing all firestorm daemons, hjserver daemons and shared libraries generated by the build. This will be called heroengine-VV.vv-R_debug.tar.gz or heroengine-VV.vv-R_release.tar.gz, depending upon the build target chosen during the full build.

[localhost] linux > ll
total 46M
-r--r--r-- 1 heroengine heroengine 3.7K Oct 29 17:22 Makefile
drwxr-xr-x 4 heroengine heroengine 4.0K Oct 15 16:55 bin/
-r--r--r-- 1 heroengine heroengine 3.5K Oct 29 13:53 globals.cfg
-rw-r--r-- 1 heroengine heroengine  46M Oct 29 17:42 heroengine-VV.vv-R_release.tar.gz
drwxr-xr-x 4 heroengine heroengine 4.0K Oct 15 16:55 lib/
[localhost] linux >

At this point, you may relocate the binaries to any desired location by issuing the following command (this will decompress and unarchive the files, depositing them in the new location):

[localhost] linux > tar -xvz --overwrite -C /opt/heroengine -f heroengine-VV.vv-R_release.tar.gz

NOTE: The script creates the user heroengine and sets the home directory to /opt/heroengine. You will need to adjust the -C [desired target path] to fit your environment if you manually created the heroengine account and set a different home directory.

This will, as expected, decompress and extract heroengine-VV.vv-R_release.tar.gz and place the binaries into the desired target path.

[localhost /opt/heroengine ]$ ll
total 24
drwxrwxr-x 3 heroengine heroengine 4096 Oct 29 13:58 depot
drwxrwxr-x 2 heroengine heroengine 4096 Oct 29 14:33 firestorm
drwxrwxr-x 2 heroengine heroengine 4096 Oct 29 14:26 gauntlet
drwxrwxr-x 2 heroengine heroengine 4096 Oct 29 14:26 he_plugins
drwxrwxr-x 2 heroengine heroengine 4096 Oct 29 14:33 hjservers
-rwxrwxr-- 1 heroengine heroengine  263 Oct 29 14:26 START
[localhost] linux >

Next, you'll set up configuration files necessary for communication with your HE World (we assume you've configured this ahead of time).

Required configuration files:

Note: each configuration must be located in the specified directory. hjservers.cfg is placed in the hjservers directory, while the remainder of the configuration files are located in the firestorm directory.

Example configurations follow:





























At this point - assuming the World is properly configured - HeroEngine is ready to launch!

To launch FireUpDaemon and MasterControlDaemon, simply issue the following command from your new HeroEngine run directory:

[localhost] heroengine > ./START
Starting HeroEngine...
~/firestorm ~
[localhost] heroengine > ps u
501       7748  0.0  0.0   4528  1464 pts/3    S+   Oct29   0:00 -bash
501       7845  0.0  0.0   4528  1496 pts/5    Ss   Oct29   0:00 -bash
501      21576  0.0  0.0   4532  1424 pts/6    S+   06:14   0:00 -bash
501      21710  1.3  0.2  22848  6148 pts/5    Sl   06:40   0:00 ./FireUpDaemon --application-uname=fireup
501      21712  0.2  0.0   2148   872 pts/5    S    06:40   0:00 top -b
501      21714  5.5  1.2  43284 26444 pts/5    S    06:40   0:02 ./MasterControlDaemon --application-uname=1
501      21716  0.0  0.0   4244   936 pts/5    R+   06:41   0:00 ps u

Congratulations! You're now running HeroEngine!

Configuring Build Targets

Two build targets: debug and release, are available in the default configuration. These targets are specified in the ./tools/compiler/linux directory in a file named 'globals.cfg'.

The 'globals.cfg' file is included in every attempted build of a project or solution. Its primary purpose is to set up global variables used during builds and to provide specifications for each of the build targets. It also assures a properly configured environment by performing several tests against gcc version info and environment variable definitions.

The following variables in 'globals.cfg' can be modified to customize the target build procedures:

- Globals -
 CXXDEFINES_GLOBAL    - preprocessor defines used in all build targets.  These will be added
                        to the compiler options during build.
 CXXFLAGS_GLOBAL      - gcc options used in all build targets.
 LDFLAGS_GLOBAL       - linker options used in all build targets.

- Targets -
 DEFINES_DEBUG        - preprocessor defines specific to the debug target.
 DEFINES_RELEASE      - preprocessor defines specific to the release target.
 CXXFLAGS_DEBUG       - gcc options specific to the debug target.
 CXXFLAGS_RELEASE     - gcc options specific to the release target.
 LDFLAGS_DEBUG        - linker options specific to the debug target.
 LDFLAGS_RELEASE      - linker options specific to the release target.


Following is a rundown of each class of Makefile used in the HeroEngine build process. The discussion is targeted mainly at system administrators and users of the build system. For a more technical look at the inner workings of GNU Make, please refer to the official documentation hosted at The Website.

In most cases, the only modifications you will ever need to make to any HeroEngine makefiles will be in the variable definitions at the top of the file. The target and dependency rules are in all cases automatically generated from the definitions established earlier in the makefile; thus, adding, removing or modifying code or pathing can be achieved with a few minor tweaks to the variable definitions.

Master Makefile


The master makefile - located in ./tools/compiler/linux - is used to build the whole of HeroEngine. When invoked with a target (e.g. release, debug), each solution in HeroEngine will have its solution makefile called (which, in turn, will call each of its project makefiles). For most purposes, this will be the primary makefile used to build any HeroEngine solutions, as it runs through and builds all HE solution code.


The master makefile contains as its first item a list of solutions to build as part of a full build of HeroEngine. This list is set via the SOLUTIONS variable; e.g

1: include globals.cfg
3: SOLUTIONS = firestorm \
4:             gauntlet \
5:             hjservers \
6:             he_plugins

Each item in this list corresponds to a directory tree under the HeroEngine root (e.g. ./firestorm) where that solution's Makefile can be found (./firestorm/compiler/linux, ./gauntlet/compiler/linux, etc). Items in this list are used to resolve solution directories and each can be a target for 'make' commands in the ./tools/compiler/linux directory (e.g. make firestorm).

9: .PHONY: all clean cleandeps $(SOLUTIONS)

The next line is a declaration of targets which do not correspond to physical files on disk; this is used during builds to ensure commands belonging to these targets are executed even if files with the corresponding names exist in the build path (this is used for targets such as 'clean', whose commands you will want executed even if a file named 'clean' exists). For the most part, this line can be ignored unless you plan on making extensive modifications to the build system.

11: debug release all: $(SOLUTIONS)

At this point, our primary build targets are defined (debug & release). When invoked with 'debug' or 'release' targets, each solution defined earlier is included as a dependency (through variable expansion) and - as a result - each solution makefile gets invoked (more on this later):

13: $(BINDIR) $(LIBDIR):
14:         @-mkdir -p $@
32: cleandebug cleanrelease clean:
33:         @for solution in $(SOLUTIONS); do \
34:           $(MAKE) -C ../../../$$solution/compiler/linux $@; \
35:         done
36:         @-for solution in $(SOLUTIONS); do \
37:           [ -h $(BINDIR)/$$solution ] && rm -f $(BINDIR)/$$solution; \
38:           [ -h $(LIBDIR)/$$solution ] && rm -f $(LIBDIR)/$$solution; \
39:         done
41: cleandepsdebug cleandepsrelease cleandeps:
42:         @for solution in $(SOLUTIONS); do \
43:           $(MAKE) -C ../../../$$solution/compiler/linux $@; \
44:         done

After this, our utility target definitions begin. These are targets such as 'clean', 'cleandeps', '$(BINDIR)', '$(LIBDIR)', and their related debug and release variants. These should never be modified, as they automatically clean object files and dependency files, and they ensure directories required for the build process exist on-disk before any objects are generated. After the utility targets come the SOLUTION targets. These are generated automatically from the SOLUTIONS variable established earlier in the makefile (again, through variable expansion). When the makefile is invoked with 'debug' or 'release', all solution targets are invoked (and thus, the commands for each target are run). When a single solution target is used (e.g. firestorm), only commands for its target are run.

47:         $(MAKE) -C ../../../$@/compiler/linux $(BUILDAFFIX)
48:         @[ -h $(BINDIR)/$@ ] || ln -s  ../../../../../$@/compiler/linux/bin/$(BUILDAFFIX) $(BINDIR)/$@
49:         @[ -h $(LIBDIR)/$@ ] || ln -s ../../../../../$@/compiler/linux/lib/$(BUILDAFFIX) $(LIBDIR)/$@

When a solution target is invoked, 'make' is run (line 47) on the makefile at a location specified by ./SOLUTION_NAME/compiler/linux named 'Makefile'. It also appends either 'debug' or 'release' depending on the current build target (specified by adding 'debug' or 'release' to the command-line when invoking the master makefile). Line 47 could be rewritten as follows for a target of 'make release' on its 'firestorm' target:

[localhost] linux > make -C ../../../firestorm/compiler/linux release

Lines 48 and 49 create symbolic links to the ./bin and ./lib output directories in each solution's build tree. This is to provide easy access to the final executables (and libraries) once a build is complete.


The only useful modification that can be made to the master Makefile is in adding or modifying the SOLUTIONS variable. If - for some reason - you choose to add a new module to the HeroEngine source, you might want to add it in this list to ensure the build system includes it in all HE builds. e.g.

3: SOLUTIONS = firestorm \
4:             gauntlet \
5:             hjservers \
6:             he_plugins \
7:             3rdparty

Things to keep in mind:

Solution Makefiles


Solution makefiles are provided as a means of grouping logically similar projects. When called from the master makefile (or with a naked 'make' command), these makefiles will iterate over all projects in the solution, calling the project makefiles and building object files, libraries and binaries that will be output to the local ./bin and ./lib directories (as stated before). Like the master makefile, these may be called with a 'make list' to list all possible targets to be built in the solution.


The format of the solution makefiles closely parallels that of the master makefile. With one exception, they are in fact identical in format.

Unlike the master build process, the solution build separates itself into two stages:

These steps are followed sequentially so that all library dependencies are satisfied prior to building the binaries that might depend on them.

Instead of a SOLUTION variable, solution makefiles have LIBRARIES and BINARIES. The gauntletproject, for example, contains the following variable definitions:

3: LIBRARIES = MemoryServices \
4:             Gauntlet \
5:             CommandLineInterface \
6:             GetOpt \
7:             GauntletLibrary
10: BINARIES = GOMConsole

These two variables fully specify the names of all projects within the solution. From them, project makefile names are derived (as well as names of the final output libraries and binaries).

When the 'debug' or 'release' targets are invoked, the commands pertaining to each LIBRARIES and BINARIES target are executed. Since the BINARIES list the LIBRARIES as dependencies, all LIBRARIES will be build first. As mentioned earlier, this eliminates any potential unsatisfied dependencies during the building of each of the BINARIES.

53:         $(MAKE) -f $ $(BUILDAFFIX)
56:         $(MAKE) -f $ $(BUILDAFFIX)

Each name, then, corresponds to the base name of a makefile in the current build directory. It is invoked with a 'debug' or 'release' option (depending on how you called the solution makefile), and once one library or binary is fully built, it moves onto the next in the list.


As with the master makefile, the only practical changes to the solution makefile are to the LIBRARIES and BINARIES variables. Since these dictate the names of the project makefiles and the final output libraries and binaries, they are all that need adding, deleting, or modifying. To include a new project to the gauntlet solution, then, one might modify the solution makefile to resemble the following:

3: LIBRARIES = MemoryServices \
4:             Gauntlet \
5:             CommandLineInterface \
6:             GetOpt \
7:             GauntletLibrary \
8:             ExtraLibrary1 \
9:             ExtraLibrary2
11: BINARIES = GOMConsole \
12:            binary2 \
13:            binary3

This would imply that the following makefiles would exist in the solution build directory:

These makefiles would follow the Project Makefile format detailed in the following section.

Project Makefiles


Projects are the logical building blocks of Solutions. Each contains source code that - when compiled - is either added to a static library archive or linked into a final executable. Project makefiles (makefiles with a .mk extension) contain references to their contained source code, dependency information, and configuration for the build process. Before a Solution can be said to be fully built, each project contained within that Solution must be built.


Unlike the Solution and Master makefiles, Project makefiles are centered around building source code, creating static libraries, and building executable binaries. This process generally requires specifying several quantities, including:

  1. Names of source files (.cpp) and pathing information for where to find them
  2. Information for INCLUDE paths (where to find .h headers)
  3. Information for LINK paths (where to find library dependencies)
  4. Project-specific preprocessor defines
  5. Any project-specific configuration for post- or pre- build steps

With these quantities, we can set up a Project makefile to successfully compile and link a project of indeterminate size and complexity. For this explanation, let's take a look at a simple Project makefile: that of MemoryServices in Firestorm.

[localhost] HERoot > cd $/firestorm/compiler/linux
[localhost] HERoot > vi

7: include ../../../tools/compiler/linux/globals.cfg

The first line of content in a Project makefile is the globals.cfg include. As mentioned previously, the Linux build system relies heavily upon a global configuration file. Included in every makefile, this global configuration sets up variables to be used within individual makefiles that determine build targets (debug, release), compiler/linker options, and the names of the compiler, linker and archiver to use during builds. These variables are used heavily throughout project makefiles, so inclusion of this global config is very important.

 9: ### Begin Local Variable Declarations ###
10: MAKEDEPEND     = g++ -M
12: BUILDDIR       = $(BUILDBASEDIR)/MemoryServices
13: DEPDIR         = $(DEPBASEDIR)/MemoryServices
15: SolutionRoot   = ../..
16: ProjectRoot    = ../../../he_plugins/include/common/MemoryServices

Next are definitions of variables used to set up object, dependency, project/solution pathing information and - optionally - oracle username/password configuration information. Lines 12 and 13 determine the directories within which object and dependency files are to be output (and later located by the archiver or linker). These definitions rely on the globals.cfg defines of BUILDBASEDIR and DEPBASEDIR, which are set up based upon which target you've chosen to invoke the project makefile with (debug or release). All Project makefiles will have identical lines 12 and 13 with the exception of the project name. These, then, could be rewritten:

12: BUILDDIR       = $(BUILDBASEDIR)/[project name]
13: DEPDIR         = $(DEPBASEDIR)/[project name]

Lines 15 and 16 specify the root directory of the current solution (always ../.. as long as your makefiles are invoked from ./solutionname/compiler/linux) and the directory of the current project. These are often used in later definitions of INCLUDE and LINK path defines, and often-times in the source file listing.

18: ORACLE_USERID_HEAccessControl = userid=heaccesscontroluser/PASSWORD_HERE@heaccesscontroldbname
19: ORACLE_OPARMS_HEAccessControl =
21: ORACLE_USERID_HJAccessControl = userid=hjaccesscontroluser/PASSWORD_HERE@hjaccesscontroldbname
22: ORACLE_OPARMS_HJAccessControl =
24: ORACLE_USERID_IDService       = userid=idserviceuser/PASSWORD_HERE@idservicedbname
25: ORACLE_OPARMS_IDService       =
27: ORACLE_USERID_Repository      = userid=repositoryuser/PASSWORD_HERE@repositorydbname
28: ORACLE_OPARMS_Repository      =
30: ORACLE_USERID_FSService       = userid=fsserviceuser/PASSWORD_HERE@fsuserdbname
31: ORACLE_OPARMS_FSService       =

In the case of projects that utilize Oracle, the next few lines will indicate 'user ID / password @ databasename' combinations to be used as Oracle logins when generating pro*c files. These need to be valid Oracle logins for HeroEngine to operate successfully. Global pro*c parameters can also be specified via the ORACLE_OPARMS_GLOBAL variable immediately before the login information in the makefile.

Note: since these pro*c files are rarely recomputed, they are not automatically rebuilt during a full build of HeroEngine. In order to clean the old files out and re-generate them, it is necessary to perform a "make -f [makefilename] cleanproc" prior to compilation. Omitting this step will result in any changes to login information not being updated in these pro*c files; this will break connecting to the Oracle database. When changing Oracle login configurations, always perform a "make -f [makefilename] cleanproc" afterward to force a recompile.

18: INCLUDE_PATHS  = $(ProjectRoot) \
19:                  $(SolutionRoot)/common/include \
20:                  $(SolutionRoot)/ext/include

Next is the list of include paths to be used during compilation of each source file in the project. This determines where the compiler will search for non-system header files (.h). As is common throughout the HeroEngine makefiles, use of the ProjectRoot and SolutionRoot variables can simplify pathing and maintain consistancy.

21: MAKE_PATHS     = ../../../firestorm/compiler/linux \
22:                  ../../../gauntlet/compiler/linux \
23:                  ../../../hjservers/compiler/linux \
24:                  ../../../he_plugins/compiler/linux

This section dictates which directories 'make' will search for makefiles in if it cannot find library dependencies required for the current project. In short, this list should contain - at minimum - entries with build paths equal to all solutions you plan to pull library dependencies from; to be safe, this could include all HE solution build paths.


This is a list of project-specific preprocessor defines used during compilation of each source file. Note that CXXDEFINES_GLOBAL is pulled in from globals.cfg; this should always be in a project's DEFINES list to allow easy configuration of global and target-specific builds.

28: SOURCES        = $(ProjectRoot)/dlmalloc.cpp \
29:                  $(ProjectRoot)/htmltable.cpp \
30:                  $(ProjectRoot)/memalloc.cpp \
31:                  $(ProjectRoot)/ObjectPool.cpp \
32:                  $(ProjectRoot)/GlobalNewDelete.cpp

The SOURCES variable is a listing of all source files (.cpp, .c) to be compiled as part of the current project. Full relative paths are required here, as these paths will be used later in the makefile to generate a list of directories within which sources are found.

34: SOURCEDIRS     = $(sort $(dir $(SOURCES)))

The SOURCEDIRS variable is used to set up search paths for .cpp and .c files. Provided we generate a listing of all directories sources can reside, 'make' will condense and simplify our target rules for us so we can use a catch-all for .cpp and .c files. This line never needs to be changed, as it generates its list solely based on the contents of SOURCES.

37: LIBRARY        = libMemoryServices.a
37: BINARY         = BinaryName

Line 37 lists the name of the library or binary to be built as part of this project. There are subtle differences to a few lines in project makefiles depending upon the type of object built. For a binary executable, this line should define BINARY. For a static library, this line should define LIBRARY.


Lines 39-41 define the project-specific compiler and linker options. Note the inclusion of GLOBAL_CXXFLAGS and GLOBAL_LDFLAGS: these are brought in from globals.cfg.

43: ###Begin VPath Declarations ###
44: vpath %.d $(DEPDIR)
45: vpath %.o $(BUILDDIR)
46: vpath %.cpp $(SOURCEDIRS)
47: vpath %.c $(SOURCEDIRS)
48: vpath %.a $(LINK_PATHS)
49: vpath $(LINK_PATHS)
50: vpath $(MAKE_PATHS)

Lines 43-50 are used internally by 'make' to determine automatic search paths for various file types (also called v-paths). They are used to simplify 'make' targets and to leverage 'make's directory search features. If interested, please see the official gnu make documentation for a more complete explanation of v-paths. These lines need never be changed, as they rely only upon the contents of the variables used.

52: ###Begin Calculated Local Variable Declarations ###
53: OBJECTS        = $(addsuffix .o, $(basename $(notdir $(SOURCES))))
54: DEPENDS        = $(addsuffix .d, $(basename $(notdir $(SOURCES))))
56: INCLUDE_PATHS_ = $(addprefix -I, $(INCLUDE_PATHS))
57: LINK_PATHS_    = $(addprefix -L, $(LINK_PATHS))
58: DEPENDENCIES_  = $(addprefix -l, $(DEPENDENCIES))
59: OBJECTS_       = $(addprefix $(BUILDDIR)/, $(OBJECTS))
60: DEPENDS_       = $(addprefix $(DEPDIR)/, $(DEPENDS))
61: DEFINES_       = $(addprefix -D, $(DEFINES))
62: LIBRARY_       = $(addprefix $(LIBDIR)/, $(LIBRARY))
62: BINARY_       = $(addprefix $(BINDIR)/, $(BINARY))

This section defines variables used internally by the makefile to refer to object names (OBJECTS), dependency file (.d) names (DEPENDS), and to prepend the appropriate compiler and linker flags to each entry in the previously defined INCLUDE_PATHS, LINK_PATHS, DEPENDENCIES, OBJECTS, DEPENDS, DEFINES and LIBRARY/BINARY variables. These lines need never be changed, as they rely solely upon information contained in previously defined variables.

64: debug release all: $(LIBRARY_)
64: debug release all: $(BINARY_)

Now begins our target definitions. Targets are on the left, dependencies are on the right. In this case - regardless of which target we're building - we're expecting to generate the LIBRARY_ or BINARY_ whose name and path we've defined earlier in the makefile. Upon hitting this target, 'make' will skip to that dependency's target rule and process it.

70:         @-[ -d $@ ] || mkdir -p $@

As in the other makefiles, these lines ensure output directories exist before the generation of objects, libraries, dependency files or binaries.

72: cleanrelease cleandebug clean: | $(BUILDDIR) $(LIBDIR) $(BINDIR)
73:         @-for file in $(OBJECTS_); do [ -f $$file ] && echo removing $$file     && rm $$file; done
74:         @-for file in $(LIBRARY_); do [ -f $$file ] && echo removing $$file     && rm $$file; done
75:         @-[ -d $(BUILDDIR) ] && rmdir $(BUILDDIR) 2>/dev/null
77: cleandepsdebug cleandepsrelease cleandeps: | $(DEPDIR)
78:         @-for file in $(DEPENDS_); do [ -f $$file ] && echo removing $$file     && rm $$file; done
79:         @-[ -d $(DEPDIR) ] && rmdir $(DEPDIR) 2>/dev/null

These rules will clean object files or dependencies from the appropriate directories. They need not be modified.

81: %.o: %.cpp | $(BUILDDIR) $(DEPDIR)
82:         $(MAKEDEPEND) $(CXXFLAGS) $(INCLUDE_PATHS_) $(DEFINES_) $< | sed -e     's-^\s*$*.o-$*.o $*.d-g' >
84:         $(CXX) -c $(CXXFLAGS) $(INCLUDE_PATHS_) $(DEFINES_) $< -o $(BUILDDIR    )/$*.o
86: %.o: %.c | $(BUILDDIR) $(DEPDIR)
87:         $(MAKEDEPEND) $(CXXFLAGS) $(INCLUDE_PATHS_) $(DEFINES_) $< | sed -e     's-^\s*$*.o-$*.o $*.d-g' >
89:         $(CC) -c $(CXXFLAGS) $(INCLUDE_PATHS_) $(DEFINES_) $< -o $(BUILDDIR)    /$*.o

These are the targets responsible for building object files from source files. In lines 82 and 87, we run each source file through a dependency generation command; this will generate a .d file on every build of this source file and ensure auto-dependencies are kept up to date. These auto-dependencies are included as the last line of our project makefiles to set up rules for rebuilding objects whose dependencies have changed. In short, these lines ensure that any changes to source or header files used by this project will trigger a rebuild of all affected objects.

Lines 84 and 89 perform the actual compilation. They take our previously defined compiler-centric variables (CXXFLAGS, DEFINES_, INCLUDE_PATHS_, BUILDDIR) and pass them to gcc as part of the compilation step. After these rules have executed on each source file, resultant object code (.o) will be placed in the appropriate BUILDDIR.

92:         @-for mdir in $(MAKE_PATHS); do \
93:           [ -f $$mdir/$(addsuffix .mk, $(subst -l,,$@)) ] && $(MAKE) -C $$md    ir -f $(addsuffix .mk, $(subst
 -l,,$@)); \
94:         done

In the case that any of the DEPENDENCIES_ (library dependencies for a binary executable) are not found in the LINK_PATHS specified earlier, this section is triggered. When triggered, this rule will iterate over all entries in the MAKE_PATHS list and attempt to find a project makefile (.mk) that matches the name of the dependency. MemoryServices, for example, would be found in $/firestorm/compiler/linux. This makefile is then invoked and the library built before proceding to the linker stage.

97:         $(AR) -r $@ $(OBJECTS_)
97:         $(CXX) -o $@ $(OBJECTS_) $(LINK_PATHS_) $(DEPENDENCIES_) $(LDFLAGS)

Finally, the library or binary is built from the DEPENDENCIES_ and/or OBJECTS_ that were built previously. In the case of linking, the linker-centric variables we defined earlier are included (LDFLAGS, DEPENDENCIES_, LINK_PATHS_)


As with the Solution and Master makefiles, the only changes that need be made to Project makefiles are in the initial variable definitions at the top of the makefile:

These fully specify where to look for header, source, library and dependency files, and they fully specify which configurations to use when building each.


See also

Personal tools