Using FAKE to Build .NET Projects
What is FAKE?
FAKE is one of the many clones of the Make build system. FAKE uses the F# language to create build tasks. There are many other build systems named after MAKE. Probably the first and most infamous one is Rake, which was created by the Ruby community. In the .NET ecosystem there are three that I know of, FAKE, Cake, and PSake (pronounced S-ah-key). That last one uses PowerShell to create build tasks.
The first thing we will need is a file that will run our scripts. In the root of you project directory create a file called "build.bat". This file will be used run the FAKE build script. But before we can do anything with FAKE we’ll need to have it installed in the project.
Installing FAKE with NuGet Command Line
In the end our build.bat file will call FAKE.exe to run the build script. Right now we need a way to install FAKE into our project. FAKE like a lot of other .NET projects is hosted in a nuget package available from nuget.org. NuGet has a commandline executable that we can use to download the FAKE package.
Add a ".nuget" directory to the root of your project. Download nuget.exe here, and put it into the ".nuget" directory. Now that you have "nuget.exe", you can install FAKE using your build.bat. Open the "build.bat" file and add the following lines.
@echo off cls .nuget\nuget.exe install FAKE -ExcludeVersion -OutputDirectory .FAKE
If you run "build.bat" now FAKE will be installed into the .FAKE directory.
Creating the FAKE Script
Next lets add the FAKE script. In the project root create a file named "build.fsx". In this file you can create tasks to do things like build project, run unit tests, version assemblies, create nuget packages, and anything else you want to do. Let’s create a build task now.
Open the "build.fsx" file in your favorite editor. Next add the following lines to the file.
#r ".FAKE/FAKE/tools/FAKELib.dll" open FAKE let buildDir = "./build" Target "Build" (fun _ -> !! "source/**/*.*proj" |> MSBuildRelease "build" buildDir |> Log "Building: " ) RunTargetOrDefault "Build"
Let’s go through each of these sections so we can understand what each of them is doing.
Referencing a .NET Library
In the first line the "#r" is used to reference a .NET library file. In this case we need to reference FAKELib which contains all the helper functions that we’ll use in the build tasks.
Importing a Namespace
The next line, starting with "open" imports the functions in the FAKE namespace so the functions maybe called in each task.
Setting Build Properties
The "let" keyword is used to create values in F# in this context we use it to set up build properties that can be referenced in the rest of the script.
let buildDir = "./build"
The line that begins with "Target" is the beginning of a task. A task is created in the following format.
Target "TargetName" (fun _ -> trace "this is a task" )
Each task will have a name in quotes followed by an F# function. There are several helper functions in the FAKELib that can be used to execute tasks as part of a build process.
Creating a Task to build a .NET Project
Above you learned how to create a FAKE Task. The following shows you how to use FAKE’s built in MSBuild functions to build a .NET project. The build task will look like this.
Target "Build" (fun _ -> !! "source/**/*.*proj" |> MSBuildRelease "build" buildDir |> Log "Building: " )
For this example I’ll named the FAKE task "Build". This task is going to do a few things. The first line of the function uses a FAKE operator "!!" which will use a globbing expression to get a sequence of file names at the path passed to it.
This will find all .NET project files and pass them to the next function. The |> is the F# pipe operator, this says take the previous argument (the .net project files sequence) and pass it as the last argument of the next function (MSBuildRelease). The MSBuildRelease function takes three arguments.
|> MSBuildRelease "build" buildDir
The first is the MSBuild target that you want executed. In our case we just want MSBuild to build our project files. There for "build" is passed as the target. The second argument to MSBuildRelesase is the output directory. This is where the build output will be put after a build is completed. All .pdbs, dlls, exes, xml files and any other MSbBuild artifacts will be placed in this directory upon build. The final argument to MSBuildRelease is a sequence of files. MSBuild expects solution files or project files. We previously defined the property buildDir as "./build"
let buildDir = "./build"
You might be curiuos how MSBuild processes more than one project file at a time. Well it doesn’t. The MSBuildRelease function will loop through each of these files and run msbuild on each one of the files on at a time.
As for the last line…
|> Log "Building: "
This will pipe the output from MSBuildRelease into the log function as the second argument. This function will write the msbuild output to the console.
The line starting with RunTargetOrDefault specifies which task to run if a target is not specified.
Calling "build.fsx" from "build.bat"
Open build.bat and add a new line that will execute FAKE.exe and pass the FAKE.fsx file to it.
The build.bat file should now look like this.
@echo off cls .nuget\nuget.exe install FAKE -ExcludeVersion -OutputDirectory .FAKE .FAKE\FAKE\tools\FAKE.exe build.fsx
Running a FAKE Build
After you have a build.bat and a build.fsx file set up you can simply execute the build.bat file from a cmd or PowerShell console.
After running the build you should see output like this.
Adding Dependent Tasks
Usually if you are taking control of your build you will want to run more than one task. You might want to run your unit tests, build nuget packages, deploy an application, resize images, run gulp tasks, tag your git repository, or just about anything else. Fortunately you can do pretty much anything you want in a task. Also its likely that some of your tasks will depend on others succeeding. You probably wouldn’t want to have your deploy task run if your unit tests failed. Fortunately FAKE makes this simple.
Dependent Task Example
I’m going to show you a very simple real world example of how you might do this. In this example we want to always clean out our build directory before we perform the build task. If we didn’t do this there might be left over files in our build directory. FAKE has a simple function to help us with this common need. Its called CleanDirs. Let’s add a task named "Clean" to your build.fsx script.
Prior to the "Build" task add the following code.
Target "Clean" (fun _ -> CleanDirs[buildDir] )
At this point the "Clean" task won’t be run. We have made a new task but we haven’t made it a dependency of the "Build" task. To add a dependency we’ll add the following right before calling RunTargetOrDefault.
"Clean" ==> "Build"
This tells FAKE that before you run the "Build" task you must run the "Clean" task. In FAKE the tasks in this section represent a dependency chain and as far as I know there can be only one chain. If you want to add a task that runs your tests for example you would create your task and then add it where you want it in the execution chain. As we said before you would likely want your tests to run after a build so your new dependency chain would look like this.
"Clean" ==> "Build" ==> "RunTests"
If you have a task named "RunTests" if your RunTargetOrDefault still has "Build" as its parameter then "RunTests" won’t be executed. One practice to avoid having to change this when you add a new task is to add a "Default" task which is always the last one in the dependency chain and then always have RunTargetOrDefault execute "Default".
Adding a Default Task
You can create the "Default" task anywhere in your tasks section, but I’d suggest putting it right before the dependency chain just because you likely won’t be looking at the bottom of the file as much as you will other tasks. The simplest default tasks looks like this.
Target "Default" (fun _ -> trace "Default task running." )
Then you will want to modify your dependency chain so that "Default" is the last task.
"Clean" ==> "Build" ==> "Default"
Then modify the RunTargetOrDefault to actually run the "Default" task.
You may have noticed I didn’t supply any .NET projects as a part of this article. If you look at the "build" task it is looking for project files anywhere under the source directory. These could be .csproj, .xproj, .fsproj, .vbproj or whatever. This was intentional so it could be used for almost any .NET project build.
You can find the FAKE website at [http://fake.build}(http://FAKE.build), or on GitHub at https://github.com/fsharp/FAKE. There are several helper functions for the most common tasks that you would probably want to execute in a build script.
Now you should have a basic understanding of how FAKE works. Using Fake you can take complete control of your builds, and run the same build locally as on your build server. In future posts I will go over more tasks that Fake provides, and show you how to execute your build script using some of the most popular build servers.