Skip to content

Improve hosting support experience#2417

Open
manodasanW wants to merge 104 commits into
staging/3.0from
manodasanw/hostingNuget4
Open

Improve hosting support experience#2417
manodasanW wants to merge 104 commits into
staging/3.0from
manodasanw/hostingNuget4

Conversation

@manodasanW
Copy link
Copy Markdown
Member

  • Added some native targets for where if CsWinRT package is included by a native app, it will handle making sure the right WinRT.Host.dll gets copied which was an issue in the past when the component was AnyCPU but app was like x64. It will also handle making the JIT hosting scenarios less complicated in our new model given we need a single-entry point for TypeMap to work.
  • To improve the JIT hosting scenarios, WinRT.Host.dll now calls into WinRT.Component.dll which serves as the main entry point (and sets it) and then redirects it to each components activation factory to try to find the type.
  • Added sample for AOT scenarios too where we are not doing something similar to JIT but instead providing the developer a way to create a project to merge all the components that they want to use and merge the activation factories for them. A developer can also choose to publish each component individually which is part of why we are not automating this process. This is similar to the experience before.
  • Added missing binaries in nuget and also cleaned up the included targets.
  • There are scenarios where WinRT.Projection.dll may not get generated, so adding handling for that.

manodasanW and others added 30 commits May 1, 2026 21:34
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The shim is a hosting asset (only deployed for authored components), not a managed reference for consumers. Placing it under lib\net10.0 caused NuGet/SDK to auto-resolve it as a compile reference everywhere, requiring CsWinRTRemoveHostingDllReferences to scrub it from multiple item groups. Moving it under hosting\ matches WinRT.Host.dll's layout, removes the auto-resolution problem entirely, and lets the targets logic in Authoring.targets be the single source of deployment for the shim.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WinRT.Host.dll lives under hosting\<arch>\native\ which NuGet does not auto-resolve, so the runtimes\**\native\WinRT.Host.dll Remove entries never matched anything. Leftover from a previous layout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Microsoft.Windows.CsWinRT.Authoring.targets and Microsoft.Windows.CsWinRT.Authoring.Transitive.targets were commented out in the nuspec, and Microsoft.Windows.CsWinRT.Authoring.WinMD.targets was missing entirely. All three are needed by component-authoring builds (CsWinRTComponent=true).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Neither targets file is imported by anything in CsWinRT 3.0 nor packaged in the NuGet. They are CsWinRT 2.x leftovers. Updated cswinrt.slnx to drop the entries and add the Authoring.WinMD.targets entry alongside the other authoring targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pack Microsoft.Windows.CsWinRT.Native.targets at build\native\Microsoft.Windows.CsWinRT.targets so NuGet auto-imports it for native (vcxproj) consumers via the package's <id>.targets convention. Drop the inline CsWinRTBuildComponentInterop target from the AuthoringConsumptionTest vcxproj in favor of importing Native.targets directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The synthesized aggregator csproj ProjectReferences each CsWinRT component, which flows the component's .winmd into the aggregator's resolved reference set. The .NET SDK rejects that with NETSDK1130 (.NET 5+ disallows direct .winmd references). The aggregator doesn't import CsWinRT.targets (no PackageReference), so the standard CsWinRTRemoveWinMDReferences target isn't available.

Inline a winmd-scrub target into the generated aggregator csproj content. It runs between ResolveProjectReferences and ResolveAssemblyReferences, removing any .winmd from _ResolvedProjectReferencePaths (and defensively from Reference/ReferencePath) so the SDK's NETSDK1130 check sees no winmd references.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The synthesized aggregator csproj had no PackageReference to cswinrt and no other path to import cswinrt's targets, so its CsWinRTBuildForNativeConsumer / CsWinRTGenerateInteropAssembly properties had no consumer in scope. cswinrtinteropgen never ran on the aggregator, and the merged WinRT.Component.dll / WinRT.Interop.dll never got produced. The .winmd scrub from the prior commit handled NETSDK1130 but didn't fix the missing consumer of those properties.

