6. Build
Overview
Fantom comes bundled with its own standard build engine to promote consistency and make sharing code easy. Build scripts are normal Fantom scripts which subclass from 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 Fantom 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 predefinedBuildScript
classes to handle common scripts - one you will use all the time isBuildPod
used to build Fantom 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 Fantom code.
Example
Let's create a build script called "buildtest.fan":
using build class Build : BuildScript { @Target { help = "Compile everything" } Void compile() { log.info("Compile away!") } @Target { help = "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. Set the help field to a short summary. 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 -dumpEnv Debug dump of script env targets: 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. 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]!
Note: see Setup to configure your environment to run scripts using just the script filename.
BuildScript
The BuildScript
is the base class for all build scripts. It provides many useful slots you will find handy:
log
: standardized loggingscriptFile
: the file of the script itselfscriptDir
: the directory containing the scriptdevHomeDir
: home directory of your development Fantom environment
Lifecycle
A build script lifecycle is composed of these steps:
- Base class constructor is run and will immediately setup the environment variables such as
log
andscriptFile
. - Your subclass constructor is run, which is where you can configure your own scripts fields.
- The
targets
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. - Command line parsed to find all the specified targets, if no targets specified then we use the first target from the
targets
method.. - Each target method is invoked in the order specified
- 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 Fantom pod. The BuildPod
script defines a bunch of fields to be filled in. Plus it predefines several targets ready to use:
compile
: recompiles Fantom code into a pod file along with all associated natives (JavaScript, Java, .NET)clean
: deletes all the intermediate and derived target filestest
: runs all tests declared by the podfull
: clean, compile, test
By convention pod source directories are organized as follows:
foo/ build.fan fan/ ClassAlpha.fan ClassBeta.fan java/ ClassAlphaPeer.java js/ ClassAlphaPeer.js dotnet/ ClassAlphaPeer.cs test/ TestClassAlpha.fan TestClassBeta.fan res/ icon.png resource-file.txt
If you don't have tests or native code, then those directories aren't included. The build script for our directory structure above would look like:
// build.fan using build class Build : BuildPod { new make() { podName = "foo" summary = "Description of foo pod. Foo is good." depends = ["sys 1.0+", "bar 1.1+"] srcDirs = [`fan/`, `test/`] javaDirs = [`java/`] jsDirs = [`js/`] dotnetDirs = [`dotnet/`] resDirs = [`res/`] } }
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
, dotnetDirs
, and jsDirs
settings. If you don't have resource files you can omit resDirs
. See HelloWorld for a simpler example.
Typically the pod's version is set by the build script's version
field. By default this is set to the config property buildVersion
configured in "etc/build/config.props".
Init Tool
Fantom version 1.0.72 and after includes an "init" tool to simplify creating new projects:
$ fan build init hello Created new env 'hello/' Created new pod 'src/hello/' INIT SUCCESS!
Which generates the following directory:
hello ├── etc ├── fan.props ├── lib └── src ├── build.fan └── hello ├── build.fan ├── fan └── test
A few things to note about this structure:
- If the "hello" directory does not exist, the init tool assumes this is a new project and generates a "fan.props" file. This file indicates to the build and runtime tools this directory is a PathEnv. Now when you build pods they will be written to the local "lib/" directory and not pollute your global Fantom "lib" folder.
- By convention Fantom projects are organized as
<project-name>/src/<pod-name>
. So the "hello" pod source will be contained under its own subdirectory. - The root
src/build.fan
is your top-level build script. This script subclassesBuildGroup
. Whenever you add a new pod to your project, be sure to add it here, so you can build everything in on step.
If you need additional pods for your project, just rerun the init tool under your project directory:
~/hello$ fan build init next Created new pod 'src/next/' Remember to add 'next/build.fan' to 'src/build.fan'! INIT SUCCESS!
Your project directory now looks like:
hello ├── etc ├── fan.props ├── lib └── src ├── build.fan ├── hello │ ├── build.fan │ ├── fan │ └── test └── next ├── build.fan ├── fan └── test
Note that the init tool supports relative paths when generating new pod source trees:
$ fan build init backend/webserver Created new pod 'src/backend/webserver/' Remember to add 'backend/webserver/build.fan' to 'src/build.fan'! INIT SUCCESS!