microsoft / CsWinRT

C# language projection for the Windows Runtime
MIT License
555 stars 107 forks source link

CsWinRT not removing WinMD references from deps.json (CoreCLR, 0x80070057) #1811

Open AliveDevil opened 1 month ago

AliveDevil commented 1 month ago

Describe the bug When referencing a NuGet package that includes for further CsWinRT referencing a .winmd file in net8.0-windows10.X subsequent launches of the application will fail (i.e. Projection B references Projection A, because Component B uses types from Component A).

To Reproduce NuSpec file:

<file src="../../artifacts/bin/Component/Win32/Release/Component.winmd" target="lib/net8.0-windows10.0.17134.0" />
<file src="../../artifacts/bin/Component.Projection/Release/net8.0-windows10.0.22621.0/Component.Projection.dll" target="lib/net8.0-windows10.0.17134.0" />

Create a console application, that references this Nuget package. The deps.json will include the .winmd files, resulting in the .NET 8 host to fail with

.\ConsoleApp1\bin\Debug\net8.0-windows10.0.22621.0\ConsoleApp1.exe
Failed to create CoreCLR, HRESULT: 0x80070057

Expected behavior As mentioned here I expect this to seamlessly just work and not crash the CoreCLR runtime immediately.

Version Info CsWinRT: 2.1.5

Additional context Repro: CsWinRTNuGet.zip Follow steps in embedded readme.md.

My workaround is to move the winmd to lib/netstandard2.0, then create a package build/net8.0-windows10.X/build.props file, that adds a reference to the netstandard2.0 winmd, which does get collected later. This has the bad sideeffect, that Visual Studio complains about a non-existing file (the MSBuildThisFileDirectory macro doesn't resolve while a project is loaded).

<Project>
  <PropertyGroup>
    <_ComponentWinMDLibDir>$(MSBuildThisFileDirectory)../../lib/netstandard2.0/</_ComponentWinMDLibDir>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="Component">
      <HintPath>$(_ComponentWinMDLibDir)Component.winmd</HintPath>
      <IsWinMDFile>true</IsWinMDFile>
    </Reference>
  </ItemGroup>
</Project>
AliveDevil commented 1 month ago

Looking at the binlog: Target GenerateBuildDependencyFile, task GenerateDepsFile, argument CompileReferences="@(ResolvedCompileFileDefinitions)" isn't stripped from the WinMD references, thus is written to the deps.json regardless.

dongle-the-gadget commented 1 month ago

You shouldn't add the WinMD into the lib\net8.0-xxx folders, as .NET will interpret that to mean you wish to include that WinMD as a reference, which isn't allowed. I think you should create a target that copies that WinMD to the output directory and add that to the build folder instead.

AliveDevil commented 1 month ago

Well, the use case is: There is one projection project, that exposes one WinRT component.

There is another WinRT component, that has a PackageReference to the first WinRT component, which is exposed by another CsWinRT projection.

This won't work, as CsWinRT doesn't find the type information for the first WinRT component in the NuGet package, because it's nowhere to be found, the guidance previously was to just include it in the lib/-folder (see linked issue).

Additionally, when including the WinRT in the lib/-folder the AppHost crashes immediately on startup, as it validates that there are no winmd-files referenced n the deps.json or side-by-side with the startup application.

This abomination of a nuspec works, but I see this as highly counter-intuitive:

``` CsWinRT.ComponentA CsWinRT ComponentA Test $version$ microsoft/CsWinRT ```

With the <reference>-group everything works fine, but is opposite to the previously stated way of including and referencing a WinMD file for consumption in CsWinRT.

So, to make sure, that .NET Sdk doesn't include the winmd as a runtime artifact, the winmd file must not end up in lib/, but for CsWinRT to realize there are consumable WinMD files, one has to duplicate the projection Dll into ref/, including the WinMD:

With:

<references>
  <group targetFramework="net8.0-windows10">
    <reference file="Component.winmd" /> <!-- CsWinRT will remove this, but allow consumption when a WinMD ProjectReference uses a type from this -->
    <reference file="Component.Projection.dll" /> <!-- Make sure that C# can reference types in the Component.winmd -->
  </group>
</references>
dongle-the-gadget commented 1 month ago

What I'd recommend you do is to put the WinMD somewhere else, then add the CsWinRTInputs item in an <ItemGroup> in a target.

AliveDevil commented 1 month ago

With CsWinRTInputs there is another bad interaction with WindowsAppSdk, where target _CalculateInputsForGenerateCurrentProjectAppxManifest won't find the referenced WinMD, thus failing to successfully build the required ActivatableClass metadata.