diff --git a/build/AzurePipelineTemplates/CsWinRT-Build-Steps.yml b/build/AzurePipelineTemplates/CsWinRT-Build-Steps.yml index e426c86bdd..64b692a3bb 100644 --- a/build/AzurePipelineTemplates/CsWinRT-Build-Steps.yml +++ b/build/AzurePipelineTemplates/CsWinRT-Build-Steps.yml @@ -244,8 +244,6 @@ steps: - task: CopyFiles@2 displayName: Stage WinRT.Host.Shim condition: and(succeeded(), eq(variables['BuildPlatform'], 'x86'), eq(variables['BuildConfiguration'], 'release')) - enabled: false - continueOnError: True inputs: SourceFolder: $(Build.SourcesDirectory)\src\Authoring\WinRT.Host.Shim\bin\$(BuildConfiguration)\net10.0 Contents: | @@ -284,6 +282,26 @@ steps: cswinrtprojectiongen.pdb TargetFolder: $(StagingFolder)\net10.0\native + - task: CopyFiles@2 + displayName: Stage cswinrtprojectionrefgen + condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'arm64')), eq(variables['BuildConfiguration'], 'release')) + inputs: + SourceFolder: $(Build.SourcesDirectory)\src\WinRT.Projection.Ref.Generator\bin\$(BuildConfiguration)\net10.0\win-$(BuildPlatform)\publish + Contents: | + cswinrtprojectionrefgen.exe + cswinrtprojectionrefgen.pdb + TargetFolder: $(StagingFolder)\net10.0\native + + - task: CopyFiles@2 + displayName: Stage cswinrtwinmdgen + condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x64'), eq(variables['BuildPlatform'], 'arm64')), eq(variables['BuildConfiguration'], 'release')) + inputs: + SourceFolder: $(Build.SourcesDirectory)\src\WinRT.WinMD.Generator\bin\$(BuildConfiguration)\net10.0\win-$(BuildPlatform)\publish + Contents: | + cswinrtwinmdgen.exe + cswinrtwinmdgen.pdb + TargetFolder: $(StagingFolder)\net10.0\native + - task: CopyFiles@2 displayName: Stage WinRT.Generator.Tasks condition: and(succeeded(), eq(variables['BuildPlatform'], 'x86'), eq(variables['BuildConfiguration'], 'release')) diff --git a/build/AzurePipelineTemplates/CsWinRT-BuildAndTest-Stage-OneBranch.yml b/build/AzurePipelineTemplates/CsWinRT-BuildAndTest-Stage-OneBranch.yml index cac7e4ae89..adf3ba1190 100644 --- a/build/AzurePipelineTemplates/CsWinRT-BuildAndTest-Stage-OneBranch.yml +++ b/build/AzurePipelineTemplates/CsWinRT-BuildAndTest-Stage-OneBranch.yml @@ -72,6 +72,8 @@ jobs: net10.0/native/cswinrtinteropgen.exe; net10.0/native/cswinrtimplgen.exe; net10.0/native/cswinrtprojectiongen.exe; + net10.0/native/cswinrtprojectionrefgen.exe; + net10.0/native/cswinrtwinmdgen.exe; search_root: $(StagingFolder) - task: onebranch.pipeline.signing@1 diff --git a/build/AzurePipelineTemplates/CsWinRT-PublishToNuGet-Steps.yml b/build/AzurePipelineTemplates/CsWinRT-PublishToNuGet-Steps.yml index b9f73b43ae..c19ab5179f 100644 --- a/build/AzurePipelineTemplates/CsWinRT-PublishToNuGet-Steps.yml +++ b/build/AzurePipelineTemplates/CsWinRT-PublishToNuGet-Steps.yml @@ -95,7 +95,7 @@ steps: command: pack searchPatternPack: nuget/Microsoft.Windows.CsWinRT.nuspec configurationToPack: Release - buildProperties: cswinrt_nuget_version=$(NugetVersion);interop_winmd=$(Build.SourcesDirectory)\release_x86\native\WindowsRuntime.Internal.winmd;net10_runtime=$(Build.SourcesDirectory)\release_x86\net10.0\WinRT.Runtime.dll;net10_runtime_xml=$(Build.SourcesDirectory)\release_x86\net10.0\WinRT.Runtime.xml;source_generator=$(Build.SourcesDirectory)\release_x86\net10.0\WinRT.SourceGenerator.dll;winrt_shim=$(Build.SourcesDirectory)\release_x86\net10.0\WinRT.Host.Shim.dll;winrt_host_x86=$(Build.SourcesDirectory)\release_x86\native\WinRT.Host.dll;winrt_host_x64=$(Build.SourcesDirectory)\release_x64\native\WinRT.Host.dll;winrt_host_arm64=$(Build.SourcesDirectory)\release_arm64\native\WinRT.Host.dll;winrt_host_resource_x86=$(Build.SourcesDirectory)\release_x86\native\WinRT.Host.dll.mui;winrt_host_resource_x64=$(Build.SourcesDirectory)\release_x64\native\WinRT.Host.dll.mui;winrt_host_resource_arm64=$(Build.SourcesDirectory)\release_arm64\native\WinRT.Host.dll.mui;cswinrtinteropgen_x64=$(Build.SourcesDirectory)\release_x64\net10.0\native\cswinrtinteropgen.exe;cswinrtinteropgen_arm64=$(Build.SourcesDirectory)\release_arm64\net10.0\native\cswinrtinteropgen.exe;cswinrtimplgen_x64=$(Build.SourcesDirectory)\release_x64\net10.0\native\cswinrtimplgen.exe;cswinrtimplgen_arm64=$(Build.SourcesDirectory)\release_arm64\net10.0\native\cswinrtimplgen.exe;cswinrtprojectiongen_x64=$(Build.SourcesDirectory)\release_x64\net10.0\native\cswinrtprojectiongen.exe;cswinrtprojectiongen_arm64=$(Build.SourcesDirectory)\release_arm64\net10.0\native\cswinrtprojectiongen.exe;run_cswinrt_generator_task=$(Build.SourcesDirectory)\release_x86\netstandard2.0\WinRT.Generator.Tasks.dll;branch=$(Build.SourceBranchName);commit=$(Build.SourceVersion) + buildProperties: cswinrt_nuget_version=$(NugetVersion);interop_winmd=$(Build.SourcesDirectory)\release_x86\native\WindowsRuntime.Internal.winmd;net10_runtime=$(Build.SourcesDirectory)\release_x86\net10.0\WinRT.Runtime.dll;net10_runtime_xml=$(Build.SourcesDirectory)\release_x86\net10.0\WinRT.Runtime.xml;source_generator=$(Build.SourcesDirectory)\release_x86\net10.0\WinRT.SourceGenerator.dll;winrt_shim=$(Build.SourcesDirectory)\release_x86\net10.0\WinRT.Host.Shim.dll;winrt_host_x86=$(Build.SourcesDirectory)\release_x86\native\WinRT.Host.dll;winrt_host_x64=$(Build.SourcesDirectory)\release_x64\native\WinRT.Host.dll;winrt_host_arm64=$(Build.SourcesDirectory)\release_arm64\native\WinRT.Host.dll;winrt_host_resource_x86=$(Build.SourcesDirectory)\release_x86\native\WinRT.Host.dll.mui;winrt_host_resource_x64=$(Build.SourcesDirectory)\release_x64\native\WinRT.Host.dll.mui;winrt_host_resource_arm64=$(Build.SourcesDirectory)\release_arm64\native\WinRT.Host.dll.mui;cswinrtinteropgen_x64=$(Build.SourcesDirectory)\release_x64\net10.0\native\cswinrtinteropgen.exe;cswinrtinteropgen_arm64=$(Build.SourcesDirectory)\release_arm64\net10.0\native\cswinrtinteropgen.exe;cswinrtimplgen_x64=$(Build.SourcesDirectory)\release_x64\net10.0\native\cswinrtimplgen.exe;cswinrtimplgen_arm64=$(Build.SourcesDirectory)\release_arm64\net10.0\native\cswinrtimplgen.exe;cswinrtprojectiongen_x64=$(Build.SourcesDirectory)\release_x64\net10.0\native\cswinrtprojectiongen.exe;cswinrtprojectiongen_arm64=$(Build.SourcesDirectory)\release_arm64\net10.0\native\cswinrtprojectiongen.exe;cswinrtprojectionrefgen_x64=$(Build.SourcesDirectory)\release_x64\net10.0\native\cswinrtprojectionrefgen.exe;cswinrtprojectionrefgen_arm64=$(Build.SourcesDirectory)\release_arm64\net10.0\native\cswinrtprojectionrefgen.exe;cswinrtwinmdgen_x64=$(Build.SourcesDirectory)\release_x64\net10.0\native\cswinrtwinmdgen.exe;cswinrtwinmdgen_arm64=$(Build.SourcesDirectory)\release_arm64\net10.0\native\cswinrtwinmdgen.exe;run_cswinrt_generator_task=$(Build.SourcesDirectory)\release_x86\netstandard2.0\WinRT.Generator.Tasks.dll;branch=$(Build.SourceBranchName);commit=$(Build.SourceVersion) packDestination: $(ob_outputDirectory)\packages - ${{ if eq(parameters.IsGitHub, false) }}: diff --git a/build/AzurePipelineTemplates/CsWinRT-Test-Steps.yml b/build/AzurePipelineTemplates/CsWinRT-Test-Steps.yml index 01a55b79f3..e9a909b82f 100644 --- a/build/AzurePipelineTemplates/CsWinRT-Test-Steps.yml +++ b/build/AzurePipelineTemplates/CsWinRT-Test-Steps.yml @@ -70,6 +70,18 @@ steps: dir _build\$(BuildPlatform)\$(BuildConfiguration)\AuthoringConsumptionTest\bin _build\$(BuildPlatform)\$(BuildConfiguration)\AuthoringConsumptionTest\bin\AuthoringConsumptionTest.exe --gtest_output=xml:AUTHORINGTEST-$(Build.BuildNumber).xml +# Run Multi-Component Authoring Consumption Tests + - task: CmdLine@2 + displayName: Run Multi-Component Authoring Consumption Tests + enabled: true + condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x86'), eq(variables['BuildPlatform'], 'x64'))) + continueOnError: True + inputs: + workingDirectory: $(Build.SourcesDirectory)\src + script: | + dir _build\$(BuildPlatform)\$(BuildConfiguration)\AuthoringConsumptionTest2\bin + _build\$(BuildPlatform)\$(BuildConfiguration)\AuthoringConsumptionTest2\bin\AuthoringConsumptionTest2.exe --gtest_output=xml:AUTHORINGTEST2-$(Build.BuildNumber).xml + # Run WUX Tests - task: CmdLine@2 displayName: Run WUX Tests diff --git a/nuget/Microsoft.Windows.CsWinRT.Authoring.targets b/nuget/Microsoft.Windows.CsWinRT.Authoring.targets index d33ee6e931..728341b378 100644 --- a/nuget/Microsoft.Windows.CsWinRT.Authoring.targets +++ b/nuget/Microsoft.Windows.CsWinRT.Authoring.targets @@ -67,7 +67,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. --> - + WinRT.Host.Shim.dll PreserveNewest @@ -171,6 +171,9 @@ Copyright (C) Microsoft Corporation. All rights reserved. true $(TargetFramework) + + $(WindowsSdkPackageVersion) @@ -293,7 +296,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. --> - + true lib\$(TargetFramework) diff --git a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets index 05a56e97b7..bf683b9dff 100644 --- a/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets +++ b/nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets @@ -45,6 +45,11 @@ Copyright (C) Microsoft Corporation. All rights reserved. High true + + false + <_CsWinRTGeneratorInteropAssemblyName>WinRT.Interop <_CsWinRTGeneratorInteropAssemblyFileName>$(_CsWinRTGeneratorInteropAssemblyName).dll @@ -352,8 +357,9 @@ Copyright (C) Microsoft Corporation. All rights reserved. MaxDegreesOfParallelism="$(CsWinRTGeneratorMaxDegreesOfParallelism)" LogStandardErrorAsError="$(CsWinRTGeneratorLogStandardErrorAsError)" /> - - + + WinRT.Projection .NETCoreApp @@ -409,6 +415,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. AssemblyName="WinRT.Component" CsWinRTToolsDirectory="$(CsWinRTMergedProjectionEffectiveToolsDirectory)" CsWinRTToolsArchitecture="$(CsWinRTToolsArchitecture)" + EmitEntryPointInitializer="$(CsWinRTEmitEntryPointInitializer)" StandardOutputImportance="$(CsWinRTGeneratorStandardOutputImportance)" StandardErrorImportance="$(CsWinRTGeneratorStandardErrorImportance)" MaxDegreesOfParallelism="$(CsWinRTGeneratorMaxDegreesOfParallelism)" diff --git a/nuget/Microsoft.Windows.CsWinRT.Embedded.targets b/nuget/Microsoft.Windows.CsWinRT.Embedded.targets deleted file mode 100644 index ff8015f1f1..0000000000 --- a/nuget/Microsoft.Windows.CsWinRT.Embedded.targets +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - $(DefineConstants);EMBED - - true - $(CsWinRTPath)embedded\ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/nuget/Microsoft.Windows.CsWinRT.IIDOptimizer.targets b/nuget/Microsoft.Windows.CsWinRT.IIDOptimizer.targets deleted file mode 100644 index 640a3a4b7e..0000000000 --- a/nuget/Microsoft.Windows.CsWinRT.IIDOptimizer.targets +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - $(CsWinRTPath)build\tools\IIDOptimizer\ - - @(BuiltProjectOutputGroupKeyOutput->'%(Identity)') - - $([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)', 'IIDOptimizer')) - - @(ReferencePathWithRefAssemblies->'--refs %(Identity)', ' ') - - ---target -$(CsWinRTIIDOptimizerTargetAssembly) ---outdir -$(IIDOptimizerInterimDir) -$(GuidPatchTargetAssemblyReferences) - - - - - - - $(IIDOptimizerInterimDir)cswinrt_iidoptimizer.rsp - "$(CsWinRTIIDOptimizerPath)IIDOptimizer.exe" %40"$(CsWinRTIIDOptimizerResponseFile)" - - - - - - - - - - - - - - - - - - - - - - - - - - - $([System.IO.Directory]::GetParent($(CsWinRTIIDOptimizerTargetAssembly))) - - - - - - - - \ No newline at end of file diff --git a/nuget/Microsoft.Windows.CsWinRT.Native.targets b/nuget/Microsoft.Windows.CsWinRT.Native.targets deleted file mode 100644 index 6542a26038..0000000000 --- a/nuget/Microsoft.Windows.CsWinRT.Native.targets +++ /dev/null @@ -1,133 +0,0 @@ - - - - - <_CsWinRTNativeConsumerIntermediateDir>$(IntDir)cswinrt\ - - - - - - - - <_CsWinRTComponentProjectPaths Include="%(_ResolvedProjectReferencePaths.MSBuildSourceProjectFile)" - Condition="'%(_ResolvedProjectReferencePaths.CsWinRTComponent)' == 'true'" /> - - - - - <_CsWinRTComponentTargetFramework>%(_ResolvedProjectReferencePaths.CsWinRTComponentTargetFramework) - <_CsWinRTComponentTargetFramework Condition="'$(_CsWinRTComponentTargetFramework)' == ''">net10.0-windows10.0.26100.1 - - - - - - - - - - <_CsWinRTHasComponentReferences Condition="'@(_CsWinRTComponentProjectPaths)' != ''">true - - - - - - - <_CsWinRTTempProjectDir>$(_CsWinRTNativeConsumerIntermediateDir) - <_CsWinRTTempProjectPath>$(_CsWinRTTempProjectDir)CsWinRT.NativeConsumer.csproj - - <_CsWinRTTempProjectTFMDir>$(_CsWinRTComponentTargetFramework) - - - - - - <_CsWinRTTempProjectLines Include="<Project Sdk="Microsoft.NET.Sdk">" /> - <_CsWinRTTempProjectLines Include=" <PropertyGroup>" /> - <_CsWinRTTempProjectLines Include=" <TargetFramework>$(_CsWinRTComponentTargetFramework)</TargetFramework>" /> - <_CsWinRTTempProjectLines Include=" <Platforms>x64%3Bx86%3BARM64</Platforms>" /> - <_CsWinRTTempProjectLines Include=" <CsWinRTBuildForNativeConsumer>true</CsWinRTBuildForNativeConsumer>" /> - <_CsWinRTTempProjectLines Include=" <CsWinRTGenerateInteropAssembly>true</CsWinRTGenerateInteropAssembly>" /> - <_CsWinRTTempProjectLines Include=" <CsWinRTGenerateProjection>false</CsWinRTGenerateProjection>" /> - <_CsWinRTTempProjectLines Include=" <EnableDefaultItems>false</EnableDefaultItems>" /> - <_CsWinRTTempProjectLines Include=" <GenerateAssemblyInfo>false</GenerateAssemblyInfo>" /> - <_CsWinRTTempProjectLines Include=" <ProduceReferenceAssembly>false</ProduceReferenceAssembly>" /> - <_CsWinRTTempProjectLines Include=" <NoWarn>$(NoWarn)%3BNETSDK1130</NoWarn>" /> - <_CsWinRTTempProjectLines Include=" </PropertyGroup>" /> - <_CsWinRTTempProjectLines Include=" <ItemGroup>" /> - <_CsWinRTTempProjectLines Include=" <ProjectReference Include="%(_CsWinRTComponentProjectPaths.Identity)" />" /> - <_CsWinRTTempProjectLines Include=" </ItemGroup>" /> - <_CsWinRTTempProjectLines Include="</Project>" /> - - - - - - - - - - - - <_CsWinRTTempProjectIntermediateDir>$(_CsWinRTTempProjectDir)obj\$(Configuration)\$(_CsWinRTTempProjectTFMDir)\ - - - - <_CsWinRTGeneratedDlls Include="$(_CsWinRTTempProjectIntermediateDir)WinRT.Component.dll" Condition="Exists('$(_CsWinRTTempProjectIntermediateDir)WinRT.Component.dll')" /> - <_CsWinRTGeneratedDlls Include="$(_CsWinRTTempProjectIntermediateDir)WinRT.Interop.dll" Condition="Exists('$(_CsWinRTTempProjectIntermediateDir)WinRT.Interop.dll')" /> - <_CsWinRTGeneratedDlls Include="$(_CsWinRTTempProjectIntermediateDir)WinRT.Projection.dll" Condition="Exists('$(_CsWinRTTempProjectIntermediateDir)WinRT.Projection.dll')" /> - <_CsWinRTGeneratedDlls Include="$(_CsWinRTTempProjectIntermediateDir)WinRT.Sdk.Projection.dll" Condition="Exists('$(_CsWinRTTempProjectIntermediateDir)WinRT.Sdk.Projection.dll')" /> - <_CsWinRTGeneratedDlls Include="$(_CsWinRTTempProjectIntermediateDir)WinRT.Sdk.Xaml.Projection.dll" Condition="Exists('$(_CsWinRTTempProjectIntermediateDir)WinRT.Sdk.Xaml.Projection.dll')" /> - - - - - - - diff --git a/nuget/Microsoft.Windows.CsWinRT.nuspec b/nuget/Microsoft.Windows.CsWinRT.nuspec index 44bf5c8952..28a6113f82 100644 --- a/nuget/Microsoft.Windows.CsWinRT.nuspec +++ b/nuget/Microsoft.Windows.CsWinRT.nuspec @@ -24,11 +24,13 @@ - + + + - + @@ -37,7 +39,7 @@ - + + $([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)', '..\metadata\WindowsRuntime.Internal.winmd')) + + @@ -127,22 +143,6 @@ Copyright (C) Microsoft Corporation. All rights reserved. - - - - $(CsWinRTPath)lib\net10.0 - $(CsWinRTPath)runtimes\**\native - - - - - - - - - - - @@ -150,6 +150,23 @@ Copyright (C) Microsoft Corporation. All rights reserved. + + + + + + + diff --git a/nuget/native/Microsoft.Windows.CsWinRT.targets b/nuget/native/Microsoft.Windows.CsWinRT.targets new file mode 100644 index 0000000000..6fad7ca266 --- /dev/null +++ b/nuget/native/Microsoft.Windows.CsWinRT.targets @@ -0,0 +1,262 @@ + + + + + <_CsWinRTNativeConsumerIntermediateDir>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)', '$(IntDir)', 'cswinrt')) + + + normal + + + <_CsWinRTNormalizedPlatform Condition="'$(Platform)' == 'Win32'">x86 + <_CsWinRTNormalizedPlatform Condition="'$(_CsWinRTNormalizedPlatform)' == ''">$(Platform) + + + + + + + + <_CsWinRTComponentProjectPaths Include="%(_ResolvedProjectReferencePaths.MSBuildSourceProjectFile)" + Condition="'%(_ResolvedProjectReferencePaths.CsWinRTComponent)' == 'true'" /> + + + <_CsWinRTComponentTargetFrameworkValues Include="%(_ResolvedProjectReferencePaths.CsWinRTComponentTargetFramework)" + Condition="'%(_ResolvedProjectReferencePaths.CsWinRTComponent)' == 'true' and '%(_ResolvedProjectReferencePaths.CsWinRTComponentTargetFramework)' != ''" /> + <_CsWinRTComponentTargetFrameworkValues Include="%(CsWinRTNativeComponent.CsWinRTComponentTargetFramework)" + Condition="'%(CsWinRTNativeComponent.CsWinRTComponentTargetFramework)' != ''" /> + + + <_CsWinRTComponentSdkPackageVersionValues Include="%(_ResolvedProjectReferencePaths.CsWinRTComponentWindowsSdkPackageVersion)" + Condition="'%(_ResolvedProjectReferencePaths.CsWinRTComponent)' == 'true' and '%(_ResolvedProjectReferencePaths.CsWinRTComponentWindowsSdkPackageVersion)' != ''" /> + <_CsWinRTComponentSdkPackageVersionValues Include="%(CsWinRTNativeComponent.CsWinRTComponentWindowsSdkPackageVersion)" + Condition="'%(CsWinRTNativeComponent.CsWinRTComponentWindowsSdkPackageVersion)' != ''" /> + + + + <_CsWinRTHasComponentReferences Condition="'@(_CsWinRTComponentProjectPaths)' != '' or '@(CsWinRTNativeComponent)' != ''">true + + + <_CsWinRTDistinctComponentTargetFrameworks>@(_CsWinRTComponentTargetFrameworkValues->Distinct(), ';') + + + <_CsWinRTComponentTargetFramework Condition="'$(CsWinRTComponentTargetFrameworkOverride)' != ''">$(CsWinRTComponentTargetFrameworkOverride) + <_CsWinRTComponentTargetFramework Condition="'$(_CsWinRTComponentTargetFramework)' == '' and '$(_CsWinRTDistinctComponentTargetFrameworks)' != '' and !$(_CsWinRTDistinctComponentTargetFrameworks.Contains(';'))">$(_CsWinRTDistinctComponentTargetFrameworks) + + + + + + + + + + + <_CsWinRTAggregatorWindowsSdkPackageVersion Condition="'$(CsWinRTNativeConsumerWindowsSdkPackageVersion)' != ''">$(CsWinRTNativeConsumerWindowsSdkPackageVersion) + <_CsWinRTAggregatorWindowsSdkPackageVersion Condition="'$(_CsWinRTAggregatorWindowsSdkPackageVersion)' == ''">@(_CsWinRTComponentSdkPackageVersionValues->Distinct()) + + + + + <_CsWinRTAggregatorInputs Include="@(_ResolvedProjectReferencePaths)" + Condition="'%(_ResolvedProjectReferencePaths.CsWinRTComponent)' == 'true'" /> + <_CsWinRTAggregatorInputs Include="@(CsWinRTNativeComponent)" /> + <_CsWinRTAggregatorInputs Include="$(MSBuildThisFileFullPath)" /> + + + + + + + + + + + + + + <_CsWinRTTempProjectDir>$(_CsWinRTNativeConsumerIntermediateDir) + <_CsWinRTTempProjectPath>$(_CsWinRTTempProjectDir)CsWinRT.NativeConsumer.csproj + <_CsWinRTTempProjectTFMDir>$(_CsWinRTComponentTargetFramework) + + + <_CsWinRTTempProjectBaseIntermediateDir>$(_CsWinRTTempProjectDir)obj\ + <_CsWinRTTempProjectIntermediateConfigDir>$(_CsWinRTTempProjectBaseIntermediateDir)$(Configuration)\ + <_CsWinRTTempProjectIntermediateDir>$(_CsWinRTTempProjectIntermediateConfigDir)$(_CsWinRTTempProjectTFMDir)\ + + + <_CsWinRTPropsForAggregator>$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)..\Microsoft.Windows.CsWinRT.props')) + <_CsWinRTTargetsForAggregator>$([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)..\Microsoft.Windows.CsWinRT.targets')) + + + + + + <_CsWinRTTempProjectLines Include="<Project>" /> + <_CsWinRTTempProjectLines Include=" <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />" /> + <_CsWinRTTempProjectLines Include=" <Import Project="$(_CsWinRTPropsForAggregator)" />" /> + <_CsWinRTTempProjectLines Include=" <PropertyGroup>" /> + <_CsWinRTTempProjectLines Include=" <TargetFramework>$(_CsWinRTComponentTargetFramework)</TargetFramework>" /> + <_CsWinRTTempProjectLines Include=" <Platforms>x64%3Bx86%3BARM64</Platforms>" /> + <_CsWinRTTempProjectLines Condition="'$(_CsWinRTAggregatorWindowsSdkPackageVersion)' != ''" + Include=" <WindowsSdkPackageVersion>$(_CsWinRTAggregatorWindowsSdkPackageVersion)</WindowsSdkPackageVersion>" /> + + <_CsWinRTTempProjectLines Include=" <BaseIntermediateOutputPath>$(_CsWinRTTempProjectBaseIntermediateDir)</BaseIntermediateOutputPath>" /> + <_CsWinRTTempProjectLines Include=" <IntermediateOutputPath>$(_CsWinRTTempProjectIntermediateConfigDir)</IntermediateOutputPath>" /> + <_CsWinRTTempProjectLines Include=" <CsWinRTBuildForNativeConsumer>true</CsWinRTBuildForNativeConsumer>" /> + <_CsWinRTTempProjectLines Include=" <CsWinRTEmitEntryPointInitializer>true</CsWinRTEmitEntryPointInitializer>" /> + <_CsWinRTTempProjectLines Include=" <CsWinRTGenerateInteropAssembly>true</CsWinRTGenerateInteropAssembly>" /> + <_CsWinRTTempProjectLines Include=" <CsWinRTGenerateProjection>false</CsWinRTGenerateProjection>" /> + <_CsWinRTTempProjectLines Include=" <EnableDefaultItems>false</EnableDefaultItems>" /> + <_CsWinRTTempProjectLines Include=" <GenerateAssemblyInfo>false</GenerateAssemblyInfo>" /> + <_CsWinRTTempProjectLines Include=" <ProduceReferenceAssembly>false</ProduceReferenceAssembly>" /> + <_CsWinRTTempProjectLines Include=" </PropertyGroup>" /> + <_CsWinRTTempProjectLines Include=" <ItemGroup>" /> + + <_CsWinRTTempProjectLines Condition="'@(_CsWinRTComponentProjectPaths)' != ''" + Include=" <ProjectReference Include="%(_CsWinRTComponentProjectPaths.Identity)" />" /> + + <_CsWinRTTempProjectLines Condition="'@(CsWinRTNativeComponent)' != ''" + Include=" <Reference Include="%(CsWinRTNativeComponent.Filename)">" /> + <_CsWinRTTempProjectLines Condition="'@(CsWinRTNativeComponent)' != ''" + Include=" <HintPath>%(CsWinRTNativeComponent.Identity)</HintPath>" /> + <_CsWinRTTempProjectLines Condition="'@(CsWinRTNativeComponent)' != ''" + Include=" <CsWinRTComponent>true</CsWinRTComponent>" /> + <_CsWinRTTempProjectLines Condition="'@(CsWinRTNativeComponent)' != ''" + Include=" <Private>true</Private>" /> + <_CsWinRTTempProjectLines Condition="'@(CsWinRTNativeComponent)' != ''" + Include=" </Reference>" /> + <_CsWinRTTempProjectLines Include=" </ItemGroup>" /> + <_CsWinRTTempProjectLines Include=" <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />" /> + <_CsWinRTTempProjectLines Include=" <Import Project="$(_CsWinRTTargetsForAggregator)" />" /> + <_CsWinRTTempProjectLines Include="</Project>" /> + + + + + + + + + + <_CsWinRTGeneratedDlls Include="$(_CsWinRTTempProjectIntermediateDir)WinRT.Component.dll" Condition="Exists('$(_CsWinRTTempProjectIntermediateDir)WinRT.Component.dll')" /> + <_CsWinRTGeneratedDlls Include="$(_CsWinRTTempProjectIntermediateDir)WinRT.Interop.dll" Condition="Exists('$(_CsWinRTTempProjectIntermediateDir)WinRT.Interop.dll')" /> + <_CsWinRTGeneratedDlls Include="$(_CsWinRTTempProjectIntermediateDir)WinRT.Projection.dll" Condition="Exists('$(_CsWinRTTempProjectIntermediateDir)WinRT.Projection.dll')" /> + <_CsWinRTGeneratedDlls Include="$(_CsWinRTTempProjectIntermediateDir)WinRT.Sdk.Projection.dll" Condition="Exists('$(_CsWinRTTempProjectIntermediateDir)WinRT.Sdk.Projection.dll')" /> + <_CsWinRTGeneratedDlls Include="$(_CsWinRTTempProjectIntermediateDir)WinRT.Sdk.Xaml.Projection.dll" Condition="Exists('$(_CsWinRTTempProjectIntermediateDir)WinRT.Sdk.Xaml.Projection.dll')" /> + + + + + + + + + + + <_CsWinRTHostDllPath>$(MSBuildThisFileDirectory)..\..\hosting\$(_CsWinRTNormalizedPlatform)\native\WinRT.Host.dll + <_CsWinRTHostMuiPath>$(MSBuildThisFileDirectory)..\..\hosting\$(_CsWinRTNormalizedPlatform)\native\en-US\WinRT.Host.dll.mui + + + + <_CsWinRTHostingItemsToReplace Include="@(ReferenceCopyLocalPaths)" + Condition="'%(Filename)%(Extension)' == 'WinRT.Host.dll' or '%(Filename)%(Extension)' == 'WinRT.Host.dll.mui'" /> + + + + + + + + diff --git a/nuget/readme.md b/nuget/readme.md index fa9f165dad..e74e375d69 100644 --- a/nuget/readme.md +++ b/nuget/readme.md @@ -76,6 +76,62 @@ If a Windows SDK is installed, $(WindowsSDKVersion) is defined when building fro The C#/WinRT package can be used both to consume WinRT types, and to produce them (via CsWinRTComponent). It is also possible to combine these settings and do both. For example, a developer might want to *produce* a library that's implemented in terms of another WinRT runtime class (*consuming* it). +## Consuming CsWinRT components from a native (C++) app + +When a native (C++) `.vcxproj` references one or more managed CsWinRT components, the CsWinRT package's +`build/native/Microsoft.Windows.CsWinRT.targets` walks the component references and synthesizes a temporary +aggregator project. That aggregator runs the cswinrt projection, interop and component pipeline once across +the union of all referenced components, producing a single deduplicated `WinRT.Interop.dll` plus +`WinRT.Component.dll`, `WinRT.Projection.dll` and `WinRT.Sdk.Projection.dll`. The merged hosting bundle is +copied to the consumer's output directory next to `WinRT.Host.dll` / `WinRT.Host.Shim.dll`. + +Component references are picked up from two sources, treated as equivalent inputs: + +* `` items whose `CsWinRTComponent` metadata is `true`. Components built with + `CsWinRTComponent=true` automatically expose this metadata. +* `@(CsWinRTNativeComponent)` items contributed by component-package targets files (see below). + +Consumer-side properties: + +| Property | Description | +|-|-| +| `CsWinRTDisableNativeComponentInterop` | Set to `true` to disable aggregator generation. | +| `CsWinRTComponentTargetFrameworkOverride` | TFM to use for the aggregator project. Required when referenced components disagree on TFM, or when no referenced component supplies a TFM. | +| `CsWinRTNativeConsumerWindowsSdkPackageVersion` | `WindowsSdkPackageVersion` to pass to the aggregator. Falls back to a value picked from any component reference, then to the SDK default. | +| `CsWinRTSkipNativeHostingAssetsOverride` | Set to `true` to opt out of the arch-correct replacement of `WinRT.Host.dll` / `.mui` in the consumer's reference closure. | + +### Distributing a CsWinRT component for native consumers + +A CsWinRT component is built with `CsWinRTComponent=true` in its `.csproj`. The build produces a managed +`{AssemblyName}.dll` plus a `.winmd`. Two distribution shapes are supported: + +#### Option 1 — Ship the managed component .dll (JIT hosting, supports merged multi-component aggregation) + +Native consumers reference the managed component and the consumer's own `WinRT.Host.dll`/`WinRT.Host.Shim.dll` +load it at runtime. Component packages opt in by shipping a small `build/native/{PackageId}.targets` that +contributes the component .dll to `@(CsWinRTNativeComponent)` and its `.winmd` to `@(CsWinRTInputs)`: + +```xml + + + + + net10.0-windows10.0.26100.1 + + + + +``` + +The `Identity` is the path to the component .dll. `CsWinRTComponentTargetFramework` is required and is used +by the consumer to derive the aggregator's target framework. + +#### Option 2 — AOT-publish the component and ship the resulting native binary + +The component author runs `dotnet publish -p:PublishAot=true` on the component themselves and distributes +the resulting native dll. Native consumers load that binary directly through whatever activation mechanism +they prefer; they do not go through the aggregator. + ## Troubleshooting The MSBuild verbosity level maps to MSBuild message importance as follows: diff --git a/src/Authoring/WinRT.Host/WinRT.Host.cpp b/src/Authoring/WinRT.Host/WinRT.Host.cpp index 82566f5995..aa738ad080 100644 --- a/src/Authoring/WinRT.Host/WinRT.Host.cpp +++ b/src/Authoring/WinRT.Host/WinRT.Host.cpp @@ -376,16 +376,6 @@ void GetActivationFactory(void* hstr_class_id, void** activation_factory) init_runtime(host_module.wstring().c_str(), host_config.c_str()); - // If no explicit target assembly mapping found, probe for it by naming convention - if (target_path.empty()) - { - target_path = probe_for_target_assembly(host_module, class_id); - if (target_path.empty() || !std::filesystem::exists(target_path)) - { - winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND)); - } - } - // Load shim (managed portion of host) and retrieve get_activation_factory pointer if (::get_activation_factory == nullptr) { @@ -395,11 +385,57 @@ void GetActivationFactory(void* hstr_class_id, void** activation_factory) shim_path.wstring().c_str(), L"WinRT.Host.Shim, WinRT.Host.Shim", L"GetActivationFactory", - L"WinRT.Host.Shim+GetActivationFactoryDelegate, WinRT.Host.Shim", + L"WinRT.Host.Shim+GetActivationFactoryDelegate, WinRT.Host.Shim", nullptr, (void**)&::get_activation_factory)); } + // If no explicit target assembly mapping was supplied via runtimeconfig, prefer a deployed + // 'WinRT.Component.dll'. This is the merged-projection / merged-activation dll produced by + // the aggregator's projection generator for native-consumer scenarios with one or more + // CsWinRT components. Its merged 'ABI.WinRT.Component.ManagedExports.GetActivationFactory' + // dispatches across each input component. A 'CLASS_E_CLASSNOTAVAILABLE' from the shim + // means the merged dll didn't aggregate that runtime class, so we fall through to the + // existing per-component prefix probe below. Any other failing HRESULT is fatal. + if (target_path.empty()) + { + auto winrt_component_path = host_module; + winrt_component_path.replace_filename(L"WinRT.Component.dll"); + + if (std::filesystem::exists(winrt_component_path)) + { + winrt::hstring hstr_winrt_component_path(winrt_component_path.c_str()); + *activation_factory = nullptr; + + HRESULT hr = ::get_activation_factory( + winrt::get_abi(hstr_winrt_component_path), hstr_class_id, activation_factory); + + if (SUCCEEDED(hr)) + { + return; + } + + if (hr != CLASS_E_CLASSNOTAVAILABLE) + { + check_hostfxr_hresult(hr); + } + + // 'WinRT.Component.dll' did not aggregate this runtime class; fall through + // to the existing per-component probing logic below. + *activation_factory = nullptr; + } + } + + // If no explicit target assembly mapping found, probe for it by naming convention + if (target_path.empty()) + { + target_path = probe_for_target_assembly(host_module, class_id); + if (target_path.empty() || !std::filesystem::exists(target_path)) + { + winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND)); + } + } + // Load target assembly and get managed runtime class activation factory winrt::hstring hstr_target_path(target_path.c_str()); check_hostfxr_hresult(::get_activation_factory( diff --git a/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.cs b/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.cs index 0fc02a3df8..cb09452714 100644 --- a/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.cs +++ b/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.cs @@ -40,10 +40,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return options.GlobalOptions.GetCsWinRTUseWindowsUIXamlProjections(); }); - // Get whether the project is being built for a native consumer - IncrementalValueProvider isBuildForNativeConsumer = context.AnalyzerConfigOptionsProvider.Select(static (options, token) => + // Get whether the current project is itself a component + IncrementalValueProvider isComponent = context.AnalyzerConfigOptionsProvider.Select(static (options, token) => { - return options.GlobalOptions.GetBuildForNativeConsumer(); + return options.GlobalOptions.GetCsWinRTComponent(); }); // Get whether the current project is a library published with Native AOT @@ -52,11 +52,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Combine(isPublishAot) .Select(static (flags, token) => flags.Left && flags.Right); - // Get whether the generator should actually run or not + // Get whether the generator should actually run or not. + // The gate fires for any unit that ships TypeMap entries: an executable, a NativeAOT-published library, + // or a Windows Runtime component (the component .dll itself is the deployable when consumed natively). IncrementalValueProvider isGeneratorEnabled = isOutputTypeExe .Combine(isPublishAotLibrary) - .Combine(isBuildForNativeConsumer) + .Combine(isComponent) .Select(static (flags, token) => flags.Left.Left || flags.Left.Right || flags.Right); // Gather all PE references from the current compilation @@ -80,12 +82,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Select(static (name, _) => name!) .Collect(); - // Combine all matching assembly names (reference + component) and filter out the Windows SDK ones - IncrementalValueProvider> filteredAssemblyNames = + // Collect non-Windows-SDK reference assembly names. These feed the merged projection (WinRT.Projection.dll). + IncrementalValueProvider> nonSdkReferenceAssemblyNames = referenceAssemblyNames .Where(static name => name is not null and not "Microsoft.Windows.SDK.NET" and not "Microsoft.Windows.UI.Xaml") .Select(static (name, _) => name!) - .Collect() + .Collect(); + + // Combine reference and component assembly names for the per-assembly '[TypeMapAssemblyTarget]' entries. + IncrementalValueProvider> filteredAssemblyNames = + nonSdkReferenceAssemblyNames .Combine(collectedComponentAssemblyNames) .Select(static (pair, token) => pair.Left.AddRange(pair.Right)); @@ -94,9 +100,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) filteredAssemblyNames .Select(static (names, token) => names.Sort(StringComparer.Ordinal).AsEquatableArray()); - // Whether the merged projection will be generated + // Whether the merged projection will be generated. Only non-Windows-SDK reference assemblies feed + // WinRT.Projection.dll; component assemblies are projected into WinRT.Component.dll instead. IncrementalValueProvider hasMergedProjection = - filteredAssemblyNames + nonSdkReferenceAssemblyNames .Select(static (assemblyNames, token) => !assemblyNames.IsDefaultOrEmpty); // Generate the attributes for all matching assemblies @@ -108,12 +115,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Also generate the '[TypeMapAssemblyTarget]' entry for the merged projection context.RegisterImplementationSourceOutput(hasMergedProjection, Execute.EmitMergedProjectionTypeMapAssemblyTargetAttributes); - // Get whether the current project is a component - IncrementalValueProvider isComponent = context.AnalyzerConfigOptionsProvider.Select(static (options, token) => - { - return options.GlobalOptions.GetCsWinRTComponent(); - }); - // Whether any component assemblies are referenced, or the current project is itself a component IncrementalValueProvider hasComponentAssembly = collectedComponentAssemblyNames diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 056a8ddd2e..10c00ed6ea 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -21,11 +21,21 @@ + + + + + + + @@ -103,7 +113,7 @@ diff --git a/src/Tests/AuthoringConsumptionTest/AuthoringConsumptionTest.vcxproj b/src/Tests/AuthoringConsumptionTest/AuthoringConsumptionTest.vcxproj index 8d55f37bf5..d4f5a2ab0e 100644 --- a/src/Tests/AuthoringConsumptionTest/AuthoringConsumptionTest.vcxproj +++ b/src/Tests/AuthoringConsumptionTest/AuthoringConsumptionTest.vcxproj @@ -99,7 +99,7 @@ {41e2a272-150f-42f5-ad40-047aad9088a0} - TargetFramework=net10.0 + TargetFramework=net10.0-windows10.0.26100.1 - + - - - <_CsWinRTComponentProjectPath>..\AuthoringTest\AuthoringTest.csproj - <_CsWinRTComponentOutputDir>..\AuthoringTest\bin\$(_WinMDPlatform)\$(Configuration)\net10.0\ - <_CsWinRTComponentIntermediateDir>..\AuthoringTest\obj\$(_WinMDPlatform)\$(Configuration)\net10.0\ - - - - - - - <_CsWinRTJitGeneratedDlls Include="$(_CsWinRTComponentIntermediateDir)WinRT.Component.dll" Condition="Exists('$(_CsWinRTComponentIntermediateDir)WinRT.Component.dll')" /> - <_CsWinRTJitGeneratedDlls Include="$(_CsWinRTComponentIntermediateDir)WinRT.Interop.dll" Condition="Exists('$(_CsWinRTComponentIntermediateDir)WinRT.Interop.dll')" /> - <_CsWinRTJitGeneratedDlls Include="$(_CsWinRTComponentIntermediateDir)WinRT.Projection.dll" Condition="Exists('$(_CsWinRTComponentIntermediateDir)WinRT.Projection.dll')" /> - <_CsWinRTJitGeneratedDlls Include="$(_CsWinRTComponentIntermediateDir)WinRT.Sdk.Projection.dll" Condition="Exists('$(_CsWinRTComponentIntermediateDir)WinRT.Sdk.Projection.dll')" /> - <_CsWinRTJitGeneratedDlls Include="$(_CsWinRTComponentIntermediateDir)WinRT.Sdk.Xaml.Projection.dll" Condition="Exists('$(_CsWinRTComponentIntermediateDir)WinRT.Sdk.Xaml.Projection.dll')" /> - - - - ..\AuthoringTest\bin\$(_WinMDPlatform)\$(Configuration)\net10.0\win-$(Platform)\AuthoringTest.winmd + ..\AuthoringTest\bin\$(_WinMDPlatform)\$(Configuration)\net10.0-windows10.0.26100.1\win-$(Platform)\AuthoringTest.winmd true - + - - + + diff --git a/src/Tests/AuthoringConsumptionTest2.AOT/AuthoringConsumptionTest2.AOT.csproj b/src/Tests/AuthoringConsumptionTest2.AOT/AuthoringConsumptionTest2.AOT.csproj new file mode 100644 index 0000000000..6d39f286da --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2.AOT/AuthoringConsumptionTest2.AOT.csproj @@ -0,0 +1,35 @@ + + + + net10.0-windows10.0.26100.1 + x64 + win-x64 + + true + + true + Shared + true + Exe + true + true + + + + + + + + + + + + + all + + + + + + + diff --git a/src/Tests/AuthoringConsumptionTest2.AOT/Directory.Build.props b/src/Tests/AuthoringConsumptionTest2.AOT/Directory.Build.props new file mode 100644 index 0000000000..6a86d69f2e --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2.AOT/Directory.Build.props @@ -0,0 +1,9 @@ + + + + true + + + + + diff --git a/src/Tests/AuthoringConsumptionTest2.AOT/EntryPoint.cs b/src/Tests/AuthoringConsumptionTest2.AOT/EntryPoint.cs new file mode 100644 index 0000000000..ae24a069c0 --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2.AOT/EntryPoint.cs @@ -0,0 +1,8 @@ +[assembly: global::System.Runtime.Versioning.SupportedOSPlatform("Windows")] + +internal static class Program +{ + static void Main() + { + } +} diff --git a/src/Tests/AuthoringConsumptionTest2/AuthoringConsumptionTest2.AOT.exe.manifest b/src/Tests/AuthoringConsumptionTest2/AuthoringConsumptionTest2.AOT.exe.manifest new file mode 100644 index 0000000000..33eb965487 --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2/AuthoringConsumptionTest2.AOT.exe.manifest @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/Tests/AuthoringConsumptionTest2/AuthoringConsumptionTest2.exe.manifest b/src/Tests/AuthoringConsumptionTest2/AuthoringConsumptionTest2.exe.manifest new file mode 100644 index 0000000000..e4fbe38c96 --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2/AuthoringConsumptionTest2.exe.manifest @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/Tests/AuthoringConsumptionTest2/AuthoringConsumptionTest2.vcxproj b/src/Tests/AuthoringConsumptionTest2/AuthoringConsumptionTest2.vcxproj new file mode 100644 index 0000000000..cf00d96d7d --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2/AuthoringConsumptionTest2.vcxproj @@ -0,0 +1,247 @@ + + + + + + + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {8a4b1f2a-9c2e-4d3a-bf68-2a4f5b7c8d9e} + Win32Proj + AuthoringConsumptionTest2 + Application + v143 + v142 + Unicode + <_WinMDPlatform>$(Platform) + <_WinMDPlatform Condition="'$(Platform)' == 'Win32'">x86 + false + true + + + + + + + + + + + + + + Create + + + + + + + + true + + + + + + {ffa9a78b-f53f-43ee-af87-24a80f4c330a} + TargetFramework=net10.0 + + + {7B803846-91AE-4B98-AC93-D3FCFB2DE5AA} + TargetFramework=net10.0 + + + {0bb8f82d-874e-45aa-bca3-20ce0562164a} + TargetFramework=net10.0 + + + {7e33bcb7-19c5-4061-981d-ba695322708a} + + + {25244ced-966e-45f2-9711-1f51e951ff89} + TargetFramework=net10.0 + + + TargetFramework=net10.0-windows10.0.26100.1 + + + TargetFramework=net10.0-windows10.0.26100.1 + + + + + + TargetFramework=net10.0-windows10.0.26100.1 + RuntimeIdentifier=win-$(Platform) + false + false + + + TargetFramework=net10.0-windows10.0.26100.1 + RuntimeIdentifier=win-$(Platform) + false + false + + + + AuthoringConsumptionTest2.AOT + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\$(NativeLibraryProjectName)'))\ + $(NativeLibraryProjectDirectory)$(NativeLibraryProjectName).csproj + $(NativeLibraryProjectDirectory)bin\$(Platform)\$(Configuration)\net10.0-windows10.0.26100.1\win-$(Platform)\publish\ + $(NativeLibraryPublishFolder)$(NativeLibraryProjectName).dll + + + + + + + + + + + ..\AuthoringTest3\bin\$(_WinMDPlatform)\$(Configuration)\net10.0-windows10.0.26100.1\AuthoringTest3.winmd + true + + + ..\AuthoringTest2\bin\$(_WinMDPlatform)\$(Configuration)\net10.0-windows10.0.26100.1\AuthoringTest2.winmd + true + + + + + + ..\AuthoringTest3\bin\$(_WinMDPlatform)\$(Configuration)\net10.0-windows10.0.26100.1\AuthoringTest3.winmd + true + AuthoringConsumptionTest2.AOT.dll + + + ..\AuthoringTest2\bin\$(_WinMDPlatform)\$(Configuration)\net10.0-windows10.0.26100.1\AuthoringTest2.winmd + true + AuthoringConsumptionTest2.AOT.dll + + + + + + + + + + + + + + + + + + + + + + + Use + pch.h + Disabled + X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + Level3 + + + true + Console + + + + + Use + pch.h + MaxSpeed + X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + true + MultiThreadedDLL + Level3 + + + true + true + true + Console + + + + + Use + pch.h + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + Level3 + + + true + Console + + + + + Use + pch.h + MaxSpeed + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + true + MultiThreadedDLL + Level3 + + + true + true + true + Console + + + + + + + diff --git a/src/Tests/AuthoringConsumptionTest2/Directory.Build.targets b/src/Tests/AuthoringConsumptionTest2/Directory.Build.targets new file mode 100644 index 0000000000..855c176a63 --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2/Directory.Build.targets @@ -0,0 +1,16 @@ + + + + $(MSBuildProjectDirectory)/DoNotImport_MsAppxPackageTargets.targets + CopyTestAssets;$(PrepareForRunDependsOn) + + + + + + + + + diff --git a/src/Tests/AuthoringConsumptionTest2/DoNotImport_MsAppxPackageTargets.targets b/src/Tests/AuthoringConsumptionTest2/DoNotImport_MsAppxPackageTargets.targets new file mode 100644 index 0000000000..74f0916e0a --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2/DoNotImport_MsAppxPackageTargets.targets @@ -0,0 +1,3 @@ + + + diff --git a/src/Tests/AuthoringConsumptionTest2/WinRT.Host.runtimeconfig.json b/src/Tests/AuthoringConsumptionTest2/WinRT.Host.runtimeconfig.json new file mode 100644 index 0000000000..88b184e831 --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2/WinRT.Host.runtimeconfig.json @@ -0,0 +1,10 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "rollForward": "LatestMinor", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + } + } +} diff --git a/src/Tests/AuthoringConsumptionTest2/packages.config b/src/Tests/AuthoringConsumptionTest2/packages.config new file mode 100644 index 0000000000..0470fd6c7d --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2/packages.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/Tests/AuthoringConsumptionTest2/pch.cpp b/src/Tests/AuthoringConsumptionTest2/pch.cpp new file mode 100644 index 0000000000..1d9f38c57d --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/Tests/AuthoringConsumptionTest2/pch.h b/src/Tests/AuthoringConsumptionTest2/pch.h new file mode 100644 index 0000000000..ce364f2e67 --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2/pch.h @@ -0,0 +1,22 @@ +#pragma once + +// Undefine GetCurrentTime macro to prevent +// conflict with Storyboard::GetCurrentTime +#undef GetCurrentTime + +#include +#include +#include + +#pragma push_macro("X86") +#pragma push_macro("X64") +#undef X86 +#undef X64 +#include "winrt/Windows.System.h" +#pragma pop_macro("X64") +#pragma pop_macro("X86") + +#include +#include + +#include "gtest/gtest.h" diff --git a/src/Tests/AuthoringConsumptionTest2/test.cpp b/src/Tests/AuthoringConsumptionTest2/test.cpp new file mode 100644 index 0000000000..242dfc49a0 --- /dev/null +++ b/src/Tests/AuthoringConsumptionTest2/test.cpp @@ -0,0 +1,67 @@ +#include "pch.h" + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; + +// Activation tests across two CsWinRT components aggregated into one merged AOT host. + +TEST(MultiComponent, CalculatorStatics) +{ + EXPECT_EQ(AuthoringTest3::Calculator::GetDefaultFactor(), 1); + EXPECT_EQ(AuthoringTest3::Calculator::GetDefaultNumber(), 2); +} + +TEST(MultiComponent, GreeterMethods) +{ + AuthoringTest2::Greeter greeter; + EXPECT_EQ(greeter.Greet(L"world"), hstring(L"Hello, world!")); + EXPECT_EQ(greeter.Add(2, 3), 5); +} + +TEST(MultiComponent, BothComponentsActivateInOneProcess) +{ + AuthoringTest3::Calculator first; + AuthoringTest2::Greeter second; + + EXPECT_EQ(first.GetFactor(), 1); + EXPECT_EQ(second.Add(10, 20), 30); +} + +// Generic instantiations from both components flow through the merged interop closure. +// If per-component interop generation had run independently, type-map registration would +// fail at publish time or these calls would fail at runtime. + +TEST(MultiComponent, GenericCollectionsFromBothComponents) +{ + AuthoringTest2::Greeter greeter; + auto numbers = greeter.GetNumbers(); + ASSERT_EQ(numbers.Size(), 6u); + EXPECT_EQ(numbers.GetAt(0), 1); + EXPECT_EQ(numbers.GetAt(5), 13); + + AuthoringTest3::Calculator calculator; + auto bools = calculator.GetBools(); + EXPECT_GT(bools.Size(), 0u); + + auto uris = calculator.GetUris(); + EXPECT_GT(uris.Size(), 0u); +} + +TEST(MultiComponent, GenericMapFromComponent2) +{ + AuthoringTest2::Greeter greeter; + auto counts = greeter.GetCounts(); + + ASSERT_EQ(counts.Size(), 3u); + EXPECT_EQ(counts.Lookup(L"alpha"), 1); + EXPECT_EQ(counts.Lookup(L"beta"), 2); + EXPECT_EQ(counts.Lookup(L"gamma"), 3); +} + +int main(int argc, char** argv) +{ + init_apartment(); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/Tests/AuthoringTest/AuthoringTest.csproj b/src/Tests/AuthoringTest/AuthoringTest.csproj index 5b12ed3449..debc0ac945 100644 --- a/src/Tests/AuthoringTest/AuthoringTest.csproj +++ b/src/Tests/AuthoringTest/AuthoringTest.csproj @@ -1,7 +1,7 @@ - net10.0 + net10.0-windows10.0.26100.1 x64;x86 true true @@ -41,7 +41,10 @@ - + + + compile;runtime + all diff --git a/src/Tests/AuthoringTest/Module.cs b/src/Tests/AuthoringTest/Module.cs index 1bc22ba3d2..c909bfc470 100644 --- a/src/Tests/AuthoringTest/Module.cs +++ b/src/Tests/AuthoringTest/Module.cs @@ -4,22 +4,6 @@ namespace AuthoringTest; -// CsWinRT makes use of the .NET typemap to register all the projected types. -// As part of this, .NET uses TypeMapAssemblyTarget to discover the assemblies with the type map. -// But this needs to be on the launching executable for it to discover them by default or use the -// RuntimeHostConfiguration which isn't available on current builds. Due to this, -// we manually set the entry assembly which allows .NET to discover it. -internal static class ProjectionTypesInitializer -{ -#pragma warning disable CA2255 // The 'ModuleInitializer' attribute should not be used in libraries - [System.Runtime.CompilerServices.ModuleInitializer] -#pragma warning restore CA2255 // The 'ModuleInitializer' attribute should not be used in libraries - internal static void InitializeProjectionTypes() - { - Assembly.SetEntryAssembly(typeof(ProjectionTypesInitializer).Assembly); - } -} - internal class Program { static void Main(string[] args) diff --git a/src/Tests/AuthoringTest2/AuthoringTest2.csproj b/src/Tests/AuthoringTest2/AuthoringTest2.csproj new file mode 100644 index 0000000000..3de3658513 --- /dev/null +++ b/src/Tests/AuthoringTest2/AuthoringTest2.csproj @@ -0,0 +1,18 @@ + + + + net10.0-windows10.0.26100.1 + x64;x86 + true + true + 10.0.18362.0 + win-x86;win-x64;win-arm64 + + + + + + + + + diff --git a/src/Tests/AuthoringTest2/Directory.Build.props b/src/Tests/AuthoringTest2/Directory.Build.props new file mode 100644 index 0000000000..6a86d69f2e --- /dev/null +++ b/src/Tests/AuthoringTest2/Directory.Build.props @@ -0,0 +1,9 @@ + + + + true + + + + + diff --git a/src/Tests/AuthoringTest2/Greeter.cs b/src/Tests/AuthoringTest2/Greeter.cs new file mode 100644 index 0000000000..2895b53c78 --- /dev/null +++ b/src/Tests/AuthoringTest2/Greeter.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace AuthoringTest2; + +public sealed class Greeter +{ + public string Greet(string name) + { + return $"Hello, {name}!"; + } + + public int Add(int a, int b) + { + return a + b; + } + + // Generic collection returns; exercise the merged interop's marshalling closure. + public IList GetNumbers() + { + return new List { 1, 2, 3, 5, 8, 13 }; + } + + public IDictionary GetCounts() + { + return new Dictionary + { + { "alpha", 1 }, + { "beta", 2 }, + { "gamma", 3 }, + }; + } +} diff --git a/src/Tests/AuthoringTest2/Module.cs b/src/Tests/AuthoringTest2/Module.cs new file mode 100644 index 0000000000..56fd158d77 --- /dev/null +++ b/src/Tests/AuthoringTest2/Module.cs @@ -0,0 +1,12 @@ +using System.Reflection; + +[assembly: global::System.Runtime.Versioning.SupportedOSPlatform("Windows")] + +namespace AuthoringTest2; + +internal class Program +{ + static void Main(string[] args) + { + } +} diff --git a/src/Tests/AuthoringTest3/AuthoringTest3.csproj b/src/Tests/AuthoringTest3/AuthoringTest3.csproj new file mode 100644 index 0000000000..3de3658513 --- /dev/null +++ b/src/Tests/AuthoringTest3/AuthoringTest3.csproj @@ -0,0 +1,18 @@ + + + + net10.0-windows10.0.26100.1 + x64;x86 + true + true + 10.0.18362.0 + win-x86;win-x64;win-arm64 + + + + + + + + + diff --git a/src/Tests/AuthoringTest3/Calculator.cs b/src/Tests/AuthoringTest3/Calculator.cs new file mode 100644 index 0000000000..0bfb64c716 --- /dev/null +++ b/src/Tests/AuthoringTest3/Calculator.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace AuthoringTest3; + +public sealed class Calculator +{ + public static int GetDefaultFactor() => 1; + public static int GetDefaultNumber() => 2; + + public int GetFactor() => 1; + + public int Multiply(int a, int b) => a * b; + + // Generic collection returns; exercise the merged interop's marshalling closure. + public IList GetBools() + { + return new List { true, false, true }; + } + + public IList GetUris() + { + return new List + { + new Uri("https://example.com/a"), + new Uri("https://example.com/b"), + }; + } +} diff --git a/src/Tests/AuthoringTest3/Directory.Build.props b/src/Tests/AuthoringTest3/Directory.Build.props new file mode 100644 index 0000000000..6a86d69f2e --- /dev/null +++ b/src/Tests/AuthoringTest3/Directory.Build.props @@ -0,0 +1,9 @@ + + + + true + + + + + diff --git a/src/Tests/AuthoringTest3/Module.cs b/src/Tests/AuthoringTest3/Module.cs new file mode 100644 index 0000000000..2a778079f1 --- /dev/null +++ b/src/Tests/AuthoringTest3/Module.cs @@ -0,0 +1,12 @@ +using System.Reflection; + +[assembly: global::System.Runtime.Versioning.SupportedOSPlatform("Windows")] + +namespace AuthoringTest3; + +internal class Program +{ + static void Main(string[] args) + { + } +} diff --git a/src/WinRT.Generator.Tasks/RunCsWinRTMergedProjectionGenerator.cs b/src/WinRT.Generator.Tasks/RunCsWinRTMergedProjectionGenerator.cs index fd066319a6..ccc1b15585 100644 --- a/src/WinRT.Generator.Tasks/RunCsWinRTMergedProjectionGenerator.cs +++ b/src/WinRT.Generator.Tasks/RunCsWinRTMergedProjectionGenerator.cs @@ -93,6 +93,13 @@ public sealed class RunCsWinRTMergedProjectionGenerator : ToolTask /// If not set, no debug repro will be produced. public string? DebugReproDirectory { get; set; } + /// + /// Gets or sets whether to emit the 'ProjectionTypesInitializer' module initializer + /// (Assembly.SetEntryAssembly) into 'WinRT.Component.dll'. Only needed under JIT to + /// enable TypeMap discovery, AOT uses a separate exe-project workaround. + /// + public bool EmitEntryPointInitializer { get; set; } + /// protected override string ToolName => "cswinrtprojectiongen.exe"; @@ -223,6 +230,11 @@ protected override string GenerateResponseFileCommands() AppendResponseFileCommand(args, "--max-degrees-of-parallelism", MaxDegreesOfParallelism.ToString()); AppendResponseFileOptionalCommand(args, "--debug-repro-directory", DebugReproDirectory); + if (EmitEntryPointInitializer) + { + AppendResponseFileCommand(args, "--emit-entry-point-initializer", "true"); + } + // Add any additional arguments that are not statically known foreach (ITaskItem additionalArgument in AdditionalArguments ?? []) { diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs index 58e6fba9ee..b64717ab0e 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs @@ -51,11 +51,17 @@ private static ProjectionGeneratorProcessingState ProcessReferences(ProjectionGe out string outputFolder, out HashSet projectionReferenceAssemblies, out bool hasTypesToProject, - out ProjectionWriterOptions writerOptions); + out ProjectionWriterOptions writerOptions, + out List componentAssemblyNames); string[] referencesWithoutProjections = [.. args.ReferenceAssemblyPaths.Where(r => !projectionReferenceAssemblies.Contains(r))]; - return new ProjectionGeneratorProcessingState(outputFolder, referencesWithoutProjections, writerOptions, hasTypesToProject); + return new ProjectionGeneratorProcessingState( + outputFolder, + referencesWithoutProjections, + writerOptions, + hasTypesToProject, + componentAssemblyNames); } /// @@ -83,18 +89,21 @@ private static void GenerateSources(ProjectionGeneratorProcessingState processin /// The projection reference assemblies which were used. /// Whether any types were found to include in the projection. /// The resulting writer options. + /// Sorted simple names of input [WindowsRuntimeComponentAssembly] references (populated only in component mode). private static void BuildWriterOptions( ProjectionGeneratorArgs args, out string outputFolder, out HashSet projectionReferenceAssemblies, out bool hasTypesToProject, - out ProjectionWriterOptions writerOptions) + out ProjectionWriterOptions writerOptions, + out List componentAssemblyNames) { args.Token.ThrowIfCancellationRequested(); outputFolder = GetTempFolder(); projectionReferenceAssemblies = []; hasTypesToProject = false; + componentAssemblyNames = []; List includes = []; List excludes = []; @@ -117,7 +126,7 @@ private static void BuildWriterOptions( if (isComponentMode) { // Collect the names of all component assemblies from the references - HashSet componentAssemblyNames = []; + HashSet componentAssemblyNameSet = []; foreach (string refPath in args.ReferenceAssemblyPaths) { @@ -130,16 +139,20 @@ private static void BuildWriterOptions( if (IsComponentAssembly(refModule) && refModule.Assembly?.Name is Utf8String name) { - _ = componentAssemblyNames.Add(name.Value); + _ = componentAssemblyNameSet.Add(name.Value); } } + // Sort for stable codegen output + componentAssemblyNames = [.. componentAssemblyNameSet]; + componentAssemblyNames.Sort(StringComparer.Ordinal); + // Scan WinMD files matching component assembly names (e.g. 'MyComponent.winmd') foreach (string winmdPath in args.WinMDPaths) { string winmdFileName = Path.GetFileNameWithoutExtension(winmdPath); - if (!componentAssemblyNames.Contains(winmdFileName)) + if (!componentAssemblyNameSet.Contains(winmdFileName)) { continue; } diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.WinRTComponentSources.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.WinRTComponentSources.cs new file mode 100644 index 0000000000..58d13a77ab --- /dev/null +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.WinRTComponentSources.cs @@ -0,0 +1,304 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using AsmResolver.DotNet; +using WindowsRuntime.ProjectionWriter.Writers; + +namespace WindowsRuntime.ProjectionGenerator.Generation; + +/// +internal partial class ProjectionGenerator +{ + /// + /// Component-mode only. Emits the supporting source files that make WinRT.Component.dll + /// the merged aggregator dll: + /// + /// A module initializer that designates this assembly as the entry assembly for .NET interop type map discovery. + /// + /// A + /// union covering each component plus shared infrastructure assemblies. + /// + /// + /// A merged ABI.WinRT.Component.ManagedExports.GetActivationFactory that WinRT.Host.Shim reflects on. + /// + /// + /// + /// + /// These emissions write directly into + /// (the same folder cswinrt.exe targets), so they are picked up automatically by the Roslyn compile + /// step in . + /// + private static void EmitWinRTComponentSources(ProjectionGeneratorArgs args, ProjectionGeneratorProcessingState processingState) + { + // Only emit these files when producing 'WinRT.Component.dll' + if (args.AssemblyName != "WinRT.Component") + { + return; + } + + // Nothing to merge if there are no input components. In this case, the + // projection generator invocation in component mode would not have produced + // any 'ABI.{ComponentName}.ManagedExports' types either. + if (processingState.ComponentAssemblyNames.Count == 0) + { + return; + } + + if (args.EmitEntryPointInitializer) + { + WriteProjectionTypesInitializer(processingState); + } + + WriteTypeMapAssemblyTargets(args, processingState); + WriteMergedManagedExports(processingState); + } + + /// + /// Emits a -driven call + /// to in WinRT.Component.dll. + /// This designates WinRT.Component.dll as the entry assembly the .NET runtime uses + /// to root + /// discovery. This method is idempotent: skipped if the host has already set an entry assembly. + /// Per-component '.dll'-s intentionally do not emit a similar initializer. Consumers using a single + /// component '.dll' directly must call + /// themselves if they need interop type map discovery rooted there. + /// + private static void WriteProjectionTypesInitializer(ProjectionGeneratorProcessingState processingState) + { + const string source = """ + // + #pragma warning disable + + namespace ABI.WinRT.Component; + + /// + /// Designates WinRT.Component.dll as the entry assembly for the hosted .NET + /// runtime so that [TypeMapAssemblyTarget] discovery (rooted at the entry + /// assembly) sees the union assembly attributes emitted on this dll. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal static class ProjectionTypesInitializer + { + #pragma warning disable CA2255 // The 'ModuleInitializer' attribute should not be used in libraries + [global::System.Runtime.CompilerServices.ModuleInitializer] + #pragma warning restore CA2255 + internal static void InitializeProjectionTypes() + { + if (global::System.Reflection.Assembly.GetEntryAssembly() is null) + { + global::System.Reflection.Assembly.SetEntryAssembly(typeof(ProjectionTypesInitializer).Assembly); + } + } + } + """; + + string destinationPath = Path.Combine(processingState.SourcesFolder, "ProjectionTypesInitializer.g.cs"); + + File.WriteAllText(destinationPath, source); + } + + /// + /// Emits the union [TypeMapAssemblyTarget] assembly attributes on + /// WinRT.Component.dll. The union covers each input component, the shared CsWinRT + /// infrastructure assemblies (WinRT.Interop, WinRT.Runtime, + /// WinRT.Sdk.Projection), and (when applicable) WinRT.Sdk.Xaml.Projection + /// and WinRT.Projection. + /// + /// + /// Only assemblies that will actually be deployed alongside WinRT.Component.dll are + /// included. The .NET runtime fails with when it tries + /// to enumerate a target assembly that doesn't exist, which would in turn fail the static + /// constructor of any type that depends on the type map (e.g. WindowsRuntimeComWrappers). + /// + private static void WriteTypeMapAssemblyTargets(ProjectionGeneratorArgs args, ProjectionGeneratorProcessingState processingState) + { + // Build the list of target assembly simple names. Order is deterministic for stable + // codegen output. The runtime treats the order as irrelevant for discovery. + List targetAssemblyNames = + [ + "WinRT.Interop", + "WinRT.Runtime", + "WinRT.Sdk.Projection", + ]; + + if (args.WindowsUIXamlProjection) + { + targetAssemblyNames.Add("WinRT.Sdk.Xaml.Projection"); + } + + // 'WinRT.Projection.dll' is generated only when there are non-Windows-SDK, non-component + // Windows Runtime reference assemblies in scope (i.e. third-party projection references). + // Only include it in the union when that condition holds, otherwise the runtime would + // fail with 'FileNotFoundException' trying to enumerate a non-existent assembly. + if (HasMergedProjectionReferences(args)) + { + targetAssemblyNames.Add("WinRT.Projection"); + } + + // Per-component targets + targetAssemblyNames.AddRange(processingState.ComponentAssemblyNames); + + StringBuilder builder = new(); + + _ = builder.AppendLine("// "); + _ = builder.AppendLine("#pragma warning disable"); + _ = builder.AppendLine(); + + // Append all interop type map entries for each target assembly we discovered + foreach (string targetTypeMapGroup in (ReadOnlySpan)[ + "global::WindowsRuntime.InteropServices.WindowsRuntimeComWrappersTypeMapGroup", + "global::WindowsRuntime.InteropServices.WindowsRuntimeMetadataTypeMapGroup", + "global::WindowsRuntime.InteropServices.DynamicInterfaceCastableImplementationTypeMapGroup"]) + { + foreach (string targetAssembly in targetAssemblyNames) + { + _ = builder.AppendLine($"[assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget<{targetTypeMapGroup}>(\"{targetAssembly}\")]"); + } + } + + string destinationPath = Path.Combine(processingState.SourcesFolder, "TypeMapAssemblyTargets.g.cs"); + + File.WriteAllText(destinationPath, builder.ToString()); + } + + /// + /// Returns true if there is at least one non-Windows-SDK, non-component Windows Runtime + /// reference assembly in scope - i.e. the same condition the merged projection target + /// uses to determine whether WinRT.Projection.dll needs to be generated. + /// + private static bool HasMergedProjectionReferences(ProjectionGeneratorArgs args) + { + string[] resolverPaths = [.. args.ReferenceAssemblyPaths.Where(p => !p.EndsWith(".winmd", StringComparison.OrdinalIgnoreCase))]; + + PathAssemblyResolver resolver = new(resolverPaths); + + foreach (string refPath in args.ReferenceAssemblyPaths) + { + if (refPath.EndsWith(".winmd", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + ModuleDefinition refModule; + + try + { + refModule = ModuleDefinition.FromFile(refPath, resolver.ReaderParameters); + } + catch + { + continue; + } + + // Skip non-projection references (only WinRT projection .dll-s are interesting) + if (!IsReferenceAssembly(refModule) || !IsWindowsRuntimeReferenceAssembly(refModule)) + { + continue; + } + + // Windows SDK projections ('Microsoft.Windows.SDK.NET' and 'Microsoft.Windows.UI.Xaml') go into + // 'WinRT.Sdk.Projection.dll' and 'WinRT.Sdk.Xaml.Projection.dll', not 'WinRT.Projection.dll'. + if (IsWindowsSdkAssembly(refModule)) + { + continue; + } + + // Component reference projections also don't go into 'WinRT.Projection.dll' + // (they are listed separately in the per-component target group above). + if (IsComponentAssembly(refModule)) + { + continue; + } + + // A third-party Windows Runtime reference projection, 'WinRT.Projection.dll' + // will be generated by '_RunCsWinRTMergedProjectionGenerator' for these. + return true; + } + + return false; + } + + /// + /// Emits ABI.WinRT.Component.ManagedExports.GetActivationFactory, a merged dispatcher + /// that walks each per-component ABI.{ComponentName}.ManagedExports.GetActivationFactory + /// (already emitted by the projection generator invocation in component mode) and returns the + /// first non- factory. + /// + /// + /// This type is what WinRT.Host.Shim.GetActivationFactory reflects on when the + /// loaded target assembly is WinRT.Component.dll (the moduleName the shim + /// computes from the '.dll' filename is "WinRT.Component", so it looks up + /// ABI.WinRT.Component.ManagedExports). + /// + private static void WriteMergedManagedExports(ProjectionGeneratorProcessingState processingState) + { + IndentedTextWriter writer = new(); + + // Writes the invocations to all available components for the current type being activated + void WriteActivationFactoryInvocations(IndentedTextWriter writer) + { + foreach (string componentAssemblyName in processingState.ComponentAssemblyNames) + { + writer.WriteLine($$""" + factory = global::ABI.{{componentAssemblyName}}.ManagedExports.GetActivationFactory(activatableClassId); + if (factory is not null) + { + return factory; + } + """); + } + } + + // Write the managed exports for 'GetActivationFactory' + writer.WriteLine($$""" + // + #pragma warning disable + + namespace ABI.WinRT.Component; + + /// + /// Merged managed activation entry point for WinRT.Component.dll. Reflectively + /// invoked by WinRT.Host.Shim.GetActivationFactory when the manifest-free + /// activation flow loads WinRT.Component.dll; iterates per-component + /// ABI.{ComponentName}.ManagedExports.GetActivationFactory entries. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public static unsafe class ManagedExports + { + /// + /// Retrieves the activation factory for the requested runtime class by + /// dispatching to each input component's ManagedExports in turn. + /// + /// The runtime class identifier. + /// A pointer to the factory, or if no component handles the class. + public static void* GetActivationFactory(global::System.ReadOnlySpan activatableClassId) + { + void* factory; + + {{WriteActivationFactoryInvocations}} + + return null; + } + + /// + /// Reflection-friendly overload used by managed runtime hosts (e.g. WinRT.Host.Shim). + /// + /// The runtime class identifier. + /// A pointer to the factory, or if no component handles the class. + public static nint GetActivationFactory(string activatableClassId) + { + return (nint)GetActivationFactory(global::System.MemoryExtensions.AsSpan(activatableClassId)); + } + } + """); + + string destinationPath = Path.Combine(processingState.SourcesFolder, "MergedManagedExports.g.cs"); + + writer.FlushToFile(destinationPath); + } +} diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index 7bb3a9ffb1..7b642cb3c5 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -58,6 +58,14 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) logMessage: "Generating projection code", body: _ => GenerateSources(processingState)); + // In component mode (i.e. producing 'WinRT.Component.dll'), emit the supporting source files + // alongside the projection writer's output so the merged '.dll' plays the entry-assembly and + // merged-activation roles (interop type map union, 'SetEntryAssembly' module init, merged + // 'ABI.WinRT.Component.ManagedExports.GetActivationFactory', and AOT native export). + runner.RunPhase( + phaseName: "winrt-component-sources", + body: args => EmitWinRTComponentSources(args, processingState)); + // Invoke Roslyn to compile the generated sources into 'WinRT.Projection.dll' runner.RunPhase( phaseName: "emit", diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs index e01fb8d7fa..905274ceaa 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs @@ -57,6 +57,16 @@ internal sealed class ProjectionGeneratorArgs : IGeneratorArgs [CommandLineArgumentName("--max-degrees-of-parallelism")] public required int MaxDegreesOfParallelism { get; init; } + /// + /// Gets whether to emit the ProjectionTypesInitializer module initializer + /// (calls Assembly.SetEntryAssembly) into WinRT.Component.dll. Only + /// needed under JIT to enable [TypeMapAssemblyTarget] discovery at the merged + /// component dll; AOT uses a separate exe-project workaround. Will become + /// unnecessary once the TypeMappingEntryAssembly MSBuild property is available. + /// + [CommandLineArgumentName("--emit-entry-point-initializer")] + public bool EmitEntryPointInitializer { get; init; } + /// public required CancellationToken Token { get; init; } diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs index b4c09a58bb..5e16600992 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using WindowsRuntime.ProjectionWriter; namespace WindowsRuntime.ProjectionGenerator.Generation; @@ -12,11 +13,13 @@ namespace WindowsRuntime.ProjectionGenerator.Generation; /// The reference assembly paths excluding projection assemblies. /// The options to pass to . /// Whether any types were found to project. +/// Sorted simple names of all input [WindowsRuntimeComponentAssembly] references (component-mode only). internal sealed class ProjectionGeneratorProcessingState( string sourcesFolder, string[] referencesWithoutProjections, ProjectionWriterOptions writerOptions, - bool hasTypesToProject = true) + bool hasTypesToProject = true, + IReadOnlyList? componentAssemblyNames = null) { /// /// Gets the path to the folder where sources will be generated. @@ -38,5 +41,12 @@ internal sealed class ProjectionGeneratorProcessingState( /// and emit phases should be skipped (no DLL will be produced). /// public bool HasTypesToProject { get; } = hasTypesToProject; + + /// + /// Gets the simple names of all input assemblies marked with + /// [WindowsRuntimeComponentAssembly]. Empty unless this is a component-mode run + /// (i.e. producing WinRT.Component.dll). + /// + public IReadOnlyList ComponentAssemblyNames { get; } = componentAssemblyNames ?? []; } diff --git a/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendIfInterpolatedStringHandler.cs b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendIfInterpolatedStringHandler.cs index 5e93bff165..a3627d8eac 100644 --- a/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendIfInterpolatedStringHandler.cs +++ b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendIfInterpolatedStringHandler.cs @@ -11,7 +11,7 @@ namespace WindowsRuntime.ProjectionWriter.Writers; /// -internal partial class IndentedTextWriter +public partial class IndentedTextWriter { /// /// Provides a handler used by the language compiler to conditionally append interpolated strings into instances. diff --git a/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendInterpolatedStringHandler.cs b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendInterpolatedStringHandler.cs index b04ac0a3cf..cb87dbe065 100644 --- a/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendInterpolatedStringHandler.cs +++ b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.AppendInterpolatedStringHandler.cs @@ -11,7 +11,7 @@ namespace WindowsRuntime.ProjectionWriter.Writers; /// -internal partial class IndentedTextWriter +public partial class IndentedTextWriter { /// /// Provides a handler used by the language compiler to append interpolated strings into instances. diff --git a/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.cs b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.cs index 77a8b2e16e..fe151d6fd9 100644 --- a/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.cs +++ b/src/WinRT.Projection.Writer/Writers/IndentedTextWriter.cs @@ -30,7 +30,7 @@ namespace WindowsRuntime.ProjectionWriter.Writers; /// indentation, so raw multi-line literals with blank lines do not gain trailing whitespace. /// /// -internal sealed partial class IndentedTextWriter +public sealed partial class IndentedTextWriter { /// /// The default indentation (4 spaces). @@ -696,38 +696,15 @@ public void Clear() } /// - /// Flushes the current buffer to (skipping the write if the file - /// already exists with identical content), then clears the buffer. + /// Flushes the current buffer to , then clears the buffer. /// - /// - /// If the destination file exists but cannot be read (e.g. due to a transient I/O failure or - /// access denial), the catch block silently falls through to a fresh write. This is the - /// intended behavior for a build tool: the worst case is an extra write of identical content - /// that the OS will then re-permit; the alternative (failing the build) would create - /// brittleness around incidental file-system noise. - /// /// The destination file path. public void FlushToFile(string path) { string content = _buffer.ToString(); - if (File.Exists(path)) - { - try - { - if (File.ReadAllText(path) == content) - { - _ = _buffer.Clear(); - return; - } - } - catch (Exception e) when (e is IOException or UnauthorizedAccessException) - { - // Intentional: see -- a failed read falls through to a fresh write. - } - } - File.WriteAllText(path, content); + _ = _buffer.Clear(); } diff --git a/src/WinRT.Projection.Writer/Writers/IndentedTextWriterCallback.cs b/src/WinRT.Projection.Writer/Writers/IndentedTextWriterCallback.cs index 175abfccea..a3ed960f5e 100644 --- a/src/WinRT.Projection.Writer/Writers/IndentedTextWriterCallback.cs +++ b/src/WinRT.Projection.Writer/Writers/IndentedTextWriterCallback.cs @@ -19,4 +19,4 @@ namespace WindowsRuntime.ProjectionWriter.Writers; /// /// /// The writer to emit content to at its current position and indentation level. -internal delegate void IndentedTextWriterCallback(IndentedTextWriter writer); +public delegate void IndentedTextWriterCallback(IndentedTextWriter writer); diff --git a/src/WinRT.Projection.Writer/Writers/IndentedTextWriterCallbackExtensions.cs b/src/WinRT.Projection.Writer/Writers/IndentedTextWriterCallbackExtensions.cs index df7756fdd5..31e41779ef 100644 --- a/src/WinRT.Projection.Writer/Writers/IndentedTextWriterCallbackExtensions.cs +++ b/src/WinRT.Projection.Writer/Writers/IndentedTextWriterCallbackExtensions.cs @@ -6,7 +6,7 @@ namespace WindowsRuntime.ProjectionWriter.Writers; /// /// Extension methods for . /// -internal static class IndentedTextWriterCallbackExtensions +public static class IndentedTextWriterCallbackExtensions { /// /// Writes the delegate's content into a pooled at indent diff --git a/src/WinRT.Projection.Writer/Writers/IndentedTextWriterPool.cs b/src/WinRT.Projection.Writer/Writers/IndentedTextWriterPool.cs index 328c12f006..e081838134 100644 --- a/src/WinRT.Projection.Writer/Writers/IndentedTextWriterPool.cs +++ b/src/WinRT.Projection.Writer/Writers/IndentedTextWriterPool.cs @@ -29,7 +29,7 @@ namespace WindowsRuntime.ProjectionWriter.Writers; /// parallel load the pool naturally caps at the worker-thread high-water mark. /// /// -internal static class IndentedTextWriterPool +public static class IndentedTextWriterPool { /// /// The backing pool. is the right primitive for thread-local @@ -54,7 +54,8 @@ public static IndentedTextWriterOwner GetOrCreate() writer.Clear(); return new IndentedTextWriterOwner(writer); } - return new IndentedTextWriterOwner(new IndentedTextWriter()); + + return new(new IndentedTextWriter()); } /// @@ -82,7 +83,7 @@ internal static void Return(IndentedTextWriter writer) /// property throws . /// /// -internal ref struct IndentedTextWriterOwner : IDisposable +public ref struct IndentedTextWriterOwner : IDisposable { private IndentedTextWriter? _writer; diff --git a/src/build.cmd b/src/build.cmd index d94c32c817..51f471e172 100644 --- a/src/build.cmd +++ b/src/build.cmd @@ -228,6 +228,25 @@ if ErrorLevel 1 ( rem exit /b !ErrorLevel! ) +if /I "%cswinrt_platform%"=="x64" ( + call :exec %this_dir%_build\%cswinrt_platform%\%cswinrt_configuration%\AuthoringConsumptionTest2\bin\AuthoringConsumptionTest2.exe --gtest_output=xml:%this_dir%authoringtest2_%cswinrt_version_string%.xml + if ErrorLevel 1 ( + echo. + rem Not skipping due to known issue. + rem echo ERROR: Multi-component authoring test failed, skipping NuGet pack + rem exit /b !ErrorLevel! + ) +) +if /I "%cswinrt_platform%"=="x86" ( + call :exec %this_dir%_build\%cswinrt_platform%\%cswinrt_configuration%\AuthoringConsumptionTest2\bin\AuthoringConsumptionTest2.exe --gtest_output=xml:%this_dir%authoringtest2_%cswinrt_version_string%.xml + if ErrorLevel 1 ( + echo. + rem Not skipping due to known issue. + rem echo ERROR: Multi-component authoring test failed, skipping NuGet pack + rem exit /b !ErrorLevel! + ) +) + :functionaltest rem Run functional tests if "%run_functional_tests%" EQU "true" ( @@ -262,11 +281,12 @@ set cswinrtinteropgen_%cswinrt_platform%=%this_dir%WinRT.Interop.Generator\bin\% set cswinrtimplgen_%cswinrt_platform%=%this_dir%WinRT.Impl.Generator\bin\%cswinrt_configuration%\net10.0\win-%cswinrt_platform%\publish\cswinrtimplgen.exe set cswinrtprojectiongen_%cswinrt_platform%=%this_dir%WinRT.Projection.Generator\bin\%cswinrt_configuration%\net10.0\win-%cswinrt_platform%\publish\cswinrtprojectiongen.exe set cswinrtprojectionrefgen_%cswinrt_platform%=%this_dir%WinRT.Projection.Ref.Generator\bin\%cswinrt_configuration%\net10.0\win-%cswinrt_platform%\publish\cswinrtprojectionrefgen.exe +set cswinrtwinmdgen_%cswinrt_platform%=%this_dir%WinRT.WinMD.Generator\bin\%cswinrt_configuration%\net10.0\win-%cswinrt_platform%\publish\cswinrtwinmdgen.exe set run_cswinrt_generator_task=%this_dir%WinRT.Generator.Tasks\bin\%cswinrt_configuration%\netstandard2.0\WinRT.Generator.Tasks.dll rem Now call pack echo Creating nuget package -call :exec %nuget_dir%\nuget pack %this_dir%..\nuget\Microsoft.Windows.CsWinRT.nuspec -Properties interop_winmd=%interop_winmd%;net10_runtime=%net10_runtime%;net10_runtime_xml=%net10_runtime_xml%;source_generator=%source_generator%;cswinrt_nuget_version=%cswinrt_version_string%;winrt_host_x86=%winrt_host_x86%;winrt_host_x64=%winrt_host_x64%;winrt_host_arm=%winrt_host_arm%;winrt_host_arm64=%winrt_host_arm64%;winrt_host_resource_x86=%winrt_host_resource_x86%;winrt_host_resource_x64=%winrt_host_resource_x64%;winrt_host_resource_arm=%winrt_host_resource_arm%;winrt_host_resource_arm64=%winrt_host_resource_arm64%;winrt_shim=%winrt_shim%;cswinrtinteropgen_x64=%cswinrtinteropgen_x64%;cswinrtinteropgen_arm64=%cswinrtinteropgen_arm64%;cswinrtimplgen_x64=%cswinrtimplgen_x64%;cswinrtimplgen_arm64=%cswinrtimplgen_arm64%;cswinrtprojectiongen_x64=%cswinrtprojectiongen_x64%;cswinrtprojectiongen_arm64=%cswinrtprojectiongen_arm64%;cswinrtprojectionrefgen_x64=%cswinrtprojectionrefgen_x64%;cswinrtprojectionrefgen_arm64=%cswinrtprojectionrefgen_arm64%;run_cswinrt_generator_task=%run_cswinrt_generator_task%; -OutputDirectory %cswinrt_bin_dir% -NonInteractive -Verbosity Detailed -NoPackageAnalysis +call :exec %nuget_dir%\nuget pack %this_dir%..\nuget\Microsoft.Windows.CsWinRT.nuspec -Properties interop_winmd=%interop_winmd%;net10_runtime=%net10_runtime%;net10_runtime_xml=%net10_runtime_xml%;source_generator=%source_generator%;cswinrt_nuget_version=%cswinrt_version_string%;winrt_host_x86=%winrt_host_x86%;winrt_host_x64=%winrt_host_x64%;winrt_host_arm=%winrt_host_arm%;winrt_host_arm64=%winrt_host_arm64%;winrt_host_resource_x86=%winrt_host_resource_x86%;winrt_host_resource_x64=%winrt_host_resource_x64%;winrt_host_resource_arm=%winrt_host_resource_arm%;winrt_host_resource_arm64=%winrt_host_resource_arm64%;winrt_shim=%winrt_shim%;cswinrtinteropgen_x64=%cswinrtinteropgen_x64%;cswinrtinteropgen_arm64=%cswinrtinteropgen_arm64%;cswinrtimplgen_x64=%cswinrtimplgen_x64%;cswinrtimplgen_arm64=%cswinrtimplgen_arm64%;cswinrtprojectiongen_x64=%cswinrtprojectiongen_x64%;cswinrtprojectiongen_arm64=%cswinrtprojectiongen_arm64%;cswinrtprojectionrefgen_x64=%cswinrtprojectionrefgen_x64%;cswinrtprojectionrefgen_arm64=%cswinrtprojectionrefgen_arm64%;cswinrtwinmdgen_x64=%cswinrtwinmdgen_x64%;cswinrtwinmdgen_arm64=%cswinrtwinmdgen_arm64%;run_cswinrt_generator_task=%run_cswinrt_generator_task%; -OutputDirectory %cswinrt_bin_dir% -NonInteractive -Verbosity Detailed -NoPackageAnalysis goto :eof :exec diff --git a/src/cswinrt.slnx b/src/cswinrt.slnx index 08bde562fe..27e04f3c4e 100644 --- a/src/cswinrt.slnx +++ b/src/cswinrt.slnx @@ -77,10 +77,9 @@ + - - @@ -183,6 +182,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -191,6 +211,22 @@ + + + + + + + + + + + + + + + +