.NET build tool using Bullseye and SimpleExec

Photo by Fleur on Unsplash

.NET build tool using Bullseye and SimpleExec

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 page
  • package.json at the root folder which has dev dependencies to esbuild 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
  • ESBuild is used to minify the static assets i.e. css files from src/WebApp/static/css and adds it under wwwroot/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 below
    dotnet new console
  • Add Bullseye and SimpleExec NuGet packages to build.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:

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
    # In this example, `esbuild:dist` is the build target
    ./build.sh esbuild:dist
    You will see an output as below:
    ⚡ 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 :-)