Managing NuGet dependencies with Paket
Since we mainly write .NET code at work, the only real choice for packing dependencies and applications is NuGet and Chocolatey, everything stored in different feeds on the excellent ProGet server. For CI we build our code with TFS and Team City using some hand-made build scripts written in scriptcs, while deployment is done with good old Octopus Deploy.
All this works perfectly in our in-house environments, everything is just a choco install
away.
The only catch her is that we want to ship these packages to our customers as standalone installations. To do this we have written some custom powershell-scripts that wraps chocolatey and points to a local packages-folder where the correct .nupkg-files for the applications are stored. Since we want to use this exact installation mechanism in our CI pipeline (aka eating your own dog food) we had to be creative.
Chocolatey is great for installing software, but the easiest way to actually grab the .nupkg-files without installing them (and get the version number as a part of the .nupkg filename, chocolatey does not include this for some reason) is to use the NuGet CLI itself:
nuget install mypackage-server -Source http://myproget -OutputDirectory "C:\SetupScript\Packages"
Here we ran in to a much debated issue: NuGet's way of managing dependencies. By default NuGet resolves the lowest available (and legal) version of all dependencies. I understand that this is a safe choice, but for CI we always want to test and deploy the latest version of all packages and its dependencies. The worst part is, after hours of googling and trying we found no way to force NuGet to use the highest available versions of dependencies. To solve this issue I turned to Paket.
Paket is a wonderful project that takes care of dependencies in your project for you in a much more elegant (and Ruby-like) way then what NuGet itself does. Here is an example:
$ mkdir .paket
$ wget https://github.com/fsprojects/Paket/releases/download/1.23.0/paket.exe -p .paket/
$ touch paket.dependencies
In the paket.dependencies
file, put in your dependencies like this:
source https://nuget.org/api/v2
nuget Castle.Windsor-log4net >= 3.2
nuget NUnit
github forki/FsUnit FsUnit.fs
To install dependencies:
$ .paket/paket.exe install
All dependencies (including correct, transitive dependencies in latest versions!) are stored in the paket.lock
file:
NUGET
remote: https://nuget.org/api/v2
specs:
Castle.Core (3.3.3)
Castle.Core-log4net (3.3.3)
Castle.Core (>= 3.3.3)
log4net (1.2.10)
Castle.LoggingFacility (3.3.0)
Castle.Core (>= 3.3.0)
Castle.Windsor (>= 3.3.0)
Castle.Windsor (3.3.0)
Castle.Core (>= 3.3.0)
Castle.Windsor-log4net (3.3.0)
Castle.Core-log4net (>= 3.3.0)
Castle.LoggingFacility (>= 3.3.0)
log4net (1.2.10)
NUnit (2.6.4)
GITHUB
remote: forki/FsUnit
specs:
FsUnit.fs (81d27fd09575a32c4ed52eadb2eeac5f365b8348)
The files end up in a folder called Packages
with .npkg files and all, just how we like it.
As a bonus, here is the powershell-script Octopus Deploys runs:
$SetupRoot = "C:\SetupScript"
$SetupPs1 = "SetupRoot\Setup.ps1"
$OFS = "`r`n"
Write-Host "Clearing old cache..."
Remove-Item SetupRoot\packages\* -Recurse -Force -ExcludeSetup.psm1,Setup
if (-not $packageName) {
throw "Please specify the name of a package to install."
}
if($version){
$versionString = "-Version $version"
}
Write-Host "Fetching packages..."
cd $SetupRoot
Set-Content -Value "source $sourceFeed $OFS" -Path $SetupRoot\paket.dependencies
Add-Content -Value "nuget $packageName" -Path $SetupRoot\paket.dependencies
if(Test-Path $SetupRoot\paket.lock){
Write-Host "Removing paket.lock file"
Remove-Item $DIPSSetupRoot\paket.lock
}
& .paket/paket.exe install
Write-Host "Discovering chocolatey packages..."
Copy-Item $SetupRoot\packages\*\*.nupkg $SetupRoot\packages
Write-Host "Installing chocolatey package..."
if ($myval -eq $null) { "new value" } else { $myval }
if (-not $version){
$version = [String]::Join(".",(Get-ChildItem $SetupRoot\packages\$packageName*.nupkg)[0].Name.Split('.'),1,4)
}
$Config = Get-Item "$SetupRoot\Packages.config"
$ConfigContent = [xml]@"
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="$packageName" version="$version" />
</packages>
"@
$ConfigContent.Save($Config)
& $SetupRoot\Setup.ps1