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.
This adds the following files to the .nuget folder in your solution:
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.)
- 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.