NuGet package build a solution of projects

NuGet package build a solution of projects

Have you ever created a .Net shared library solution with project references between them; and then wanted to share the projects as individual packages in a NuGet feed? When you run the NuGet ‘pack’ command on a project that has a project reference to another project in your solution, then the referenced project would get included in the package. That isn’t what you want to happen if you plan on making that referenced project a separate package.

Bad Solution: Package References All the Time

To deploy each library as a separate package they need to have package references between them. You can build the core assembly, and then make a post build task to copy it to an internal feed, and then reference that package from the other project. There are a few problems with this solution.

If you are publishing to an internal file share feed, then you will have problems with performance. Each compilation puts a new version in your feed. Then when the consuming library does a NuGet update on the post build event, it takes longer to parse the file feed the more package files are in it. Visual Studio nor NuGet cache metadata for file feeds; instead they reopen the compressed nupkg files on each update to check dependencies.

In order to make sure the new referenced package from your solution is used by the consuming library the build number must change on each build. This means lots of packages in that internal feed.

The biggest problem with this approach is you lose refactoring ability. If you refactor something in the shared library, the refactoring tooling does not realize that another project is referencing it as a package. So the consuming library does not get refactored with the core library change. You find out about that when the consuming library has a build error after the package is updated.

Better Solution: Build NuSpec Files by Script

Update Aug 19,2013: See ‘Best Solution’ section below for an even better solution for NuGet 2.5 or later.

You can leave project references between your projects and tweak the nuget packages as they are built for release. You also do not need to publish each build of your project to a feed. Just build and test as you normally would if NuGet was not involved. Then build a custom set of MsBuild tasks to create your NuGet packages.

II. Create a Project to Update NuSpec Files

NuSpec files can reference other packages that they depend on based on the packages.config in the project folder, plus any additional packages referenced in the nuspec file. Since this approach does not use NuGet references between the solution’s projects, we need to modify the nuspec files to add them as dependencies before the nupkg files are generated.

Create a new console application in your solution (optionally, hidden under a ‘Release’ solution folder per the screenshot) called PrepareNuSpecsForRelase. To determine project references that are also to become NuGet packages, we need to parse the project files. Unfortunately there is no public API by Microsoft to do this, but John Leidegren answered a question on stackoverflow with a way to read solution and project files. Add a class called ‘Solution’ with this code in the PrepareNuSpecsForRelase project.

The Program.cs entry point of the console application needs to take in the information about the solution and the desired version number of the NuGet packages to output.

Once we have a list of projects we need to clean up any dependencies added during the last release. Clean up is needed in case one of the dependencies from last build is no longer a dependency, such as due to a refactoring. This next code block iterates over all the projects that are to become NuGet packages and removes all dependencies that are NuGet packages. This just removes them from the dependencies node in the nuspec file; It doesn’t delete the reference in the project file.

Now we need to iterate over the projects again, and this time we want to add back in any dependencies to the nuspec file for project references where that project is also to be built into a NuGet package. We tell that by checking if it has a nuspec file in the folder matching the project name.

The code above calls into a bunch of other support methods listed below. All of these methods are making some assumptions. First, all projects in the solution that are packaged for NuGet will have a nuspec file of the same name as the project file, where the only difference is swapping out the .csproj extension with a .nuspec extension.

Notice this code does not add dependency nodes for other NuGet packages that your projects may reference. Those are read by the NuGet pack command from your packages.config. Since the project references within this solution are not package references, NuGet will not know it will eventually be a NuGet package. Maybe a future version of NuGet will know this, and you won’t need to follow the directions in this article?

These helper methods in the console application are just XML DOM manipulation following the assumptions above, so I won’t explain them in detail.

III. Create a Release Project File

It is best not to modify the project files for building the NuGet package release. Doing so could slow down your frequent build and unit test flow. Created a dedicated project file for the purpose of release means that only an intentional release process needs to go through the extra steps to build packages. You also don’t want to end up with a NuGet release folder for every build you do during development.

A Release project file is just an MSBuild format file describing what to do at release time. If you open it and compare it to any other .net project file, it will look very similar. Create a project file in a ‘Release’ folder under the solution and call it “Release.proj”. Scott Kirkland supplied a good starting point in his article on “Simple MSBuild Configuration: Updating Assemblies with a Version Number“. Scott calls the file Build.proj, but he is only doing a build. We are creating a release to NuGet, which is why we are calling it release.proj.

One deviation from Scott’s MSBuild file is that this file updates version attribute in a SolutionInfo.cs at the solution root, instead of in each individual project file. In shared libraries I like to have the same version on all assemblies in the solution. So I put that attribute one time in the SolutionInfo.cs file, and then link that file into all the projects. Then I remove the version attribute from all the project AssemblyInfo.cs files.

The next step is to build on top of that build file and add steps for running unit tests (you have some, right?), preparing the nuspec files, and then running the ‘nuget pack‘ command. You can also add FxCop checks by un-commenting that section.

As shown by the ‘DefaultTargets’ attribute on the ‘Project’ node, the default ‘Go’ target will be called when this project is built. Skip down to that section to see what it does. To see the whole file in one uninterrupted block, check it out on github.

