Fan

 

Build

Overview

Fan comes bundled with its own standard build engine to promote consistency and make sharing code easy. Build scripts are normal Fan scripts which subclass from build::BuildScript. Characteristics of build scripts:

  • BuildScript base class handles common functions like command line parsing, environment setup, and logging
  • Subclasses define targets which are things the build script can do such as "compile", "clean", or "test"
  • Targets are implemented as normal Fan methods which can execute any procedural code needed using the APIs installed
  • The build pod provides libraries of tasks which are designed to provide common chunks of functionality which can be composed to define targets
  • The build pod provides a library of predefined BuildScript classes to handle common scripts - one you will use all the time is BuildPod used to build Fan pods.

The build toolkit is designed to provide a consistent way to organize build scripts - it doesn't provide a comprehensive library of everything you might need to perform a build. But by predefining common scripts such as BuildPod it reduces most scripts down to purely declarative information. When you do need the power of custom code, you can implement it cleanly as normal Fan code.

Example

Let's create a build script called "buildtest.fan":

using build

class Build : BuildScript
{
  @target="Compile everything"
  Void compile() { log.info("Compile away!") }

  @target="Clean it all up"
  Void clean() { log.info("Clean away!") }
}

The script above defines two targets: compile and clean. The targets are annotated with the "target" facet - the value is a string description. To print the usage of this script use "-?":

C:\dev\fan\src>buildtest -?
usage:
  build [options] <target>*
options:
  -? -help       print usage summary
  -v             verbose debug logging
targets:
  dumpenv        Dump env details to help build debugging
  compile*       Compile everything
  clean          Clean it all up

Note that the "compile" target is marked with an asterisk indicating it is the default target because it is the first one declared. You can also explicitly override defaultTarget. If we invoke the script with no arguments the "compile" target is executed:

C:\dev\fan\src>buildtest
Compile away!
BUILD SUCCESS [3ms]!

Or we can pass one or more targets as arguments:

C:\dev\fan\src>buildtest clean
Clean away!
BUILD SUCCESS [2ms]!

C:\dev\fan\src>buildtest clean compile
Clean away!
Compile away!
BUILD SUCCESS [2ms]!

BuildScript

The BuildScript is the base class for all build scripts. It provides many useful slots you will find handy:

  • log: standardized logging
  • scriptFile: the file of the script itself
  • scriptDir: the directory containing the script
  • @buildVersion: a version configured in "sys.props" often used to version all your pods consistently when you perform full builds
  • binDir: bin directory of your development environment for the current operating system

Lifecycle

A build script lifecycle is composed of these steps:

  1. Constructor is run and will immediately setup the environment variables such as log and scriptFile.
  2. The setup callback is invoked to give the script a chance to initialize itself
  3. The makeTargets callback is invoked to get the list of targets - most often you will let the default implementation build this list by searching for methods which implement the "target" facet. Once initialized the list of targets is available via the targets field.
  4. Command line parsed to find all the specified targets, if no targets specified then we use defaultTarget.
  5. Each target method is invoked in the order specified
  6. If an exception is raised, the script fails and returns -1, otherwise 0 is returned.

Problems during the script should be reported via the BuildScript.log. If an error is encountered which should terminate the script, then throw a FatalBuildErr via this pattern:

throw fatal("I just can't go on!")

BuildPod

BuildPod is the base class for build scripts which build a Fan pod. The BuildPod script defines a bunch of fields to be filled in. Plus it predefines several targets ready to use:

  • compile: recompiles the Fan code into a pod file
  • javaNative: recompiles the Java native code
  • dotnetNative: recompiles the C# .NET native code
  • clean: deletes all the intermediate and derived target files
  • doc: compile fandoc to HTML
  • test: runs all tests declared by the pod
  • full: clean, compile, natives, docs

By convention pod source directories are organized as follows:

foo/
  build.fan
  fan/
    ClassAlpha.fan
    ClassBeta.fan
  java/
    ClassAlphaPeer.java
  dotnet/
    ClassAlphaPeer.cs
  test/
    TestClassAlpha.fan
    TestClassBeta.fan

If you don't have tests or native code, then those directories aren't included. The build file for our directory structure above would look like:

using build

class Build : BuildPod
{
  override Void setup()
  {
    podName     = "foo"
    version     = globalVersion
    description = "Foo is good"
    depends     = ["sys 1.0+", "bar 1.1-1.3"]
    srcDirs     = [`fan/`, `test/`]
    javaDirs    = [`java/`]
    dotnetDirs  = [`dotnet/`]
  }
}

Note that the code above is mostly declarative, designed to concisely express only the information needed to identify and compile our pod. But remember that this a script which can run any Fan code needed to setup these values.

We typically set version to globalVersion so that we update the build number in one place - although you can initialize that field with a Version literal or any other expression.

Also note that all the directories are specified as Uris relative to the script directory. If you don't have native code, you can omit the javaDirs and dotnetDirs initialization.