Resolution: have Native.targets compute the absolute paths of Microsoft.Windows.CsWinRT.props and Microsoft.Windows.CsWinRT.targets relative to its own location and inject explicit <Import> elements at the top and bottom of the aggregator csproj content. This brings cswinrt's full target tree into the aggregator's evaluation, so CsWinRTGen.targets correctly wires cswinrtinteropgen and CsWinRTRemoveWinMDReferences scrubs the .winmd inputs (replacing the inline scrub from the prior commit). Zero version skew (the imported targets are the same cswinrt that's running Native.targets) and no NuGet restore overhead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When Native.targets is consumed from the in-repo source tree (e.g. by the AuthoringConsumptionTest vcxproj's local-dev <Import>), it sits at nuget\Microsoft.Windows.CsWinRT.Native.targets next to the props/targets. When consumed from the shipped package, it sits at build\native\Microsoft.Windows.CsWinRT.targets and the props/targets are one folder up at build\. The previous fixed '..\' path worked only for the package layout and failed in the source layout with MSB4019.

Probe both relative locations via Exists() and pick whichever resolves first, preferring the package layout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move nuget\Microsoft.Windows.CsWinRT.Native.targets to nuget\native\Microsoft.Windows.CsWinRT.targets so the source tree matches the shipped package layout (build\native\Microsoft.Windows.CsWinRT.targets). Native.targets's '..\Microsoft.Windows.CsWinRT.props' / '..\Microsoft.Windows.CsWinRT.targets' relative imports now resolve identically in both source and package layouts; no Exists() probing or repo-specific special casing required.

Update the nuspec source path and the AuthoringConsumptionTest vcxproj's local-dev import to match.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CsWinRTGen.targets has many import-time PropertyGroups that derive paths from $(IntermediateOutputPath) (e.g. CsWinRTGeneratorInteropAssemblyDirectory, _RunCsWinRTGeneratorPropertyInputsCachePath, _CsWinRTGeneratorMergedProjectionAssemblyPath, _CsWinRTGeneratorComponentAssemblyPath, _CsWinRTSdkProjectionAssemblyPath, _CsWinRTRefAssemblyPath, CsWinRTGeneratorForwarderAssemblyDirectory). When the aggregator imports cswinrt.targets via the implicit <Project Sdk=...> form, the import happens before Sdk.targets sets IntermediateOutputPath, so all those derived paths evaluate to empty  leading to MSB4044 (RunCsWinRTMergedProjectionGenerator missing GeneratedAssemblyDirectory) and similar.

Switch the aggregator to the explicit <Project> form with explicit <Import Sdk.props/Sdk.targets> tags so we can place the cswinrt.targets <Import> after Sdk.targets. cswinrt.props still goes between Sdk.props and Sdk.targets so its BeforeMicrosoftNETSdkTargets contribution is in scope when Sdk.targets evaluates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The repo's Directory.Build.targets cascades into the synthesized aggregator csproj that Native.targets generates under src\_build\...\obj\cswinrt\, but the aggregator's restore doesn't resolve Microsoft.DiaSymReader.Pdb2Pdb (the package is added implicitly via Directory.Build.targets, but GeneratePathProperty doesn't always materialize for synthesized projects). Result: PkgMicrosoft_DiaSymReader_Pdb2Pdb is empty, the Pdb2Pdb path collapses to '\tools\Pdb2Pdb.exe', and the Exec fails.

Gate the target on the package property being non-empty. This is a defensive change that benefits any csproj where Pdb2Pdb isn't resolvable, not aggregator-specific. Keeps the shipping artifact (Native.targets) free of repo-local concerns.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The .NET SDK's default IntermediateOutputPath injects \ for non-AnyCPU builds, so an x86/x64/ARM64 aggregator outputs to obj\<Platform>\<Config>\<TFM>\. Native.targets's post-build Copy reads from a fixed obj\<Config>\<TFM>\ path (no platform). Path mismatch -> Exists() guards no-op -> WinRT.Component.dll / WinRT.Interop.dll / projection assemblies never bin-place -> all 49 AuthoringConsumptionTest activation tests fail with 'Unknown C++ exception' at first activation.

Pass IntermediateOutputPath and BaseIntermediateOutputPath as global properties to the aggregator's Restore/Build invocations, pinning them to the same path Native.targets will read from. Both sides agree by construction; no SDK platform-folder logic to mirror.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Embed the pinned BaseIntermediateOutputPath / IntermediateOutputPath inside the aggregator csproj's PropertyGroup rather than passing them on the parent MSBuild call. Project-local properties stay scoped to the aggregator; global properties propagate transitively into ProjectReferences (component csproj, then the C++ cswinrt.vcxproj it references), where the legacy packages.config NuGet resolver fails because the inherited intermediate path has no lockfile for that vcxproj's packages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pin the aggregator's IntermediateOutputPath only up through Configuration. The .NET SDK appends a TargetFramework segment to IntermediateOutputPath in its post-evaluation pass; previously we passed the full path including TFM and the SDK appended a second TFM, producing obj\Debug\net10.0\net10.0\. cswinrtinteropgen wrote merged DLLs there while Native.targets's post-build Copy step read from the single-TFM path.