Replace these next three projects and the following Unit test project with the relative paths of your shared libraries and name of your unit tests project. You can have more than one unit test project. Just check out where the $(UnitTestsProj) variable is used elsewhere in this build file to see what you need to do. You can also have as many shared libraries as you want in this build file. Just make sure any you add/remove are reflected in the other sections of this file below.

Replace these first three projects with however many shared libraries you want from your solution. They must reference the same projects you replaced above. You should rename these and the corresponding nodes they reference above. But also remember to replace that new name in the whole file. There are other places below to update also.

The Go Target runs each of the other sections in order as shown in the ‘DependsOnTargets’ attribute. CheckFxCop is currently disabled because that section is commented out. The first task run is the ‘UpdateVersion’ which is described in Scott Kirkland’s article. Next the EnsureNuSpecPreparationToolBuilt, PrepareNuSpecs, and at the very end we RunTests and then finally BuildPackages. The other steps ensure folders are created and build the project, and optionally run FxCop when uncommented.

This is where we execute the PrepareNuSpecsForRelease command line project to ensure all the NuSpec files are ready for the nuget pack command. Notice we pass in the solution file name and the desired version number of the nuget packages to match the version of this build. Also notice above we made sure the PrepareNuSpecsForRelease project was built and ready to be run.

If the following unit tests fail, then the nuget release will not continue and there will be no packages created. If you really don’t have any unit tests (shame on you), then go ahead and comment out all the contents of the RunTests node.

Here is the third and final location where you need to update this MSBuild file with the names of the projects in your solution that are to become NuGet packages. The $(…) section of each Exec node after the ‘pack’ command is where you need to update with the names of the nodes you created above to represent those projects.

IV. Create a Release Console Command

As a start we can use Scott Kirkland’s Build.cmd. In that he gathers 2 parameters, one for build configuration and another for version number. However, I’ll stray a little and add a separate parameter for each version part, and always assume the Release configuration. After all, our command line project and script are called ‘Release’.

This calls msbuild in it’s install directory passing in the relative location to current where the msbuild file is located, and a parameter for each of the version attributes the msbuild file is expecting (per the comments in the build file).

So why is the build part of the version (4th place) not an option? I went along with the notion that the build number should just be based on the date. The release.proj file builds that value from the current date. The fist digit is the year marker (configurable, such as from the start of your project). The next two digits are the month, and the final two digits are the day. But it doesn’t really matter, since you should manually increment the patch, minor, or major version with every release anyway. Having something based on the date at the end helps you remember later when a given release was created.

V. What can go wrong?

You may have build errors. Your unit tests can fail. You may not have replaced nodes in the Release.proj file correctly, preventing the entire build from running. But most of those problems will supply a decent error message on the command line, or test results will appear in an xml output file. However, NuGet pack does not give a good error message on the command line. Here is one example.

To troubleshoot this you can run this command in your Nuget package manager console inside Visual Studio. It should give you a more detailed error message. If anyone knows of a command prompt argument for showing detail in this build script, then let me know please. I am using NuGet 2.2.31210.9045.

The problem is usually because the nuspec file in question is malformed or one of the replacement parameters fails. To generate this error, I emptied the Description attribute in my AssemblyInfo.cs file for this project. For some reason NuGet does not like the description attribute in the nuspec if the Description attribute is missing a value.

From the nuspec:
$description$

From the AssemblyInfo.cs
[assembly: AssemblyDescription(“”)]

To fix this particular issue, I just added a description value and the pack command works. After all, this is what people will see as the summary when deciding if they want to consume your package. So put something in there.

Best Solution: NuGet 2.5 Improved project reference support

Update Aug 19,2013: As of NuGet 2.5 (April 25, 2013) projects with nuspec files and that have project references between them will now be properly referenced in the appropriate nuspec files. So you can build and debug normally with project references. Then when you want to release, just run ‘nuget pack’ against each csproj file and it will update the nuspec file with any project references before creating the package.

I will now version ‘candor common’ libraries independently. I could keep them the same by leaving the version number in SolutionInfo.cs, but now I am not required to. I found that too many releases I only changed classes in one component, but since my build process updated all package versions I was publishing the update to them all. Now I will only publish the packages that change within the solution.

The only drawback to changing the ‘candor common’ build process so far is the update of package versions in the content only packages. I have two projects that just package a controller with it’s associated models and views. The NuGet update does not handle updating those package references in the nuspec. For now I am adding the nuspec file for those MSbuild projects to the solution items to help me remember when I need to update them. If it doesn’t work out I will fix the build process using MSBuild and write about it.

My new build process has a release.cmd file as follows. Note the content packages run MSBuild to copy select files from the bootstrapper MVC project, then follow with a ‘nuget pack’ to create the package. The ‘nuget pack’ command does not work against .proj files. I also tried renaming them to .csproj files and running nuget pack, but it failed with a generic error. I think the ‘includeReferencedProjects’ is what failed, since the content only projects have no references. The script as follows does work.