loading...

Include both Nuget Package References *and* project reference DLL using “dotnet pack” 📦

wabbbit profile image Rich Originally published at yer.ac on ・3 min read

Original: http://yer.ac/blog/2019/09/05/dotnet-pack-project-reference-and-nuget-dependency/

Recently I have been trying to generate more Nuget packages for our dotnet core projects, utilizing the dotnet pack command. One issue I have been encountering is that the command was either referencing the required nuget packages, or the project reference DLLs, never both.

The current problem.

If you have Project A which has a project reference to Project B as well as including a nuget package called Package A you would expect the generated package to contain a link to both the required nuget package, and the DLL(s) for Project B, yes? This however is not how the dotnet pack command works.

This issue is widely reported on their repo (I.e. https://github.com/NuGet/Home/issues/3891 ) and unfortunately it seems the developers and the community are in a bit of a disagreement to what is “correct”. The official stance (as I understood it) is that the project references won’t be included as they should be their own packages. This however is not always practical or desired.

The workaround.

Plenty of workarounds have been suggested around Stack Overflow and Github including having a seperate nuspec file, using Powershell to inject things into the generated nupkg and so on…

The solution below worked for me, but of course, YMMV.

In the end I ditched having my own .nuspec file within my project (as per some SO posts) and instead used the CSPROJ (as recommended). Below you can see the required fields for the packaging (version, naming, etc), a reference to a nuget package, and a reference to another project within the solution.

CSProj Snippet of dotnet core project
Snippet of CSPROJ with basic package info filled in.

If you run dotnet pack now, it will generate an appropriately named package which will contain a nuget dependancy on SomeNugetPackage. This can be confirmed by opening the nupkg with an archive tool (7Zip,WinRar, WinZip…) and seeing that the only DLL in the lib folder will be the DLL of the project being packed.

The fix is as follows:

  • Alter the project reference to set the ReferenceOutputAssembly flag to true, and IncludeAssets to the DLL name
<ProjectReference Include="..\ProjectB.csproj">
  <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
  <IncludeAssets>ProjectB.dll</IncludeAssets>
</ProjectReference>  
  • Add the following line into the <PropertyGroup> element
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
  • Add new target between <project> tags
<Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
    <ItemGroup>
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))"/>
    </ItemGroup>
  </Target>

So now you end up with something that looks like this

<Project Sdk="Microsoft.NET.Sdk"><br>
  <PropertyGroup><br>
    <TargetFramework>netstandard2.0</TargetFramework><br>
    <Version>1.0.9</Version><br>
    <Product>MyProduct</Product><br>
    <id>MyProduct</id><br>
    <PackageId>MyProduct</PackageId><br>
    <Authors>Your name</Authors><br>
    <Company>Company Name</Company><br>
    <Description>My library</Description><br>
    <Copyright>Copyright © 2019 MyCompany</Copyright><br>
    <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput><br>
  </PropertyGroup><br>
  <ItemGroup><br>
    <PackageReference Include="SomeNugetPackage" Version="1.2.3"/>  <br>
  </ItemGroup><br>
  <ItemGroup><br>
    <ProjectReference Include="..\ProjectB.csproj"><br>
      <ReferenceOutputAssembly>true</ReferenceOutputAssembly><br>
      <IncludeAssets>ProjectB.dll</IncludeAssets><br>
    </ProjectReference>  <br>
  </ItemGroup><br>
  <!--Next line is to ensure that dependant DLLS are copied--><br>
  <Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage"><br>
    <ItemGroup><br>
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))"/><br>
    </ItemGroup><br>
  </Target><br>
</Project><br>
End result CSPROJ. (Click to enlarge)

Now if you run dotnet pack you should see any project reference DLL under the lib folder of the package, and if you inspect the nuspec file inside the package (or upload it to your package repo) you should see the nuget dependencies.

Hopefully this helps someone, as there is a lot of conflicting info around. Please let me know if this would cause any issues!

The post Include both Nuget Package References and project reference DLL using “dotnet pack” 📦 appeared first on yer.ac | Adventures of a developer, and other things..

Posted on by:

wabbbit profile

Rich

@wabbbit

True full stack in #fintech. Analysis, solutionizing, developing and delivering. Mostly working in dotnet and javascript, with a passion for process optimising!

Discussion

markdown guide
 

Wanted to cross-reference a similar implementation on SO - stackoverflow.com/a/59893520/968003.

BTW, in order to bring XML-docs add BuildOnlySettings to "Target DependsOnTargets". It also will add PDBs to *.snupkg if you generate one.

So the final snippet looks like:

<PropertyGroup>
    <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target DependsOnTargets="BuildOnlySettings;ResolveReferences" Name="CopyProjectReferencesToPackage">
    <ItemGroup>
        <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
    </ItemGroup>
</Target>
 
 

Thanks, it looks like promising solution!
However I found one not fully solved issue with it.. It copies all needed files correctly and it works in general, but it still adds a package reference to not existing package inside generated *.nuspec file.
Trying to restore "master" NuGet package from Visual Studio - it's trying to find the dependent (not existing) package, and obviously fails. However if I add the "master" package manually through *.csproj file - it's added and can be successfully restored. There is also no resolved code - so everything works. Except some better tooling support..

 

Nevermind.. seems like "ProjectB.dll" did the work, and now it works more correctly. I just didn't want to reference a dll in a "hardcoded" way initially..

 

Hi
thanks a lot for this solution. I like it to structure my nuget solutions in projects.
Is there a way to include the xml doc files form the referenced projects into the nuget.
This would make IntelliSense possible for the nuget.

 

I manged to include *.xml and *.pdb files in the output folder into the nuget with this changes:

  <PropertyGroup>
    ...
    <TargetsForTfmSpecificBuildOutput>
      $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
    </TargetsForTfmSpecificBuildOutput>
    <AllowedOutputExtensionsInPackageBuildOutputFolder>
      $(AllowedOutputExtensionsInPackageBuildOutputFolder);.xml
      $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
    </AllowedOutputExtensionsInPackageBuildOutputFolder>
  </PropertyGroup>
  <Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
    <ItemGroup>
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->Replace('.dll', '.xml'))" />
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->Replace('.dll', '.pdb'))" />
    </ItemGroup>
  </Target>
 

Thanks! Solved my issue today :)

I managed to get it working without those lines:

  <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
  <IncludeAssets>ProjectB.dll</IncludeAssets>
 

This worked great for the project dll to be included but did not add the packages references for the packages referenced form ProjectB.dll