FMS Build System Explanation and Hints


This build system has two key features: support for multiple architectures
in one build tree, and automatic dependency generation.  It also makes
it easy to create libraries and executables with all of the source files
found in a single directory.

The "makedefs.mk" and "makerules.mk" files define the global rules that
apply to all makefiles.  Exactly one of the "makedefs-xxx.mk" files, which
contain the statements necessary to define a toolchain, is also included.


Multi-Architecture Support
==========================

When used correctly, it should be possible to have builds for multiple
platforms available simultaneously.  This means you can make a minor
change and perform a quick build for two or more platforms, instead of
needing to maintain multiple source trees or performing a "make clean"
between each run.  This can be invaluable when two devices or a real and
a simulated device are behaving differently.

The current platform is defined by variables in a file called "buildspec.mk"
in the top level of the build tree.  Some of the variables are combined
into a build specification string, FMS_BUILDSPEC.

By default, object files are stored in the source tree in a directory
called ".obj-$(FMS_BUILDSPEC).  The libraries and executables you build
are also stored there.  Source code generated by programs like lex and
yacc is written there too, because it's often platform-specific, and you
want it to be removed as part of "make clean".

If you define FMS_OBJECTTOP in your buildspec.mk, the objects will be
stored in a separate tree.  For example, if FMS_OBJECTTOP is "/tmp/objects"
and you're building in a subdirectory called "foo/bar/", the objects that
would normally go into "foo/bar/.obj-$(FMS_BUILDSPEC)/" will now go into
"/tmp/objects/foo/bar/obj-$(FMS_BUILDSPEC)/".  If the makefile are
structured correctly, builds will work equally well whether FMS_OBJECTTOP
is defined or not.

If you want to link against libraries built earlier in the build process,
you either need to copy them out to the install directory or find them
where they are built.  If you decide to copy them out to the install
directory, you should consider swapping the order of "all" and "install"
in makerules.mk, so that "install" is the default behavior.

Linking against libraries inside the build/object tree will be awkward
if you want to allow FMS_OBJECTTOP to be either defined or undefined.
Either link against them in the install directory, or mandate that
FMS_OBJECTTOP is used for all builds or no builds.


Automatic Dependencies
======================

Dependency generation systems that rely on tools like "makedepend" are
cumbersome at best.  If the wrong set of compilation flags is passed
in, the wrong header files could be listed.  Compiler-specific statements
can cause the dependency generator to become confused.

A better way is to use the dependency generation feature (-M) of the
GNU C compiler.  This tells gcc to output the dependency information it
gleans from the source file.  Because it's coming from the compiler itself,
problems associated with external tools do not arise.

The dependencies are stored in ".d" files in the same directory as the
object files.  The dependency files are updated every time a file is
recompiled, and left alone otherwise.  A "make clean" will wipe out the
objects and dependencies at the same time.

One problematic situation can arise.  If you do a build, delete a header
file, and build again, the build will fail because one or more of the ".d"
files will reference a nonexistent header.  This can be avoided by doing
a "make clean" following a checkout, or by intelligently trashing ".d"
files after headers are removed.


Collections of Sources
======================

Defining FMS_BUILD_APP, FMS_BUILD_LIB, or FMS_BUILD_SHLIB makes it easy
to generate an executable or linkable object.  When combined with GNU
make features like $(wildcard ...), it's easy to build an object from
all of the sources in a single directory.

This is probably the best way to organize source code.  You are not
required to do things this way, but it's much easier than specifying
the objects to build in an explicit rule.  Note that makerules.mk will
generate OBJS from SRCS if SRCS is defined and OBJS is not.

Hierarchical build systems can be created by listing all subdirectories
in the FMS_SUBDIRS variable.


Concealment of Build Commands
=============================

Most people are probably used to seeing the actual compile and link
commands.  If you're developing the build system, this is important,
but if you're just trying to build a product you don't need to see
all of the compile flags on every build.

The default behavior is to show the standard makefile output.  If you set
"FMS_BRIEF=true" in your environment or buildspec.mk, the compile and
link lines are replaced with simple statements that indicate which file
is being compiled or linked.

The chief advantage of this system is that compiler warning messages
are no longer drowned out by lengthy sets of -I and -D directives.
(This is less useful if you have a "warnings are errors" policy.)


Including External Code
=======================

It's not always necessary to rewrite the makefiles of 3rd-party code
included in the build system.  If you're not building for multiple
platforms, you can just kick off the sub-build from any makefile.

If you are adapting a package for use with multiple platforms, you need
to consider the following:

 - If it has a "configure" script, you will need to play with it to get
   the right answers for cross-compile environments.
 - If it creates "build helper" binaries, you will need to build them
   with the local compiler rather than the cross-compile toolchain.
 - The installation rule must use the platform identifier string to
   find the right output directory.


Miscellaneous
=============

The values in buildspec.mk could also be specified through environment
variables.  If you switch frequently between projects, empty out
buildspec.mk and create a set of aliases that set the variables in your
environment.

Any code that is fundamentally cross-platform (shell scripts, Python)
doesn't really need to be in the build tree.  It may be convenient to have
it in the build tree for source control purposes; if so, just have a "here"
target that does nothing and an "install" target that copies the scripts.

Files that are "content", such as HTML pages and JPEG images, should
probably go into a separate "content tree" that is directly under source
control and is not platform-dependent.  Checking out the content tree and
then copying it into an installation directory is a big waste of time.
Exceptions must be made for platform-specific or vendor-specific content
(e.g. corporate logos).

If you use shared libraries, you will need to set up an LD_LIBRARY_PATH.
On a device this will usually point to a fixed location where the object
package is installed.  If you have one or more native builds, you will need
to configure it to point into the install directory, ideally overriding
the standard paths to ensure that your version of standard libraries
(e.g. libc.so) matches up with what you just built.  The "ldd" command
is invaluable here.

The FMS_BUILDSPEC string is composed from FMS_ARCH and FMS_TARGET.
You can redefine this in any way you see fit.  For example, you may not
feel the need for debug vs. release builds.  You may want to add a third
item for "simulator" vs. "physical" because the simulator build requires
additional libraries.  Fiddle with it as needed.  If you get it wrong,
"make clobber" will conceal a multitude of sins.

All of the "internal" targets and variables used in the make system are
prefixed with "FMS_" or "fms-".  Items typically found in makefiles are
not, e.g. SRCS, OBJS, CFLAGS, and so on.

