This is a simple 1-file build system template for C#/F# solutions.
This build system expects following solution layout:
solution_root/
build.ps1 -- PowerShell build CLI
docker-compose.yaml -- Docker Compose definition of the complete environment, including all required infrastructure
src/
Project1/
Project1.csproj -- project base filename matches directory name
Project1.Tests/
Project1.Tests.csproj -- tests projects are xUnit-based; project name must have suffix '.Tests'
Project2/
Project2.fsproj
imagename.Dockerfile -- if the `<imagename>.Dockerfile` is present, we'll build Docker image `<imagename>`
Project2.Tests/
Project2.Tests.fsproj
SolutionName.sln -- only one '.sln' file in 'src'
version.yaml -- core part of solution SemVer
- .NET SDK: https://dotnet.microsoft.com/download
- PowerShell: https://github.com/powershell/powershell
- Docker with Docker Compose: https://www.docker.com/,
or Podman https://podman.io/ with
dockerwrapper
-
Copy
build.ps1file to the root of your solution -
Make sure that the file is executable. On macOS/Linux:
chmod +x build.ps1
On Windows it can be invoked as-is locally, but if you will be using it e.g. in GitHub Actions, make it executable before committing:
git add build.ps1 git update-index --chmod=+x build.ps1
-
For cross-platform compatibility (e.g. if your local dev environment is Windows but CI/CD uses Linux-based build agents), it is recommended to use
LFline endings. Add following lines to your.gitattributes:* text=auto *.ps1 text eol=lf
-
Edit the
build.ps1file to add / remove build targets (see "Build Targets" section below), and possibly change the approach to versioning, change testing framework, etc., as needed
./build.ps1 <Target> [-Configuration Debug|Release] [-VersionSuffix <suffix>]Examples:
./build.ps1 Dotnet.Test
./build.ps1 Dotnet.Build -Configuration Release -VersionSuffix 240831
./build.ps1 DockerCompose.StartDetachedSample output:
./build.ps1 DotNet.Build*** BUILD: DotNet.Build (Release) in /Users/iblazhko/Projects/DDD-CQRS-ES/cqrs-fsharp
--- PRELUDE: .NET CLI
--- TARGET: Dotnet.Restore
--- STEP: dotnet restore /Users/iblazhko/Projects/DDD-CQRS-ES/cqrs-fsharp/src/CQRS.sln --verbosity quiet -nologo
--- TARGET: Dotnet.Build
--- STEP: dotnet build /Users/iblazhko/Projects/DDD-CQRS-ES/cqrs-fsharp/src/CQRS.sln --no-restore --configuration Release --verbosity quiet -nologo /p:Version=0.1.0
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.93
DONE
*** Completed in: 00:00:01.7404229
Convention used in this build system is that we have a set of build targets and a build target may depend on another target.
E.g. to only compile the code, we need to do
dotnet restoredotnet build
and to run tests, we need to do
dotnet restoredotnet builddotnet test
We can implement this with a set of following targets:
Dotnet.RestoreDotnet.Build(depends onDotnet.Restore, uses--no-restore)Dotnet.Test(depends onDotnet.Build, uses--no-build)
Naming conventions used in this build system:
-
targets are implemented as a function with internal prefix
Target_followed by target name (.replaced with_), e.g. functionTarget_Dotnet_BuildimplementsDotnet.Buildtarget -
each target may have one ore more steps implemented as separate functions with
Step_prefix -
targets dependencies are declared with
DependsOn "<Target.Name>"as a first statement in the target definition, e.g.function Target_Dotnet_Test { DependsOn "Dotnet.Build" ...
-
you will see two internal targets
Prelude.DotnetCliandPrelude.DockerClithat check ifdotnetordockerCLI is available, and will fail the build process immediately if the respective CLI is not available.
Following targets are available in this template:
Dotnet.RestoreDotnet.BuildDotnet.TestDotnet.PublishFullBuild(default target,Dotnet.Build+Dotnet.Test+Dotnet.Publish)Docker.BuildDockerCompose.StartDockerCompose.StartDetachedDockerCompose.StopPrune.BuildPrune.DockerPrune(Prune.Build+Prune.Docker)