Monday, November 7, 2016

Delivering files using NuGet 2 so they're copied to the output directory

I recently had a situation where a NuGet package I was creating had to deliver some supporting DLLs which needed to be copied to the output directory (actually into a sub-directory named /x86). We're using NuGet 2 at work because our internal NuGet feed is managed via ProGet, which doesn't yet support NuGet 3.x. While NuGet 3.3+ gives the copyToOutput option to have content files delivered to the output directory, there's no such support in NuGet 2. In researching how to do this I came across two very helpful posts on StackOverflow which gave me the direction I needed. The posts were http://stackoverflow.com/a/30386836/123147 and http://stackoverflow.com/a/30316946/123147.

The approach I took was to have the files I needed to distribute be included in the NuGet package in the /build folder, as opposed to /content. In fact, I put them in a sub-folder named /x86 which was the desired target location in the output directory. I then used an MSBuild targets file to mark all the files in that sub-folder as always needing to be copied to the output directory. I had not worked with MSBuild targets (or properties) files before - so this approach was totally new to me.

This blog post seeks to expand on using a MSBuild targets file in a NuGet package. The NuGet documentation talks about this technique - but I thought a more step-by-step set of instructions might help others.

The sample code I'm providing in this post is a little different from what I did at work in that there's no sub-folder created in the target directory. I am using a sub-folder in the NuGet package's /build folder, though, so don't have to act on each file individually.

The sample scenario

The contrived example for this post is to create a package which delivers files to the output directory of any project that installs my NeededFiles package; an image and a readme file.

What does an MSBuild targets file do?

If you were to open a Visual Studio project file (e.g. csproj or vbproj) in a text editor you would see it's an XML format which defines properties and actions (i.e. "targets"). I found it pretty similar to a NANT script. What a targets file lets us do is define a target that can be imported into the project file later. The NuGet documentation explains that when a package includes a targets file with the same name as the package in its /build folder the NuGet installation will add an import element to the project file.

What's in the NeededFiles package?

The NeededFiles.nuspec file looks like this:
1. <?xml version="1.0"?>
2. <package >
3.   <metadata>
4.     <id>NeededFiles</id>
5.     <version>1.0.0</version>
6.     <authors>@Rob_Hale_VT</authors>
7.     <requireLicenseAcceptance>false</requireLicenseAcceptance>
8.     <description>
9.   An example of delivering files to the consuming project's output via build targets
10.  </description>
11.     <releaseNotes>Created</releaseNotes>
12.     <copyright>Copyright 2016</copyright>
13.     <tags>Demo</tags>
14.   </metadata>
15.   <files>
16.  <!-- In this instance, the file(s) to be delivered are in the sub-folder named "howdy" -->
17.  <file src="howdy\*.*" target="build\howdy" />
18. 
19.  <!-- The targets file must have the same name as this package -->
20.  <!-- More info: https://docs.nuget.org/ndocs/create-packages/creating-a-package#including-msbuild-props-and-targets-in-a-package -->
21.  <file src="NeededFiles.targets" target="build" />
22 .  </files>
23. </package>

The key lines for what I'm doing are 15 - 22, where the files element is defined. On line 17 I'm added all the contents of a folder called "howdy" to the /build/howdy target folder. This folder contains the image and readme file I want to have delivered

Additionally, line 21 adds the NeededFiles.targets file to the /build target folder.

What does the NeededFiles.targets file do?

Now that the NeededFiles package includes the /build/NeededFiles.targets file we can take a look at what it does.
1. <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2.   <ItemGroup>
3.     <NativeLibs Include="$(MSBuildThisFileDirectory)howdy\*.*" />
4.     <None Include="@(NativeLibs)">
5.       <Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
6.       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
7.     </None>
8.   </ItemGroup>
9. </Project>

This file is imported by the consuming project. This means when the project is built line 3 declares an element named NativeLibs which is comprised of the contents of the howdy folder (delivered as part of the NeededFiles package). Lines 4 - 7 are acting like a loop, instructing MSBuild to mark each file in NativeLibs so they are always copied to the output directory.

If I wanted the files to all appear in a sub-folder of the output directory I could modify line 5 to be:
<Link>%(RecursiveDir)howdy/%(FileName)%(Extension)</Link>

Summary

At this point the package will deliver the contents of the Howdy folder to the output directory of any project that installs the NeededFiles package. Unlike files marked as content, the files in the build target folder don't even appear in the Solution Explorer in Visual Studio. "It just works."

The source for my contrived example, NeededFiles, can be found at https://github.com/robhalevt/NeededFilesExample