Alan Dean

CTO, Developer, Agile Practitioner

Photograph of Alan Dean

Wednesday, October 12, 2011

Not-so-simple NuGet Packaging

Moving beyond the simple case, here is what I did to package the Cavity log4net trace listener which I'm sharing because I encountered some frustration points.

First, the .nuspec file:

<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>Cavity.Diagnostics.Log4Net</id>
    <version>1.1.0.444</version>
    <title>Cavity log4net trace listener</title>
    <authors>Alan Dean</authors>
    <owners />
    <licenseUrl>http://www.opensource.org/licenses/mit-license.php</licenseUrl>
    <projectUrl>http://code.google.com/p/cavity/</projectUrl>
    <iconUrl>http://www.alan-dean.com/nuget.png</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>A trace listener for log4net, allowing provider-agnostic tracing.</description>
    <summary />
    <copyright>Copyright © 2010 - 2011 Alan Dean</copyright>
    <language />
    <tags>Diagnostics</tags>
    <releaseNotes>Switched off license acceptance dialog.</releaseNotes>
    <dependencies>
      <dependency id="log4net" version="1.2.10" />
    </dependencies>
  </metadata>
  <files>
    <file src="content\app.config.transform" target="content\app.config.transform" />
    <file src="content\log4net.config.transform" target="content\log4net.config.transform" />
    <file src="content\web.config.transform" target="content\web.config.transform" />
    <file src="content\Properties\log4net.cs" target="content\Properties\log4net.cs" />
    <file src="lib\net35\Cavity.Diagnostics.Log4Net.dll" target="lib\net35\Cavity.Diagnostics.Log4Net.dll" />
    <file src="lib\net40\Cavity.Diagnostics.Log4Net.dll" target="lib\net40\Cavity.Diagnostics.Log4Net.dll" />
    <file src="tools\Install.ps1" target="tools\Install.ps1" />
  </files>
</package>

Right off the bat. you can see that more is going on here. The trace listener is dependent on log4net and I also have a bunch of content in additional to my two assemblies.

The target project will need to have either its' app.config or web.config extended so there is a transform for each. The target will need an assembly attribute applied, so I took the simple expedient of dropping log4net.cs into Properties alongside AssemblyInfo.cs. However, log4net also requires XmlConfigurator.Configure() to be called. This call needs to be in either Main() or Application_Start() and therefore existing code needs to be edited (rather than a new code file dropped in). To accomplish this, we have to use EnvDTE, as exposed in the Install.ps1 PowerShell script (I also mark the log4net.config file to copy to output during build):

param($installPath, $toolsPath, $package, $project)

$project.ProjectItems.Item("log4net.config").Properties.Item("CopyToOutputDirectory").Value = 1

try
{
  $item = $project.ProjectItems.Item("Program.cs")
}
catch [System.Management.Automation.MethodInvocationException]
{
}

if (!$item)
{
  $item = $project.ProjectItems.Item("global.asax").ProjectItems.Item("global.asax.cs")
}

$terminator = ""
if ($item.FileCodeModel.Language -eq "{B5E9BD34-6D3E-4B5D-925E-8A43B79820B4}")
{
  $terminator = ";"
}

$win = $item.Open("{7651A701-06E5-11D1-8EBD-00A0C90F26EA}")
$text = $win.Document.Object("TextDocument");
$namespace = $item.FileCodeModel.CodeElements | where-object {$_.Kind -eq 5}
$class = $namespace.Children | where-object {$_.Kind -eq 1}

$methods = $class.Children | where-object {$_.Name -eq "Main"}
if (!$methods)
{
  $methods = $class.Children | where-object {$_.Name -eq "Application_Start"}
  if (!$methods)
  {
    [system.windows.forms.messagebox]::show("methods is null")
  }
}

$edit = $methods.StartPoint.CreateEditPoint();
$edit.LineDown()
$edit.CharRight(1)
$edit.Insert([Environment]::NewLine)
$edit.Insert(" log4net.Config.XmlConfigurator.Configure()")
$edit.Insert($terminator)

I have to say that the PowerShell + EnvDTE experience was rather less than enjoyable but I got the basics of what I needed to work.

Simple NuGet Packaging

Over the last week I have started publishing my Cavity libraries on to NuGet, starting with my Unit Testing Fluent API.

The API is implemented in a single assembly with no non-BCL dependencies, which makes it the simplest case to pack for NuGet.

This is the .nuspec file:

<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>Cavity.Testing.Unit</id>
    <version>1.1.0.444</version>
    <title>Cavity Unit Testing</title>
    <authors>Alan Dean</authors>
    <owners />
    <licenseUrl>http://www.opensource.org/licenses/mit-license.php</licenseUrl>
    <projectUrl>http://code.google.com/p/cavity/</projectUrl>
    <iconUrl>http://www.alan-dean.com/nuget.png</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Fluent API for asserting types and properties.</description>
    <summary />
    <copyright>Copyright © 2010 - 2011 Alan Dean</copyright>
    <language />
    <tags>TDD</tags>
    <releaseNotes>Switched off license acceptance dialog.</releaseNotes>
  </metadata>
  <files>
    <file src="lib\net35\Cavity.Testing.Unit.dll" target="lib\net35\Cavity.Testing.Unit.dll" />
    <file src="lib\net40\Cavity.Testing.Unit.dll" target="lib\net40\Cavity.Testing.Unit.dll" />
  </files>
</package>

I have implemented framework targeting in my build process, so in this case I have two assemblies (.NET 3.5 and .NET 4.0) which I have copied into the lib subdirectory.

For a simple package like this, that is all you need to configure; just pack and push.