Part 1: Introduction and Create NuGet packages

Introduction

In our team, we are using Azure DevOps quite extensively. We manage quite a few projects, and most have continuous integration and deployment setup. We use quite a few features of Azure DevOps Pipelines, from Azure Artifacts Nuget packages to local agents to run on-premise tools during release and local deployments.

We have started using the yaml pipelines definition on small projects since they were introduced but waited that they got a bit more mature to migrate our most important projects to that new definition. We took the opportunity of that migration to rework the pipelines and take advantage of the improvements from the “old” UI style builds. For example, we use multi-stage to better separate the builds from the different level of releases.

In this series of article, I want to describe some of our recipes, how we defined our builds for each type of projects.

The Yaml pipelines

I won’t go into too much details on how to setup the yml pipeline. You should have already basic knowledge on how to create and configure a new yml pipeline. If you need help in getting started, head to the Azure Pipelines documentation.

Yaml pipelines have a lot of advantages compared to the old “UI” based one. The most prominent being that, since they are part of the repository, they are simple source code that goes alongside the rest of the application code. If a change in the source code mandates a change of the pipeline, you can do it in the same commit / branch. It can be part of a pull request, and the changes can even be executed during that pull request.

.Net library to NuGet packages

Now let’s discuss our first recipe. We have a lot of .Net class library projects that we transform into NuGet packages to include in our main applications.

The code

I have probably lost already half the readers with my introduction, so let’s get to the point. Here’s the code, I will explain it in details after.

Using templates

One disadvantage of migrating to the Yaml pipelines is that we lose “task groups”. Task groups, as the name implies, allowed to create a group of tasks that could be used in multiple pipelines and could use variables to set properties. This way, we could keep recipes in those task groups, use them in multiple pipelines and, if a change was needed, we could do it in all the pipelines at once.

One way to remedy that is by separating our yaml pipeline file into multiple files using templates.

Templates are nothing complex, they are a simple yaml file that you can add alongside your “normal” yaml pipeline file (or in a subfolder) that will define the “steps” part of a job definition.

Example of using and defining a pipeline that uses a template

azure-pipeline.yml jobs:

- job: demo build

displayName: 'Build project'

steps:

- template: build-steps.yml

build-steps.yml steps:

- checkout: self

clean: true

lfs: true - task: DotNetCoreCLI@2

displayName: 'NuGet Restore'

inputs:

command: restore

projects: '**/*.csproj' - task: DotNetCoreCLI@2

displayName: Build all projects

inputs:

projects: '**/*.csproj'

arguments: '--configuration $(buildConfiguration) --no-restore'

One approach to share the steps between different projects could be to store the “common” build templates in a repository and use git submodule to include the common templates into all your projects.

Versioning

For those packages, we use the GitHub flow and do automatic versioning. Simply put, it means that the master branch of the repository is locked and we mandate using a PR to push work to it. Work on the branch are either new features or bug fixes. We do this to continuously improve our code through code reviews.

When the PR passes and the code is merged to master, we automatically create a beta release. Once we are confident about our version and want to create a new official release, we create a tag on master which triggers a new build and publish the major version.

To automate the version management, we use GitVersion. It is a tool that, from the current state of the currently checked out git branch and a set of overridable rules, can calculate a version number. It can be installed from the Visual Studio Marketplace. It adds a GitVersion task that will run the calculation and create a series of environment variables containing the version in different “styles”. Those environment variables can then be used in the build steps or to update the assembly versions.

Update the assembly version

GitVersion supports updating AssemblyInfo files, but in a very limited way. It doesn’t support modifying the assembly information that are now stored in the “new” csproj files.

To overcome this issue, we use another DevOps extension called Assemly Info. It offers two tasks, one for the Net Framework (with AssemblyInfo files) and one for the new Net core / standard project.

You simply add the task in the pipeline and use GitVersion’s environment variables to use them to modify the file info or assembly info. As you can see in the sample, I’m using the $(GitVersion.AssemblySemVer) variable for VersionNumber and FileVersionNumber but $(GitVersion.InformationalVersion) that contains the SHA1 of the git commit for the informational version.

Build the project

The build process is very standard, just call nuget restore followed by the build. I’m using wildcards to restore / build all csproj at once.

Publish the build binaries

Artifacts have two use. First, they will be available to download on the build result page. This allows us to check that the build stage was correctly setup. The second use is that they will be automatically downloaded on the second stage, the deployment stage.

I’m copying the compiled binaries to the artifact staging directory. This is completely optional, you could also ignore it and only publish the nupkg file. I like to copy the binaries in the artifact because it helps making sure that the builds work as expected.

Create the nuget package and publish

Finally, I’m using the .Net Core CLI task to pack the project and copy it directly into a subdirectory of the artifacts staging directory. I’m also setting the versionEnvVar parameter to the GitVersion.NuGetVersionV2 environment variable have a version compatible with the nuget versioning scheme.

Finally, I’m using the PublishBuildArtifacts step to publish the two folders as artifacts.

Deploy

The deployment stage is configured to trigger only if the build_pack stage has succeeded and if the commit triggering the pipeline is on master branch.

It has one single step, a NuGetCommand using the push command that pushes all .nupkg files into an internal feed.

Obviously, for public NuGet package you might want to add other steps or other deployment, for example based on a tag to deploy on the public NuGet feed.

Conclusion

As you can see, it’s fairly easy to automate the deployment of a NuGet package using Azure DevOps pipeline. Using GitVersion allows to also automate the version number. Using template is also a good way to simplify reusing the same part of the pipeline in multiple projects.

In the next part, we’ll see a recipe for building a desktop application, creating and deploying an installer using WiX.

If you have questions, remarks, or improvement feel free to add a comment.