Rash thoughts about .NET, C#, F# and Dynamics NAV.


"Every solution will only lead to new problems."

Wednesday, 1. April 2009


Getting started with “FAKE – F# Make” – Get rid of the noise in your build scripts.

Filed under: C#,English posts,F#,FAKE - F# Make,Informatik,NaturalSpec,Tools — Steffen Forkmann at 21:02 Uhr

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:

Hello World from FAKE

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:

Clean is a dependency 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.

App-Build output 

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.

Test output

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: , , , , ,

Verwandte Artikel

3 Comments »

  1. [...] 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

  2. [...] 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

  3. [...] 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

RSS feed for comments on this post. | TrackBack URI

Leave a comment

XHTML ( You can use these tags): <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> .