Jun 24 2011

Building And Publishing NuGet Packages With psake

Category: Matt @ 05:57

I’m a big fan of psake for build and deployment automation.  I know there are a ton of tools for this sort of thing out there, but as an avid user of Powershell, writing build and deploy scripts with psake feels more natural than the alternatives.  In this post, I’ll show you how you can use psake to automate publishing NuGet packages. 

Why psake?

I chose psake primarily because I love Powershell, and because I found psake scripts to be far cleaner than similar scripts that I’ve created using MSBuild and NAnt.  With psake, it is very easy to script out even complicated steps, and since you have the full capabilities of Powershell at your disposal, you’re unlikely to find any step in your deployment process that can’t be automated. 

I’ve used psake to automate publishing SpecsFor NuGet packages, which is what I’ll be walking you through in this post.  As SpecsFor is open-source, you’re free to hop on over and checkout the code, including the psake script, anytime!

Folder Setup

Below is the folder structure I’ve adopted for deployments (check it out on github). I’ve got a couple of Powershell scripts and my NuGet spec file.  The batch script and the corresponding “publish.ps1” Powershell script exist simply to support “double-click to publish,” so I don’t even have to pop open a Powershell console to kick off a new release anymore. 

image

There is also an “extensions” folder.  This folder houses Powershell modules that support my deployment, namely psake itself and the Powershell Community Extensions (pscx)

The “default.ps1” script is a psake build script that contains the actual meat of the deployment process.  Let’s dive in to that and see how it all works.

Using psake to Build Your Solution

A typical psake build script consists of properties (which can be overridden later when you invoke the build) and tasks.  My build script for packaging and publishing SpecsFor is no different.  Check it out:

properties {
    $BaseDir = Resolve-Path "..\"
    $SolutionFile = "$BaseDir\SpecsFor.sln"
    $OutputDir = "$BaseDir\Deploy\Package\"
    $SpecsForOutput = "$BaseDir\Deploy\Package\_PublishedApplications\SpecsFor"
    #Gets the number of commits since the last tag. 
    $Version = "1.1." + (git describe --tags --long).split('-')[1]
    $Debug="false"
    
    $NuGetPackageName = "SpecsFor"
    $NuGetPackDir = "$OutputDir" + "Pack"
    $NuSpecFileName = "SpecsFor.nuspec"
    
    $ArchiveDir = "$OutputDir" + "Archive"
}

$framework = '4.0'

task default -depends Pack,Archive

task Init {
    cls
}

task Clean -depends Init {
    if (Test-Path $OutputDir) {
        ri $OutputDir -Recurse
    }
    
    ri SpecsFor.*.nupkg
    ri specsfor.zip -ea SilentlyContinue
}

task Build -depends Init,Clean {
    exec { msbuild $SolutionFile "/p:OutDir=$OutputDir" }
}

task Archive -depends Build {
    #More on this in a second!
}

task Pack -depends Build {
    #More on this in a second!
}

task Publish -depends Pack {
    #More on this in a second!
}

One interesting thing to note is how the $Version property is populated.  I’ve taken an approach similar to the one described by Joshua Flanagan: I’ve tagged the major version number of SpecsFor using the “git tag" command, and I’m determining a minor version number based on how many commits there have been since the last major version number.  It’s not perfect, but it works.

Aside from that interesting tidbit, the rest of the script is fairly simple.  The “Init” task just clears the console, the “Clean” task removes output files that might be left over from a previous run, and the “Build” task just builds the solution and redirects the output using the excellent PublishedApplications package.  The interesting tasks are Archive, Pack, and Publish.  Let’s look at each in turn.

Creating an Archive

Releases of SpecsFor are published in two different ways.  The first is as a NuGet package, the second is as a zip file.  The “Archive” task automates the creation of the zip file. 

task Archive -depends Build {
    mkdir $ArchiveDir
    
    cp "$SpecsForOutput\Moq.dll" "$ArchiveDir"
    cp "$SpecsForOutput\nunit.framework.dll" "$ArchiveDir"
    cp "$SpecsForOutput\SpecsFor.dll" "$ArchiveDir"
    cp "$SpecsForOutput\StructureMap.AutoMocking.dll" "$ArchiveDir"
    cp "$SpecsForOutput\StructureMap.dll" "$ArchiveDir"
    
    cp "$BaseDir\Templates" "$ArchiveDir" -Recurse
    Remove-Item -Force "$ArchiveDir\Templates\.gitignore"

    Write-Zip -Path "$ArchiveDir\*" -OutputPath specsfor.zip
}

The task copies the required assemblies as well as Resharper and Visual Studio templates to a temporary folder, then uses PSCX’s Write-Zip cmdlet to create a zip file.  After that, it’s a simple, but manual, process of uploading the zip to github (if anyone knows of a way to automate that piece, let me know!).

Creating a NuGet Package

The “Pack” and “Publish” tasks are responsible for creating a NuGet package and publishing it to www.nuget.org respectively.  Let’s start with “Pack:”

task Pack -depends Build {

    mkdir $NuGetPackDir
    cp "$NuSpecFileName" "$NuGetPackDir"

    mkdir "$NuGetPackDir\lib"
    cp "$SpecsForOutput\SpecsFor.dll" "$NuGetPackDir\lib"

    cp "$BaseDir\Templates" "$NuGetPackDir" -Recurse
    Remove-Item -Force "$NuGetPackDir\Templates\.gitignore"
    
    $Spec = [xml](get-content "$NuGetPackDir\$NuSpecFileName")
    $Spec.package.metadata.version = ([string]$Spec.package.metadata.version).Replace("{Version}",$Version)
    $Spec.Save("$NuGetPackDir\$NuSpecFileName")

    exec { nuget pack "$NuGetPackDir\$NuSpecFileName" }
}

The “Pack” task is similar to the “Archive” task.  It creates a directory structure and copies in the required files (find out more about the folder structure of a NuGet package), updates the version number in the NuSpec file, and finally uses “nuget.exe” to create the package. 

Publishing the Package

Once the package has been created, it needs to be pushed to the official NuGet feed.  This is what the “Publish” task takes care of:

task Publish -depends Pack {
    $PackageName = gci *.nupkg
    #We don't care if deleting fails..
    nuget delete $NuGetPackageName $Version -NoPrompt
    exec { nuget push $PackageName }
}

First, the task attempts to remove a package with the same version number you are about to publish.  This makes the task repeatable, as attempting to push a version that already exists will make “nuget.exe” throw an error.  Finally, the package can be published using the “nuget push” command.  Assuming you have your API key saved, this step will publish the package without requiring you to manually specify your API key each time.

Final Thoughts

Creating a NuGet package can be a little tricky, but with a little Powershell magic, you can automate the process.  I’m using this process to publish SpecsFor, and I plan to apply it to other packages I publish on NuGet in the future as well.  I might even package up this deployment process itself as it’s own NuGet package! Let me know if you’d like to see that.

Questions or comments?  Please shout-out below!

Tags:

blog comments powered by Disqus