I dove very deeply into Nuget packages this week, and found some pretty useful steps in controlling how my package can control properties in the consuming project.
Problem at Hand
It’s been a long time since I built anything more than a trivial Nuget package. By “trivial” I mean telling the .Net project to package the version, and letting it take care of generating the Nuget package based on the project. If I added a .nuspec
file, it was for things like description or copyright.
I am working on building a package that contains some of our common settings for Unit Test execution, including Coverlet settings and XUnit runner settings. Additionally, I wanted the references in my package to be used in the consuming package, so that we did not need to update xunit
or coverlet
packages across all of our Unit Test projects.
With these requirements, I needed to build a package that does two things:
- Copy settings files to the appropriate location in the consuming project.
- Make sure references in the package were added to the consuming project.
Content Files… What happened??
It would seem that the last time I used content files, packages.config
was the way to include Nuget packages in a project. However, with the migration to using the PackageReference
attribute, some things changed, including the way content files are handled.
The long and short of it is, packages are no longer “copied on install,” so additional steps are required to move files around. However, I wanted those additional steps to be part of my package, rather than added to every referencing project.
Out comes “props and targets.” You can add .props
and/or .targets
files to your package, using the same name as the package, and those props will be included in the consuming project. I have to admit, I spent a great deal of time researching MSBuild and how to actually make this do what I wanted. But, I will give you a quick summary and solution.
My Solution
myCompany.Test.UnitTest.Core.props
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<RunSettingsFilePath>$(MSBuildThisFileDirectory)\coverlet.runsettings</RunSettingsFilePath>
</PropertyGroup>
<ItemGroup>
<Content Include="$(MSBuildThisFileDirectory)xunit.runner.json">
<Link>xunit.runner.json</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>False</Visible>
</Content>
</ItemGroup>
</Project>
myCompany.Test.UnitTest.Core.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<OutputType>Library</OutputType>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" PrivateAssets="None" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" PrivateAssets="None" />
<PackageReference Include="xunit" Version="2.6.3" PrivateAssets="None" />
<PackageReference Include="xunit.runner.utility" Version="2.6.3" PrivateAssets="None" />
<PackageReference Include="JustMock" Version="2023.3.1122.188" PrivateAssets="None" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" PrivateAssets="None" />
<PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="None" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5" PrivateAssets="None" />
</ItemGroup>
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="Always">
<PackagePath>build</PackagePath>
</Content>
<Content Include="coverlet.runsettings" CopyToOutputDirectory="Always">
<PackagePath>build</PackagePath>
</Content>
<Content Include="myCompany.Test.UnitTest.Core.props" CopyToOutputDirectory="Always">
<PackagePath>build</PackagePath>
</Content>
</ItemGroup>
</Project>
Using Content Files
We have a common settings file (xunit.runner.json
) that needs to be copied to the output directory of the Unit Tests, so that XUnit runs with common settings. To accomplish this, I added the file to my package’s .csproj
file as a content file (lines 21-23 in the .csproj
file). Then, in the .props
file (lines 7-11), I added a step to ensure that the file is copied to the output directory.
For the coverlet.runsettings
file, all I need is to make sure that the consuming project uses that file as for its run settings. I set the RunSettingsFilePath
in the .props
file, and pointed it to the file in the package.
Using References
In the .csproj
file, you’ll notice that I have a number of references to packages with PrivateAssets="None"
configured. These packages have some assets that are “hidden” from the consuming project, meaning when these projects are installed as transitive packages, those assets are hidden.
In this case, I do not want to hide any assets from these packages, I want them to flow through to the consuming project. Setting PrivateAssets="None"
tells the consuming project that these packages should be used in full. This post and the MSDN documentation shed most of the light on this particular topic for me.
Miscellaneous Notes
You may have noticed that, for the settings files, I sent them to the build folder alongside the .props
file, rather than in content
or contentFiles
. Since these things are only used during build or execution of unit tests, this seemed the appropriate place.
It’s also worth noting that I went through SEVERAL iterations of solutions on this. There is a ton of flexibility in MSBuild and its interactions with the packages installed, but I ended up with a solution which requires minimal effort on the package consumer side. And, for me, this is the intent: keep it DRY (Don’t Repeat Yourself).