microsoft / CsWin32

A source generator to add a user-defined set of Win32 P/Invoke methods and supporting types to a C# project.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

IncludeAssets=all advice causes problems with NuGet packages

reflectronic opened this issue · comments

The documentation in the README suggests that the default <IncludeAssets> boilerplate generated by .NET tooling is removed:

 <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.1.647-beta">
   <PrivateAssets>all</PrivateAssets>
-  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 </PackageReference>

Though this usually causes no ill effects, this advice is dangerous for projects which produce a NuGet package. Since CsWin32 is marked as PrivateAssets=all, System.Memory and System.Runtime.CompilerServices.Unsafe will not be marked as dependencies of the built package, even though the built DLL depends on their assemblies. In other words, using this technique on a project which produces a NuGet package will break the package.

Easy reproduction:

  1. Create a new netstandard2.0 project and add the following package reference:
    <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta" PrivateAssets="All" />
  2. Write some code in the project that uses System.Runtime.CompilerServices.Unsafe so DLL has an assembly reference to it.
  3. Pack the project, and inspect the resulting nupkg to find that it is authored incorrectly:
    image
    Note that, according to the nuspec, this package does not have any dependencies, but the DLL in the package has an assembly reference to System.Runtime.CompilerServices.Unsafe 6.0.0. This will cause errors for consumers of this package.

I'm not sure how actionable this is, but this is a pretty bad bug that's easy to miss. One way of addressing it to emit an error from the CsWin32 package when this situation is detected. I don't know if anything further is possible without altering the behavior of NuGet.

The missing dependencies seems like a valid report. Thanks for sharing.
I can't see what that has to do with the IncludeAssets metadata though. It seems more related to the PrivateAssets metadata which you also call out.
Am I missing something?

I can't see what that has to do with the IncludeAssets metadata though.

The documentation in the README suggests setting IncludeAssets=all (by removing the auto-generated metadata). This suggestion can lead to broken NuGet packages. Something should probably be done about this suggestion to make it less problematic, either by amending it, or adding build targets to warn on this specific situation, or perhaps something else (I'm not really sure).

I don't yet understand how removing IncludeAssets metadata breaks anything. The specific break you speak of in the description relates to missing references to System.Memory and System.Runtime.CompilerServices.Unsafe. Those don't propagate to your own consumers automatically when you set PrivateAssets=all. That's not the same thing as IncludeAssets=all.

Yes, but without setting IncludeAssets=All, you cannot use those dependencies, so it does not matter that they don't flow. You don't get access to System.Memory so you can't introduce an assembly reference to it. The source generator would, of course, like to use these dependencies--that's why you suggest setting it in the first place. But it causes problems in some instances (like this one).

Ah, OK I get it now. And at least in some cases, the source generator is smart enough to avoid emitting APIs that depend on these when they aren't referenced. Thank you for your patience in helping me understand.
I'm still not sure what to do about it. I'll have to ponder the trade-offs.

I also ran into this issue.
I was using an assembly reference and Visual Studio/msbuild did not copy over System.Runtime.CompilerServices.Unsafe.dll to the output folder.
For example, Project1 has a NuGet package reference to CsWin32, and Project2 has an assembly reference to Project1.
I expected System.Runtime.CompilerServices.Unsafe.dll to be copied to Project2's output folder.

This is the error I was getting:

System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
File name: 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at System.Span`1.GetPinnableReference()
   at Windows.Win32.PInvoke....  in C:\Solution1\Project1\Microsoft.Windows.CsWin32\Microsoft.Windows.CsWin32.SourceGenerator\Windows.Win32.PInvoke.....dll.g.cs:line x
   at Project1.Class1.Method1(...) in C:\Solution1\Project1\Class1.cs:line x
   at Project2.Program.Main(...) in C:\Solution2\Project2\Program.cs:line x

=== Pre-bind state information ===
LOG: DisplayName = System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
 (Fully-specified)
LOG: Appbase = file:///C:/Solution2/Project2/bin/
LOG: Initial PrivatePath = NULL
Calling assembly : System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51.
===
LOG: This bind starts in LoadFrom load context.
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: Using application configuration file: C:\Solution2\Project2\bin\Project2.exe.config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Post-policy reference: System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
LOG: Attempting download of new URL file:///C:/Solution2/Project2/bin/System.Runtime.CompilerServices.Unsafe.DLL.
...

As a work-around I had to add a reference to the System.Runtime.CompilerServices.Unsafe NuGet package in Project2.

Microsoft.Windows.CsWin32 v0.3.49-beta references System.Runtime.CompilerServices.Unsafe v6.0.0, so I also referenced that specific version.

I also had to add an assembly binding redirection in Project2.exe.config, or have Visual Studio/msbuild do it automatically by setting the AutoGenerateBindingRedirects property to true in Project2.csproj:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Thank you.

I'd love to get you folks to validate the effectiveness of my fix in #1172.