Run the same build PROCESS everywhere

For the purpose of this post, ‘build’ refers to the Commit phase of a common deployment pipeline, e.g. version, compilation (I often work in .NET environments), unit test and package for deployment later on.

Most people that have worked with me have probably been subjected to the bee in my bonnet that I’m immediately sceptical of a build process if I *cannot* run it locally on a development machine. I believe there are a number of benefits of being able to do this.

  • Fail earlier. Catch more failed builds earlier on by changing fewer variables, i.e. don’t compile and build in a completely different way on your Continuous Integration (CI) server than your developers do on their development machines.
  • Own a debuggable build process. Building software is tricky, let me (or anyone) debug that locally, so we don’t have to constantly break the CI builds to see things fail.
  • Make the build process a first class citizen. Yes, let’s put the build process under version control and treat it as code, I care about changes and versions… A  LOT. Not to mention I want an easy way to clone that build process in to as many applications and branches as I want.
  • Improve quality. If we treat the build process as code, then we can also test it as part of its own pipeline.
  • Decouple yourself from the CI server vendor. I love TeamCity, have a long running tempestuous relationship with Team Foundation Server (TFS), have had a few flings with Jenkins  and chatted up various other CI and build servers/services, including but not limited to http://www.appveyor.com/, https://travis-ci.org/ and https://appharbor.com/. The one thing they all have in common (with the notable exception of https://appharbor.com/) is they give you an inch, but allow you to take a mile. It is incredibly easy to use their out-the-box and add-on tasks, runners and plug-ins, however this means it is almost impossible to move to another CI server technology or run a build in its entirety locally.

 

I don’t like re-inventing the wheel on every project, so have a pattern for the above, it’s called OneBuild and I’ve now made it Open Source on github and available as a nuget package, here’s a summary:

OneBuild

Tech disclaimer: This is very Windows/.NET focused, we’re using PowerShell and a few selective open source libraries, along with a few necessities to allow us to build .NET projects, not least MSBuild. That said, the concept is transferable across technology stacks.

Encapsulated build logic

Build logic is encapsulated in PowerShell script modules, for example:

lloydholman_OneBuild_Modules_1

Those modules are loosely covered with Pester unit tests, like so:

lloydholman_OneBuild_Tests_1

Task based

Orchestration of OneBuild’s build and loading of modules is handled by an Invoke-Build script, giving OneBuild a light weight task orientated build script. Invoke-Build has some thorough documentation about how it works and its concepts for those interested in the detail.

OneBuild currently defines and executes the following top-level tasks for each commit build, for an up to date list see the OneBuild site.

Task Synopsis
Invoke-Commit The default OneBuild task. Invoke-Commit is the entry point to and initiates the complete commit build.
Invoke-HardcoreClean

Aggressively and recursively deletes all /obj and /bin folders from the build path as well as the \BuildOutput folder.

Set-VersionNumber Sets the consistent build number of the form [major].[minor].[buildCounter].[revision]. This task has a few dependent tasks that either read from or create the OneBuild VersionNumber.xml file.
Invoke-Compile

Cleans and Rebuilds a Visual Studio solution file (identified by convention) to generate compiled .NET assemblies.

Invoke-UnitTests Executes all unit tests (currently only supports NUnit) for compiled .NET assemblies matching a defined naming convention, outputting results to XML file.
New-Packages

Generates new Nuget ([packageName].[version].nupkg) and optional Symbols({packageName].[version].symbols.nupkg) package(s) by passing all .nuspec files found in the solution root folder.

Dogfooding

image

OneBuild bootstraps itself, i.e. it is responsible for versioning, packaging and publishing itself to nuget.org

Convention over configuration

OneBuild is heavily convention based:

  • Will attempt to build the first Visual Studio Solution (.sln) file it finds. (during task Invoke-Compile)
  • Intelligently versions locally in the same way it does on the CI server. (during task Set-VersionNumber)
  • Will run unit test projects that match a basic naming convention (currently supports NUnit) and are compiled in to a ‘\bin’ folder. (during task Invoke-UnitTests)
  • Will attempt to create NuGet packages from any ‘.nuspec’ files that are found sitting next to the Visual Studio Solution file. (during task New-Packages)

Use

Assuming we install OneBuild using the Visual Studio Package Manager (there are other ways too) like so.

PM> Install-Package OneBuild

Convention means we can simply do this to run the commit build, no parameters required.

C:\>cd “Path to your solution”
C:\Path to your solution\>OneBuild.bat

OneBuild is in its early days, all feedback is greatly appreciated, as is contribution, after checking the documentation site feel free to get involved through the github issues page.