This article will be an in-depth guide into how you can seamlessly integrate assembly versioning from continuous integration system by using MSBuild with C# projects (
.csproj). This article relies on you having some working knowledge of how MSBuild works, including understanding many of the concepts that MSBuild relies on in order to function.
For a brief guide of MSBuild syntax, check out the MSDN documentation that is available here.
In Visual Studio 2015, the MSBuild team introduced the concept of Directory.Build.targets and Directory.Build.props files. These are MSBuild project files (separated by file extension) that cascade based on directory hierarchy and are automatically imported at compile-time. What this means exactly, is that if either one of these files is available within the same directory as the .csproj file, then it will get imported. If not, MSBuild will automatically go up the directory tree until it finds one of these files, and that will be used as the default
This is very powerful functionality, as it means that can we can now intercept the build process of a
.csproj file without having to make any modifications to the MSBuild compiler tools for integrating this.
Custom Build Tasks
One of the great features of MSBuild is the ability to inject your own build logic through the usage of build tasks. A build task comprises of logic that is written using a language that is supported by the .NET Framework and Common-Language Runtime (i.e. C#, F#, and VB.NET).
A custom build task can be written in two ways.
- A class derived from the base build task implementation available from the Microsoft.Build.Tasks.Core namespace.
- An "inline task" that is defined directly from a MSBuild project file, and is compiled dynamically, and launched by the CodeTaskFactory at runtime.
Integrating custom build tasks from a class library is arguably the best approach if you are intending on developing a custom task that is both complex in its implementation, requires regular maintenance and breakpoint debugging, and has reliance on third-party assemblies. It also works best if you need to unit test any logic that is featured within your code.
Inline custom build tasks are more ideal if you simply require specific code to be executed at build time, and it does not have any dependency on third-party assemblies for executing the specified logic. An inline class can also be defined as C# code, but is featured as CDATA that is part of the MSBuild project file's definition.
Examine the example below to further understand how a custom inline task can be defined.
<UsingTask TaskName="ArcaneTools_ModifyMultipleAssemblyInfo" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> <ParameterGroup> <AllItemsGivenToTask ParameterType="Microsoft.Build.Framework.ITaskItem" Required="true" /> <ParameterIsRequired ParameterType="System.String" Required="true" /> <ParameterThatIsNotRequired ParameterType="System.String" Required="false" /> </ParameterGroup> <Task> <Using Namespace="System" /> <Using Namespace="System.IO" /> <Code Type="Fragment" Language="cs"> <![CDATA[ // Your C# code goes here! ]]> </Code> </Task> </UsingTask>
Integration with Continuous Integration
This can be easily integrated with continuous integration systems such as Jenkins and TeamCity, through the command-line interface for the MSBuild build engine.
Alternatively, they can be defined as environment variables that can be ingested implicitly by MSBuild without any parameter definitions on the command-line argument.
Any new build generated by your continuous integration or build system should contain relevant versioning information that outlines its build or revision version. You're likely also going to want to start or initiate builds as soon as something submits a new change to your code repository (or VCS).
Auxiliary MSBuild Project
First, you are going to want to create your Directory.Build.targets so that your
.csproj has something to import during compile time. This file will consist of another definition for an MSBuild project, that will specify targets that will be executed when it's time to perform operations on intermediary
*.cs files before they are sent to the compiler. Typically these files are first placed in the /obj directory during the compilation process, but we are going to want to ensure that the contents of those files are updated with relevant versioning information before the compiler has to produce a binary.
The versioning information that we are going to define will be based on semantic versioning. More information can be found here.
Jenkins is a free-to-use, highly configurable, cross-platform continuous integration and automation system. It also provides plug-in support for using MSBuild directly as a part of defined build configurations.
You can specify build properties without having to write a single command-line argument.
Developer Command Prompt
Alternatively, you can load the necessary environment variables for launching your scripts through MSBuild by using a simple batch (
*.bat) file. Depending on the version of Visual Studio that you are using, you would achieve this by using something similar to the following.