Now the pinned PropertyGroup value ends at <Config>\\, the SDK appends <TFM>\\, and the final on-disk path matches _CsWinRTTempProjectIntermediateDir that the Copy step reads from.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The temp-aggregator approach can't see component assemblies in its ReferencePath because Authoring.targets reports the component's primary output as the .winmd (with the .dll only as ManagedImplementation metadata). After CsWinRTRemoveWinMDReferences scrubs the winmd, nothing from the component is left in ReferencePath, so the aggregator's component projection generator finds no component types and emits no WinRT.Component.dll.

Replace the aggregator with per-component direct MSBuild dispatch (matching what the original inline target in AuthoringConsumptionTest.vcxproj was doing): for each detected CsWinRTComponent project reference, invoke its Build with CsWinRTBuildForNativeConsumer=true so the component's own pipeline produces the merged hosting bundle in its own intermediate dir, then copy from there. Translates the C++ \='Win32' to the managed platform 'x86' for the path math.

Single-component scenarios (the only ones currently exercised by tests) now work end to end. Multi-component aggregation - which has type-map duplicate-key concerns and was the original motivation for the aggregator csproj - is deferred as separate work.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bring back the temp-aggregator csproj that ProjectReferences every detected CsWinRTComponent and runs cswinrt's full generator pipeline once across the union, so a single deduplicated WinRT.Interop.dll / WinRT.Component.dll / projection bundle is produced and the multi-component scenario (preventing type-map duplicate-key collisions across components) keeps working.

Fix the previous structural blocker: each component's TargetPathWithTargetPlatformMoniker reports the .winmd as primary, with the .dll only as ManagedImplementation metadata, so after CsWinRTRemoveWinMDReferences nothing from the component remained in the aggregator's ReferencePath. Native.targets now captures %(_ResolvedProjectReferencePaths.ManagedImplementation) for component refs and emits an explicit <Reference Include='...componentdll' Private='false'> per component into the aggregator csproj, alongside the existing <ProjectReference>. cswinrt's component projection generator now sees the [WindowsRuntimeComponentAssembly] markers and produces WinRT.Component.dll.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Switch AuthoringTest's TargetFramework from net10.0 to net10.0-windows10.0.26100.1 (the CsWinRT 3.0 convention - .1 revision identifies the 3.0 stack). Update AuthoringConsumptionTest.vcxproj's hardcoded references to the AuthoringTest output path to match.

Now that the aggregator's TargetFramework propagates from the component as a Windows-targeted TFM, Microsoft.NET.Windows.targets imports into the aggregator and its built-in RemoveManagedWinRTComponentWinMDReferences / AddWinRTComponentImplementationReference targets fire automatically. They scrub the component's .winmd from _ResolvedProjectReferencePaths and re-inject the managed implementation .dll (from ManagedImplementation metadata that Authoring.targets:166 already attaches) as an explicit Reference. So cswinrt's component projection generator sees [WindowsRuntimeComponentAssembly] on the .dll naturally.

Drop the manual <Reference Include=...> / _CsWinRTComponentManagedImplementationPaths plumbing in Native.targets - the SDK now provides this for us.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The .NET 10 SDK auto-injects Microsoft.Windows.SDK.NET.Ref.CsWinRT3.Windows (and .CsWinRT3.Xaml when UseUwp=true) for projects targeting a CsWinRT3 TFM (revision .1). The repo previously only scrubbed the 2.x-era names; AuthoringTest's recent move to net10.0-windows10.0.26100.1 means it now picks up the CsWinRT3 variants. Add removes for those so repo-local builds keep using cswinrt's locally-generated projection rather than the SDK's pinned reference assemblies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AuthoringTest builds against the locally-generated WinAppSDK projection (via WinAppSDK.csproj), not the strong-named Microsoft.WinUI shipped in the WindowsAppSDK package. With AuthoringTest's TFM at net10.0 (non-Windows), the package's assets didn't activate. With net10.0-windows10.0.26100.1, they do, and the package's Microsoft.WinUI 3.0.0.0 (PKT de31ebe4ad15742b) conflicts with the locally-built unsigned 1.0.0.0, surfacing as MSB3243 + NETSDK1148 ('a referenced assembly was compiled using a newer version of Microsoft.Windows.SDK.NET.dll').

