Recently I wrote about packaging an MVC controller from a live application as a NuGet package. This article will show how to alter the nuspec for the NuGet content (non-assembly based) packages to have the same version number as the overall solution and reference the updated version of the NuGet packaged assemblies in the solution. The assembly based NuGet packages are handled by another process in the solution ‘PrepareNuSpecsForRelease‘, which only works for projects in the solution file and with project references between them.
There may be multiple operations you want to do on the nuspec files. You can keep your MSBuild project file DRY if you define a variable to hold them.
The following first defines a property for each file, and then it creates a property holding them both as a list.
<PropertyGroup> <BootstrapSecurityNuSpec>$(SharedRoot)\Candor.Web.Mvc.Bootstrap.Security\Candor.Web.Mvc.Security.nuspec</BootstrapSecurityNuSpec> <BootstrapErrorHandlerNuSpec>$(SharedRoot)\Candor.Web.Mvc.Bootstrap.ErrorHandler\Candor.Web.Mvc.ErrorHandler.nuspec</BootstrapErrorHandlerNuSpec> <TransformNuSpecs> $(BootstrapSecurityNuSpec); $(BootstrapErrorHandlerNuSpec); </TransformNuSpecs> </PropertyGroup>
There are two types of version number we want to replace in the nuspec. The first is the version node of the NuGet package created with the nuspec file.
The version number will be replaced with a variable in the MSBuild file called ‘Version’. We want to make sure we only replace a 4 part version number in the version node, not all strings in the file that appear like a version number, since that may inappropriately replace some dependency version numbers.
Notice the Include attribute of the RegexTransform below points at the variable containing the nuspec files. The find expression is a regular expression to be matched. However, some characters in the expression cannot be used literally since MSBuild would interpret them for an MSBuild purpose. The MSBuild special characters documentation lists the characters you need to escape. This expression puts 3 parts of the match into capture groups defined by the sets of parenthesis. The purpose of the capture groups is so that we can put those captures values back into the replaced result.
The ReplaceWith parameter defines what to replace any matches with. The $1 is the first capture group, and the $3 is for the 3rd capture group. The second capture group is what we want to replace with the version number of the solution. Note that $0 is really the first since captures are 0 based, but this ‘real’ first (0 index) is the whole expression, and thus useless for this purpose. The logical captures, as defined by the sets of parenthesis, are defined as starting with $1.
<ItemGroup> <RegexTransform Include="$(TransformNuSpecs)"> <Find>(%3Cversion)%3E\s*(\d\.\d+\.\d+\.\d+)\s*(%3C%2Fversion%3E)</Find> <ReplaceWith>$1%3E$(Version)$3</ReplaceWith> </RegexTransform> </ItemGroup>
Initially I started with “$1$(version)$2” as the replacewith expression and then the %3E was inside the expression’s first capture group. However, MSBuild had an issue with this and the result was a literal $1 being output into the file instead of the first capture group. Apparently, it did not like the $Version appearing directly after the $1 without a space or other character in between. I didn’t find anything in a search about why that may be.
This solution has a number of assemblies that are packaged into NuGet packages. All of them start with “Candor.” as a root namespace and NuGet package id. We do not want to replace the dependency versions not part of the ‘Candor’ solution. Only the version attribute values of the candor package depedencies should be replaced.
<dependencies> <dependency id="Candor.Security" version="0.6.1.30220"/> <dependency id="Candor.Web.Mvc" version="0.6.1.30220"/> <dependency id="T4MVC" version="3.4.0" /> <dependency id="T4MVCExtensions" version="3.4.0" /> </dependencies>
The unique portion of the find expression below is ‘[^”]+”‘. This means anything inside the node attribute except a double quote. Before that is a specific name ‘Candor’ which is my root namespace. Everything before the version number attribute value is in the first capture group. The third capture group is the node ending ‘/>’; of which the > is replaced with %3E. This isn’t an MSBuild special character, but just an xml character that needs to be escaped. You could use > instead of %3E since this is to escape the xml character, not to escape an MSBuild recognized character. I kept with the ASCII codes to be consistent with the rest of the expression escaped characters.
<ItemGroup> <RegexTransform Include="$(TransformNuSpecs)"> <Find>(dependency\s*id="Candor[^"]+"*\s*version=)"(\d\.\d+\.\d+\.\d+)"\s*(\s*/%3E)</Find> <ReplaceWith>$1"$(Version)"$3</ReplaceWith> </RegexTransform> </ItemGroup>
Update Aug 19, 2013: The sample candor-common project no longer uses this technique. Instead, I am moving it to version each project in the solution independently. Look for more articles on Build Process.
MSBuild Special Characters
Full release.proj MSBuild file