NAnt was quite a popular build tool used by projects of all sizes targeting .NET Framework on Windows. I have written many complex build systems using NAnt in the past. But XML based DSL was quite clunky to use and maintain. Besides, it was always a daunting task to explain to a new dev on the team. With .NET Core/.NET becoming a cross-platform framework, CAKE and FAKE gained a lot of adoption providing a C# and F# based DSL for build tasks accordingly.
I personally advocate Bullseye in combination with SimpleExec as a build tool for .NET projects due to its sheer simplicity by providing a bare bones API to define the tasks and their dependent tasks in plain C# code. Both these libraries are build by Adam Ralph. It does not enforce any specific model or structure to write your build tasks. When someone runs through the build script, it is fairly straight forward to understand whats going on. We use this build tool setup for Marten for both local dev and CI build tasks and has worked well so far. Also many OSS projects have adopted it.
We will take a simple sample .NET project available in GitHub which has the following setup and then attempt to add a build script for it:
src/WebApp
folder contains a .NET 6 web app with a Razor page to serve the index. The project uses minimal API.src/WebApp/static/css/site.css
contains the css file for the web app which is referenced in the Razor pagepackage.json
at the root folder which has dev dependencies toesbuild
and defines 2 scripts as below:- minify -
esbuild --minify ./src/WebApp/static/css/site.css --outdir=./src/WebApp/wwwroot/css
- watch -
esbuild --watch ./src/WebApp/static/css/site.css --outdir=./src/WebApp/wwwroot/css
- minify -
- ESBuild is used to minify the static assets i.e. css files from
src/WebApp/static/css
and adds it underwwwroot/css
Now let us look at adding a build script using Bullseye and SimpleExec:
- Add a
build
folder at the top level - Add a new console project under the
build
folder as belowdotnet new console
- Add
Bullseye
andSimpleExec
NuGet packages tobuild.csproj
dotnet add package Bullseye dotnet add package SimpleExec
- Open
Program.cs
to add the build targets in your favourite editor and add the following code (uses .NET 6 minimal API). The inline comments should be self explanatory.
using static Bullseye.Targets;
using static SimpleExec.Command;
// Create a build target for npm install to make esbuild available for use
Target("npm:install", () => Run("npm","install"));
// Create build target esbuild:dist target which will minify
// CSS under static/css and add it to wwwroot/css folder
// This build target has a depedency on `npm:install` build target
Target("esbuild:dist", DependsOn("npm:install"), () => Run("npm","run minify"));
// Create build target esbuild:dist target which will allow to
// watch for changes in dev env
// This build target has a depedency on `npm:install` build target
Target("esbuild:watch", DependsOn("npm:install"), () => Run("npm","run watch"));
// Create a build target Clean the web project output from previous build
Target("clean", () => Run("dotnet","clean src/WebApp/WebApp.csproj"));
// Create a build target to compile the web project
// This build target depends on `clean` build target
Target("compile", DependsOn("clean"), () => Run("dotnet","build ./src/WebApp/WebApp.csproj -c Release"));
// Create a build target to watch for changes in dev
Target("watch", () => Run("dotnet", "watch --project ./src/WebApp/WebApp.csproj"));
// Create a default target
// This target depends on `esbuild:dist` and `compile` build targets
Target("default", DependsOn("esbuild:dist", "compile"));
// run the build target requested and exit
await RunTargetsAndExitAsync(args);
- At the top level folder, let us create
build.sh
with executable permission as below to be used in *nix environments to run a the build
#!/usr/bin/env bash
set -euo pipefail
dotnet run --project build/build.csproj -c Release -- "$@"
- Create
build.cmd
with the following content to be used in Windows commandline
@echo Off
dotnet run --project build/build.csproj -- %*
- Create
build.ps1
with the following content to be used from PowerShell prompt
$ErrorActionPreference = "Stop";
dotnet run --project build/build.csproj -- $args
- To run the default build target, we use the below:
./build.sh
You will see an output as below:
...
...
Time Elapsed 00:00:04.88
build: compile: Succeeded (5.66 s)
build: default: Succeeded
build: ──────────────────────────────────────
build: Target Outcome Duration
build: ──────────── ───────── ─────────────
build: npm:install Succeeded 868 ms 10.1%
build: esbuild:dist Succeeded 206 ms 2.4%
build: clean Succeeded 1.82 s 21.3%
build: compile Succeeded 5.66 s 66.1%
build: default Succeeded
build: ──────────────────────────────────────
build: Succeeded (default) (8.55 s)
- To run any other build target, pass the target name as the parameter to the build file
You will see an output as below:# In this example, `esbuild:dist` is the build target ./build.sh esbuild:dist
... ... ⚡ Done in 4ms build: esbuild:dist: Succeeded (184 ms) build: ────────────────────────────────────── build: Target Outcome Duration build: ──────────── ───────── ───────────── build: npm:install Succeeded 1.58 s 89.6% build: esbuild:dist Succeeded 184 ms 10.4% build: ────────────────────────────────────── build: Succeeded (esbuild:dist) (1.76 s)
If you want to make changes with hot reload in dev, you would run the .NET project with watch using build script ./build.sh watch
and also the ESBuild task for CSS ./build.sh esbuild:watch
on two terminal windows and work with it. Build tool shortens the typing required to run things.
Bullseye generates a nice coloured output on the terminal and also adapts the colour profile according to the CI environments such as GitHub, GitLab etc.
If you are looking at a more full fledged build tool in action, you can take a look at the build tool setup for Marten here.
Happy coding :-)