Tips, Troubleshooting

How NuGet Chooses the Nuspec File When Building From a Project File

If you have a class library project, it’s a good idea to learn how to use NuGet to package the resulting assembly. It’s a good idea even if you do not plan to share your assembly with the world, because you can still take advantage of the smarts built into NuGet to make managing your own packages easier. It’s not very hard at all to get up and running with your first package, and then you can start customizing from there.

Once you start customizing, you begin to realize there are some scenarios where you might want some slightly different packages. For example, maybe you have some code contract assemblies, but you don’t always want them included. Perhaps you have a library and a shell, and you want to give people the option of downloading just the library in one package, but to download both in another.

I have a scenario like that in at least a couple of my projects. I copied my nuspec file, customized it, added it back to my project then setup a script in the post-build event to create both packages. Worked great the first time I tried it, then failed when I tried to replicate the process in the next project. In the both projects, I kept the “easy” path (described below) which builds against the project file and finds the nuspec on its own. In both projects, I used the same scripts and naming conventions. Yet when it came time to create the project-based package the first project correctly selected the nuspec file that belongs with the project-based package, while the second project consistently selected the nuspec file that belongs with the customized package.

I searched around for an answer to how NuGet was making its decision, but could not find anything. Maybe I wasn’t using the right search terms, but I decided it would be best to stop being lazy and just go read the source. I have written a few CLI applications over the years, but I have to say I have never designed one as nice as NuGet. Next time I have a CLI to write, I’m going to study NuGet more carefully before writing any code.

As you may have guessed by the title of this post, I did find the answer to my question. You can go read the source, or you can read this post.

(Go ahead and read the source anyway, it is great.)

The Easy Way

The easiest way to get your assembly into a NuGet package is to follow the outline here: Creating and Publishing a Package. First download NuGet.exe then skip down to the section under the heading “From a Project.” Read the docs.nuget.org page for full details, but it boils down to this once you are used to the process:

  • Open command window and navigate to your project folder. I will assume your project is called “foo.csproj”
  • nuget spec
  • notepad foo.nuspec
  • Delete a few lines of junk from the nuspec then save.
  • nuget pack foo.csproj –Prop Configuration=Release

Done, now you can copy your package to your local package source or upload it to NuGet.org.

The Question

Now you want to create a custom nuspec to go with the one that you generated with NuGet.exe. Let’s call it “foo.custom.nuspec.” Now when you run “nuget pack” the process may or may not pick the correct nuspec. If it picks the custom nuspec file, it will build the incorrect package. After all, the premise is that you want both the default package and a custom package. If NuGet does pick the wrong metadata file, how do you get it to change its mind? You could be like me and try a bunch of things.

  • Maybe it’s picking the newest file?
  • Maybe it’s picking the oldest?
  • Shortest file name?
  • Longest?
  • Blah, blah blah, blah blah blah

Actually, I didn’t try all of those. I did try a couple, and then I went to the code.

The Answer

Here is the code that selects the nuspec file in version 1.6:

private string GetNuspec()
{
  return GetNuspecPaths().FirstOrDefault(File.Exists);
}

private IEnumerable<string> GetNuspecPaths()
{
  // Check for a nuspec in the project file
  yield return GetContentOrNone(file => Path.GetExtension(file).Equals(
      Constants.ManifestExtension, 
      StringComparison.OrdinalIgnoreCase));

  // Check for a nuspec named after the project
  yield return Path.Combine(
    _project.DirectoryPath, 
    Path.GetFileNameWithoutExtension(_project.FullPath) + Constants.ManifestExtension);
}

We can see right away that the search ends as soon as it finds an existing file that ends with “nuspec.” We can also see that the process prefers to get that file from the project file before it goes looking in the project directory. The “GetContentOrNone” method looks at the project file and chooses files marked with build action “Content” or “None” and yields back the matches. That was the key for me to figuring out why NuGet picked out the wrong file.

I added my nuspec files to the project for convenience. In one project, I had decided from the start to keep both Nuspec files, so I left the original alone and created a copy, renamed and added the copy to the project. This caused the new file’s ProjectItem to appear after the original in the csproj xml file.

In the other project, I had only thought that I wanted to customize the nuspec, then realized later that I wanted both. In the second project, I had customized the nuspec that was already there, then generated a new “default” nuspec, then added the new default to the project. This caused the default nuspec file’s ProjectItem to appear after the customized nuspec in the project xml. The order of operations mattered. I opened the misbehaving csproj in a text editor, rearranged the order of the ProjectItems, and tried again, problem solved.

Summary

For easy reference, here is the search path NuGet takes to choosing the file when the pack target is a project file:

  1. Project Members are preferred
    • NuGet checks items that have “None” or “Content” build actions for the .nuspec extension.
    • The first of these found is used.
  2. If there are no nuspec files in the project, NuGet tries to find a .nuspec file in the project file’s directory with the same name as the file.

Maybe this information is already out there somewhere, but if it is, I had a hard time finding it. If you have this problem, I hope this helps you.

2 thoughts on “How NuGet Chooses the Nuspec File When Building From a Project File

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s