Source Code Only NuGet Packages
NuGet Packages are the way how people distributing reusable components for some time now, it is the npm of .NET (just better).
It happens that someone like to share source code as reusable pieces and not binaries. “Why not compile and package it” — you are asking.
I can think of multiple scenarios when this can be useful, and I’m sure others will come up with additional good ideas as well:
- The code you want to share is meant for setting up some internal things in your system and does not meant to be part of your publicly exposed types.
- The code contains some useful helper, utility classes that’s used by other components within your system and/or can be useful for others as well, but only if consumed as source code, hence the type is internal.
- Last but not at least you are a framework developer and want to share a reusable “zero configuration” host for your framework. Of course can compile it and distribute as a binary exe in a NuGet package, boom, you are done. But then your consumers will face the “worst of all fears”: Binding Redirect errors. This was a thing that happened to us in the past when I was working on Microsoft Orleans and we had the Microsoft.Orleans.OrleansHost package. It would be much easier if we would be able just to release it as a source code only package which gets included in your own host’s source code.
Some background on how I got to the solution
If you just like to get the solution, grab the gist and run 😉
Personally I only saw one place where source code packages was utilized: ASP.NET Core.
When looked at it closer I noticed that these are just folders and no csproj files there, so what the heck I thought and moved on.
Last week I had a need to have source code only packages, so I dived in on how it is done in the ASP.NET Core repository. As I’ve a hacker inside of course I wanted to start from the result and reverse engineer the NuGet package, so I went on nuget.org and searched for one of the packages: Microsoft.Extensions.Buffers.Sources. This is what I saw:
This was not what I expected because I remembered, that I saw sources packages consumed by other ASP.NET Core projects like MVC:
I also remembered that ASP.NET Core is built by their own set of tools named KoreBuild, now it is just simply BuildTools. So I jumped in and checked out how these packages are built and extracted a reusable csproj file in a gist:
During the process I reached out to 2 of the few reputable guys in the MSBuild space: David Kean and Nate McMaster, because I did not know is it Project System or MSBuild related thing I am looking for:
And a pointer from Nate came pretty fast:
After I found a working solution and shared, Nate titled the used Sdk import MSBuild feature as a pro-tip.
Hmm, so this thing even has a name.
With the arrival of the new Project System we got a much simpler csproj, only the required pieces are inside, we get a lot of infrastructural thing defined by the .Net Core Sdks for us. All csproj files are doing an automatic import of an Sdk. If you wanted to override something Sdk defined thing in back in the early days you wanted to fiddle with reordering of tasks and other MSBuild dark magic things.
Ok, dig into the solution and some “newish” MSBuild features
A normal csproj file looks like this, specifyes to use an Sdk:
This imports a .NET Core Sdk and everything will be handled for you.
What was new for me is that the Sdk can be specified as a separate element:
Another possibility — which gives the ultimate customization possibility — is the explicit import of the Sdks as Top and Bottom imports. With the new Project System we already knew that MSBuild imports the Sdk props file before our csproj and imports the Sdk targets file after our csproj. With this feature we can control this import to happen, so we can define or redefine properties, items or targets for MSBuild’s evaluation phase, so the execution will happen with our overrides. Here is the csproj file with explicit imports:
No matter which one of the first 2 versions you’re using, MSBuild is ending up adding 2 implicit imports, so in memory you’re ending up with the 3rd solution.
If you’re curious, this is the code that does this for the Project element.
Specifying Sdk versions
Now, as we saw how can we specify the Sdk, lets add some spice to it, by telling MSBuild which version we like to use or if not available, which minimum version we are depending on.
In the background, what MSBuild parser does is adding 2 implicit imports, one to the top and one to the bottom of your csproj file.
You can use this format:
Note: based on this code I think only 2.1.0 or min=2.0.0 can be specified on the Project element and I think it is a bug.
If you use the Sdk element or the Import element you can add the same thing in a Version and MinimumVersion respectively.
Enough about these useful MSBuild features, jump into the Source Code NuGet packages!
Dissecting the gist line by line
I go over the gist and will try to add some reasoning why or what was specified/changed with that given property, so perhaps you’ll not treat that csproj file as a black box, when you’re using it.
Instruct MSBuild to make this project a target for the NuGet task and if someone invokes the Pack target on the whole solution or just on the project, then create a NuGet package from this project:
<IsPackable>true</IsPackable>
This project has only source files and we don’t like to copy any build artifact to be included as “build output”, since that can become a target for packing:
<IncludeBuildOutput>false</IncludeBuildOutput>
Tell NuGet where do we want the content files within the NuGet package:
<ContentTargetFolders>contentFiles</ContentTargetFolders>
The Sdk impicitly adding package references for NetStandard or similar framework libraries, we’ve to make sure we’re not getting any of those:
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
Now it is a handy thing that we don’t have to generate, maintain an AssemblyInfo.cs file in the Properties folder 😉 because the Sdk handles the generation of such file, but in this case we don’t need it, because if would become part of the Compile items and would get into the NuGet package, so disable it:
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
The Sdk by defaults generates a file containing a TargetFrameworkAttribute file into your Temp directory we don’t need that:
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
Because of the above statement we don’t have an attribute value defined and the C# compiler would create a “CS8021: No value for RuntimeMetadataVersion found.” warning, so we’re simply disabling it:
<NoWarn>CS8021</NoWarn>
This seems to be redundant with the latest 2.1 Sdk and not needed, because the next property below will trigger to set this one to true, but cannot hurt to be explicit about something when it comes to MSBuild.
This property just tells NuGet to make sure that there is no Build target executed before generating the NuSpec file:
<NoBuild>true</NoBuild>
This property tells to generate the NuGet package as part of a regular build too, not just if someone executes the Pack target explicitly:
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
We’re done with the properties, let add some content, because that’s why we here for.
We can put source files — which are behind the Compile item in the MSBuild or resource (resx) files into NuGet Packages.
This 2 item definition will update the definition for these elements to add the NuGet specific properties. We’re explicitly saying that we want to Pack them and specifying where do we want them inside the package:
I had to include it as an image, because code block formatting on Medium is not the best.
As a cleanup we’re making sure that no NuGet package references are getting into our package, so remove them:
<PackageReference Remove="@(PackageReference)" />
And lasts but not at least we’ve to rewrite the Compile and CopyFilesToOutputDirectory targets to be a “do nothing” task. You can see that THIS is the point where the explicit importing of the Sdk has a big role, without that you would not be able to do this in such a simple way:
<Target Name="Compile" />
<Target Name="CopyFilesToOutputDirectory" />
It was simple, isn’t it?!
Oh, one more thing on how to use it
Copy the full gist as it is into a folder, add some C# and/or Resx file build and distribute your NuGet packages. Since we’re using a standard Sdk, versioning, Directory.Build.props/targets file will just work the same way as for other projects.
Edit: Yishai Galatzer, who was the leader of the NuGet team back in the day, when this feature was implemented on the NuGet side, added some history what I don’t like it to vanish in twitterverse, so here it goes:
The csproj gist can be found here.
The versioning csproj sample files are here.