Framewave Build System
Architecture
Introduction |
Building
Framewave requires some features that are currently not provided by any
existing build system publicly available. Driven by this, we wrote a custom
build framework, based on the SCons build tool.
This build
system currently works on four different operating systems, using three
different toolsets. It provides features to accomplish all the requirements
that have come up in the last six months of its existence.
Using SCons
gives it access to some very useful features, primarily,
·
Having
a full scale language available to write the build scripts in
·
SCons’s
dependency scanning system, which works on actual changes to files versus time
stamps
·
SCon’s
ability to do efficient parallel builds
Prerequisites |
·
Working
knowledge of SCons. We are currently using version 0.97. Scons documentation is
available from, http://www.scons.org
·
Working
knowledge of Python
·
Working
knowledge of SIMD programming from C++
·
Expert
knowledge of compilers and linkers
This
document can be ‘understood’ without the above prerequisites but to be able
truly grasp how this build system works, they are essential.
Requirements |
The initial
requirement for this build system was to accomplish a multi-pass compile. A
multi-pass compile would be described as:
The ability to compile each source
file ‘n’ number of times, where ‘n’ is the number of different optimization
paths implemented in the source file. The ‘n’ different object files are then
linked into one final binary. Also, a stub function is generated, compiled, and
linked into this binary. The stub functions’ purpose is to pose as the actual
exported function, check the dispatch type of the current processor, and then execute
the correct function in the appropriate object file.
So if we have an exported function
called fwAdd_32f, which had different implementations for reference code, SSE2
optimized code and Family 10h optimized code, we would compile the file
containing fwAdd_32f three time; during each compilations, a prefix is added to
fwAdd_32f function and an object file is produced.. We would then compile a
stub for fwAdd_32f which would call the appropriate version of the original
fwAdd_32f function based on which processor we were running on. Then we would
link all of these three object files and the object file with the stub
function, into one final binary, which could be a shared library, static
library or an executable.
Considering
the complexity of this task, it made sense to have ‘one’ build system in place
that would do this for all our current operating systems (back then only
Windows and Linux) and toolsets (msvc and gcc). With these motivations, the
build work was started and over time a large number of requirements were added.
As of now,
the current list of requirements stand at;
·
Perform
a multi-pass compile for all files designated for it
o
For
files we do not want compiled multi-pass, the build system should still be able
to compile them as they would have been under a regular single pass build
system
·
Work
with largely the same code base on all supported operating systems and for all
supported toolsets
o
We
currently support builds for,
§
Windows
§
Linux
§
Solaris
§
Mac
(Darwin)
o
We
can currently build with,
§
MSVC
§
GCC
§
Sun
CC
·
Provide
the ability to use flags based on any of the following conditions
o
Compiler
(implicitly)
o
Operating
System
o
Variant
o
Bitness
o
Library
type
o
Optimization
path compiled for
o
Library
compiled for
o
Filename
·
Additionally,
provide the ability to give exceptions in either compilation or linking based
on the same set of conditions
o
These
exceptions can range from not building a particular file at all, to using a
completely different set of flags for a particular build of a particular file
·
Provide
incremental builds based on changes in the requisite dependency chains
·
Provide
parallel builds
·
Provide
the ability to automatically pick up CPP files and Header files in the source
and include directories designated by the project directory structure (plus
header files from the common includes)
o
The
idea behind providing the includes was to not have to give the path for a
header file, ever; which were starting to get long, complicated and made it
difficult to move things around
§
As
a side effect of this feature, we cannot have two header files of the same
name, unless we specify the relative or absolute path to it (in essence, don’t
use this feature)
·
Provide
command line support for
o
What
to build
o
Which
variant to build
o
Which
library type to build
o
Which
bitness to build
o
Provide
arbitrary flags
o
Provide
access to special services like,
§
Providing
debug info with release builds
§
Linking
with different CRT models on MSVC
Organization |
SCons
requires us to provide it with a main SConstruct file for a base project and
.sconscript files for each sub project. We currently have SConstructs for the
Framewave and Test projects.
The projects
are organized such;
·
Framewave\trunk
o
FW_Sourceforge\Framewave [Sconstruct here]
§
domain\fwBase
[sconscript
here]
§
domain\fwImage
[sconscript
here]
§
domain\fwJPEG
[sconscript
here]
§
domain\fwSignal [sconscript
here]
§
domain\fwVideo [sconscript
here]
o
FW_AMD\Test
[Sconstruct here]
§
G3
[sconscript here]
§
TestLib [Not
under SCons yet]
§
UnitTests [Not
under SCons yet-]
[-No plans as of now]
o
BuildTools\buildscripts
§
Our build system lives here
Usage |
For the
build system to function correctly, the paths to the build tools to be used
need to be setup before the invocation of SCons
To build,
we need to invoke SCons in either of the directories that contain the
SConstructs. The following command line parameters are supported;
subProjectName
variant=[debug|release]
libtype=[shared|static]
bitness=[32|64]
If any of
these are not provided, the default value (in bold) would be used. If no
subproject is provided, all existing subprojects would be built.
The
following parameters have default values that will be picked on multiple
different build factors.
debuginfo=[on|off]
wincrt =[mt|mtd|md|mdd]
toolset =[msvc|gcc|suncc]
Along with
these, we can specify a target name on the command line to build only that one
project. For example;
scons fwImage
will build only fwImage
High Level Design |
The purpose
of the build system is to setup a hierarchy of SCons objects, ending in top
level targets. The top level target would either be a library or an executable.
We provide
specific flags for every object that we setup.
We
accomplish this by providing our own factory objects for both object files and
library files. These factory objects return an SCons object with all the flags
correctly set up.
Taking a
basic example of the fwSignal shared library on Windows, compiling only with
Add.cpp, this is the SCons object hierarchy we finally end up with at the end
of the execution of our scripts.
The three
object files with the prefixes “_refr_”, “_sse2” and “_f10h” are the multi-pass
compiled object files generated from Add.cpp. The fwSignal_opt.obj file is the
file containing the stub functions for all the exported functions from Add.cpp.
fwSignal.obj is the object file generated from compiling fwSignal.cpp with a
single pass compilation.
High Level Flow of Control (FOC) |
FoC in the SConstruct file:
Our
execution begins in the SConstruct file and then goes onto the sconscript
files. This transition is not controlled by us, we simply register which
sconscript files are needed by this project and SCons takes the onus of calling
them. The high level execution of our SCons script files is as follows;
1.
Create
an fwBuildRoot object. This object at the moment is always called “oRoot”
2.
Create
the fwBase subproject from the oRoot object
a.
This
calls into the subProject method of the oRoot in fwbuild.py. This function
registers the sconscript for the subproject, at which point SCons transfers
execution to the given sconscript file and then returns control to our script
3.
Iterate
through the list of subprojects
a.
Create
subprojects from the oRoot object for all
i.
For
each subproject, as we register the requisite sconscript, SCons executes that
sconscript and then returns control to our script
The
subProject method in oRoot returns a Library or Program build object, which is
a target a SCons.
We then
issue an installation directive to SCons for this object and create an alias
for the object returned by that directive which maps to just the name of the
library the SCons would be building.
At that
point our execution of the SConscript (and of course, any and all of our build
scripts) is over. SCons takes over and builds the libraries using the hierarchy
and flags that we have specified.
Dependency
checking and parallel builds are handled by SCons based on the object,
operating system, toolset and flags that we have setup.
FoC in a sconscript file:
Once the
sconscript is executed, we create a fwProject object for the subproject. Then
we call either the initBuildObjects or initMultipassBuildObjects method of the
fwProject object. These methods are also present in the fwbuild.py file.
FoC in the initBuildObjects method:
The
initBuildObjects method gets the list of CPP files and finally returns a SCons
Library or Program object back to the SConscript file. This is its flow of
control:
1.
Get
a list of all the CPP files we need to build
2.
Call
the constructObjects to create all the requisite object files. The function
return a list of SCons SharedObject or StaticObject objects to us
3.
Create
an fwLibrary factory object, passing this list of objects as it’s dependencies
4.
Return
the SCons Library object from the fwLibrary object
FoC in the initMultipassBuildObjects
function:
The
initMultipassBuildObjects method gets the list of CPP files and finally returns
a SCons Library or Program object back to the SConscript file. During this
process it also builds the stub CPP and processes any files given to it in its
exclude list with single pass processing. This is its flow of control:
1.
Get
a list of all the CPP files we need to build
2.
Create
the stub CPP file using a call to constructMultipassCPP [which exists in
fwparse.py]
3.
Extract
a list of exclude files, which includes,
a.
Any
files given in the exclude list parameter
b.
The
fwProjectName.cpp file
c.
The
created stub CPP file
4.
Call
constructMultipassObjects to create all the requisite object files. The
functions return a list of SCons SharedObject or StaticObject objects
5.
Call
constructObjects on the list of exclude files to generate object files for the
single pass CPP files
6.
Add
both the lists together into one final list of object files
7.
Create
an fwLibrary factory object, passing this list of objects as it’s dependencies
8.
Return
the SCons Library object from the fwLibrary object
FoC in the constructObjects function:
constructObjects
returns a list of SCons SharedObject or StaticObject objects, based on the list
of CPP files that it gets as an input. The flow of control is as follows:
1.
From
the CPP filename, get the Object filename
2.
For
each CPP file;
a.
Create
a fwObject factory object, passing it the object filename and CPP filename
b.
Get
the SCons SharedObject or StaticObject and add it to the list of objects
3.
Return
the list of objects
FoC in the constructMultipassObjects
function:
1.
From
the CPP filename, get the Object filename
2.
For
each CPP file;
a.
Create
a fwMPObject factory object, passing it the object filename and CPP filename
b.
Get
the list of SCons SharedObject or StaticObject objects and add it to the list
of objects
3.
Return
the list of objects
The
functionality of the fwObject, fwMPObject and fwLibrary object factory classes
will be discussed in their respective low level design documents
Issues |
The sub
components of the build system have been completely thrown out and redesigned
to meet the current list of requirements. The fwbuild.py file, though fairly
changed around, has been relatively redesigned less. This code has a lot of
legacy features. This section lists all the know ones for now. To find all the
features that either need to be fixed or enhanced, search through the build
code for the strings “BUGBUG:” or “TODO:” with that exact case.