Skip to content

Commit cf4aba3

Browse files
Sergio0694Copilot
andauthored
Add CsWinRTMarshallingMode option for non-Windows TFM marshalling (#2482)
* Support enum-typed arguments in the response file parser Add case-insensitive, name-based enum parsing to ResponseFileParser and matching name-based serialization to ResponseFileBuilder, so generator args can expose enum-typed options that round-trip through response files (and debug repros) deterministically. Numeric values are rejected to avoid silently accepting out-of-range enum inputs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Add base class library assembly detection to the generator core Add BaseClassLibraryIdentity, which identifies .NET base class library (BCL) and shared-framework assemblies by their well-known Microsoft public key tokens. This is used to let the interop generator skip framework assemblies when discovering user-defined/CCW/generic types. The token set was validated against the Microsoft.NETCore.App, Microsoft.WindowsDesktop.App, and Microsoft.AspNetCore.App shared frameworks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Add CsWinRTMarshallingMode to the interop generator Introduce the CsWinRTMarshallingMode option ('all', 'minimal', 'strict'), controlling which assemblies are analyzed when discovering user-defined/CCW types and generic instantiations: - 'all' (default): analyze every assembly, including those that don't reference any CsWinRT assembly. This lets projects that don't target a Windows TFM (e.g. a class library with just MVVM viewmodels) contribute the marshalling code their types need. - 'minimal': analyze every assembly except those from the BCL, to reduce binary size. - 'strict': only analyze assemblies referencing the Windows Runtime assembly (the historical behavior). Assemblies that reference the Windows Runtime assembly are always analyzed, regardless of the mode. Adds the CsWinRTMarshallingMode enum, the --marshalling-mode argument (defaulting to 'all'), an IsBaseClassLibraryModule module helper, and factors the discovery filter into ShouldProcessModule. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Wire CsWinRTMarshallingMode through the MSBuild task and targets Add a MarshallingMode parameter to the RunCsWinRTInteropGenerator task (with validation against the well-known values) that emits '--marshalling-mode' to the response file. Add the user-facing CsWinRTMarshallingMode MSBuild property (defaulting to 'all'), pass it to the task, and include it in the generator's property-inputs cache hash so changing the mode correctly re-runs generation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Document CsWinRTMarshallingMode Document the new CsWinRTMarshallingMode option in docs/usage.md and nuget/readme.md, and update the interop generator skill with the new --marshalling-mode argument and the mode-based input filtering behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Default the marshalling mode to 'minimal' Change the default CsWinRTMarshallingMode from 'all' to 'minimal', so the interop generator analyzes every assembly (even those not targeting a Windows TFM) except the .NET base class library (BCL) by default. This keeps the usability win for plain .NET projects while avoiding the binary-size and analysis cost of crawling the BCL. The enum members are kept ordered from least to most restrictive (All, Minimal, Strict); the default is expressed via [DefaultValue] and the MSBuild/task defaults. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Polish MarshallingMode task member ordering and XML docs Move the 'DefaultMarshallingMode' constant and 'ValidMarshallingModes' static field to the top of the class (constants and static fields before instance members), and quote the mode string literals in the XML docs (e.g. <c>"all"</c>) to make clear they are string values. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Skip legacy-runtime assemblies during interop discovery With the marshalling mode defaulting to 'minimal', the interop generator now analyzes assemblies that don't reference the Windows Runtime, including third-party libraries that target .NET Standard or .NET Framework (e.g. Roslyn and BenchmarkDotNet, referenced by the SourceGenerator2Test and Benchmarks projects). Such assemblies are scoped to a legacy corlib ('netstandard' or 'mscorlib'), while the emit phase builds its references from the application's modern .NET runtime corlib ('System.Runtime'). Because AsmResolver's 'SignatureComparer' treats these corlib scopes as distinct, generic collection instantiations from those assemblies (e.g. 'IEnumerator<char>', 'IEnumerator<object>') failed the emit-time well-known interface IID lookup, producing CSWINRTINTEROPGEN0055/0020 errors. Legacy/portable-runtime assemblies cannot reference the Windows Runtime 3.0 projections in the first place, so they never contain marshalling-relevant types. Skip them during discovery via the new 'ModuleDefinition.TargetsLegacyRuntime' check. This restores the pre-marshalling-mode behavior for such assemblies (previously only Windows-Runtime-referencing assemblies, which always target a modern .NET runtime, were ever analyzed). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Add CsWinRTMarshallingEnabledAssembly opt-in item and diagnostics Add the 'CsWinRTMarshallingEnabledAssembly' MSBuild item, which lets users force the interop generator to analyze specific assemblies for discovery, regardless of the marshalling mode. This enables fine-tuning for size: for example, using the 'strict' mode while opting in a few specific assemblies known to be needed, without switching to 'minimal' (which increases binary size more). The item flows through the RunCsWinRTInteropGenerator task as the '--marshalling-enabled-assembly-names' argument, and is matched (by assembly name, ignoring the '.dll' extension and any directory) in 'ShouldProcessModule' so opted-in assemblies are always analyzed. Add three diagnostics for the opt-in entries: - CSWINRTINTEROPGEN0098 (warning): an entry doesn't match any referenced assembly (likely a typo or stale reference). - CSWINRTINTEROPGEN0099 (message): an entry already targets Windows and is therefore always analyzed, so it's redundant and can be removed. - CSWINRTINTEROPGEN0100 (message): the marshalling mode is 'all' (which already analyzes everything), so all entries are redundant. Add a WellKnownInteropMessage type (surfaced by MSBuild as a build message rather than a warning) to support the two informational diagnostics. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Document CsWinRTMarshallingEnabledAssembly and the legacy-runtime skip Document the new CsWinRTMarshallingEnabledAssembly opt-in item in docs/usage.md and nuget/readme.md, and update the interop generator skill with the new --marshalling-enabled-assembly-names argument, the three opt-in diagnostics (CSWINRTINTEROPGEN0098-0100), and the legacy-runtime skip behavior. Also bump the interop generator error ID range in the copilot instructions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Extract a helper for comma-separated response file arguments Move the inline 'MarshallingEnabledAssemblies' comma-joining logic in RunCsWinRTInteropGenerator into a new AppendResponseFileOptionalCommand overload (taking an ITaskItem[]), keeping GenerateResponseFileCommands easy to read and consistent with the other AppendResponseFile* helpers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Move the generator message type to the shared core project Move WellKnownInteropMessage from the interop generator to the shared WinRT.Generator.Core project (renamed WellKnownGeneratorMessage) so all generators can use it. Its Log method now writes through a provided logger delegate (ConsoleApp.Log from ConsoleAppFramework) instead of calling Console.WriteLine directly, so any future change to that logger is reflected here too. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Correct the legacy-runtime skip documentation The previous rationale for skipping legacy/portable-runtime assemblies was inaccurate: such assemblies can still use custom-mapped types (e.g. 'IEnumerable<T>') that would need marshalling. The actual reason the skip is relevant is that the entire interop generator infrastructure identifies well-known types by comparing against type references scoped to the modern .NET corlib (e.g. 'System.Runtime'), which the emit phase also uses. Legacy-runtime assemblies declare those types against a different corlib ('netstandard' or 'mscorlib'), which AsmResolver's SignatureComparer treats as a distinct scope, so the generator can't match them. Update the TargetsLegacyRuntime doc comment, the discovery comment, and the usage/skill docs accordingly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Move the legacy-runtime skip into ShouldProcessModule The 'TargetsLegacyRuntime' check is part of the 'should this module be analyzed' decision, so move it (and its explanatory comment) from LoadAndProcessModule into ShouldProcessModule, keeping LoadAndProcessModule easier to read. The check runs first (before the opt-in check), preserving the existing behavior that legacy-runtime assemblies are skipped even when explicitly opted in. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 7228284 commit cf4aba3

16 files changed

Lines changed: 557 additions & 8 deletions

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,7 @@ All five .NET build tools (`cswinrtprojectionrefgen`, `cswinrtprojectiongen`, `c
711711
| Projection Generator (host) | `CSWINRTPROJECTIONGENxxxx` | `0001``0011`, `9999` |
712712
| Projection Writer (library) | `CSWINRTPROJECTIONGEN5xxx` | `5003``5021`, `9999` (shares the `CSWINRTPROJECTIONGEN` prefix with the host; the writer reserves the 5000+ range so the two never collide) |
713713
| Impl Generator | `CSWINRTIMPLGENxxxx` | `0001``0014`, `9999` |
714-
| Interop Generator | `CSWINRTINTEROPGENxxxx` | `0001``0097`, `9999` |
714+
| Interop Generator | `CSWINRTINTEROPGENxxxx` | `0001``0100`, `9999` |
715715
| WinMD Generator | `CSWINRTWINMDGENxxxx` | `0001``0010`, `9999` |
716716
| Runtime (obsolete markers) | `CSWINRT3xxx` | `CSWINRT3001` |
717717

.github/skills/interop-generator/SKILL.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ WinRT.Interop.Generator/
6161
├── Errors/ # Error/warning infrastructure
6262
│ ├── UnhandledInteropException.cs # Wraps unexpected errors with phase context
6363
│ ├── WellKnownInteropException.cs # Structured errors with CSWINRTINTEROPGEN### codes
64-
│ ├── WellKnownInteropExceptions.cs # Factory for all error/warning codes (80+ codes)
65-
│ └── WellKnownInteropWarning.cs # Non-fatal warnings (can be promoted to errors)
64+
│ ├── WellKnownInteropExceptions.cs # Factory for all error/warning/message codes (100+ codes)
65+
│ ├── WellKnownInteropWarning.cs # Non-fatal warnings (can be promoted to errors)
66+
│ └── WellKnownInteropMessage.cs # Informational messages (never promoted to errors)
6667
├── Extensions/ # Extension methods (23 files)
6768
│ ├── WindowsRuntimeExtensions.cs # Core: IsProjectedWindowsRuntimeType, IsBlittable, etc.
6869
│ ├── TypeSignatureExtensions.cs # GetAbiType, EnumerateAllInterfaces, etc.
@@ -230,6 +231,8 @@ The generator is invoked via a response file: `cswinrtinteropgen.exe @path/to/re
230231
| `--winrt-component-assembly-path` | `string?` | `WinRT.Component.dll` path (for authored components) |
231232
| `--generated-assembly-directory` | `string` | Output folder for `WinRT.Interop.dll` |
232233
| `--use-windows-ui-xaml-projections` | `bool` | Use UWP XAML (`Windows.UI.Xaml`) instead of WinUI |
234+
| `--marshalling-mode` | `CsWinRTMarshallingMode` | Which assemblies to analyze for discovery: `All`, `Minimal` (default; skip BCL), or `Strict` (only WinRT-referencing) |
235+
| `--marshalling-enabled-assembly-names` | `string[]` | Assembly names (`.dll`/directory ignored) always analyzed regardless of mode (from the `CsWinRTMarshallingEnabledAssembly` item) |
233236
| `--validate-winrt-runtime-assembly-version` | `bool` | Check version compatibility |
234237
| `--validate-winrt-runtime-dll-version-2-references` | `bool` | Reject CsWinRT 2.x references |
235238
| `--enable-incremental-generation` | `bool` | Enable incremental generation (caching) |
@@ -282,6 +285,15 @@ The generator processes two categories of assemblies:
282285
- `WinRT.Projection.dll` — 3rd-party component projections (optional)
283286
- `WinRT.Component.dll` — Authored component projections (optional)
284287

288+
**Marshalling mode** (`--marshalling-mode`, `ShouldProcessModule` in `InteropGenerator.Discover.cs`): controls which modules are analyzed for discovery. Modules referencing the Windows Runtime assembly (i.e. those built targeting a Windows TFM) are **always** analyzed; the mode only affects modules that don't reference any CsWinRT assembly:
289+
- `All` — analyze every module, even plain `.NET` assemblies (including the BCL).
290+
- `Minimal` (default) — analyze every module except those from the BCL (detected via well-known .NET/Microsoft public key tokens, see `BaseClassLibraryIdentity` in `WinRT.Generator.Core` and the `ModuleDefinition.IsBaseClassLibraryModule` extension), to reduce binary size. This still lets projects that don't target a Windows TFM (e.g. a class library with just MVVM viewmodels) contribute the marshalling code their types need.
291+
- `Strict` — only analyze modules referencing the Windows Runtime assembly (the historical behavior). Corresponds to `!ReferencesWindowsRuntimeAssembly && !IsWindowsRuntimeModule → skip`.
292+
293+
**Legacy-runtime skip** (`ModuleDefinition.TargetsLegacyRuntime` in `InteropGenerator.Discover.cs`): regardless of mode, modules targeting a legacy/portable runtime (i.e. a `netstandard` or `mscorlib` corlib scope) are **never** analyzed. The entire generator infrastructure identifies well-known types (including custom-mapped types such as `IEnumerable<T>`) by comparing against type references scoped to the modern .NET corlib (e.g. `System.Runtime`), which the emit phase also uses. Legacy-runtime assemblies declare those same types against a different corlib (`netstandard`/`mscorlib`), which `AsmResolver`'s `SignatureComparer` treats as a distinct scope — so the generator can't match them, and generating marshalling code for them would fail with `CSWINRTINTEROPGEN0055`/`0020`. Such assemblies could in principle still use custom-mapped types that need marshalling, but for simplicity they're skipped entirely.
294+
295+
**Opt-in assemblies** (`--marshalling-enabled-assembly-names`, from the `CsWinRTMarshallingEnabledAssembly` MSBuild item): assemblies listed here are **always** analyzed (matched by simple name, ignoring `.dll`/directory), regardless of the mode — checked first in `ShouldProcessModule`. This lets users fine-tune size (e.g. `strict` mode + a few opted-in assemblies). It does **not** override the legacy-runtime skip (legacy assemblies still can't be marshalled). `ValidateMarshallingEnabledAssemblies` emits three diagnostics: `CSWINRTINTEROPGEN0098` (warning: entry doesn't match any referenced assembly), `CSWINRTINTEROPGEN0099` (message: entry already targets Windows, redundant), and `CSWINRTINTEROPGEN0100` (message: mode is `all`, all entries redundant).
296+
285297
**Type exclusions** (`Helpers/TypeExclusions.cs`):
286298
- `System.Threading.Tasks.Task<T>` — Cannot be marshalled across Windows Runtime boundary
287299
- `System.Collections.Concurrent.ConditionalWeakTable<,>` — Memory semantics conflict

docs/usage.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,47 @@ Below are the most commonly used MSBuild properties. For a full list, refer to t
136136
| `CsWinRTGenerateProjection` | `true` | Generate C# projection sources from `.winmd` metadata. |
137137
| `CsWinRTGenerateReferenceProjection` | `false` | Generate reference-only projections (for NuGet distribution). |
138138
| `CsWinRTComponent` | `false` | Enable Windows Runtime component authoring mode. |
139+
| `CsWinRTMarshallingMode` | `minimal` | Controls which assemblies the interop generator analyzes for marshalling code (`all`, `minimal`, or `strict`). See below. |
139140
| `CsWinRTIncludes` | *(empty)* | Semicolon-separated namespaces to include in the projection. |
140141
| `CsWinRTExcludes` | `Windows;Microsoft` | Semicolon-separated namespaces to exclude from the projection. |
141142
| `CsWinRTMessageImportance` | `normal` | Build message verbosity (`normal` or `high`). |
142143

144+
### Marshalling mode
145+
146+
By default (`minimal`), the interop generator analyzes **every** assembly in the app to discover user-defined
147+
(CCW) types and generic instantiations that need marshalling code — even assemblies that don't target a
148+
Windows TFM and don't reference any CsWinRT assembly — with the exception of the .NET base class library
149+
(BCL). This means a plain `.NET` class library (e.g. one holding MVVM viewmodels) no longer needs to target
150+
a `-windows` TFM just so its types can be marshalled to native code, while the BCL is skipped to keep the
151+
generated interop assembly small.
152+
153+
`CsWinRTMarshallingMode` controls this behavior:
154+
155+
| Value | Behavior |
156+
|-------|----------|
157+
| `all` | Analyze all assemblies, including those from the .NET base class library (BCL). |
158+
| `minimal` (default) | Same as `all`, but skip assemblies from the .NET base class library (BCL) to reduce binary size. |
159+
| `strict` | Only analyze assemblies referencing the Windows Runtime assembly (i.e. those targeting a Windows TFM). |
160+
161+
Assemblies that reference the Windows Runtime assembly are always analyzed, regardless of the mode. Assemblies targeting a legacy or portable runtime (i.e. .NET Standard or .NET Framework) are never analyzed, since the interop generator can only marshal types declared against a modern .NET runtime.
162+
163+
### Opting in specific assemblies
164+
165+
The `CsWinRTMarshallingEnabledAssembly` item lets you force the interop generator to analyze specific assemblies, regardless of the marshalling mode. This is useful for fine-tuning binary size: for example, you can keep the `strict` mode (the smallest option) while opting in just the few assemblies you know need marshalling support:
166+
167+
```xml
168+
<PropertyGroup>
169+
<CsWinRTMarshallingMode>strict</CsWinRTMarshallingMode>
170+
</PropertyGroup>
171+
172+
<ItemGroup>
173+
<CsWinRTMarshallingEnabledAssembly Include="MyApp.ViewModels" />
174+
<CsWinRTMarshallingEnabledAssembly Include="MyApp.Models" />
175+
</ItemGroup>
176+
```
177+
178+
Each item is an assembly name (the `.dll` extension is optional). The interop generator reports a warning if an entry doesn't match any referenced assembly, and an informational message if an entry is redundant (e.g. it already targets Windows, or the mode is `all` and thus already analyzes everything).
179+
143180
## Author and consume a C#/WinRT component
144181

145182
This is coming soon for CsWinRT 3.0.

nuget/Microsoft.Windows.CsWinRT.CsWinRTGen.targets

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ Copyright (C) Microsoft Corporation. All rights reserved.
4545
<CsWinRTGeneratorStandardErrorImportance Condition="'$(CsWinRTGeneratorStandardErrorImportance)' == ''">High</CsWinRTGeneratorStandardErrorImportance>
4646
<CsWinRTGeneratorLogStandardErrorAsError Condition="'$(CsWinRTGeneratorLogStandardErrorAsError)' == ''">true</CsWinRTGeneratorLogStandardErrorAsError>
4747

48+
<!--
49+
The marshalling mode controls which assemblies 'cswinrtinteropgen' analyzes to discover
50+
user-defined/CCW/generic types that need marshalling code:
51+
- 'minimal' (default): analyze all assemblies (even those not referencing any CsWinRT
52+
assembly), except those from the .NET base class library (BCL). This lets projects that
53+
don't target a Windows TFM (e.g. a class library with just MVVM viewmodels) contribute
54+
the marshalling code their types need, while skipping the BCL to reduce binary size.
55+
- 'all': same as 'minimal', but also analyze assemblies from the .NET base class library (BCL).
56+
- 'strict': only analyze assemblies referencing the Windows Runtime assembly (i.e. those
57+
targeting a Windows TFM).
58+
-->
59+
<CsWinRTMarshallingMode Condition="'$(CsWinRTMarshallingMode)' == ''">minimal</CsWinRTMarshallingMode>
60+
4861
<!-- Emits a SetEntryAssembly module initializer into WinRT.Component.dll to enable JIT TypeMap discovery.
4962
Not needed for AOT (handled via a separate exe-project workaround). Will become unnecessary once
5063
the TypeMappingEntryAssembly MSBuild property is available. -->
@@ -133,6 +146,8 @@ Copyright (C) Microsoft Corporation. All rights reserved.
133146
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorInteropAssemblyDirectory)" />
134147
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorDebugReproDirectory)" />
135148
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTUseWindowsUIXamlProjections)" />
149+
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTMarshallingMode)" />
150+
<_RunCsWinRTGeneratorInputsCacheToHash Include="@(CsWinRTMarshallingEnabledAssembly)" />
136151
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorValidateWinRTRuntimeAssemblyVersion)" />
137152
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorValidateWinRTRuntimeDllVersion2References)" />
138153
<_RunCsWinRTGeneratorInputsCacheToHash Include="$(CsWinRTGeneratorEnableIncrementalGeneration)" />
@@ -185,6 +200,8 @@ Copyright (C) Microsoft Corporation. All rights reserved.
185200
CsWinRTToolsDirectory="$(CsWinRTInteropGenEffectiveToolsDirectory)"
186201
CsWinRTToolsArchitecture="$(CsWinRTToolsArchitecture)"
187202
UseWindowsUIXamlProjections="$(CsWinRTUseWindowsUIXamlProjections)"
203+
MarshallingMode="$(CsWinRTMarshallingMode)"
204+
MarshallingEnabledAssemblies="@(CsWinRTMarshallingEnabledAssembly)"
188205
ValidateWinRTRuntimeAssemblyVersion="$(CsWinRTGeneratorValidateWinRTRuntimeAssemblyVersion)"
189206
ValidateWinRTRuntimeDllVersion2References="$(CsWinRTGeneratorValidateWinRTRuntimeDllVersion2References)"
190207
EnableIncrementalGeneration="$(CsWinRTGeneratorEnableIncrementalGeneration)"

