0

I have created two NuGet packages that contain Native Libraries. The first one has two subfolders (amd64 and x86) the second one includes the DLL flat under the build directory. The NativeLibraries of the the first package are supposed to be copied into subfolders of the OutputPath. The only DLL in the second package should be copied flat under the OutputPath. I used the following stackoverflow entry as a guide for creating the package: https://stackoverflow.com/a/30316946/4496150

The first NuGet package folder structure looks like this (amd64 and x86 subfolder under build):

  • build
    • amd64
      • DLL1.dll
      • DLL2.dll
    • x86
      • DLL1.dll
      • DLL2.dll
    • packagename.targets

First targets file:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
    <None Include="@(NativeLibs)">
      <Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

Please notice $(MSBuildThisFileDirectory)** as NativeLibs Include and %(RecursiveDir) as part of Link attribute.

The second NuGet package structure looks like this (no subfolders under build):

  • build
    • DLL1.dll
    • packagename.targets

Second targets file:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <NativeLibs Include="$(MSBuildThisFileDirectory)\*.dll" />
    <None Include="@(NativeLibs)">
      <Link>%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

Please notice $(MSBuildThisFileDirectory) (without **) as NativeLibs Include and missing %(RecursiveDir) in Link attribute.

If you reference both packages, the DLLs from the first package are additionally copied flat into the output folder, which is not desired. I suspect this is because the second include step also reads the DLLs from the first package, but then does not use %(RecursiveDir).

A workaround is to also include in the second package both. and NativeLibs Include="$(MSBuildThisFileDirectory)***.dll" /> exactly as in the first package.

However, I would prefer to understand why the second package ensures that the DLLs from the first are copied flat.

If I read https://learn.microsoft.com/de-de/visualstudio/msbuild/msbuild-reserved-and-well-known-properties?view=vs-2019 correctly, $(MSBuildThisFileDirectory) points to the folder in my NuGet cache where the targets file of the NuGet package is located. So actually everything would be correct. But still (probably) wrong DLLs are copied.

Edit

I added the following snippet in both targets files to get some output:

<Target Name="OutputPackage1NativeLibs" BeforeTargets="Build">
    <Message Text="Package1 %(NativeLibs.Identity)" Importance="high"/>
</Target>

For the seconds targets file I changed the target name to OutputPackage2NativeLibs and started the text Output with Packag2.

When I clean my NuGet Package Cache and rebuild the solution everything is fine. But after the third or forth rebuild operation the DLLs of the first package are copied flat under the Output path and I get the following output:

Package1 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\amd64\DLL1.dll
Package1 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\amd64\DLL2.dll
Package1 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\x86\DLL1.dll
Package1 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\x86\DLL2.dll
Package1 C:\Users\USERNAME\.nuget\packages\PACKAGENAME2\1.0.0.0\build\DLL1.dll

Package2 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\amd64\DLL1.dll
Package2 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\amd64\DLL2.dll
Package2 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\x86\DLL1.dll
Package2 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\x86\DLL2.dll
Package2 C:\Users\USERNAME\.nuget\packages\PACKAGENAME2\1.0.0.0\build\DLL1.dll

So NativeLibs are added from the other NuGet package apparently after the third or fourth rebuild.

MisterGray
  • 329
  • 3
  • 15
  • I think you are using non-sdk net framework projects with packages.config nuget package management format, right? In my side, use PackageReference with new-sdk projects get the right behavior. And in fact, the two targets file should not be access for each other since they are two different single nuget packages. – Sara Liu - MSFT Apr 22 '21 at 09:01
  • I am using a non-sdk net framework project but with PackageReference. – MisterGray Apr 22 '21 at 11:28

1 Answers1

0

I think you are using Packages.config nuget package format with non-sdk net framework projects. If so, that could be explained.

For packages.config nuget management format, it imports the target files directly under csproj file. In my side, I created two nuget packages called flat 1.0.0 and faltt 1.0.0 nuget packages.

You can check my csproj file:

enter image description here

If so, the first import targets file flat.targets file is the same as your first nuget package's targets file:

It includes the x86 and amd64 folder files into output folder\x86 and output folder\amd64 folder, that is right as we excepted.

However, under pakckages.config, since the two targets file are in the same csproj file, they can access each other(they have the same life cycle in the same CSProj), when msbuild reads faltt.targets file after flat.targets file, also you did not change NativeLibs item for the second targets file, itself has the file:

