This is about fbuild not felix .. it contains a lot of my opinion on build 
systems.

The first thing to note is: make sucks. Totally. It is completely unprincipled.

What make does is: there is a dependency tree, and attributes on the nodes
of the tree, primarily the leaves, consist of build rules. The idea is simple 
enough:

if A depends on B, A and B must be files and the date stamps are compared to see
if A is newer than B. If not, the build rule is invoked.

Although the dependency tree is declarative, the build rules are not: they're 
executable
and there only coupling between the target and the build rule is "gratuitous", 
that is,
an accident of the programmer using the right command.

The structure cannot cope with non-file targets, and it can't cope with build 
commands
that produce multiple outputs. It also can't cope with dynamic changes, nor with
recursion. Furthermore, the trees can't easily be decomposed (there's lots of 
stuff
about how bad recursive make is).

It would seem the idea of "make target" is a good one but it is not. There is 
rarely
any need to make anything much other than the whole system, or at least a 
significant
part of it, eg: make config, make stuff, make tests, make docs: these things 
make
sense, but they're not actually targets.

There's a hidden, KILLER bug in the make concept as well: the idea you can
actually express all the dependencies. In the 1970's this may have been the
case, since really building C programs was the only thing of interest..
for modern systems commands are generative: for example a doxygen pass
on a suite of code generates a lot of files.

The *correct* way to construct a build system is to give up the idea that
it is a functional, declarative thing. It's not. Build systems are executable,
imperative code. Imperative implies sequenced, so there's no need to
mess around stating dependencies, all you need to do is get the build
order correct. Once. And fix it up if things change. [Within this concept
you can still use dependency checking for subparts, but that checking
will be *specialised* for example, using ocamldep to build ocaml code
in the right order .. this is unrelated to the overall build system 
architecture]

With an imperative program, it is easy to have a few switches and do:

// raw options
if "config" in options: do_config=true;
if "test" in options: do_test=true;
...
// calculate dependencies  between phases
if do_test: do_config=true;
..
// do the work:

if do_config: do_the_config();
if do_build: do_the_build(); 
...

These switches represent gross concepts like building documentation,
or running tests: they're not micro-targets, they're commands.

So given all this, it is easy to see how to write build scripts: they're
programs. It's that simple! And the programming language is a full scale
one like Python or whatever, not crud like "make" or "autoconf" or other
rubbish mini-languages lacking in proper structure and capability.

but there's a problem: rebuilding everything when you make a change is SLOW.

Sure, it should usually get the right results without stating dependencies .. 
but few
programmers can drink that much coffee and live ... 

Erick has come up with a brilliant solution to this problem.

Actually, it's a very well known old fashioned solution everyone uses
everywhere .. 

Caching. By writing functions with input and outputs made explicit,
(including files) it is possible to cache the result of applying a function.
So instead of invoking the C compiler, fbuild can just use the cached
value if it exists and is up-to date. The "up to date-ness" is calculated
based on a a small set of inputs and outputs for that function.

Failing to cache something is not a sin, it has no impact on semantics,
it just isn't optimal. The build will be slower than required.

And if you change the build script itself? Fbuild can cache that too.

There is a subtle core difference from make here. Make takes a dependency
tree and does a recursive descent until it finds a leaf (source file) and a 
target,
and checks dates, running the build commands if required to update the target:
using the date stamps is a crude way to do caching.

But the system is driven top down. This is the BIG MISTAKE.

Build systems should work the other way. They should work UP from modified
source files, building everything than is will be changed.

You may say: but it's the same! Recursive descent IS working up!

Yes, it is,  provided the dependencies are complete and correct,
it is working up because that is the only way. The point is you don't need
the dependencies. Instead you just note all the source files that change,
and the re-execute the steps that use them. That changes more files,
so the process continues until nothing changes .. note in passing this
will handle recursion by achieving a fixpoint.

The trick here is to *capture* the dependencies as you're working up:
they're the outputs of the build functions. The point is that if the inputs
do NOT change, you can calculate these outputs fast using a cache.

This means you can just run the whole build process every time,
and it will be fast. 

the main caveat here is similar to make: if you cache something you have
to capture all the significant inputs and outputs.

the difference is: this is a local decision, not a global requirement.
You don't have to cache anything.

It's a very clever solution. Nice work Erick!


--
john skaller
skal...@users.sourceforge.net





------------------------------------------------------------------------------
This SF Dev2Dev email is sponsored by:

WikiLeaks The End of the Free Internet
http://p.sf.net/sfu/therealnews-com
_______________________________________________
Felix-language mailing list
Felix-language@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/felix-language

Reply via email to