Mirror the existing ExcludeAssets=all pattern already used for Microsoft.Web.WebView2 on the next line.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On Windows-targeted TFMs the .NET SDK's RemoveManagedWinRTComponentWinMDReferences runs AfterTargets=ResolveProjectReferences and scrubs CsWinRT-component winmds from _ResolvedProjectReferencePaths. Our CsWinRTRemoveWinMDReferences runs later (AfterTargets=ResolveReferences) and only sees the post-scrub state, so component winmds never reach CsWinRTInputs / _WinMDPathsList. cswinrtprojectiongen then runs without the winmd in --winmd-paths, finds no component types, emits no WinRT.Component.dll, and cswinrtinteropgen fails with CSWINRTINTEROPGEN0091.

Add CsWinRTCaptureProjectReferenceWinMDs that runs AfterTargets=ResolveProjectReferences and BeforeTargets=RemoveManagedWinRTComponentWinMDReferences. It captures Extension=.winmd + Implementation=WinRT.Host.dll items from _ResolvedProjectReferencePaths into CsWinRTInputs before the SDK removes them. _WinMDPathsList now includes the component winmds and cswinrt's generators see the component types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ExcludeAssets=all also excludes the build asset, which removes WinAppSDK's MakePri targets from the import graph. That's why AuthoringTest.pri was no longer being generated under the new Windows-targeted TFM. Narrow the exclusion so the Microsoft.WinUI 3.0.0.0-vs-1.0.0.0 compile/runtime conflict is still suppressed but the PRI generation step still runs.
The .1-revision Windows TFM causes the .NET SDK to inject Microsoft.Windows.SDK.NET.Ref.CsWinRT3.{Windows,Xaml}, which carry the prebuilt SDK projection assemblies. This repo builds those projections from source, so let the local copies be the only ones in scope by removing the SDK-injected framework references.
The aggregator csproj sets CsWinRTBuildForNativeConsumer=true on itself, but
that property doesn't flow into transitive ProjectReferences by default. Each
referenced component (e.g. AuthoringTest.csproj) compiles with the property
unset, so TypeMapAssemblyTargetGenerator's gate (isOutputTypeExe ||
isPublishAotLibrary || isBuildForNativeConsumer) stays off. Result: no
[TypeMapAssemblyTarget] assembly attributes are emitted on the component
.dll, and runtime activation through WinRT.Host fails because the type map
groups can't be resolved.

Set the property explicitly via AdditionalProperties on the synthesized
ProjectReference so each component sees it.
manodasanW and others added 28 commits May 9, 2026 20:19
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AuthoringTest's Directory.Build.props sets PublishAot=true; SelfContained=true
for Release|x64 (so the single-component AuthoringConsumptionTest can publish
it as a self-contained native dll). When AuthoringConsumptionTest2.AOT.csproj
PRs AuthoringTest, that block fires too, and AuthoringTest copies the
WindowsAppSDK runtime payload to its bin. .AOT then publishes itself
self-contained, pulling those runtimes from both AuthoringTest's bin and its
own NuGet restore - NETSDK1152 duplicate publish output errors.

Pass PublishAot=false; SelfContained=false; NativeLib=; OutputType=Library;
CustomNativeMain=false as global properties on the PR. Globals override
the Directory.Build.props PropertyGroup, so AuthoringTest builds as a plain
managed library here. The merged AOT host handles all AOT linking.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WinAppSDK has its own self-contained mode property separate from .NET's
SelfContained. Setting OutputType=Library already suppresses the WinAppSDK
default, but pass WindowsAppSDKSelfContained=false explicitly as defense
in depth.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
OutputType=Library passed as a global propagated transitively through
AuthoringTest's PRs (Projections/Windows -> WinRT.Impl.Generator etc.),
overriding those projects' OutputType=Exe and breaking compilation with
CS8805 (top-level statements require executable).

