Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions nuget/Microsoft.Windows.CsWinRT.targets
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,73 @@ Copyright (C) Microsoft Corporation. All rights reserved.
<Error Text="Support for .NET 6 and .NET 7 ended with C#/WinRT 2.2. For .NET 6 or .NET 7 support, use C#/WinRT version 2.2.x." />
</Target>

<!--
Resolve Windows metadata from the .NET SDK projection ref pack when no Windows SDK is registered.

The default 'CsWinRTWindowsMetadata' above (from '$(WindowsSDKVersion)' or '$(TargetPlatformVersion)')
is a bare Windows SDK version number, e.g. '10.0.19041.0'. When that bare version is passed to
'cswinrt.exe' as '-input', the tool locates the metadata by reading the Windows SDK install root from
the registry ('HKLM\SOFTWARE\Microsoft\Windows Kits\Installed Roots\KitsRoot10'). On hosts without a
registered Windows SDK (clean CI agents, containers, SDK-less developer machines) that lookup throws
"Could not find the Windows SDK in the registry", which cascades into downstream (e.g. XAML) failures.

For modern .NET TFMs (e.g. 'net*-windows10.0.x'), the .NET SDK auto-restores the
'Microsoft.Windows.SDK.NET.Ref' projection ref pack, which ships the Windows metadata (.winmd) files -
versioned to match '$(TargetPlatformVersion)' - under its '\winmd' folder. 'cswinrt.exe' already
accepts '-input <folder>', so we can point 'CsWinRTWindowsMetadata' at that folder and have projection
and authoring work with no registered Windows SDK. Setting the property here covers both the projection
response file ('-input $(CsWinRTWindowsMetadata)') and the authoring source generator (which reads the
'CsWinRTWindowsMetadata' compiler-visible property).

This is intentionally gated so that machines with a registered Windows SDK (Visual Studio, SDK-installed
CI) keep their existing behavior byte-for-byte. It only takes effect when ALL of the following hold:
- 'CsWinRTWindowsMetadata' is still the bare-version default: it is non-empty, is not one of the
tool's own sentinels ('local'/'sdk'/'sdk+'), and does not point at an existing path (an explicit
file/folder provided by the user is left untouched).
- No Windows SDK is registered (the 'KitsRoot10' registry value is empty).
- The 'Microsoft.Windows.SDK.NET.Ref' ref pack was resolved and its '\winmd' folder exists.
-->
<Target Name="CsWinRTResolveWindowsMetadataFromSdkRefPack"
Condition="$(CsWinRTEnabled)"
DependsOnTargets="ResolveReferences"
BeforeTargets="CsWinRTGenerateProjection;CsWinRTSetAuthoringWinMDs;GenerateMSBuildEditorConfigFile;GenerateMSBuildEditorConfigFileCore">

<PropertyGroup>
<!-- Empty when no Windows SDK is registered. Mirrors the 32-bit-then-default view order used by cswinrt.exe. -->
<_CsWinRTKitsRoot10>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots', 'KitsRoot10', null, Microsoft.Win32.RegistryView.Registry32, Microsoft.Win32.RegistryView.Registry64))</_CsWinRTKitsRoot10>

<!-- True only when the current value is a bare SDK version that needs the registry to resolve. -->
<_CsWinRTWindowsMetadataIsBareVersion Condition="'$(CsWinRTWindowsMetadata)' != '' and
'$(CsWinRTWindowsMetadata)' != 'local' and
'$(CsWinRTWindowsMetadata)' != 'sdk' and
'$(CsWinRTWindowsMetadata)' != 'sdk+' and
!Exists('$(CsWinRTWindowsMetadata)')">true</_CsWinRTWindowsMetadataIsBareVersion>
</PropertyGroup>

<ItemGroup Condition="'$(_CsWinRTKitsRoot10)' == '' and '$(_CsWinRTWindowsMetadataIsBareVersion)' == 'true'">
<!--
The ref pack resolves as several targeting-pack items (Windows/Xaml/CsWinRT3) that share one root; de-duplicate.
We resolve the folder from the actually-restored targeting pack rather than scanning for a pack whose version
string equals '$(TargetPlatformVersion)'. The restored ref-pack version does not always string-match the TPV
(e.g. a '10.0.26100.67-preview' ref pack for a project targeting '10.0.26100.0'); @(ResolvedTargetingPack) goes
through the SDK's own targeting-pack resolution, so it tolerates preview/suffix versions that an exact-match
lookup would miss.
-->
<_CsWinRTSdkNetRefPackDir Include="@(ResolvedTargetingPack->'%(Path)')"
Condition="'%(ResolvedTargetingPack.NuGetPackageId)' == 'Microsoft.Windows.SDK.NET.Ref'" />
<_CsWinRTSdkNetRefPackDirDistinct Include="@(_CsWinRTSdkNetRefPackDir->Distinct())" />
</ItemGroup>

<PropertyGroup Condition="'$(_CsWinRTKitsRoot10)' == '' and '$(_CsWinRTWindowsMetadataIsBareVersion)' == 'true'">
<_CsWinRTSdkNetRefPackWinMDDir Condition="'@(_CsWinRTSdkNetRefPackDirDistinct)' != ''">@(_CsWinRTSdkNetRefPackDirDistinct)\winmd</_CsWinRTSdkNetRefPackWinMDDir>
<CsWinRTWindowsMetadata Condition="'$(_CsWinRTSdkNetRefPackWinMDDir)' != '' and Exists('$(_CsWinRTSdkNetRefPackWinMDDir)')">$(_CsWinRTSdkNetRefPackWinMDDir)</CsWinRTWindowsMetadata>
</PropertyGroup>

<Message Condition="'$(_CsWinRTSdkNetRefPackWinMDDir)' != '' and '$(CsWinRTWindowsMetadata)' == '$(_CsWinRTSdkNetRefPackWinMDDir)'"
Importance="$(CsWinRTMessageImportance)"
Text="C#/WinRT: no Windows SDK is registered; using Windows metadata from the Windows SDK projection ref pack: '$(_CsWinRTSdkNetRefPackWinMDDir)'." />
</Target>

<!-- Note this runs before the msbuild editor config file is generated because that is what is used to pass properties to the source generator. -->
<Target Name="CsWinRTSetGeneratorProperties" BeforeTargets="GenerateMSBuildEditorConfigFile;GenerateMSBuildEditorConfigFileCore">
<PropertyGroup>
Expand Down
10 changes: 10 additions & 0 deletions nuget/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ Windows Metadata is required for all C#/WinRT projections, and can be supplied b

If a Windows SDK is installed, $(WindowsSDKVersion) is defined when building from a VS command prompt.

If no Windows SDK is registered on the machine (for example on clean CI agents, containers, or SDK-less
developer machines), the default `$(WindowsSDKVersion)`/`$(TargetPlatformVersion)` value is a bare SDK
version number that `cswinrt.exe` can only resolve through the registry, so the build would otherwise fail
with "Could not find the Windows SDK in the registry". In that case, for modern .NET TFMs (e.g.
`net*-windows10.0.x`), C#/WinRT automatically falls back to the Windows metadata (`.winmd`) files shipped in
the `Microsoft.Windows.SDK.NET.Ref` projection ref pack that the .NET SDK restores (its `\winmd` folder,
versioned to `$(TargetPlatformVersion)`). This fallback only applies when no Windows SDK is registered and
`$(CsWinRTWindowsMetadata)` is still the bare-version default; an explicit `$(CsWinRTWindowsMetadata)` value
(a path, or `local`/`sdk`) and registered-SDK builds are unaffected.

## Consuming and Producing

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).
Expand Down