Using Azure Artifacts, we can publish NuGet packages to a private (or public) NuGet feed. These feeds can be scoped in Azure DevOps at either an organization level or at a project level.
Creating a private NuGet feed in Azure DevOps is really simple. This article below shows have you can set one up. If you’re following along and you haven’t set up an internal feed yet, stop reading this article and check out the article here. Once you’re done with that, you can return here.
This post will show you how to use a YAML build file to publish the NuGet packages we create to our own Private feeds in Azure Artifacts.
Now you might have an opinion on YAML in general, and yes, we can achieve the same result via the Classic user interface. Still, I like being able to include our build files within our code repository and providing me with the ability to check the history of our build file using the git history of that YAML file.
To publish our NuGet package to our internal feed, we need to do the following:
For this post, I will be using a helper library that I’ve been using for my own health application. If you want to look at it while reading this (it includes the YAML file), check it out here.
Before jumping into the meat of our build file, We need to set some things up:
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
ProjectName: 'MyHealth.Common'
ProjectPath: '**/MyHealth.Common.csproj'
buildConfiguration: 'Release'
steps:
- task: UseDotNet@2
displayName: 'Install .NET Core SDK'
inputs:
packageType: 'sdk'
version: '3.x'
- task: DotNetCoreCLI@2
displayName: 'dotnet restore'
inputs:
command: 'restore'
projects: '$(ProjectPath)'
- task: DotNetCoreCLI@2
displayName: 'Build $(ProjectName)'
inputs:
command: 'build'
arguments: '--configuration $(buildConfiguration)'
projects: '$(ProjectPath)'
Let’s break this down:
To create a NuGet package in our build file, we need to add a DotNetCoreCLI task like so:
- task: DotNetCoreCLI@2
displayName: 'Pack $(ProjectName)'
inputs:
command: 'pack'
arguments: '--configuration $(buildConfiguration)'
packagesToPack: '$(ProjectPath)'
nobuild: true
versioningScheme: 'off'
In this task, we run the pack command and tell the task to pack our project path that I’ve defined earlier in the YAML file. I’ve set the nobuild argument to true since I’ve already built my project.
My package is a .NET Standard package. For .NET Core and .NET Standard packages, Microsoft recommends that you use the DotNetCoreCLI tasks. If you’re building packages for .NET Framework, you can use a NuGet task.
There are a couple of ways we can do this. In my package .csproj file, I’ve specified my Version number like so:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Description>Common Helper Library for MyHealth Applications</Description>
<Authors>Will Velida</Authors>
<Product>MyHealth</Product>
<Version>1.4.0</Version>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.1.2" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.8.1" />
<PackageReference Include="Microsoft.Azure.Cosmos.Table" Version="1.0.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>
If we wanted to achieve this via a build task defined in YAML, we could do so like this:
variables:
Major: '1'
Minor: '0'
Patch: '0'
steps:
- task: NuGetCommand@2
inputs:
command: pack
versioningScheme: byPrereleaseNumber
majorVersion: '$(Major)'
minorVersion: '$(Minor)'
patchVersion: '$(Patch)'
With the NuGet task, we can use the Major.Minor.Patch semantic versioning scheme for our builds. However, once a version has been produced, we can’t update or replace that version. They are immutable. To produce new versions of our package each time we update them, we can do the following:
Use the $(rev:.r) variable for the version number that we wish to increment. This will automatically increment the build number for that variable each time we push to our branch while keeping the other variables constant.
Use the $(date:yyyyMMdd) variable. This is ideal for creating prelease labels for the build while keeping our major, minor, and patch versions constant.
Now that we have a package, we can publish it in our feed. To publish to our Azure Artifacts feed, we’ll need to set the Project Collection Build Service identity to be a Contributor on the feed.
Once we have enabled that, we can add the following YAML to our build pipeline:
- task: NuGetAuthenticate@0
- task: NuGetCommand@2
displayName: 'Publish $(ProjectName)'
inputs:
command: push
feedsToUse: 'select'
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg'
nuGetFeedType: 'internal'
publishVstsFeed: '<projectname>/<feedname>'
versioningScheme: 'off'
allowPackageConflicts: true
Here, we are using the NuGetAuthenticate command to authenticate our Build server to push packages to our internal NuGet feed. We then run a NuGetCommand task with the push command to push our package (stored in our Artifact Staging Directory) to our internal NuGet feed.
Here, we have used <projectname>/<feedname> as our VSTS feed to publish to. Remember, we can scope our feeds at either the project level or an organization level in Azure DevOps. Since my feed is scoped at the project level, I need to put the project name here.
I’ve also included the nuGetFeedType argument and stated that our target field is an internal feed. Since we are using the push command in the NuGet command task, this argument is required.
Here’s our complete YAML file:
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
ProjectName: 'MyHealth.Common'
ProjectPath: '**/MyHealth.Common.csproj'
buildConfiguration: 'Release'
steps:
- task: UseDotNet@2
displayName: 'Install .NET Core SDK'
inputs:
packageType: 'sdk'
version: '3.x'
- task: DotNetCoreCLI@2
displayName: 'dotnet restore'
inputs:
command: 'restore'
projects: '$(ProjectPath)'
- task: DotNetCoreCLI@2
displayName: 'Build $(ProjectName)'
inputs:
command: 'build'
arguments: '--configuration $(buildConfiguration)'
projects: '$(ProjectPath)'
- task: DotNetCoreCLI@2
displayName: 'Pack $(ProjectName)'
inputs:
command: 'pack'
arguments: '--configuration $(buildConfiguration)'
packagesToPack: '$(ProjectPath)'
nobuild: true
versioningScheme: 'off'
- task: NuGetAuthenticate@0
- task: NuGetCommand@2
displayName: 'Publish $(ProjectName)'
inputs:
command: push
feedsToUse: 'select'
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg'
nuGetFeedType: 'internal'
publishVstsFeed: 'projectname/feedname'
versioningScheme: 'off'
allowPackageConflicts: true
Hopefully, you can see how simple it is to publish our NuGet packages to a private NuGet feed in Azure Artifacts. This isn’t just limited to private NuGet feeds. We can also publish packages from DevOps to NuGet.org.
If you want to learn more about Azure Artifacts, I recommend that you check out the documentation that covers topics such as configuring feeds, publish NuGet (and other) types of packages, and more.
If you have any questions about this article, please let me know in the comments, or you can reach out to me on Twitter.
Previously published on dev.to.