Getting started with FAKE - F# Make

Note: This documentation is for FAKE.exe before version 5 (or the non-netcore version). The new documentation can be found here

In this tutorial you will learn how to set up a complete build infrastructure with "FAKE - F# Make". This includes:

  • how to install the latest FAKE version
  • how to automatically compile your C# or F# projects
  • how to automatically resolve nuget dependencies
  • how to automatically run NUnit tests on your projects
  • how to zip the output to a deployment folder

Install the F# compiler

"FAKE - F# Make" is completely written in F# and all build scripts will also be written in F#, but this doesn't imply that you have to learn programming in F#. In fact the "FAKE - F# Make" syntax is hopefully very easy to learn.

Download Calculator Sample

Now download the latest FAKE-Calculator.zip from the FAKE project site. This sample includes 3 tiny projects and has basically the following structure:

  • .paket
    • paket.exe
  • src/app
    • Calculator (command line)
    • CalculatorLib (class library)
  • src/test
    • Test.CalculatorLib
  • tools
    • NUnit
    • FxCop
  • build.bat
  • build.fsx
  • completeBuild.bat
  • completeBuild.fsx
  • Calculator.sln

Getting "FAKE - F# Make" started

In the root of the project you will find a build.bat file:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
@echo off
cls


.paket\paket.exe restore
if errorlevel 1 (
  exit /b %errorlevel%
)

"packages\FAKE\tools\Fake.exe" build.fsx
pause

If you run this batch file from the command line then the latest FAKE version will be downloaded from nuget.org and your first FAKE script (build.fsx) will be executed. If everything works fine you will get the following output:

alt text

Specifying dependencies

Open the paket.dependencies file in your project's root and specify a dependency in it. Currently it looks like the following: The file might look like this:

1: 
2: 
3: 
source https://nuget.org/api/v2

nuget FAKE

You can now run Paket from the command line:

1: 
$ .paket/paket.exe install

This will create the paket.lock file in your project's root. The file might look like this:

1: 
2: 
3: 
4: 
NUGET
    remote: https://nuget.org/api/v2
    specs:
        FAKE (4.7.2)

The build script

Now open the build.fsx in Visual Studio or any text editor. It should look like this:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
// include Fake lib
#r @"packages/FAKE/tools/FakeLib.dll"
open Fake

// Default target
Target "Default" (fun _ ->
	trace "Hello World from FAKE"
)

// start build
RunTargetOrDefault "Default"

As you can see the code is really simple. The first line includes the FAKE library and is vital for all FAKE build scripts.

After this header the Default target is defined. A target definition contains 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").

The last line runs the "Default" target - which means it executes the defined action of the target.

Cleaning the last build output

A typical first step in most build scenarios is to clean the output of the last build. We can achieve this by modifying the build.fsx to the following:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
// include Fake lib
#r "packages/FAKE/tools/FakeLib.dll"
open Fake

// Properties
let buildDir = "./build/"

// Targets
Target "Clean" (fun _ ->
	CleanDir buildDir
)

Target "Default" (fun _ ->
	trace "Hello World from FAKE"
)

// Dependencies
"Clean"
  ==> "Default"

// start build
RunTargetOrDefault "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 use 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 has a dependency on the Clean target. In other words Clean is a prerequisite of Default and will be run before the execution of Default:

alt text

Compiling the application

In the next step we want to compile our C# libraries, which means we want to compile all csproj-files under /src/app with MSBuild:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
// include Fake lib
#r "packages/FAKE/tools/FakeLib.dll"
open Fake

// Properties
let buildDir = "./build/"

// Targets
Target "Clean" (fun _ ->
	CleanDir buildDir
)

Target "BuildApp" (fun _ ->
	!! "src/app/**/*.csproj"
	  |> MSBuildRelease buildDir "Build"
	  |> Log "AppBuild-Output: "
)

Target "Default" (fun _ ->
	trace "Hello World from FAKE"
)

// Dependencies
"Clean"
  ==> "BuildApp"
  ==> "Default"

// start build
RunTargetOrDefault "Default"

We defined a new build target named "BuildApp" which compiles all csproj-files with the MSBuild task and the build output will be copied to buildDir.

In order to find the right project files FAKE 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 extended again. Now Default is dependent on BuildApp and BuildApp needs Clean as a prerequisite.

This means the execution order is: Clean ==> BuildApp ==> Default.

alt text

Compiling test projects

Now our main application will be built automatically and it's time to build the test project. We use the same concepts as before:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
// include Fake lib
#r "packages/FAKE/tools/FakeLib.dll"
open Fake

// Properties
let buildDir = "./build/"
let testDir  = "./test/"

// Targets
Target "Clean" (fun _ ->
	CleanDirs [buildDir; testDir]
)

Target "BuildApp" (fun _ ->
   !! "src/app/**/*.csproj"
	 |> MSBuildRelease buildDir "Build"
	 |> Log "AppBuild-Output: "
)

Target "BuildTest" (fun _ ->
	!! "src/test/**/*.csproj"
	  |> MSBuildDebug testDir "Build"
	  |> Log "TestBuild-Output: "
)

