In this tutorial I will describe how you can set up a complete build infrastructure with “FAKE – F# Make”. You will learn:
- How to automatically compile your C# or F# projects
- How to automatically run NUnit UnitTests on your projects
- How to zip the output to a deployment folder
Install F#
“FAKE – F# Make” is completely written in F# and all build scripts will also be written in F#, but this doesn’t imply you have to learn programming in F#. In fact the “FAKE – F# Make” syntax is very easy to learn. But if you need to you can use the complete power of F# and the .NET Framework.
But in order to get things working we need to install the F# environment. You can download the F# April 2010 CTP from the Microsoft F# Developer Center or install Visual Studio 2010.
Download Calculator Sample
Now download the latest CalculatorSample-*.zip from the FAKE Download site. This sample includes 3 tiny projects and basically the following structure:
- src\app
- Calculator (Command line)
- CalculatorLib (Class library)
- src\test
- Test.CalculatorLib (NUnit test library)
- tools
- FAKE Assemblies
- NUnit
- build.bat
- build.fsx
- completeBuild.bat
- completeBuild.fsx
- Calculator.sln
Getting “FAKE – F# Make” started
Open the file tools\FAKE\FakeLib.dll.config and check if the default configuration matches your system environment:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="MSBuildPath"
value="c:\Windows\Microsoft.NET\Framework\v4.0.30319\" />
<add key="FSIPath"
value="[ProgramFiles]\FSharp-2.0.0.0\bin\" />
</appSettings>
</configuration>
If you run the build.bat from the command line then your first FAKE script (build.fsx) will be executed. If everything works fine you will get the following output:

Now open the build.fsx with Visual Studio. It should look like this:
// include Fake libs
#I @"tools\FAKE"
#r "FakeLib.dll"
open Fake
// Default target
Target? Default <-
fun _ –> trace "Hello World from FAKE"
// start build
Run? Default
As you can see the code is really simple. The very first lines include the FAKE libraries and are vital in all FAKE build scripts.
After this header the Default target is defined. A target definition contains of two important parts. The first is the name of the target (here “Default”) and the second is an action (here a simple trace of “Hello world”). Action can be defined as lambda expressions or methods.
The last line runs the “Default” target – which means it executes the defined action.
Cleaning the last build output
A typical first step in nearly every build scenario is to clean the output of the last build. We can achieve this by modifying the build.fsx to the following:
// include Fake libs
#I @"tools\FAKE"
#r "FakeLib.dll"
open Fake
// Properties
let buildDir = @".\build\"
// Targets
Target? Clean <-
fun _ -> CleanDir buildDir
Target? Default <-
fun _ -> trace "Hello World from FAKE"
// Dependencies
For? Default <- Dependency? Clean
// start build
Run? Default
We introduced some new concepts in this snippet. At first we defined a global property called “buildDir” with the relative path of a temporary build folder.
In the Clean target we want the CleanDir task to clean up this build directory. This simply deletes all files in the folder or creates the directory if necessary.
In the dependencies section we say that the Default target is dependent of the Clean target. In other words Clean is a prerequisite of Default and will be run before the execution of Default:
Building the application
In the next step we want to compile our application libraries, which means we want to compile all projects under /src/app with MSBuild.
// include Fake libs
#I @"tools\FAKE"
#r "FakeLib.dll"
open Fake
// Properties
let buildDir = @".\build\"
let appReferences = Scan (!+ @"src\app\**\*.csproj")
// Targets
Target? Clean <-
fun _ -> CleanDir buildDir
Target? BuildApp <-
fun _ ->
let target = "Build"
// compile all projects below src/app/ in Release mode
let apps = MSBuildRelease buildDir target appReferences
// log the output files
Log "AppBuild-Output: " apps
Target? Default <-
fun _ -> trace "Hello World from FAKE"
// Dependencies
For? BuildApp <- Dependency? Clean
For? Default <- Dependency? BuildApp
// start build
Run? Default
Again, we defined a new build target named “BuildApp” which compiles all project files given in the property appReferences with the MSBuild task. The output will be copied to buildDir.
In order to find the right project files “FAKE – F# Make” scans the folder src/app/ and all subfolders with the given pattern. Therefore a similar FileSet definition like in NAnt or MSBuild (see project page for details) is used.
In addition the target dependencies are modified. Now Default is dependent of BuildApp and BuildApp needs Clean as a prerequisite.
This means the execution order is: Clean ==> BuildApp ==> Default.
Building Test projects
Now our main application will be built automatically and it’s time to build the test application. We use pretty the same concepts as before:
// include Fake libs
#I @"tools\FAKE"
#r "FakeLib.dll"
open Fake
// Properties
let buildDir = @".\build\"
let testDir = @".\test\"
let appReferences = !+ @"src\app\**\*.csproj" |> Scan
let testReferences = !+ @"src\test\**\*.csproj" |> Scan
// Targets
Target? Clean <-
fun _ –>
CleanDir buildDir
CleanDir testDir
Target? BuildApp <-
fun _ ->
let target = "Build"
// compile all projects below src/app/ in Release mode
let apps = MSBuildRelease buildDir target appReferences
// log the output files
Log "AppBuild-Output: " apps
Target? BuildTest <-
fun _ ->
let testApps = MSBuildDebug testDir "Build" testReferences
Log "TestBuild-Output: " testApps
Target? Default <-
fun _ -> trace "Hello World from FAKE"
// Dependencies
For? BuildApp <- Dependency? Clean
For? BuildTest <- Dependency? Clean
For? Default <-
Dependency? BuildApp
|> And? BuildTest
// start build
Run? Default
This time we define a new target “BuildTest”, which compiles all C# projects below src/test/ in Debug mode and we mark the target as a dependency of Default.
As you can see the Clean target is now a dependency of BuildApp and BuildTest, since we also have to clean the test dir. Although the Clean target is referenced twice the “FAKE – F# Make” runtime ensures that it will only be executed once.
The execution order is now Clean ==> BuildApp ==> BuildTest ==> Default.
Testing the test assemblies with NUnit
Now all our projects will be compiled and we can use the NUnit task in order to test our test assemblies:
// include Fake libs
#I @"tools\FAKE"
#r "FakeLib.dll"
open Fake
// Properties
let buildDir = @".\build\"
let testDir = @".\test\"
let appReferences = !+ @"src\app\**\*.csproj" |> Scan
let testReferences = !+ @"src\test\**\*.csproj" |> Scan
let testAssemblies = !+ (testDir + @"\NUnit.Test.*.dll") |> Scan
let nunitPath = @".\Tools\NUnit\bin"
let nunitOutput = testDir + @"TestResults.xml"
// Targets
Target? Clean <-
fun _ ->
CleanDir buildDir
CleanDir testDir
Target? BuildApp <-
fun _ ->
let target = "Build"
// compile all projects below src/app/ in Release mode
let apps = MSBuildRelease buildDir target appReferences
// log the output files
Log "AppBuild-Output: " apps
Target? BuildTest <-
fun _ ->
let testApps = MSBuildDebug testDir "Build" testReferences
Log "TestBuild-Output: " testApps
Target? Test <-
fun _ ->
NUnit (fun p ->
{p with
ToolPath = nunitPath;
DisableShadowCopy = true;
OutputFile = nunitOutput})
testAssemblies
Target? Default <-
fun _ -> trace "Hello World from FAKE"
// Dependencies
For? BuildApp <- Dependency? Clean
For? BuildTest <- Dependency? Clean
For? Test <-
Dependency? BuildApp
|> And? BuildTest
For? Default <- Dependency? Test
// start build
Run? Default
Our new target “Test” scans the test directory for test assemblies and runs them with NUnit. The mysterious part (fun p –> …) simply overrides the default parameters of the NUnit task and allows to specify concrete parameters.
The execution order is now Clean ==> BuildApp ==> BuildTest ==> Test ==> Default.
Deploying a zip file
Now we want to deploy a *.zip file containing our application:
// include Fake libs
#I @"tools\FAKE"
#r "FakeLib.dll"
open Fake
// Properties
let buildDir = @".\build\"
let testDir = @".\test\"
let deployDir = @".\deploy\"
let appReferences = !+ @"src\app\**\*.csproj" |> Scan
let testReferences = !+ @"src\test\**\*.csproj" |> Scan
let testAssemblies = !+ (testDir + @"\NUnit.Test.*.dll") |> Scan
let nunitPath = @".\Tools\NUnit\bin"
let nunitOutput = testDir + @"TestResults.xml"
let filesToZip = !+ (buildDir + "\**\*.*") — "*.zip" |> Scan
let zipFileName = deployDir + "Calculator.zip"
// Targets
Target? Clean <-
fun _ -> CleanDirs [buildDir; testDir; deployDir]
Target? BuildApp <-
fun _ ->
let target = "Build"
// compile all projects below src/app/ in Release mode
let apps = MSBuildRelease buildDir target appReferences
// log the output files
Log "AppBuild-Output: " apps
Target? BuildTest <-
fun _ ->
let testApps = MSBuildDebug testDir "Build" testReferences
Log "TestBuild-Output: " testApps
Target? Test <-
fun _ ->
NUnit (fun p ->
{p with
ToolPath = nunitPath;
DisableShadowCopy = true;
OutputFile = nunitOutput})
testAssemblies
Target? Deploy <-
fun _ -> Zip buildDir zipFileName filesToZip
Target? Default <- DoNothing
// Dependencies
For? BuildApp <- Dependency? Clean
For? BuildTest <- Dependency? Clean
For? Test <-
Dependency? BuildApp
|> And? BuildTest
For? Deploy <- Dependency? Test
For? Default <- Dependency? Deploy
// start build
Run? Default
The new target “Deploy” scans the build directory for all files but zip-files. The result will be zipped to \deploy\Calculator.zip via the Zip task.
The execution order is now Clean ==> BuildApp ==> BuildTest ==> Test ==> Deploy ==> Default whereas Default now is only used as a entry point.
What’s next?
Now your build file should look like completeBuild.fsx and you are ready to write your own “FAKE – F# Make” build scripts. If you have any questions or suggestions feel free to comment on this post.
In the next article I will show how we can add FxCop to our build in order to check specific naming rules.
Tags: F#, F-sharp Make, Fake, MSBuild, NAnt, Rake



[...] setup a build file just to compile the code based on Steffen Forkman’s blog post and then tried to compile the project, leading to the following error: error FS0191: A function [...]
Pingback by F#: Entry point of an application at Mark Needham — Friday, 1. May 2009 um 17:57 Uhr
[...] I just released a new version of my Open Source Build Automation Framework “FAKE – F# Make”. You can read more about FAKE on the project website or in the Getting started with "FAKE – F# Make"-article. [...]
Pingback by “FAKE – F# Make” Version 0.10 released » Rash thoughts about .NET, C#, F# and Dynamics NAV. — Tuesday, 6. October 2009 um 11:59 Uhr
[...] If you want to know how this build script works and how you could create one for your own projects please read the “Getting started with FAKE”-tutorial. [...]
Pingback by Integrating a “FAKE – F# Make” build script into CruiseControl.NET » Rash thoughts about .NET, C#, F# and Dynamics NAV. — Wednesday, 14. October 2009 um 10:43 Uhr