nuget/readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ C#/WinRT behavior can be customized with these project properties:
3333
| CsWinRTGenerateReferenceProjection | true \| *false | Generate reference-only projections (for NuGet distribution) |
3434
| CsWinRTGenerateInteropAssembly | auto | Generate interop assemblies at build time (defaults to `true` for Exe/WinExe, or Library with `PublishAot=true`) |
3535
| CsWinRTComponent | true \| *false | Enable Windows Runtime component authoring mode |
36+
| CsWinRTMarshallingMode | all \| *minimal \| strict | Controls which assemblies the interop generator analyzes for marshalling code. `all` analyzes every assembly (including the .NET base class library), `minimal` analyzes every assembly except the .NET base class library (BCL), and `strict` only analyzes assemblies referencing the Windows Runtime assembly |
37+
| CsWinRTMarshallingEnabledAssembly | *(item)* | An item listing specific assemblies (by name, `.dll` optional) to always analyze for marshalling code, regardless of `CsWinRTMarshallingMode`. Useful for fine-tuning binary size (e.g. using `strict` and opting in a few assemblies) |
3638
| CsWinRTUseWindowsUIXamlProjections | true \| *false | Use UWP XAML (`Windows.UI.Xaml`) instead of WinUI (`Microsoft.UI.Xaml`) |
3739
| CsWinRTMergeReferencedActivationFactories | true \| *false | Makes `DllGetActivationFactory` forward activation calls to all referenced WinRT components, allowing them to be merged into a single executable |
3840
| CsWinRTIncludes | "" | Semicolon-separated namespaces to include in projection output |
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
6+
namespace WindowsRuntime.Generator.Errors;
7+
8+
/// <summary>
9+
/// A well-known informational message shared by all generators.
10+
/// </summary>
11+
/// <remarks>
12+
/// Unlike a warning, a message is purely informational: it is never promoted to an error, and it is surfaced
13+
/// by MSBuild as a build message rather than a warning (the emitted line intentionally omits the
14+
/// <c>warning</c>/<c>error</c> category keyword that MSBuild's output parser looks for). It is distinct from
15+
/// <see cref="WellKnownGeneratorMessages"/>, which holds the message templates for the shared logical errors.
16+
/// </remarks>
17+
internal sealed class WellKnownGeneratorMessage
18+
{
19+
/// <summary>
20+
/// The id of the message.
21+
/// </summary>
22+
private readonly string _id;
23+
24+
/// <summary>
25+
/// The message text.
26+
/// </summary>
27+
private readonly string _message;
28+
29+
/// <summary>
30+
/// Creates a new <see cref="WellKnownGeneratorMessage"/> instance with the specified parameters.
31+
/// </summary>
32+
/// <param name="id">The id of the message.</param>
33+
/// <param name="message">The message text.</param>
34+
public WellKnownGeneratorMessage(string id, string message)
35+
{
36+
_id = id;
37+
_message = message;
38+
}
39+
40+
/// <summary>
41+
/// Logs the message through the provided logger.
42+
/// </summary>
43+
/// <param name="log">The logger to write the message to (typically <c>ConsoleApp.Log</c> from ConsoleAppFramework).</param>
44+
public void Log(Action<string> log)
45+
{
46+
log($"{_id}: {_message}");
47+
}
48+
}

