CategoryNuGet

Add msbuild parameters for NuGet to the TFS build process template

NuGet packages can be a great artifact of a build and it can easily be integrated in your build process.
Make sure the NuGet targets file is imported in the projects which should generate a NuGet package. Please read my previous post about NuGet to see how you can integrate NuGet in your solution.
By default, NuGet registers itself in a project file as follows:

<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />

The targets file contain a BuildPackage target which can be triggered by setting an msbuild parameter named BuildPackage to true.
Now we need to create a property in the Build  template which sets the BuildPackage parameter in the msbuild command in the build template.

Open Visual Studio and open the Team Explorer window. Click on Builds to display your TFS Builds. When you right click on a Build, you can select “Edit Build definitions”. This will bring up the edit screen for the selected Build.
Now click on the “Process” tab. This will display which Build template is associated with the Build and what the parameters are.

Click the “show details” icon in the top right corner to see the template.
The template is a Windows WorkFlow Foundation (WWF) template.  The xaml file is an xml based file which creates workflows in applications.
Edit the .xaml build process template in an xml editor, I prefer notepad++.
Find <x:Members> and add the following (do not remove any existing child elements):


<x:Members>
   ....
   <x:Property Name="NuGet_BuildPackage" Type="InArgument(x:Boolean)" />
</x:Members>

Now that we’ve added the properties, we can add them to a section.
Find the <this:Process.Metadata> element and add the following items (and again, do not delete any existing child elements):


<this:Process.Metadata>
    .....
    <mtbw:ProcessParameterMetadataCollection>
    <mtbw:ProcessParameterMetadata BrowsableWhen="Always" Category="#400 NuGet" Description="Set this to true to create NuGet packages" DisplayName="NuGet : Build Package" ParameterName="NuGet_BuildPackage" />
    </mtbw:ProcessParameterMetadataCollection>
</this:Process.Metadata>

This will create a section called “NuGet” and add the parameters to this section. The #400 sets the priority of the section, it will be positioned in fourth place (just beneath the default sections).

build-process-parameters

Now that we can set the parameters in the build process, we need to provide them as build arguments to msbuild.
Switch from the xml editor to the Workflow editor and find “Run MsBuild for project“.

Edit the command arguments in the properties window and add the new build property to the CommandLineArguments property:


String.Format("/p:SkipInvalidConfigurations=true {0} /p:BuildPackage={1}", MSBuildArguments, NuGet_BuildPackage)

commandline-arguments

You can now enable or disable NuGet package Generation from the build process window of your build. Creating NuGet packages is triggered for every project which has the NuGet targets file imported.

When you want to push the NuGet packages to the NuGet repository, take a look at NuGets push command.  This can also be implemented by an msbuild task.

Continuous delivery, NuGet package generation and integration with TFS build server

Continuous Integration is getting more important and more popular and that is a good thing!
A couple of days ago, I was automating the NuGet package generation process. NuGet packages can be used for deployments to environments.

NuGet integrates in your solution when package restore is enabled.

enable-package-restore

This adds the following files to the .nuget folder in your solution:

  • NuGet.exe
  • NuGet.targets
  • NuGet.config

It also adds an import command for the NuGet.targets file to the project file:

<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />

One target of the targets file is BuildPackage. This target can be triggered by running the following msbuild command from the command line:


msbuild mysolution.sln /t:Build /p:BuildPackage=true

This will create a NuGet package for all projects which have imported the NuGet.targets file.
The following files will automatically be added to the NuGet package:

  • The project assembly
  • Files with build action set to content

For simple projects, this behavior is fine. The NuGet package holds the exact content of the project.
In my case, this was not really what I wanted. I wanted one NuGet package for multiple projects and the content files had to be placed in a different structure and location.

That’s when a nuspec file comes to the rescue!

The NuSpec file is the manifest file for NuGet packages. This file holds all information required for the NuGet packages, for example;

  • Package meta data (name, author, copyright, etc.)
  • Dependencies
  • Included Files

For a complete reference of the nuspec file, see http://docs.nuget.org/docs/reference/nuspec-reference.

I added a reference to the assembly1 and assembly2 project and added a nuspec file to the root of the project.
In the nspec file, I added the required assemblies and content files. The nuspec files section looked something like:

  <files>
    <file src="bin\assembly1.dll" target="lib\net40" />
    <file src="bin\assembly2.dll" target="lib\net40" />
    <file src="..\assembly1\Scripts\**\*.*" target="Content\Scripts" />
    <file src="..\assembly2\Scripts\**\*.*" target="Content\Scripts" />
  </files>

I ran the msbuild command on my dev environment and a package was created successfully and contained all files.
It also contains the dll of the NuGet project itself. This is not what I wanted.
I created a naming convention for all NuGet projects, the projects end with NuGet. This gives me the option to exclude the *nuget.dlls from the packages. This can be added to the build command I will describe later in this post.

Files added to the Content folder of a NuGet package are installed on the root of the target the project (the project which the NuGet package is installed to).
Assemblies added to the lib folder will be referenced in the target project. A subfolder in the lib folder can be used for providing different assemblies for a framework (.Net 3.5, .Net 4, etc).

I configured the TFS build server to run the same command and started a build.
The build failed, because NuGet could not find some of the referenced files in the files section of the nuspec file.
When I used RDP to login to the build server, I noticed that the output folder (binaries folder as it is called in the TFS build workflow template) is changed by TFS. It does not use the bin folder of every project, but it changes it to a different folder.
That is why bin\assembly1.dll and bin\assembly2.dll could not be found.

Creating packages should work on both dev environment and on the build server, so referencing the absolute output path on the build server is not an option.
The output folder is stored in the OutDir parameter. This parameter needs to be available in the nuspec file. This can be achieved by adding it to the properties list of the build command.
The build command is stored in the targets file, add “OutDir=$(OutDir.Trim(‘\\’))\\” to the property parameter of the build command. You can also exclude files by adding an Exclude parameter. This is what I have done to exclude the *Nuget.dll files.
The updated BuildCommand is as follows:

<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform);OutDir=$(OutDir.Trim('\\'))\\" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols -Exclude **\*NuGet.dll</BuildCommand>

It is now possible to use the OutDir variable in the nuspec file:

  <files>
    <file src="$OutDir$assembly1.dll" target="lib\net40" />
    <file src="$OutDir$assembly2.dll" target="lib\net40" />
    <file src="..\assembly1\Scripts\**\*.*" target="Content\Scripts" />
    <file src="..\assembly2\Scripts\**\*.*" target="Content\Scripts" />
  </files>

This solves the output issue on the build server and the build still works on the dev environment.