Target "Default" (fun _ ->
	trace "Hello World from FAKE"
)

// Dependencies
"Clean"
  ==> "BuildApp"
  ==> "BuildTest"
  ==> "Default"

// start build
RunTargetOrDefault "Default"

This time we defined a new target "BuildTest" which compiles all C# projects below src/test/ in Debug mode and we put the target into our build order.

If we run build.bat again we get an error like this:

alt text

The problem is that we didn't download the NUnit package from nuget. So let's fix this in the build script:

1: 
2: 
3: 
4: 
5: 
6: 
// include Fake lib
#r "packages/FAKE/tools/FakeLib.dll"
open Fake

RestorePackages()
// ...

With this simple command FAKE will use nuget.exe to install all the package dependencies.

You may experience this tutorial not quite working with the newest package versions. In this case you can edit the paket.dependencies file to something like this:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
source https://nuget.org/api/v2

nuget FAKE

http https://dist.nuget.org/win-x86-commandline/latest/nuget.exe NuGet/nuget.exe

nuget NUnit ~> 2.5.10

Again run Paket from the command line:

1: 
$ .paket/paket.exe install

This will fetch nuget.exe from nuget.org and also download an early version of NUnit that contains the NUnit runner. The edit to paket.dependencies does not replace the RestorePackages() step. The NUnit.Test.CalculatorLib test project references the NUnit version 2.6.2 library, so we need that version too.

Running the tests with NUnit

Now all our projects will be compiled and we can use FAKE's NUnit task in order to let NUnit test our assembly:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
// include Fake lib
#r "packages/FAKE/tools/FakeLib.dll"
open Fake

RestorePackages()

// Properties
let buildDir = "./build/"
let testDir  = "./test/"

// Targets
Target "Clean" (fun _ ->
	CleanDirs [buildDir; testDir]
)

Target "BuildApp" (fun _ ->
   !! "src/app/**/*.csproj"
	 |> MSBuildRelease buildDir "Build"
	 |> Log "AppBuild-Output: "
)

Target "BuildTest" (fun _ ->
	!! "src/test/**/*.csproj"
	  |> MSBuildDebug testDir "Build"
	  |> Log "TestBuild-Output: "
)

Target "Test" (fun _ ->
	!! (testDir + "/NUnit.Test.*.dll")
	  |> NUnit (fun p ->
		  {p with
			 DisableShadowCopy = true;
			 OutputFile = testDir + "TestResults.xml" })
)

Target "Default" (fun _ ->
	trace "Hello World from FAKE"
)

// Dependencies
"Clean"
  ==> "BuildApp"
  ==> "BuildTest"
  ==> "Test"
  ==> "Default"

// start build
RunTargetOrDefault "Default"

Our new Test target scans the test directory for test assemblies and runs them with the NUnit runner. FAKE automatically tries to locate the runner in one of your subfolders. See the NUnit task documentation if you need to specify the tool path explicitly.

The mysterious part (fun p -> ...) simply overrides the default parameters of the NUnit task and allows to specify concrete parameters.

alt text

Alternatively you could also run the tests in parallel using the NUnitParallel task:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
Target "Test" (fun _ ->
	!! (testDir + "/NUnit.Test.*.dll")
	  |> NUnitParallel (fun p ->
		  {p with
			 DisableShadowCopy = true;
			 OutputFile = testDir + "TestResults.xml" })
)

Deploying a zip file

Now we want to deploy a *.zip file containing our application:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
// include Fake lib
#r "tools/FAKE/tools/FakeLib.dll"
open Fake

RestorePackages()

// Properties
let buildDir = "./build/"
let testDir  = "./test/"
let deployDir = "./deploy/"

// version info
let version = "0.2"  // or retrieve from CI server

// Targets
Target "Clean" (fun _ ->
	CleanDirs [buildDir; testDir; deployDir]
)

Target "BuildApp" (fun _ ->
   !! "src/app/**/*.csproj"
	 |> MSBuildRelease buildDir "Build"
	 |> Log "AppBuild-Output: "
)

Target "BuildTest" (fun _ ->
	!! "src/test/**/*.csproj"
	  |> MSBuildDebug testDir "Build"
	  |> Log "TestBuild-Output: "
)

Target "Test" (fun _ ->
	!! (testDir + "/NUnit.Test.*.dll")
	  |> NUnit (fun p ->
		  {p with
			 DisableShadowCopy = true;
			 OutputFile = testDir + "TestResults.xml" })
)

Target "Zip" (fun _ ->
	!! (buildDir + "/**/*.*")
		-- "*.zip"
		|> Zip buildDir (deployDir + "Calculator." + version + ".zip")
)

Target "Default" (fun _ ->
	trace "Hello World from FAKE"
)

// Dependencies
"Clean"
  ==> "BuildApp"
  ==> "BuildTest"
  ==> "Test"
  ==> "Zip"
  ==> "Default"

// start build
RunTargetOrDefault "Default"

The new Deploy target scans the build directory for all files. The result will be zipped to /deploy/Calculator.zip via the Zip task.

What's next?

If you want you could now add a FxCop target to your build in order to check specific naming rules or framework guidelines.