The duplicate publish output payload was the WinAppSDK runtime files,
copied into AuthoringTest's bin because WinAppSDK auto-defaults
WindowsAppSDKSelfContained=true when OutputType != Library. Set
WindowsAppSDKSelfContained=false directly instead - same effect, no
collateral damage to descendant project builds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AuthoringTest's Directory.Build.props sets PublishAot=true; OutputType=Exe;
SelfContained=true; etc for Release|x64 - that's what makes the standalone
single-component AOT publish in AuthoringConsumptionTest work.

When AuthoringConsumptionTest2.AOT.csproj PRs AuthoringTest as a managed
input for the merged AOT host, that block fights us. Trying to override
the props as PR globals doesn't work cleanly:
- OutputType=Library global propagates to AuthoringTest's transitive PRs
  (Projections/Windows -> WinRT.Impl.Generator etc.), overriding their
  OutputType=Exe and breaking compile with CS8805.
- Without OutputType=Library, WinAppSDK either pulls its runtime payload
  into AuthoringTest's bin (NETSDK1152 dupes) or errors out demanding
  the Microsoft.WindowsAppSDK.Runtime PackageReference.

Add an opt-out property _AuthoringTestSkipAotPublishConfig to the gating
Condition. The merged AOT csproj sets that single property as a global
on its PR, suppressing the AOT block entirely. AuthoringTest stays a
plain CsWinRT component library - exactly what the merged host wants.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The .AOT.csproj has <PublishAot>true</PublishAot> in its PropertyGroup,
but NuGet's Restore phase invoked via <MSBuild Targets='Restore;Publish'>
didn't see it as a global property and didn't pull runtime.win-x64.
microsoft.dotnet.ilcompiler into the restore graph. Publish then ran as
a plain self-contained JIT publish, copying the whole .NET 10 framework
plus clrjit.dll to bin\publish, producing a 16KB managed .AOT.dll
instead of an AOT-compiled native dll. Tests crashed at activation
because the manifest pointed at a managed library RoActivateInstance
couldn't activate.

Pass PublishAot=true explicitly as a global property on the <MSBuild>
call so both Restore and Publish phases see it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Passing PublishAot=true as a global from CsWinRTLink (so NuGet Restore
pulls the ILC package) propagates transitively to managed-only PRs
(Projections/Windows -> WinRT.Generator.Tasks targeting netstandard2.0)
and triggers NETSDK1207 'Ahead-of-time compilation is not supported for
the target framework'.

Add <UndefineProperties>PublishAot</UndefineProperties> on each PR so
the global is stripped at the boundary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…el project

Previous attempt invoked the .AOT csproj from CsWinRTLink via <MSBuild
Targets='Restore;Publish'> with custom globals (PublishAot=true,
RuntimeIdentifier=win-x64). Those globals propagated transitively through
the .AOT csproj's PR closure (Projections/Windows -> WinRT.Generator.Tasks),
caused MSBuild to see different project instance hashes than the slnx-level
invocations of the same projects, and ran a second concurrent build that
raced for shared bin\Release\<TFM>\<assembly>.dll outputs (file lock
errors). Patching with <UndefineProperties> per global was unmaintainable
- every divergent global at every PR boundary needed enumeration.

Move .AOT.csproj back into slnx as a top-level project, BuildDependency
on AuthoringTest + AuthoringTest2, gated to build only for x64 Release.
This way:
  - .AOT.csproj's evaluation uses the same slnx-level globals as everything
    else; descendants reuse the slnx-level project instances; no race.
  - Original contamination (commit ad69028) is avoided because .AOT.csproj's
    PR to AuthoringTest passes _AuthoringTestSkipAotPublishConfig=true,
    which suppresses AuthoringTest's Directory.Build.props PublishAot block.
    AuthoringTest evaluates as a plain library here (different instance from
    AuthoringConsumptionTest's PublishAot-fired Publish-time instance, but
    different bin output paths so no race).

