I recently open-sourced a .NET library and I didn't provide a NuGet installer. I was shamed into providing one by Scott Hanselman, after he very kindly wrote a blog post about my project :)
I had never published a NuGet package before. It turns out it's really easy. I thought it'd be useful to describe how I did it for anyone else in the same situation.
There's basically three steps:
- Get an API Key from nuget.org
- Create a NuGet package
- Publish your package
Get an API Key
If you don't already have one, sign up for a NuGet account at nuget.org. If you have a Microsoft account, you can login with that.
Once you're logged in, your unique API Key is displayed alongside your account details. It's formatted like a GUID (e.g. ABCDEF12-ABCD-ABCD-ABCD-ABCDEF123456
).
Keep the key private!
Create a NuGet package
A NuGet package contains two things: metadata and the files to install. In my case, I had just one file: MarkdownLog.dll
Here is the finished NuGet package:
I'm using the excellent NuGet Package Explorer to display its contents.
The left pane lists the metadata: name, version, copyright info - all the stuff you'd expect.
The package's contents are shown in the right pane. MarkdownLog.dll
is the only file that needs to be installed. It's in a specially named folder, portable-windows8+net45
. This name tells NuGet the .NET framework version it requires.
The NuGet package itself is built using nuget.exe. I downloaded nuget.exe
to a subfolder of my project called NuGet
. This is where I put all NuGet related files.
To create a package, NuGet needs to know what files and metadata to include. This is defined in a .nuspec file.
Here's the one I used (view on GitHub):
<code><?xml version="1.0"?>
<package >
<metadata>
<id>MarkdownLog</id>
<version>$version$</version>
<authors>Stuart Wheelwright (@wheelies)</authors>
<licenseUrl>https:
<projectUrl>https:
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Lightweight .NET component for programmatically generating Markdown. Useful for producing rich diagnostic logs with minimal dependencies.</description>
<copyright>Copyright 2014 BlackJet Software Ltd</copyright>
<tags>markdown diagnostics logging pcl</tags>
</metadata>
<files>
<file src="..\MarkdownLog\bin\Release\MarkdownLog.dll" target="lib\portable-windows8+net45" />
</files>
</package>
</code>
The contents of this file should be self-explanatory, but comprehensive documentation is available if you'd like to delve deeper.
I should explain the $version$
part though.
I could have hardcoded the version (e.g. 0.9.0
), but then I'd have to manually keep it in sync with the version number in the DLL.
You see, when MarkdownLog.dll
is produced, it is automatically given a unique version number by the automated build system.
I use the wonderful AppVeyor for continuous integration. It will automatically build, test and package up the project's output every time anyone commits a file to the project's GitHub. The service is free for open source projects and I'd thoroughly recommend it.
You can configure your project's build in AppVeyor in two ways: either using its easy to use web interface, or by creating a file called appveyor.yml
in the root of your GitHub repository. I started with the web interface but moved to using a file once everything was working and I was comfortable with how it worked. Having the project's build configuration in a file means I can compare revisions, and rollback to an earlier version if anything goes wrong.
The complete build config can be seen in MarkdownLog's GitHub repository. This is the part that automatically injects the version information into the DLL:
<code>version: 0.9.0.{build}
assembly_info:
patch: true
file: AssemblyInfo.*
assembly_version: "{version}"
assembly_file_version: "{version}"
assembly_informational_version: "{version}"
</code>
{build}
is an automatically incrementing number, provided by AppVeyor's build system. This is used to create the full version number (e.g. 0.9.0.23
), which is automatically injected into AssemblyInfo.cs, prior to the .NET solution build. If you look at the file properties of MarkdownLog.dll
, you'll see the end result:
The next part of the appveyor.yml
build configuration specifies the name of the .NET solution to build:
<code>build:
project: MarkdownLog.sln
</code>
If the .NET build completes successfully, the unit tests will run. If the tests pass, a script called pack.ps1
is executed:
<code>after_test:
- ps: .\nuget\pack.ps1
</code>
pack.ps1
is a simple PowerShell script that creates the NuGet package:
<code>$root = (split-path -parent $MyInvocation.MyCommand.Definition) + '\..'
$version = [System.Reflection.Assembly]::LoadFile("$root\MarkdownLog\bin\Release\MarkdownLog.dll").GetName().Version
$versionStr = "{0}.{1}.{2}" -f ($version.Major, $version.Minor, $version.Build)
Write-Host "Setting .nuspec version tag to $versionStr"
$content = (Get-Content $root\NuGet\MarkdownLog.nuspec)
$content = $content -replace '\$version\$',$versionStr
$content | Out-File $root\nuget\MarkdownLog.compiled.nuspec
& $root\NuGet\NuGet.exe pack $root\nuget\MarkdownLog.compiled.nuspec
</code>
The pack.ps1
script is heavily based on this one I found in another GitHub project.
pack.ps1
and MarkdownLog.nuspec
both live in the NuGet folder, along with nuget.exe
The interesting part of pack.ps1
is right at the end, where a call is made to nuget pack
. This call will build a .nupkg
file using a copy of the .nuspec
file that has been injected with the full version number.
A .nupkg
file is created for every build. You can see an example on the MarkdownLog artifacts page
Publish your package
The final part of the process is the easiest.
Now we have an API Key and a .nupkg
file, we can put them together and publish the package to NuGet.org
If we were doing this manually, we'd call a couple of NuGet commands:
<code>nuget setApiKey Your-API-Key
nuget push YourPackage.nupkg
</code>
To automate in AppVeyor, it's almost as simple, but there's one extra step. We first need to encrypt our API Key. This encrypted version can then become public without needing to expose the private key.
AppVeyor provides an online tool for encrypting the key. Enter your private key in the box, click Encrypt and you'll get a key that can be placed in your appveyor.yml
build configuration file, like this:
<code>deploy:
provider: NuGet
api_key:
secure: n/2gZwCGLcoLBr5nGu5akS0IFQ1YHciI6Wz0NGC+1mLTUtO08Uy+TWn7OtyLG9v3
artifact: /.*\.nupkg/
</code>
The AppVeyor-encrypted version of the API Key is placed in the secure
value. The artifact
value tells AppVeyor where to find the package. I've used a regex pattern to match all files with a .nupkg
extension.
And that's it.
When the build successfully completes, a NuGet package is automatically created and published to NuGet.org
But, you probably don't want a NuGet package to be published every time anything in the repository changes. It would be sensible to only publish packages that contain well-tested bug fixes or new features; not one every time a typo is fixed in a code comment!
Currently, I don't have a good solution to this. I currently control the publishing by commenting out/uncommenting the deployment section of the build config. I'd really like a more elegant solution. One option I'm considering is to create a new release
branch in GitHub. The build for this branch would automatically publish the package, but the normal dev
branch wouldn't. To publish, I'd simply do a merge from dev
to release
.
Can you suggest a better alternative?