C:\Users\xxx\source\repos\flat\packages\flat.1.0.0\build\amd64\Dll1.dll
C:\Users\xxx\source\repos\flat\packages\flat.1.0.0\build\amd64\Dll2.dll
C:\Users\xxx\source\repos\flat\packages\flat.1.0.0\build\x86\Dll1.dll
C:\Users\xxx\source\repos\flat\packages\flat.1.0.0\build\x86\Dll1.dll

When reads the flatt.targets file, it also includes C:\Users\xxx\source\repos\flat\packages\flatt.1.0.0\build\x86\Dll1.dll,

So it has five files under NativeLibs item.

And then it executes <Link>%(FileName)%(Extension)</Link>, the first nuget package's dlls will be executed under the second nuget packages's node, output to the output root folder.

Because of the special nature of Packages.config import targets files, they are interlaced.

enter image description here

You should note that

Under PackageReference nuget management format, the import targets files are stored under obj\xxx.csproj.nuget.g.targets file, like this:

 <ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
    <Import Project="$(NuGetPackageRoot)flatt\1.0.0\build\flatt.targets" Condition="Exists('$(NuGetPackageRoot)flatt\1.0.0\build\flatt.targets')" />
    <Import Project="$(NuGetPackageRoot)flat\1.0.0\build\flat.targets" Condition="Exists('$(NuGetPackageRoot)flat\1.0.0\build\flat.targets')" />
  </ImportGroup>

Since the content is not in csproj file and the particularity of the file xxx.csproj.nuget.g.targets, it will read the targets files in each nuget package separately. Each file is a separate life cycle, so it does not Will affect each other. This is also one of the advantages of the latest PackagesReference nuget management format.

enter image description here

Sorry for not telling you at the beginning that I was using packagesReference and I didn't notice packages.config.

So if you want to get the right behavior to separate them, try the two approaches:

1) do not change the two nuget packages, right-click on the packages.config file of the main project which installs the two nuget packages-->click Migrate packages.config to PackageReference option

After that, click Rebuild option to get that.

2) modify the nuget packages--> change the NativeLibs item's name to another for the second nuget package targets file:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <NativeLibs1 Include="$(MSBuildThisFileDirectory)*.dll" />
    <None Include="@(NativeLibs1)">
      <Link>%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

When re-pack your project, uninstall them first, delete all cache files under C:\Users\xxx\.nuget\packages. And then re-install the packages again.

Sara Liu - MSFT
  • 6,007
  • 1
  • 21
  • 27
  • Thank you for your reply, I understand the process a little better now. But the problem exists also for non-sdk .NET Framework Projects with PackageReferences. For me, however, only after the third or fourth rebuild (I don't know why). Can you reproduce this behaviour? – MisterGray Apr 22 '21 at 11:48
  • I've added some information to my initial question that makes it a little bit easier to reproduce and investigate. – MisterGray Apr 22 '21 at 12:43
  • The two targets `OutputPackage1NativeLibs` and `OutputPackage2NativeLibs` cannot show the thing under this issue because they are both depends on build. When it executes before build process, the `NativeLibs` already contains the five files since NativeLibs item is increasing all the time but not empty.(items be read far away before all targets). Since they are both depends on build, so thay are always the same. So what you did could not be a judgment standard. So they cannot display the files by using some targets. – Sara Liu - MSFT Apr 23 '21 at 08:02
  • What I meant is that it is separate that msbuild item reads the `NativeLibs` and then copy to output folder during `obj\xxx.csproj.nuget.g.targets` which executes before all targets. When you use some targets to get that items,it already did all the copy steps and displayed all the files in the two packages that are then overlaid. In my side. it shows well and the copied files are not confused with each other with several rebuilds.Not sure whether your `obj\xxx.csproj.nuget.g.targets` is still the wrong previous file and rebuild(clean+build) will not delete it.It was taken care of by Restore. – Sara Liu - MSFT Apr 23 '21 at 08:19
  • Therefore, our judgment is based on whether the files in the final output folder are correct. Please delete the `bin` and `obj` folder. Then, rebuild again to check that. What is better you should change the `NativeLibs` item to two different names in the two nuget targets file, you will not get any problems under this situation. – Sara Liu - MSFT Apr 23 '21 at 08:20
  • I can definitely confirm that the files are mistakenly copied flat to the OutputPath when running Rebuild Solution 3 or 4 times. I have deleted the bin and obj folder and the NuGet cache. I understand what you are saying regarding the output of the files. However, it doesn't explain why the output additionally shows the DLLs from the other package exactly when the DLLs are also copied flat to the OutputPath. I would also mark your answer as correct, but from my point of view only if you add that the include name must be different in each case and not only in the case of packages.config. – MisterGray Apr 23 '21 at 12:41