Making a nuget package for managed code is really straightforward, since it is so extensively documented on Microsoft’s various websites.
But if you want to make a nuget package that contains native code like libraries (*.lib), headers (*.h) you are almost out of luck! Microsoft will give you about 3 minuscule paragraphs full of cryptic junk for documentation!
The first key to understand how to put C++ stuff into a nuget package is to understand that a nuget package (*.nupkg) is really just a zip file that has been renamed. Therefore you should just be able to stick anything in there that you like.
The second key is that we will just be using Nuget to download and unzip the nuget package for us. After that we are on our own.
The third key is that none of the Visual Studio versions will offer any aid at all in hooking up the nuget package to the project that needs it. It is up to you to break open your text editor and modify your visual studio project files (*.vcxproj etc.).
Here is a high-level summary of what needs to be done, and will be explained in detail:
- Gather or stage your native library files into a folder of your choosing.
- Create a *.nuspec file in that folder.
- Edit the *.nuspec file to include the files you want to include in your package.
- Create a *.props file
- Call nuget pack to create the package.
- Push the nuget package to a feed somewhere.
- Create a packages.config file.
- Edit the visual studio project file to point to where the restored nuget package is.
NOTE: In this document I will be using the Google Filament renderer library as my demonstration example, since I had to do that here at work recently.
Stage the native files
This should be easy, copy all the files you need for your native library to a a convenient folder. For example:
Create the *.nuspec file
I like to put this *.nuspec file inside of the directory that has the code I’m packaging up. That makes it easier and simplifies the paths we will put inside the *.nuspec file. For my example, mine is filament.nuspec:
Edit the *.nuspec file
A nuspec files is an xml file and hence must follow the syntax requirements as documented on microsoft’s website:
I chose to have the package contents follow the same layout as how Google gives them to me. Thus my nuspec package looks like this:
<?xml version="1.0"?> <package > <metadata> <id>Google.Filament</id> <version>2019.08.08</version> <description>Google Filament Renderer</description> <tags>Native, native</tags> </metadata> <files> <file src="lib\**\*.*" target="native\lib" /> <file src="include\**\*.*" target="native\include" /> <file src="docs\*" target="native\docs" /> <file src="bin\*" target="native\bin" /> <file src="README.md" target="native" /> <file src="filament.props" target="native" /> </files> </package>
The most important part here is the <tags> element that contains the text ‘native’. To be doubly sure I got it right, I added it twice, the first time with a capital ‘N’. This is useful when the package is hosted in nuget.org, you can do a search for native packages by using the search term:
Each <file> xml element has two attributes: a ‘src’ attribute and a ‘target’ attribute. Source is where it gets it’s files from, and Target is where the files will be placed when the nuget package is restored or unzipped. What you specify here is completely up to you. As you can see in my example above, I have also created a filament.props file. I will be using this later to make it simpler for any project to consume this nuget package.
Also I added a root folder called ‘native’. It worked with it, I haven’t tested it without a root folder. But you can give this root folder any arbitrary name you want.
Create a filament.props file
This will aid us later on in consuming the nuget package from another C++ project. Here we will create an MSBuild file (NOT a .vcxproj file) that will describe where the include files and library files are. This step requires a good understanding of the MSBuild xml syntax and especially a good knowledge of C++ project file (i.e. *.vcxproj) syntax. If you don’t know that, just copy and paste my code here:
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="15.0"> <PropertyGroup> <LibraryType Condition="'$(Configuration)'=='Debug'">mdd</LibraryType> <LibraryType Condition="'$(Configuration)'=='Release'">md</LibraryType> </PropertyGroup> <ItemGroup> <FilamentLibs Include="$(MSBuildThisFileDirectory)\lib\x86_64\$(LibraryType)\*.lib" /> </ItemGroup> <PropertyGroup> <!-- Expland the items to a property --> <FilamentLibraries>@(FilamentLibs)</FilamentLibraries> </PropertyGroup> <ItemDefinitionGroup> <ClCompile> <AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)\include</AdditionalIncludeDirectories> </ClCompile> <Link> <AdditionalDependencies>$(FilamentLibraries);%(AdditionalDependencies) </AdditionalDependencies> </Link> </ItemDefinitionGroup> </Project>
This specifies all the library files using a wildcard pattern (*.lib) and it points to where the include directory is too. It uses the special reserved msbuild property $(MSBuildThisFileDirectory) which helps anchor the paths that are being used inside of this file.
Call Nuget Pack
Now comes the fun part, to create the nuget package. I run a simple batch script like this:
NuGet.exe pack filament.nuspec -OutputDirectory builds\nuget\x86_64
Thereupon I can look in my
builds\nuget\x86_64 directory and see a nuget package named
Call Nuget Push
Now that the package is created, it’s time to push it up to a nuget feed. Which nuget feed you push up to is up to you, and is none of my business. But nuget.org is the defacto source. But if you have a private feed you use for your company that works too. I run a simple batch script like this:
NuGet.exe push builds\nuget\x86_64\*.nupkg -Source https://<some nuget url> -Apikey <some api key>
Create a packages.config file
This part is super easy. Create a packages.config file in the same directory as the visual studio project file (*.vcxproj) that will be consuming the nuget package.
Place the following xml snippet into the file:
<?xml version="1.0" encoding="utf-8"?> <packages> <package id="Google.Filament" version="2019.8.8" /> </packages>
Notice how the version matches what was put inside the *.nuspec file.
Edit the visual studio project file
This step involves a text editor (I prefer Notepad++ or Visual Studio Code). This step is also markedly different from what we would use if we were using visual studio to find a managed .net nuget package for a managed .net project.
But since this is a native project, we don’t get the big boy tools, and have to settle for the hand-me-downs from Microsoft.
First we will open the *.vcxproj file that will be consuming this project, and will simply add the following line of xml code at the end of the file:
<Import Project="<Your package directory>\Google.Filament.2019.8.8\native\filament.props" />
Where your packages go, is up to you when you do a nuget restore. So where-ever that is, you will have to change the snippet <Your package directory> to point to where-ever it was that nuget unpacked the files. This can be a relative or an absolute path, so it’s up to you. But in the end, msbuild needs to be able to find that *.props file, otherwise it won’t load in visual studio.
And that is it. The package is done, and you should be able to do a nuget restore and build your native project. Many of the steps were the same as creating a managed .NET nuget package. With the exception of the manual editing of the native project files.
If you are looking for more examples of nuget packages holding native projects, do a search on nuget.org, and find a project. Browse to the page for the nuget package and find the link to the ‘project’ code page for it. Usually on github.com. Click on the link to open the project in Github and then hunt around for a *.nuspec file.