Fbuild is a build system designed for configuring and compiling both small and large projects. It has extensive feature list:
- Linux, Apple, and Windows support
- Supports compiling C, C++, O'Caml, Java, Scala, GHC, and Felix
- Extensive configuration detection for the C and C++ standard library, posix, and more
- Parallel compilation
- Cross compiling
- Digest-based tracking of file changes
- Full Python scriptability
- Easy to extend
- Pretty output
- ... and it is quite fast too
Fbuild is hosted and developed on Github. It
requires Python 3. To download the current
Fbuild version, you visit the Fbuild website. Or, if you have access to the
curl
program, you can just run this command:
$ curl -L https://github.com/erickt/fbuild/tarball/v0.2 | tar -zx
To download the development version, run this git command:
$ git clone git://github.com/erickt/fbuild.git
To install, run:
$ python3 setup.py install
Fbuild has extensive support for advanced build systems, but this doesn't
complicate simple projects. Here is the classic "Hello World" example, written
in a C file named helloworld.c
:
#include <stdio.h>
int main() {
printf("hello world!\n");
return 0;
}
Along side of it we will create a Python 3 file named fbuildroot.py
, which
will drive the compilation. We'll step through each line in a moment:
import fbuild.builders.c
def build(ctx):
builder = fbuild.builders.c.guess_static(ctx)
exe = builder.build_exe('helloworld', ['helloworld.c'])
ctx.logger.log(' * running ' + exe)
ctx.execute([exe])
As you can see, it is pretty compact Python script. To compile, if you
installed Fbuild, simply run fbuild
in the same directory as the
fbuildroot.py
. Otherwise, you can run fbuild-light
out of the Fbuild
distribution, which doesn't require installation. This is what you'll see:
$ fbuild # or $FBUILD_DIR/fbuild-light
determining platform : {'bsd', 'darwin', 'macosx', 'posix'}
looking for program gcc : ok /usr/bin/gcc
checking gcc : ok
looking for program ar : ok /usr/bin/ar
looking for program ranlib : ok /usr/bin/ranlib
checking if gcc can make objects : ok
checking if gcc can make libraries : ok
checking if gcc can make exes : ok
checking if gcc can link lib to exe : ok
* gcc : helloworld.c -> build/helloworld.o
* gcc : build/helloworld.o -> build/helloworld
* running build/helloworld
hello world!
Broken down, Fbuild did:
- automatically determined that I'm on a Apple machine
- configured gcc for our c builder
- tested that the c builder worked
- compiled our code
- and finally, ran the program
Now lets go through the fbuildroot.py
:
...
def main(ctx):
...
Fbuild is written as a Python library with the fbuild
script as a simple
driver in order start up and stop the build. The default entry point is a
function called build
. If you run fbuild
without any arguments or with
fbuild build
it will call this function. You can also create your own entry
points with the fbuild.target
module, which is described further on.
import fbuild.builders.c
...
builder = fbuild.builders.c.guess_static(ctx)
...
Next we have to make a c builder. Since pretty much each platform has it's own
C compiler, Fbuild provides a mechanism to guess the platform's preferred one.
fbuild.builders.c.guess_static
creates a C builder that is capable of
creating static libraries. You can also use fbuild.builders.c.guess_shared
if you want to create dynamically loadable libraries. In this case though,
we're just creating an executable so it doesn't matter which function we use.
exe = builder.build_exe('helloworld', ['helloworld.c'])
Now that we've got a builder, we can compile our binary. The first argument is the name of the executable, and the second is a list of input sources. There's a large amount of other options available. The most important are:
argument | description |
---|---|
includes | a list of directories to add to the include search path |
libs | a list of libraries to link in |
libpaths | a list of directories to add to the library search path |
external_libs | a list of libraries Fbuild should not track modification |
macros | a list of string macros |
debug | a boolean to enable debug builds |
optimize | a boolean to enable optimized builds |
cflags | a list of arbitrary arguments to pass to the compiler |
lflags | a list of arbitrary arguments to pass to the linker |
These arguments can also be passed to fbuild.builders.c.guess_static
if you
want them to apply to everything built by the builder.
ctx.logger.log(' * running ' + exe)
ctx.execute([exe])
Finally, we'll execute the command we just compiled. We use the
ctx.logger.log
so that the message will be logged into the Fbuild log file,
normally found in build/fbuild.log
. To run the command we use ctx.execute
,
which is a wrapper around the system call execv
, and also writes the
command's output to the log file.
In comparison, this is an equivalent Makefile
, written to use gcc:
exe = helloworld
srcs = helloworld.c
objs = $(srcs:%.c=%.o)
all: helloworld
./helloworld
helloworld: $(objs)
gcc -o $@ $<
%.o: %.c
gcc -c -o $@ $<
As you can see, Fbuild's driver script is about half the size and does so much more.
To run the unit tests:
$ cd tests
$ python3 run_tests.py
If you run into any problems, don't hesitate to ask a question on the fbuild mailing list. Or, you can usually find me in the #felix channel on irc.freenode.net.