CsWinRTLink target in AuthoringConsumptionTest2.vcxproj now just picks up
the .AOT.dll from its publish folder and adds it to ReferenceCopyLocalPaths.
No <MSBuild> sub-invocation, no Restore/Publish from inside the vcxproj.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Without <Platform Solution='*|x64' Project='x64'/>, slnx defaulted .AOT.csproj
to AnyCPU for Solution=Release|x64. AnyCPU propagated through its PR to
AuthoringTest, which then tried to locate cswinrt.exe at _build\AnyCPU\Release\
(does not exist - cswinrt is built for x64).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When AuthoringConsumptionTest2.AOT.csproj's ProjectReference to AuthoringTest
sets <Properties>_AuthoringTestSkipAotPublishConfig=true</Properties>, that
global property propagates through AuthoringTest's PR closure - including its
PR to Projections/Windows.csproj. That gives Projections/Windows a different
global-property hash than its slnx-level invocation, so MSBuild creates a
SECOND project instance and runs a parallel build. Both instances write to
the same bin\x64\Release\net10.0\Microsoft.Windows.SDK.NET.dll, so the
assembly version embedded in AuthoringTest.dll's compile-time reference can
disagree with the version .AOT.csproj's RAR resolves at link time -> NETSDK1148.

Confirmed via binlog (cswinrt (13).binlog): two Windows.csproj instances,
one with no special globals (Id=2095), another with _AuthoringTestSkipAotPublishConfig=true
in its global property set (Id=2470).

TreatAsLocalProperty on the <Project> element makes MSBuild treat the
property as local within AuthoringTest's evaluation only - the gating
Condition in Directory.Build.props still sees it, but it is NOT propagated
as a global to AuthoringTest's ProjectReferences. Single Projections/Windows
instance, no race, single SDK.NET version.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Real root cause of NETSDK1148 (from binlog cswinrt (13).binlog
ResolveAssemblyReferenceUnresolvedAssemblyConflicts entry):

  Conflict: Microsoft.Windows.SDK.NET, Version=1.0.0.0, PublicKeyToken=null
                          (locally built from Projections/Windows.csproj)
         vs Microsoft.Windows.SDK.NET, Version=10.0.17763.38,
                          PublicKeyToken=31bf3856ad364e35 (official signed)

  Source of the official ref:
    microsoft.web.webview2/1.0.3179.45/lib_manual/net8.0-windows10.0.17763.0/
      Microsoft.Web.WebView2.Core.Projection.dll

WebView2.Core.Projection.dll was compiled against the official Windows SDK
projection assembly. AuthoringTest's csproj has <PackageReference> for
WebView2 with ExcludeAssets='all' which excludes WebView2 from AuthoringTest's
own compile, but does NOT block transitive flow (PrivateAssets default is
not 'all'). When .AOT.csproj resolves AuthoringTest's transitive package
graph, WebView2.Core.Projection.dll flows in, RAR cannot unify the two
SDK.NET versions, NETSDK1148 fires.

Add the same <PackageReference Microsoft.Web.WebView2 ExcludeAssets='all'>
at the .AOT.csproj level so this csproj also excludes WebView2 from its
compile/runtime closure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
slnx's metaproj passes Targets='Build' to top-level projects regardless
of DefaultTargets, so DefaultTargets='Publish' was ignored and ILC never
ran on .AOT.csproj - confirmed via binlog. Add a target that runs Publish
AfterTargets=Build so the AOT pipeline fires inside slnx-level Build.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Test3

AuthoringTest has a special Directory.Build.props block for Release|x64
that turns it into a self-contained AOT-publish executable (used by the
single-component AOT scenario AuthoringConsumptionTest). Pulling it as
a managed-only ProjectReference into the merged-AOT host required an
opt-out global (_AuthoringTestSkipAotPublishConfig=true) to suppress
that block, but the global propagated transitively, creating duplicate
MSBuild project instances for transitive references and causing brittle
build behavior locally (winmd path mismatch).

Sidestep entirely: add AuthoringTest3, a plain CsWinRT component
identical in shape to AuthoringTest2 (no AOT-publish block). The .AOT
host now PRs AuthoringTest2 + AuthoringTest3. AuthoringTest stays
untouched and continues to serve the single-component AOT test.

Revert _AuthoringTestSkipAotPublishConfig gate on AuthoringTest's
Directory.Build.props. Update tests/pch/manifests/vcxproj/slnx to
reference AuthoringTest3 instead of AuthoringTest.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AuthoringExportTypesGenerator.ShouldEmitNativeExports requires PublishAot=true
to emit the unmanaged [UnmanagedCallersOnly] DllGetActivationFactory export.
That property was exposed only via Microsoft.Windows.CsWinRT.Authoring.targets,
which is imported only when CsWinRTComponent=true.