src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ private static string FormatValue(object value)
8989
return string.Join(',', array);
9090
}
9191

92+
// Enum values are serialized by their member name (e.g. 'All'), matching the parser's
93+
// case-insensitive, name-based enum handling so the round-trip stays deterministic.
94+
if (value is Enum enumValue)
95+
{
96+
return enumValue.ToString();
97+
}
98+
9299
// All other primitive values use invariant culture, matching the parser's
93100
// invariant 'Convert.ChangeType' so the round-trip is deterministic.
94101
return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;

src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,26 @@ private static bool TryConvert(string rawValue, Type targetType, out object? con
261261
return true;
262262
}
263263

264+
// Enum-typed arguments are parsed by name (case-insensitively), so that MSBuild values such
265+
// as 'all' round-trip to the matching enum member (e.g. 'All'). Numeric values are rejected,
266+
// as they carry no meaning in a response file and would silently accept out-of-range inputs.
267+
if (targetType.IsEnum)
268+
{
269+
if (rawValue.Length > 0 &&
270+
!char.IsAsciiDigit(rawValue[0]) &&
271+
rawValue[0] != '-' &&
272+
Enum.TryParse(targetType, rawValue, ignoreCase: true, out object? parsedEnum))
273+
{
274+
converted = parsedEnum;
275+
276+
return true;
277+
}
278+
279+
converted = null;
280+
281+
return false;
282+
}
283+
264284
// For primitives ('bool', 'int', etc.) we rely on the standard 'Convert.ChangeType', which
265285
// is AOT-safe for the built-in primitives and uses invariant culture for deterministic parsing.
266286
try

0 commit comments

Comments
 (0)