/cake-build

Demonstrates a basic build of a .NET NuGet package using https://cakebuild.net/

Primary LanguageC#MIT LicenseMIT

Cake build

Package Release Pre-release
Contoso.Hello.Logic MyGet MyGet
Contoso.Hello.SuperLogic MyGet MyGet
CI Status Platform(s) Framework(s) Test Framework(s)
AppVeyor Build Status Windows netstandard2.0, net462 net8.0, net462
Azure DevOps Build Status Linux netstandard2.0 net8.0
CircleCI Build Status Docker: mcr.microsoft.com/dotnet/sdk:8.0 netstandard2.0 net8.0
GitHub Build Status Windows netstandard2.0, net462 net8.0, net462

Demonstrates a basic build of a .NET NuGet package using Cake.

I tried to create a somewhat realistic scenario without writing too much code:

  • The solution contains two projects which will be packed as NuGet packages.
    • The SuperLogic project depends from Logic and when packing this project reference will be turned into a NuGet package reference (handled out of the box by dotnet pack).
    • The Logic project references a NuGet package from nuget.org via a PackageReference, dotnet pack will turn this into a package reference.
  • The projects target both netstandard2.0 and net462 so they can be used with the .NET Framework (net462 and above).
  • The solution contains a test project.
  • Use SemVer to version the DLLs and the NuGet packages.

I wrote a blog post about this experiment.

Table of contents

Pinning the version of Cake

Pinning the version of Cake guarantees you'll be using the same version of Cake on your machine and on the build server.

This is done by using Cake as a .NET local tool. The version is specified in .config\dotnet-tools.json.

Running locally

Pre-requisites

Initial setup on Windows

.\bootstrap.ps1

Initial setup on Linux / OS X

./bootstrap.sh

Run build script

dotnet cake build.cake

Benefits over a nuspec file

  • A single file describing the package and the project instead of two (*.csproj and *.nuspec)
  • References (projects or NuGet packages) are resolved automatically. There is no need to tweak a file manually any more!

Referencing a project without turning it into a package reference

The SuperLogic project depends on the ExtraLogic project but we don't want to ship ExtraLogic as a package. Instead we want to include Contoso.Hello.ExtraLogic.dll in the SuperLogic package directly. Currently this is not supported out of the box but the team is tracking it.

Luckily this issue provides a workaround. All the modifications will take place in SuperLogic.csproj.

  • In the <PropertyGroup> section add the following line:
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeReferencedProjectInPackage</TargetsForTfmSpecificBuildOutput>
<ProjectReference Include="..\ExtraLogic\ExtraLogic.csproj">
  <PrivateAssets>all</PrivateAssets>
</ProjectReference>
  • Finally add the target responsible of copying the DLL:
<Target Name="IncludeReferencedProjectInPackage">
  <ItemGroup>
    <BuildOutputInPackage Include="$(OutputPath)Contoso.Hello.ExtraLogic.dll" />
  </ItemGroup>
</Target>

CI

Each time a commit is pushed to main or features/*; AppVeyor, Azure DevOps, CircleCI and GitHub will build the changes.

In case of a successful build GitHub will:

  • On main
    • Create a GitHub release
    • Publish the NuGet packages (including symbols) to gabrielweyer feed
  • On features/*
    • Create a GitHub pre-release
    • Publish the NuGet packages (including symbols) to gabrielweyer-pre-release feed

When running on a platform that is not Windows, we can't target the .NET full Framework, hence the build script is calling IsRunningOnLinuxOrDarwin to detect the available capabilities.

AppVeyor

Build status is visible here.

  • Supports Linux, macOS and Windows hosted agents
  • Can create a GitHub release and tag the repository if required
  • Supports artifacts and test results
  • You can modify AppVeyor's build number programatically
    • Cake integrates with AppVeyor: publish test results, upload artifacts, update build number...
  • Partially supports files exclusion (commits are skipped as soon as they contain one file in the excluded list)

Azure DevOps

Azure DevOps does not offer a free an unlimited plan for open-source projects any more.

Build status is visible here.

  • Supports Docker, Linux, macOS and Windows hosted agents
  • Supports artifacts and test results
  • Supports files exclusion

CircleCI

Build status is visible here.

  • Supports Docker, Linux, macOS and Windows hosted agents
  • Supports artifacts

CircleCI has a few limitations:

  • Test results have to be in JUnit format, you can use the package JunitXml.TestLogger for a JUnit logger
  • Can't exclude files easily

GitHub

Build status is visible here.

  • Supports Docker, Linux, macOS and Windows hosted agents
  • Supports artifacts
  • Supports files exclusion

GitHub has a few limitations:

  • A third-party / custom Action is required to report test results
  • A third-party / custom Action is required to create a GitHub release

Travis CI

I decided to remove Travis CI. Travis CI poorly handled a security vulnerability and security is of paramount importance when it comes to build systems.

Status checks

The main branch is protected:

  • Force push is disabled on main
  • main cannot be deleted
  • Non-protected branches (such as features/*) cannot be merged into main until they satisfy:
    • An AppVeyor passing build
    • An Azure DevOps passing build
    • A CircleCI passing build
    • A GitHub passing build

After a branch was configured as protected, GitHub will suggest available status checks.