A merged AOT host like AuthoringConsumptionTest2.AOT.csproj is NOT a component
(it sets CsWinRTMergeReferencedActivationFactories=true but not CsWinRTComponent
=true), so Authoring.targets isn't imported, PublishAot isn't visible to the
source generator, ShouldEmitNativeExports() returns false, NativeExports.g.cs
isn't generated, and the published native dll has no DllGetActivationFactory
export. Activation fails at runtime with check_hresult throwing.

Expose PublishAot unconditionally in the main CsWinRT.targets so the source
generator sees it for both components and merged-aggregator hosts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AuthoringTest2/AuthoringTest3 are plain CsWinRT components (no AOT publish
block in their Directory.Build.props), so their winmd output naturally lands
at bin\<Platform>\<Config>\<TFM>\<assembly>.winmd (no RID subfolder). The
old HintPath inherited the AuthoringTest pattern that expected the AOT
publish RID-appended location, which fails locally where the VS build
doesn't propagate RuntimeIdentifier through the PR Properties.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DefaultTargets='Publish' is honored only when the project is invoked
with default targets (no explicit /t:). slnx via msbuild does that, but
VS solution build passes Targets='Build' explicitly, so Publish never
runs locally and the AOT pipeline doesn't fire. Hook Publish AfterTargets
=Build so it runs in both invocation modes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Release|x64 uses the merged AOT host, not WinRT.Host. The CopyTestAssets
target tries to copy WinRT.Host.dll which isn't built in the AOT-only
config. Gate the same way AuthoringConsumptionTest does.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Activation crash root cause: the components' ABI.{Name}.ManagedExports.
GetActivationFactory bodies use [UnsafeAccessor] to delegate to a same
named type in WinRT.Component.dll (where the actual activation logic
with class-name -> ServerActivationFactory lookup lives). ILC's trimmer
doesn't follow that cross-assembly string-typed UnsafeAccessor reference,
so it drops the methods holding the activation logic. Verified by
inspecting the AOT.dll: GreeterServerActivationFactory class names
survive, but the string literals 'AuthoringTest2.Greeter' and
'AuthoringTest3.Calculator' (used inside the trimmed methods) do not.
DllGetActivationFactory then returns 0x80131534 (COR_E_TYPELOAD) at
activation -> cppwinrt's check_hresult throws.

Root the WinRT.Component assembly so all its types/methods survive.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t for component-only references

The merged projection dll (WinRT.Projection.dll) is only produced when there are
non-component reference projections (3rd-party libs with [WindowsRuntimeReferenceAssembly]).
Components are projected into WinRT.Component.dll instead. Emitting
[TypeMapAssemblyTarget("WinRT.Projection")] when only components are referenced caused
WindowsRuntimeMarshallingInfo's static cctor to throw FileNotFoundException for the
missing assembly, surfacing as 0x80131534 / COR_E_TYPELOAD at every WinRT activation site
in merged-AOT consumers.

Fix: derive hasMergedProjection from non-SDK reference assemblies only, excluding
component assemblies. The combined list is still used for the per-assembly
PrivateProjections entries.

Also remove a stray EmitCompilerGeneratedFiles property from AuthoringConsumptionTest2.AOT.csproj.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@manodasanW manodasanW requested a review from Sergio0694 May 11, 2026 22:18
Comment on lines +72 to +75
<!-- PublishAot is needed by AuthoringExportTypesGenerator.ShouldEmitNativeExports for the
merged-aggregator scenario (non-component project with CsWinRTMergeReferencedActivationFactories=true).
Authoring.targets exposes the same property but only imports when CsWinRTComponent=true. -->
<CompilerVisibleProperty Include="PublishAot" />
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nit: leave a blank line before the comment and use the same style as the comment above (L37)

/// initializer; consumers using a single component dll directly must call
/// <c>Assembly.SetEntryAssembly</c> themselves if they need TypeMap discovery rooted there.
/// </summary>
private static void WriteProjectionTypesInitializer(ProjectionGeneratorProcessingState processingState)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like this one, is there some way we can drop module initializers entirely at least in some scenarios? That is, can we statically determine whether someone else will set the entry assembly already and skip this? For instance, is this needed in all of these scenarios?

  • A standalone WinRT component published as NAOT
  • An application with also a WinRT component, with .exe/.dll stub merging

Can we figure out a way to perhaps just scope this down to the mixed C++ scenarios that need it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants