From 25149ea3d86761addc2d0debc545299d5aff0401 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 16:08:05 -0700 Subject: [PATCH 01/28] Unify response-file empty-line handling across all 5 generators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three of the five generators (Projection, ProjectionRef, WinMD) already skipped blank lines in their response-file parsers, with an explanatory comment noting that the MSBuild 'ToolTask' infrastructure may emit them. The remaining two (Impl, Interop) were ported earlier, before this MSBuild quirk was characterized, and would instead throw 'MalformedResponseFile' on a blank line. The strict behavior was never deliberate — it's the original implementation that wasn't updated — and it's a latent crash waiting to surface the moment a task wrapper emits a blank line (e.g. for grouping or as a side effect of an empty optional value). Switches Impl + Interop to the same defensive ''skip blank lines'' branch the other three already use, with the same comment, so every parser is now byte-identical at that loop. WinMD already had the behavior but used 'string.IsNullOrEmpty(trimmedLine)' — converted to 'trimmedLine.Length == 0' to match the other four exactly. This also removes the only behavioral booby-trap that would have required a per-tool hook in the upcoming shared-code extraction work. Observable change: a blank line in a '.rsp' that previously caused 'WellKnownImplException(CSWINRTIMPLGEN0003)' or 'WellKnownInteropException(CSWINRTINTEROPGEN0003)' is now silently no-op. That error path is unreachable in normal operation (no task wrapper currently emits blank lines), so this is a defensive fix with no real-world regression risk. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Generation/ImplGeneratorArgs.Parsing.cs | 6 ++++++ .../Generation/InteropGeneratorArgs.Parsing.cs | 6 ++++++ .../Generation/ProjectionGeneratorArgs.Parsing.cs | 2 +- .../Generation/ReferenceProjectionGeneratorArgs.Parsing.cs | 2 +- .../Generation/WinMDGeneratorArgs.Parsing.cs | 3 ++- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs index 27fd5e5115..12616b0d4d 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs @@ -75,6 +75,12 @@ private static ImplGeneratorArgs ParseFromResponseFile(string[] lines, Cancellat { string trimmedLine = line.Trim(); + // Skip empty lines (the MSBuild ToolTask may emit blank lines) + if (trimmedLine.Length == 0) + { + continue; + } + // Each line has the command line argument name followed by a space, and then the // argument value. If there are no spaces on any given line, the file is malformed. int indexOfSpace = trimmedLine.IndexOf(' '); diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs index 89e4559ac4..7408963ed4 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs @@ -74,6 +74,12 @@ private static InteropGeneratorArgs ParseFromResponseFile(string[] lines, Cancel { string trimmedLine = line.Trim(); + // Skip empty lines (the MSBuild ToolTask may emit blank lines) + if (trimmedLine.Length == 0) + { + continue; + } + // Each line has the command line argument name followed by a space, and then the // argument value. If there are no spaces on any given line, the file is malformed. int indexOfSpace = trimmedLine.IndexOf(' '); diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs index 2f5011d5e9..62294ece31 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs @@ -75,7 +75,7 @@ private static ProjectionGeneratorArgs ParseFromResponseFile(string[] lines, Can { string trimmedLine = line.Trim(); - // Skip empty lines (the MSBuild ToolTask may emit blank lines). + // Skip empty lines (the MSBuild ToolTask may emit blank lines) if (trimmedLine.Length == 0) { continue; diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs index c3eebe8810..358b513b3b 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs @@ -75,7 +75,7 @@ private static ReferenceProjectionGeneratorArgs ParseFromResponseFile(string[] l { string trimmedLine = line.Trim(); - // Skip empty lines (the MSBuild ToolTask may emit blank lines). + // Skip empty lines (the MSBuild ToolTask may emit blank lines) if (trimmedLine.Length == 0) { continue; diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs index 351029ab5a..8014d5195c 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs @@ -85,7 +85,8 @@ private static WinMDGeneratorArgs ParseFromResponseFile(string[] lines, Cancella { string trimmedLine = line.Trim(); - if (string.IsNullOrEmpty(trimmedLine)) + // Skip empty lines (the MSBuild ToolTask may emit blank lines) + if (trimmedLine.Length == 0) { continue; } From 6efa4abcc87d0573dd99124caeb8bb7aeec4c8e9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 18:06:59 -0700 Subject: [PATCH 02/28] Phase 1: extract shared CLI infrastructure into WinRT.Generator.Cli Creates a new internal shared library 'WinRT.Generator.Cli' to host code that was previously duplicated across the five Native AOT CLI generator projects (Impl, Interop, Projection, ProjectionRef, WinMD). This first phase does only the mechanical, behavior-preserving extractions: - Path/File extensions (G1): 'PathExtensions' and 'FileExtensions' move out of 'WinRT.Interop.Generator/Extensions/' (where four other tools were reaching in via '') and into 'WinRT.Generator.Cli/Extensions/'. The two ad-hoc cross-project links are dropped and replaced by a normal 'ProjectReference'. - Command-line argument name attribute (A1): five identical copies of 'CommandLineArgumentNameAttribute' collapse into one in 'WinRT.Generator.Cli/Attributes/'. - JSON serializer context (E1): five identical 'X*GeneratorJsonSerializerContext' copies (each carrying the same 'JsonSerializable(typeof(Dictionary))') collapse into a single shared 'GeneratorJsonSerializerContext' in 'WinRT.Generator.Cli/Helpers/'. Visibility is preserved ('internal') with 'InternalsVisibleTo' for each of the five consumer assemblies. Namespaces follow the existing per-project convention: extensions stay at the root namespace ('WindowsRuntime.GeneratorCli'), while attributes and helpers go into matching subnamespaces. The new library is wired into 'src/cswinrt.slnx'. Two ancillary build fixes were needed to unblock validation: - 'WinRT.Impl.Generator' and 'WinRT.Interop.Generator' csproj files gain a scoped 'IDE0028(;IDE0370)' for the same SDK analyzer regression already handled in 'WinRT.WinMD.Generator' and 'WinRT.Projection.Generator' (the 'new(StringComparer.Ordinal)' pattern can't be simplified to a collection literal without dropping the comparer). - Five Interop files that previously relied on implicit namespace resolution to pick up 'Path.Normalize'/'Path.IsWithinDirectoryName'/'File.ReadAllLines(Stream)' from the sibling namespace ('IgnoresAccessChecksToBuilder', 'InteropGenerator', 'InteropGenerator.Emit', 'InteropGenerator.DebugRepro', 'InteropGeneratorArgs.Parsing') gain an explicit 'using WindowsRuntime.GeneratorCli;'. Validated end-to-end with debug-repro round-trips: ProjectionRef 8/8 .cs files 0 diffs, WinMD 5632/5632 bytes 16-byte (MVID-only) diff, Projection 327/327 .cs files 0 diffs, Impl 257536/257536 bytes 0 diffs, Interop smoke-tested. All 5 generators build with 0 warnings in Release and AOT-publish cleanly for win-arm64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CommandLineArgumentNameAttribute.cs | 4 +-- .../Extensions/FileExtensions.cs | 4 +-- .../Extensions/PathExtensions.cs | 9 ++--- .../GeneratorJsonSerializerContext.cs} | 6 ++-- .../Properties/AssemblyInfo.cs | 10 ++++++ .../WinRT.Generator.Cli.csproj | 36 +++++++++++++++++++ .../Generation/ImplGenerator.DebugRepro.cs | 8 ++--- .../Generation/ImplGenerator.cs | 2 +- .../Generation/ImplGeneratorArgs.Parsing.cs | 4 +-- .../Generation/ImplGeneratorArgs.cs | 2 +- .../ImplGeneratorJsonSerializerContext.cs | 14 -------- .../WinRT.Impl.Generator.csproj | 8 +++-- .../CommandLineArgumentNameAttribute.cs | 19 ---------- .../Builders/IgnoresAccessChecksToBuilder.cs | 1 + .../Generation/InteropGenerator.DebugRepro.cs | 7 ++-- .../Generation/InteropGenerator.Emit.cs | 1 + .../Generation/InteropGenerator.cs | 1 + .../InteropGeneratorArgs.Parsing.cs | 3 +- .../Generation/InteropGeneratorArgs.cs | 2 +- .../InteropGeneratorJsonSerializerContext.cs | 14 -------- .../WinRT.Interop.Generator.csproj | 11 ++++++ .../CommandLineArgumentNameAttribute.cs | 19 ---------- .../ProjectionGenerator.DebugRepro.cs | 8 ++--- .../Generation/ProjectionGenerator.cs | 2 +- .../ProjectionGeneratorArgs.Parsing.cs | 4 +-- .../Generation/ProjectionGeneratorArgs.cs | 2 +- ...rojectionGeneratorJsonSerializerContext.cs | 14 -------- .../WinRT.Projection.Generator.csproj | 6 +--- .../CommandLineArgumentNameAttribute.cs | 19 ---------- ...ReferenceProjectionGenerator.DebugRepro.cs | 8 ++--- .../ReferenceProjectionGenerator.cs | 2 +- ...eferenceProjectionGeneratorArgs.Parsing.cs | 4 +-- .../ReferenceProjectionGeneratorArgs.cs | 2 +- ...rojectionGeneratorJsonSerializerContext.cs | 14 -------- .../WinRT.Projection.Ref.Generator.csproj | 6 +--- .../CommandLineArgumentNameAttribute.cs | 19 ---------- .../Generation/WinMDGenerator.DebugRepro.cs | 8 ++--- .../Generation/WinMDGenerator.cs | 2 +- .../Generation/WinMDGeneratorArgs.Parsing.cs | 4 +-- .../Generation/WinMDGeneratorArgs.cs | 2 +- .../WinRT.WinMD.Generator.csproj | 3 +- src/cswinrt.slnx | 1 + 42 files changed, 121 insertions(+), 194 deletions(-) rename src/{WinRT.Impl.Generator => WinRT.Generator.Cli}/Attributes/CommandLineArgumentNameAttribute.cs (91%) rename src/{WinRT.Interop.Generator => WinRT.Generator.Cli}/Extensions/FileExtensions.cs (95%) rename src/{WinRT.Interop.Generator => WinRT.Generator.Cli}/Extensions/PathExtensions.cs (88%) rename src/{WinRT.WinMD.Generator/Helpers/WinMDGeneratorJsonSerializerContext.cs => WinRT.Generator.Cli/Helpers/GeneratorJsonSerializerContext.cs} (55%) create mode 100644 src/WinRT.Generator.Cli/Properties/AssemblyInfo.cs create mode 100644 src/WinRT.Generator.Cli/WinRT.Generator.Cli.csproj delete mode 100644 src/WinRT.Impl.Generator/Helpers/ImplGeneratorJsonSerializerContext.cs delete mode 100644 src/WinRT.Interop.Generator/Attributes/CommandLineArgumentNameAttribute.cs delete mode 100644 src/WinRT.Interop.Generator/Helpers/InteropGeneratorJsonSerializerContext.cs delete mode 100644 src/WinRT.Projection.Generator/Attributes/CommandLineArgumentNameAttribute.cs delete mode 100644 src/WinRT.Projection.Generator/Helpers/ProjectionGeneratorJsonSerializerContext.cs delete mode 100644 src/WinRT.Projection.Ref.Generator/Attributes/CommandLineArgumentNameAttribute.cs delete mode 100644 src/WinRT.Projection.Ref.Generator/Helpers/ReferenceProjectionGeneratorJsonSerializerContext.cs delete mode 100644 src/WinRT.WinMD.Generator/Attributes/CommandLineArgumentNameAttribute.cs diff --git a/src/WinRT.Impl.Generator/Attributes/CommandLineArgumentNameAttribute.cs b/src/WinRT.Generator.Cli/Attributes/CommandLineArgumentNameAttribute.cs similarity index 91% rename from src/WinRT.Impl.Generator/Attributes/CommandLineArgumentNameAttribute.cs rename to src/WinRT.Generator.Cli/Attributes/CommandLineArgumentNameAttribute.cs index 95d0ace705..80e905933c 100644 --- a/src/WinRT.Impl.Generator/Attributes/CommandLineArgumentNameAttribute.cs +++ b/src/WinRT.Generator.Cli/Attributes/CommandLineArgumentNameAttribute.cs @@ -3,7 +3,7 @@ using System; -namespace WindowsRuntime.ImplGenerator.Attributes; +namespace WindowsRuntime.GeneratorCli.Attributes; /// /// An attribute indicating the name of a given command line argument. @@ -16,4 +16,4 @@ internal sealed class CommandLineArgumentNameAttribute(string name) : Attribute /// Gets the command line argument name. /// public string Name { get; } = name; -} \ No newline at end of file +} diff --git a/src/WinRT.Interop.Generator/Extensions/FileExtensions.cs b/src/WinRT.Generator.Cli/Extensions/FileExtensions.cs similarity index 95% rename from src/WinRT.Interop.Generator/Extensions/FileExtensions.cs rename to src/WinRT.Generator.Cli/Extensions/FileExtensions.cs index 46869482ef..8429bb01b6 100644 --- a/src/WinRT.Interop.Generator/Extensions/FileExtensions.cs +++ b/src/WinRT.Generator.Cli/Extensions/FileExtensions.cs @@ -5,7 +5,7 @@ using System.IO; using System.Text; -namespace WindowsRuntime.InteropGenerator; +namespace WindowsRuntime.GeneratorCli; /// /// Extensions for . @@ -35,4 +35,4 @@ public static string[] ReadAllLines(Stream stream) return [.. lines]; } } -} \ No newline at end of file +} diff --git a/src/WinRT.Interop.Generator/Extensions/PathExtensions.cs b/src/WinRT.Generator.Cli/Extensions/PathExtensions.cs similarity index 88% rename from src/WinRT.Interop.Generator/Extensions/PathExtensions.cs rename to src/WinRT.Generator.Cli/Extensions/PathExtensions.cs index fac234101c..3f468411fc 100644 --- a/src/WinRT.Interop.Generator/Extensions/PathExtensions.cs +++ b/src/WinRT.Generator.Cli/Extensions/PathExtensions.cs @@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; -namespace WindowsRuntime.InteropGenerator; +namespace WindowsRuntime.GeneratorCli; /// /// Extensions for the type. @@ -23,8 +23,9 @@ internal static class PathExtensions public static string? Normalize(string? path) { // If on Windows, no normalization is needed. Paths in debug repros will use this format. - // Note: 'cswinrtinteropgen' is only meant to be used on Windows (because CsWinRT itself is - // only supported on Windows), but this allows debugging repros on other platforms too. + // Note: the CsWinRT generators are only meant to be used on Windows (because CsWinRT + // itself is only supported on Windows), but this allows debugging repros on other + // platforms too. if (OperatingSystem.IsWindows()) { return path; @@ -61,4 +62,4 @@ public static bool IsWithinDirectoryName(ReadOnlySpan path, ReadOnlySpan -/// A for types used in the WinMD generator. +/// A for types used across the CsWinRT CLI generators. /// [JsonSerializable(typeof(Dictionary))] [JsonSourceGenerationOptions(WriteIndented = true)] -internal sealed partial class WinMDGeneratorJsonSerializerContext : JsonSerializerContext; +internal sealed partial class GeneratorJsonSerializerContext : JsonSerializerContext; diff --git a/src/WinRT.Generator.Cli/Properties/AssemblyInfo.cs b/src/WinRT.Generator.Cli/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..41f427dd1d --- /dev/null +++ b/src/WinRT.Generator.Cli/Properties/AssemblyInfo.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("cswinrtimplgen")] +[assembly: InternalsVisibleTo("cswinrtinteropgen")] +[assembly: InternalsVisibleTo("cswinrtprojectiongen")] +[assembly: InternalsVisibleTo("cswinrtprojectionrefgen")] +[assembly: InternalsVisibleTo("cswinrtwinmdgen")] diff --git a/src/WinRT.Generator.Cli/WinRT.Generator.Cli.csproj b/src/WinRT.Generator.Cli/WinRT.Generator.Cli.csproj new file mode 100644 index 0000000000..ac750676c2 --- /dev/null +++ b/src/WinRT.Generator.Cli/WinRT.Generator.Cli.csproj @@ -0,0 +1,36 @@ + + + net10.0 + 14.0 + enable + true + true + true + + + C#/WinRT Generator CLI shared infrastructure v$(VersionString) + C#/WinRT Generator CLI shared infrastructure v$(VersionString) + Copyright (c) Microsoft Corporation. All rights reserved. + $(AssemblyVersionNumber) + $(VersionNumber) + + + true + + + WindowsRuntime.GeneratorCli + + + WinRT.Generator.Cli + + + true + true + latest + latest-all + true + strict + true + + + diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs index eac02ae9f4..185f28dd30 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs @@ -12,8 +12,8 @@ using System.Text.Json; using System.Threading; using WindowsRuntime.ImplGenerator.Errors; -using WindowsRuntime.ImplGenerator.Helpers; -using WindowsRuntime.InteropGenerator; +using WindowsRuntime.GeneratorCli.Helpers; +using WindowsRuntime.GeneratorCli; #pragma warning disable IDE0008 @@ -314,7 +314,7 @@ private static void CopyPathMapToDirectory( using Stream jsonStream = File.Create(jsonFilePath); // Serialize the path map to the target file - JsonSerializer.Serialize(jsonStream, pathMap, ImplGeneratorJsonSerializerContext.Default.DictionaryStringString); + JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); } /// @@ -329,6 +329,6 @@ private static Dictionary ExtractPathMap(ZipArchiveEntry pathMap using Stream stream = pathMapEntry.Open(); // Load the mapping with all the original file paths for the included .dll-s - return JsonSerializer.Deserialize(stream, ImplGeneratorJsonSerializerContext.Default.DictionaryStringString)!; + return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; } } diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index 0f9854159b..51890b01fb 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -19,7 +19,7 @@ using ConsoleAppFramework; using WindowsRuntime.ImplGenerator.Errors; using WindowsRuntime.ImplGenerator.References; -using WindowsRuntime.InteropGenerator; +using WindowsRuntime.GeneratorCli; namespace WindowsRuntime.ImplGenerator.Generation; diff --git a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs index 12616b0d4d..d80b649bcb 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs @@ -6,9 +6,9 @@ using System.IO; using System.Reflection; using System.Threading; -using WindowsRuntime.ImplGenerator.Attributes; +using WindowsRuntime.GeneratorCli.Attributes; using WindowsRuntime.ImplGenerator.Errors; -using WindowsRuntime.InteropGenerator; +using WindowsRuntime.GeneratorCli; #pragma warning disable IDE0046 diff --git a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs index be772b2086..7ace6d93e6 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Threading; -using WindowsRuntime.ImplGenerator.Attributes; +using WindowsRuntime.GeneratorCli.Attributes; namespace WindowsRuntime.ImplGenerator.Generation; diff --git a/src/WinRT.Impl.Generator/Helpers/ImplGeneratorJsonSerializerContext.cs b/src/WinRT.Impl.Generator/Helpers/ImplGeneratorJsonSerializerContext.cs deleted file mode 100644 index bdff83c9f8..0000000000 --- a/src/WinRT.Impl.Generator/Helpers/ImplGeneratorJsonSerializerContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace WindowsRuntime.ImplGenerator.Helpers; - -/// -/// A for types used in the impl generator. -/// -[JsonSerializable(typeof(Dictionary))] -[JsonSourceGenerationOptions(WriteIndented = true)] -internal sealed partial class ImplGeneratorJsonSerializerContext : JsonSerializerContext; diff --git a/src/WinRT.Impl.Generator/WinRT.Impl.Generator.csproj b/src/WinRT.Impl.Generator/WinRT.Impl.Generator.csproj index e37bc9a077..41670c9cf9 100644 --- a/src/WinRT.Impl.Generator/WinRT.Impl.Generator.csproj +++ b/src/WinRT.Impl.Generator/WinRT.Impl.Generator.csproj @@ -37,6 +37,11 @@ true win-$(BuildToolArch) true + + + $(NoWarn);IDE0028 @@ -51,8 +56,7 @@ - - + diff --git a/src/WinRT.Interop.Generator/Attributes/CommandLineArgumentNameAttribute.cs b/src/WinRT.Interop.Generator/Attributes/CommandLineArgumentNameAttribute.cs deleted file mode 100644 index 140760c163..0000000000 --- a/src/WinRT.Interop.Generator/Attributes/CommandLineArgumentNameAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; - -namespace WindowsRuntime.InteropGenerator.Attributes; - -/// -/// An attribute indicating the name of a given command line argument. -/// -/// The command line argument name. -[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] -internal sealed class CommandLineArgumentNameAttribute(string name) : Attribute -{ - /// - /// Gets the command line argument name. - /// - public string Name { get; } = name; -} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs b/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs index d00bffcd9b..190e1e6e7c 100644 --- a/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs +++ b/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using AsmResolver.DotNet; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.InteropGenerator.Factories; using WindowsRuntime.InteropGenerator.References; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs index 22396c11b1..c8bd598166 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs @@ -11,8 +11,9 @@ using System.Text; using System.Text.Json; using System.Threading; +using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Helpers; using WindowsRuntime.InteropGenerator.Errors; -using WindowsRuntime.InteropGenerator.Helpers; #pragma warning disable IDE0008 @@ -413,7 +414,7 @@ private static void CopyPathMapToDirectory( using Stream jsonStream = File.Create(jsonFilePath); // Serialize the path map to the target file - JsonSerializer.Serialize(jsonStream, pathMap, InteropGeneratorJsonSerializerContext.Default.DictionaryStringString); + JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); } /// @@ -428,6 +429,6 @@ private static Dictionary ExtractPathMap(ZipArchiveEntry pathMap using Stream stream = pathMapEntry.Open(); // Load the mapping with all the original file paths for the included .dll-s - return JsonSerializer.Deserialize(stream, InteropGeneratorJsonSerializerContext.Default.DictionaryStringString)!; + return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; } } \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs index 6817914a14..eef7fc19a8 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs @@ -8,6 +8,7 @@ using System.Linq; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.InteropGenerator.Builders; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Fixups; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs index f330be463d..9e7179415c 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading; using ConsoleAppFramework; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.References; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs index 7408963ed4..0763556ee3 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs @@ -6,7 +6,8 @@ using System.IO; using System.Reflection; using System.Threading; -using WindowsRuntime.InteropGenerator.Attributes; +using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Attributes; using WindowsRuntime.InteropGenerator.Errors; #pragma warning disable IDE0046 diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs index 0384a4f8cd..b1bcb3583e 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Threading; -using WindowsRuntime.InteropGenerator.Attributes; +using WindowsRuntime.GeneratorCli.Attributes; namespace WindowsRuntime.InteropGenerator.Generation; diff --git a/src/WinRT.Interop.Generator/Helpers/InteropGeneratorJsonSerializerContext.cs b/src/WinRT.Interop.Generator/Helpers/InteropGeneratorJsonSerializerContext.cs deleted file mode 100644 index 2546cf4cf9..0000000000 --- a/src/WinRT.Interop.Generator/Helpers/InteropGeneratorJsonSerializerContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace WindowsRuntime.InteropGenerator.Helpers; - -/// -/// A for types used in the interop generator. -/// -[JsonSerializable(typeof(Dictionary))] -[JsonSourceGenerationOptions(WriteIndented = true)] -internal sealed partial class InteropGeneratorJsonSerializerContext : JsonSerializerContext; \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/WinRT.Interop.Generator.csproj b/src/WinRT.Interop.Generator/WinRT.Interop.Generator.csproj index 062188b98b..9c7335aa9c 100644 --- a/src/WinRT.Interop.Generator/WinRT.Interop.Generator.csproj +++ b/src/WinRT.Interop.Generator/WinRT.Interop.Generator.csproj @@ -37,6 +37,13 @@ true win-$(BuildToolArch) true + + + $(NoWarn);IDE0028;IDE0370 @@ -76,6 +83,10 @@ false + + + + diff --git a/src/WinRT.Projection.Generator/Attributes/CommandLineArgumentNameAttribute.cs b/src/WinRT.Projection.Generator/Attributes/CommandLineArgumentNameAttribute.cs deleted file mode 100644 index 044139902a..0000000000 --- a/src/WinRT.Projection.Generator/Attributes/CommandLineArgumentNameAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; - -namespace WindowsRuntime.ProjectionGenerator.Attributes; - -/// -/// An attribute indicating the name of a given command line argument. -/// -/// The command line argument name. -[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] -internal sealed class CommandLineArgumentNameAttribute(string name) : Attribute -{ - /// - /// Gets the command line argument name. - /// - public string Name { get; } = name; -} diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs index 949fbc53bb..65e056c026 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs @@ -10,9 +10,9 @@ using System.Text; using System.Text.Json; using System.Threading; -using WindowsRuntime.InteropGenerator; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.ProjectionGenerator.Errors; -using WindowsRuntime.ProjectionGenerator.Helpers; +using WindowsRuntime.GeneratorCli.Helpers; using WindowsRuntime.ProjectionWriter.Helpers; #pragma warning disable IDE0008 @@ -380,7 +380,7 @@ private static void CopyPathMapToDirectory( using Stream jsonStream = File.Create(jsonFilePath); // Serialize the path map to the target file - JsonSerializer.Serialize(jsonStream, pathMap, ProjectionGeneratorJsonSerializerContext.Default.DictionaryStringString); + JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); } /// @@ -395,6 +395,6 @@ private static Dictionary ExtractPathMap(ZipArchiveEntry pathMap using Stream stream = pathMapEntry.Open(); // Load the mapping with all the original file paths for the included assemblies - return JsonSerializer.Deserialize(stream, ProjectionGeneratorJsonSerializerContext.Default.DictionaryStringString)!; + return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; } } diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index 019a2077eb..6d9425ba07 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -5,7 +5,7 @@ using System.IO; using System.Threading; using ConsoleAppFramework; -using WindowsRuntime.InteropGenerator; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs index 62294ece31..ebc32a13b7 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs @@ -6,8 +6,8 @@ using System.IO; using System.Reflection; using System.Threading; -using WindowsRuntime.InteropGenerator; -using WindowsRuntime.ProjectionGenerator.Attributes; +using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Attributes; using WindowsRuntime.ProjectionGenerator.Errors; #pragma warning disable IDE0046 diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs index 054ddea620..173110eacc 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Threading; -using WindowsRuntime.ProjectionGenerator.Attributes; +using WindowsRuntime.GeneratorCli.Attributes; namespace WindowsRuntime.ProjectionGenerator.Generation; diff --git a/src/WinRT.Projection.Generator/Helpers/ProjectionGeneratorJsonSerializerContext.cs b/src/WinRT.Projection.Generator/Helpers/ProjectionGeneratorJsonSerializerContext.cs deleted file mode 100644 index a8c089657e..0000000000 --- a/src/WinRT.Projection.Generator/Helpers/ProjectionGeneratorJsonSerializerContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace WindowsRuntime.ProjectionGenerator.Helpers; - -/// -/// A for types used in the projection generator. -/// -[JsonSerializable(typeof(Dictionary))] -[JsonSourceGenerationOptions(WriteIndented = true)] -internal sealed partial class ProjectionGeneratorJsonSerializerContext : JsonSerializerContext; diff --git a/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj b/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj index a9f22755d4..499db8b939 100644 --- a/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj +++ b/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj @@ -54,11 +54,7 @@ - - - - - + diff --git a/src/WinRT.Projection.Ref.Generator/Attributes/CommandLineArgumentNameAttribute.cs b/src/WinRT.Projection.Ref.Generator/Attributes/CommandLineArgumentNameAttribute.cs deleted file mode 100644 index d6f296dfcc..0000000000 --- a/src/WinRT.Projection.Ref.Generator/Attributes/CommandLineArgumentNameAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; - -namespace WindowsRuntime.ReferenceProjectionGenerator.Attributes; - -/// -/// An attribute indicating the name of a given command line argument. -/// -/// The command line argument name. -[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] -internal sealed class CommandLineArgumentNameAttribute(string name) : Attribute -{ - /// - /// Gets the command line argument name. - /// - public string Name { get; } = name; -} diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs index d3d507f95d..e6d075ebac 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs @@ -10,10 +10,10 @@ using System.Text; using System.Text.Json; using System.Threading; -using WindowsRuntime.InteropGenerator; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ReferenceProjectionGenerator.Errors; -using WindowsRuntime.ReferenceProjectionGenerator.Helpers; +using WindowsRuntime.GeneratorCli.Helpers; #pragma warning disable IDE0008 @@ -294,7 +294,7 @@ private static void CopyPathMapToDirectory( using Stream jsonStream = File.Create(jsonFilePath); // Serialize the path map to the target file - JsonSerializer.Serialize(jsonStream, pathMap, ReferenceProjectionGeneratorJsonSerializerContext.Default.DictionaryStringString); + JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); } /// @@ -309,6 +309,6 @@ private static Dictionary ExtractPathMap(ZipArchiveEntry pathMap using Stream stream = pathMapEntry.Open(); // Load the mapping with all the original file paths for the included files - return JsonSerializer.Deserialize(stream, ReferenceProjectionGeneratorJsonSerializerContext.Default.DictionaryStringString)!; + return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; } } diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index 5156adf288..5e99b1c951 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -6,7 +6,7 @@ using System.IO; using System.Threading; using ConsoleAppFramework; -using WindowsRuntime.InteropGenerator; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.ProjectionWriter; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ReferenceProjectionGenerator.Errors; diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs index 358b513b3b..003f296261 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs @@ -6,8 +6,8 @@ using System.IO; using System.Reflection; using System.Threading; -using WindowsRuntime.InteropGenerator; -using WindowsRuntime.ReferenceProjectionGenerator.Attributes; +using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Attributes; using WindowsRuntime.ReferenceProjectionGenerator.Errors; #pragma warning disable IDE0046 diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs index 53f6dedea8..5bd0696498 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Threading; -using WindowsRuntime.ReferenceProjectionGenerator.Attributes; +using WindowsRuntime.GeneratorCli.Attributes; namespace WindowsRuntime.ReferenceProjectionGenerator.Generation; diff --git a/src/WinRT.Projection.Ref.Generator/Helpers/ReferenceProjectionGeneratorJsonSerializerContext.cs b/src/WinRT.Projection.Ref.Generator/Helpers/ReferenceProjectionGeneratorJsonSerializerContext.cs deleted file mode 100644 index 768cd91ab4..0000000000 --- a/src/WinRT.Projection.Ref.Generator/Helpers/ReferenceProjectionGeneratorJsonSerializerContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace WindowsRuntime.ReferenceProjectionGenerator.Helpers; - -/// -/// A for types used in the reference projection generator. -/// -[JsonSerializable(typeof(Dictionary))] -[JsonSourceGenerationOptions(WriteIndented = true)] -internal sealed partial class ReferenceProjectionGeneratorJsonSerializerContext : JsonSerializerContext; diff --git a/src/WinRT.Projection.Ref.Generator/WinRT.Projection.Ref.Generator.csproj b/src/WinRT.Projection.Ref.Generator/WinRT.Projection.Ref.Generator.csproj index ad2bd77ed0..7cca4ba52e 100644 --- a/src/WinRT.Projection.Ref.Generator/WinRT.Projection.Ref.Generator.csproj +++ b/src/WinRT.Projection.Ref.Generator/WinRT.Projection.Ref.Generator.csproj @@ -51,11 +51,7 @@ - - - - - + diff --git a/src/WinRT.WinMD.Generator/Attributes/CommandLineArgumentNameAttribute.cs b/src/WinRT.WinMD.Generator/Attributes/CommandLineArgumentNameAttribute.cs deleted file mode 100644 index 78ff10de64..0000000000 --- a/src/WinRT.WinMD.Generator/Attributes/CommandLineArgumentNameAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; - -namespace WindowsRuntime.WinMDGenerator.Attributes; - -/// -/// An attribute indicating the name of a given command line argument. -/// -/// The command line argument name. -[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] -internal sealed class CommandLineArgumentNameAttribute(string name) : Attribute -{ - /// - /// Gets the command line argument name. - /// - public string Name { get; } = name; -} \ No newline at end of file diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs index d7e1814d1a..c4d2fee556 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs @@ -11,9 +11,9 @@ using System.Text; using System.Text.Json; using System.Threading; -using WindowsRuntime.InteropGenerator; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.WinMDGenerator.Errors; -using WindowsRuntime.WinMDGenerator.Helpers; +using WindowsRuntime.GeneratorCli.Helpers; #pragma warning disable IDE0008 @@ -326,7 +326,7 @@ private static void CopyPathMapToDirectory( using Stream jsonStream = File.Create(jsonFilePath); // Serialize the path map to the target file - JsonSerializer.Serialize(jsonStream, pathMap, WinMDGeneratorJsonSerializerContext.Default.DictionaryStringString); + JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); } /// @@ -341,6 +341,6 @@ private static Dictionary ExtractPathMap(ZipArchiveEntry pathMap using Stream stream = pathMapEntry.Open(); // Load the mapping with all the original file paths for the included assemblies - return JsonSerializer.Deserialize(stream, WinMDGeneratorJsonSerializerContext.Default.DictionaryStringString)!; + return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; } } diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs index 875770bde0..0ef8a58c56 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs @@ -5,7 +5,7 @@ using System.IO; using System.Threading; using ConsoleAppFramework; -using WindowsRuntime.InteropGenerator; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.WinMDGenerator.Errors; namespace WindowsRuntime.WinMDGenerator.Generation; diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs index 8014d5195c..8190474e69 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs @@ -6,8 +6,8 @@ using System.IO; using System.Reflection; using System.Threading; -using WindowsRuntime.InteropGenerator; -using WindowsRuntime.WinMDGenerator.Attributes; +using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Attributes; using WindowsRuntime.WinMDGenerator.Errors; #pragma warning disable IDE0046 diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs index c686f116f0..7f6c96ad70 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System.Threading; -using WindowsRuntime.WinMDGenerator.Attributes; +using WindowsRuntime.GeneratorCli.Attributes; namespace WindowsRuntime.WinMDGenerator.Generation; diff --git a/src/WinRT.WinMD.Generator/WinRT.WinMD.Generator.csproj b/src/WinRT.WinMD.Generator/WinRT.WinMD.Generator.csproj index 0d13c5831c..812b791760 100644 --- a/src/WinRT.WinMD.Generator/WinRT.WinMD.Generator.csproj +++ b/src/WinRT.WinMD.Generator/WinRT.WinMD.Generator.csproj @@ -56,8 +56,7 @@ - - + diff --git a/src/cswinrt.slnx b/src/cswinrt.slnx index e62de46e37..fda1706ba4 100644 --- a/src/cswinrt.slnx +++ b/src/cswinrt.slnx @@ -416,6 +416,7 @@ + From 5435ebd4b89e7b9f40c5d9ad47e24577446f64bc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 18:20:06 -0700 Subject: [PATCH 03/28] Phase 2: extract shared error contract foundation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the shared error contract that all five CsWinRT CLI generators inherit from, into 'WinRT.Generator.Cli/Errors/': - 'WellKnownGeneratorException' (D1): abstract base capturing the 'Id' field plus the simple-form 'ToString()' that all four non-Interop tools were duplicating verbatim. - 'UnhandledGeneratorException' (D2): abstract base capturing the phase field plus the standardized error-message 'ToString()'. Per-tool subclasses provide 'ErrorPrefix' and 'GeneratorDescription' (e.g. 'impl generator', 'WinMD generator'). 'QuotePhaseInMessage' is virtual and defaults to false; the interop generator overrides it to true to preserve its historical single-quoted phase formatting. - 'GeneratorExceptionExtensions.IsWellKnown' (D3): the shared cancellation/well-known-base predicate. Per-tool 'IsWellKnown' extensions are deleted. Each per-tool 'WellKnownException' shrinks to a primary-constructor sealed subclass. Interop's 'WellKnownInteropException' keeps its rich superset locally ('_outerException' field, custom 'ToString' that adds inner/outer context, 'ThrowOrAttach' with '[StackTraceHidden]' + '[DoesNotReturn]') — it just delegates the 'Id' + ctor wiring to the shared base. Each per-tool 'UnhandledException' shrinks to a primary-constructor sealed subclass overriding the 2-3 abstract/virtual members. The standardized 'ToString()' output is bit-identical to the previous per-tool implementations (verified). Each consumer that uses '.IsWellKnown' gains a 'using WindowsRuntime.GeneratorCli.Errors;' import; the per-tool '*ExceptionExtensions.cs' files (5) are deleted. Validated end-to-end with debug-repro round-trips for all 5 generators: ref-gen 8 files 0 diffs, WinMD 5632 bytes (16-byte MVID-only delta), projection 327 files 0 diffs, impl 257536 bytes 0 diffs, interop smoke-tested. All 5 build with 0 warnings in Release and AOT-publish cleanly for win-arm64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Errors/GeneratorExceptionExtensions.cs} | 11 ++-- .../Errors/UnhandledGeneratorException.cs | 54 +++++++++++++++++++ .../Errors/WellKnownGeneratorException.cs | 35 ++++++++++++ .../Errors/UnhandledImplException.cs | 33 +++--------- .../Errors/WellKnownImplException.cs | 33 ++---------- .../Generation/ImplGenerator.cs | 3 +- .../WindowsRuntimeTypeHierarchyBuilder.cs | 1 + .../Errors/UnhandledInteropException.cs | 34 ++++-------- .../Errors/WellKnownInteropException.cs | 29 ++++------ .../Extensions/InteropExceptionExtensions.cs | 21 -------- .../Generation/InteropGenerator.Discover.cs | 1 + .../Generation/InteropGenerator.Emit.cs | 1 + .../Generation/InteropGenerator.cs | 1 + .../UnhandledProjectionGeneratorException.cs | 32 +++-------- .../WellKnownProjectionGeneratorException.cs | 33 ++---------- .../ProjectionGeneratorExceptionExtensions.cs | 21 -------- .../Generation/ProjectionGenerator.Emit.cs | 1 + .../ProjectionGenerator.Generate.cs | 1 + .../Generation/ProjectionGenerator.cs | 1 + ...edReferenceProjectionGeneratorException.cs | 32 +++-------- ...wnReferenceProjectionGeneratorException.cs | 31 ++--------- ...eProjectionGeneratorExceptionExtensions.cs | 21 -------- .../ReferenceProjectionGenerator.cs | 1 + .../Errors/UnhandledWinMDException.cs | 33 +++--------- .../Errors/WellKnownWinMDException.cs | 31 ++--------- .../Extensions/WinMDExceptionExtensions.cs | 21 -------- .../Generation/WinMDGenerator.cs | 1 + 27 files changed, 175 insertions(+), 342 deletions(-) rename src/{WinRT.Impl.Generator/Extensions/ImplExceptionExtensions.cs => WinRT.Generator.Cli/Errors/GeneratorExceptionExtensions.cs} (65%) create mode 100644 src/WinRT.Generator.Cli/Errors/UnhandledGeneratorException.cs create mode 100644 src/WinRT.Generator.Cli/Errors/WellKnownGeneratorException.cs delete mode 100644 src/WinRT.Interop.Generator/Extensions/InteropExceptionExtensions.cs delete mode 100644 src/WinRT.Projection.Generator/Extensions/ProjectionGeneratorExceptionExtensions.cs delete mode 100644 src/WinRT.Projection.Ref.Generator/Extensions/ReferenceProjectionGeneratorExceptionExtensions.cs delete mode 100644 src/WinRT.WinMD.Generator/Extensions/WinMDExceptionExtensions.cs diff --git a/src/WinRT.Impl.Generator/Extensions/ImplExceptionExtensions.cs b/src/WinRT.Generator.Cli/Errors/GeneratorExceptionExtensions.cs similarity index 65% rename from src/WinRT.Impl.Generator/Extensions/ImplExceptionExtensions.cs rename to src/WinRT.Generator.Cli/Errors/GeneratorExceptionExtensions.cs index 9bf6d91b12..f2f645dd17 100644 --- a/src/WinRT.Impl.Generator/Extensions/ImplExceptionExtensions.cs +++ b/src/WinRT.Generator.Cli/Errors/GeneratorExceptionExtensions.cs @@ -2,20 +2,19 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.ImplGenerator.Errors; -namespace WindowsRuntime.ImplGenerator; +namespace WindowsRuntime.GeneratorCli.Errors; /// -/// Extensions for interop exceptions. +/// Shared extensions for CsWinRT CLI generator exceptions. /// -internal static class ImplExceptionExtensions +internal static class GeneratorExceptionExtensions { extension(Exception exception) { /// /// Gets a value indicating whether an exception is well known (and should therefore not be caught). /// - public bool IsWellKnown => exception is OperationCanceledException or WellKnownImplException; + public bool IsWellKnown => exception is OperationCanceledException or WellKnownGeneratorException; } -} \ No newline at end of file +} diff --git a/src/WinRT.Generator.Cli/Errors/UnhandledGeneratorException.cs b/src/WinRT.Generator.Cli/Errors/UnhandledGeneratorException.cs new file mode 100644 index 0000000000..a6f8a8656d --- /dev/null +++ b/src/WinRT.Generator.Cli/Errors/UnhandledGeneratorException.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace WindowsRuntime.GeneratorCli.Errors; + +/// +/// An unhandled exception for a CsWinRT CLI generator. +/// +/// +/// Each per-tool unhandled exception inherits from this type and provides its +/// and so that the standardized +/// message remains tool-specific. +/// defaults to ; the interop generator overrides it to +/// (its historical message wraps the phase name in single quotes). +/// +/// The phase that failed. +/// The inner exception. +internal abstract class UnhandledGeneratorException(string phase, Exception exception) + : Exception(null, exception) +{ + /// + /// Gets the error prefix for the per-tool exception ID (e.g. "CSWINRTIMPLGEN"). + /// + protected abstract string ErrorPrefix { get; } + + /// + /// Gets the description of the generator used in the standard message + /// (e.g. "impl generator", "interop generator", "WinMD generator"). + /// + protected abstract string GeneratorDescription { get; } + + /// + /// Gets a value indicating whether the phase name should be wrapped in single quotes in the message. + /// + /// + /// Defaults to . The interop generator historically wraps the phase name + /// in single quotes and overrides this to to preserve that behavior. + /// + protected virtual bool QuotePhaseInMessage => false; + + /// + public override string ToString() + { + string formattedPhase = QuotePhaseInMessage ? $"'{phase}'" : phase; + + return + $"""error {ErrorPrefix}9999: The CsWinRT {GeneratorDescription} failed with an unhandled exception """ + + $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the {formattedPhase} phase. This might be due to an invalid """ + + $"""configuration in the current project, but the generator should still correctly identify that and fail gracefully. Please open an """ + + $"""issue at https://github.com/microsoft/CsWinRT and provide a minimal repro, if possible."""; + } +} diff --git a/src/WinRT.Generator.Cli/Errors/WellKnownGeneratorException.cs b/src/WinRT.Generator.Cli/Errors/WellKnownGeneratorException.cs new file mode 100644 index 0000000000..c95874623c --- /dev/null +++ b/src/WinRT.Generator.Cli/Errors/WellKnownGeneratorException.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace WindowsRuntime.GeneratorCli.Errors; + +/// +/// A well-known exception for a CsWinRT CLI generator. +/// +/// +/// Each per-tool well-known exception inherits from this type and adds a marker +/// (e.g. WellKnownImplException) so the concrete runtime type remains tool-specific. +/// The base class provides the shared field and logic, +/// which matches the simple per-tool format error {Id}: {Message}[ Inner exception: ...]. +/// +/// The id of the exception (e.g. "CSWINRTIMPLGEN0001"). +/// The exception message. +/// The inner exception, if any. +internal abstract class WellKnownGeneratorException(string id, string message, Exception? innerException) + : Exception(message, innerException) +{ + /// + /// Gets the id of the exception (e.g. "CSWINRTIMPLGEN0001"). + /// + public string Id { get; } = id; + + /// + public override string ToString() + { + return InnerException is not null + ? $"""error {Id}: {Message} Inner exception: '{InnerException.GetType().Name}': '{InnerException.Message}'.""" + : $"""error {Id}: {Message}"""; + } +} diff --git a/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs b/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs index cd391f8b66..4180d465a9 100644 --- a/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs +++ b/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs @@ -2,37 +2,20 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.ImplGenerator.Errors; /// /// An unhandled exception for the impl generator. /// -internal sealed class UnhandledImplException : Exception +/// +internal sealed class UnhandledImplException(string phase, Exception exception) + : UnhandledGeneratorException(phase, exception) { - /// - /// The phase that failed. - /// - private readonly string _phase; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The phase that failed. - /// The inner exception. - public UnhandledImplException(string phase, Exception exception) - : base(null, exception) - { - _phase = phase; - } + /// + protected override string ErrorPrefix => WellKnownImplExceptions.ErrorPrefix; /// - public override string ToString() - { - return - $"""error {WellKnownImplExceptions.ErrorPrefix}9999: The CsWinRT impl generator failed with an unhandled exception """ + - $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the {_phase} phase. This might be due to an invalid """ + - $"""configuration in the current project, but the generator should still correctly identify that and fail gracefully. Please open an """ + - $"""issue at https://github.com/microsoft/CsWinRT and provide a minimal repro, if possible."""; - } -} \ No newline at end of file + protected override string GeneratorDescription => "impl generator"; +} diff --git a/src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs b/src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs index 7bd5e646e0..26211d7680 100644 --- a/src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs +++ b/src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs @@ -2,36 +2,13 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.ImplGenerator.Errors; /// -/// A well known exceptions for the interop generator. +/// A well known exception for the impl generator. /// -internal sealed class WellKnownImplException : Exception -{ - /// - /// Creates a new instance with the specified parameters. - /// - /// The id of the exception. - /// The exception message. - /// The inner exception. - public WellKnownImplException(string id, string message, Exception? innerException) - : base(message, innerException) - { - Id = id; - } - - /// - /// Gets the id of the exception. - /// - public string Id { get; } - - /// - public override string ToString() - { - return InnerException is not null - ? $"""error {Id}: {Message} Inner exception: '{InnerException.GetType().Name}': '{InnerException.Message}'.""" - : $"""error {Id}: {Message}"""; - } -} \ No newline at end of file +/// +internal sealed class WellKnownImplException(string id, string message, Exception? innerException) + : WellKnownGeneratorException(id, message, innerException); diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index 51890b01fb..41b30d4403 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -17,9 +17,10 @@ using AsmResolver.PE; using AsmResolver.PE.DotNet.StrongName; using ConsoleAppFramework; +using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.ImplGenerator.Errors; using WindowsRuntime.ImplGenerator.References; -using WindowsRuntime.GeneratorCli; namespace WindowsRuntime.ImplGenerator.Generation; diff --git a/src/WinRT.Interop.Generator/Builders/WindowsRuntimeTypeHierarchyBuilder.cs b/src/WinRT.Interop.Generator/Builders/WindowsRuntimeTypeHierarchyBuilder.cs index 26b86be38f..b8b8b156ff 100644 --- a/src/WinRT.Interop.Generator/Builders/WindowsRuntimeTypeHierarchyBuilder.cs +++ b/src/WinRT.Interop.Generator/Builders/WindowsRuntimeTypeHierarchyBuilder.cs @@ -15,6 +15,7 @@ using AsmResolver.PE.DotNet.Metadata.Tables; using CommunityToolkit.HighPerformance; using CommunityToolkit.HighPerformance.Buffers; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.References; using static AsmResolver.PE.DotNet.Cil.CilOpCodes; diff --git a/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs index b279abc9aa..42100e1489 100644 --- a/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs +++ b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs @@ -2,37 +2,23 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.InteropGenerator.Errors; /// /// An unhandled exception for the interop generator. /// -internal sealed class UnhandledInteropException : Exception +/// +internal sealed class UnhandledInteropException(string phase, Exception exception) + : UnhandledGeneratorException(phase, exception) { - /// - /// The phase that failed. - /// - private readonly string _phase; + /// + protected override string ErrorPrefix => WellKnownInteropExceptions.ErrorPrefix; - /// - /// Creates a new instance with the specified parameters. - /// - /// The phase that failed. - /// The inner exception. - public UnhandledInteropException(string phase, Exception exception) - : base(null, exception) - { - _phase = phase; - } + /// + protected override string GeneratorDescription => "interop generator"; /// - public override string ToString() - { - return - $"""error {WellKnownInteropExceptions.ErrorPrefix}9999: The CsWinRT interop generator failed with an unhandled exception """ + - $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the '{_phase}' phase. This might be due to an invalid """ + - $"""configuration in the current project, but the generator should still correctly identify that and fail gracefully. Please open an """ + - $"""issue at https://github.com/microsoft/CsWinRT and provide a minimal repro, if possible."""; - } -} \ No newline at end of file + protected override bool QuotePhaseInMessage => true; +} diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs index 2d7ea67300..0b51a63386 100644 --- a/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs +++ b/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs @@ -6,36 +6,27 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using System.Text; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.InteropGenerator.Errors; /// /// A well-known exception for the interop generator. /// -internal sealed class WellKnownInteropException : Exception +/// +/// The interop generator extends the shared base with an +/// optional outer exception that can be attached via when re-throwing, +/// plus a richer that surfaces both the inner and outer context when present. +/// +/// +internal sealed class WellKnownInteropException(string id, string message, Exception? innerException) + : WellKnownGeneratorException(id, message, innerException) { /// /// The outer exception to include in the output, if available. /// private WellKnownInteropException? _outerException; - /// - /// Creates a new instance with the specified parameters. - /// - /// The id of the exception. - /// The exception message. - /// The inner exception. - public WellKnownInteropException(string id, string message, Exception? innerException) - : base(message, innerException) - { - Id = id; - } - - /// - /// Gets the id of the exception. - /// - public string Id { get; } - /// public override string ToString() { @@ -95,4 +86,4 @@ public void ThrowOrAttach(Exception exception) // been used as the inner exception by callers, when constructing this instance. throw this; } -} \ No newline at end of file +} diff --git a/src/WinRT.Interop.Generator/Extensions/InteropExceptionExtensions.cs b/src/WinRT.Interop.Generator/Extensions/InteropExceptionExtensions.cs deleted file mode 100644 index 28bba1fec4..0000000000 --- a/src/WinRT.Interop.Generator/Extensions/InteropExceptionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using WindowsRuntime.InteropGenerator.Errors; - -namespace WindowsRuntime.InteropGenerator; - -/// -/// Extensions for interop exceptions. -/// -internal static class InteropExceptionExtensions -{ - extension(Exception exception) - { - /// - /// Gets a value indicating whether an exception is well known (and should therefore not be caught). - /// - public bool IsWellKnown => exception is OperationCanceledException or WellKnownInteropException; - } -} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index 8e05a5797c..1f8fb5870c 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -10,6 +10,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.PE; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.InteropGenerator.Discovery; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Models; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs index eef7fc19a8..95ac0a289f 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs @@ -9,6 +9,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.InteropGenerator.Builders; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Fixups; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs index 9e7179415c..8b403b83d0 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs @@ -6,6 +6,7 @@ using System.Threading; using ConsoleAppFramework; using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.References; diff --git a/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs b/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs index de0df58c60..2dd6341434 100644 --- a/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs @@ -2,37 +2,21 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.ProjectionGenerator.Errors; /// /// An unhandled exception for the projection generator. /// -internal sealed class UnhandledProjectionGeneratorException : Exception +/// +internal sealed class UnhandledProjectionGeneratorException(string phase, Exception exception) + : UnhandledGeneratorException(phase, exception) { - /// - /// The phase that failed. - /// - private readonly string _phase; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The phase that failed. - /// The inner exception. - public UnhandledProjectionGeneratorException(string phase, Exception exception) - : base(null, exception) - { - _phase = phase; - } + /// + protected override string ErrorPrefix => WellKnownProjectionGeneratorExceptions.ErrorPrefix; /// - public override string ToString() - { - return - $"""error {WellKnownProjectionGeneratorExceptions.ErrorPrefix}9999: The CsWinRT projection generator failed with an unhandled exception """ + - $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the {_phase} phase. This might be due to an invalid """ + - $"""configuration in the current project, but the generator should still correctly identify that and fail gracefully. Please open an """ + - $"""issue at https://github.com/microsoft/CsWinRT and provide a minimal repro, if possible."""; - } + protected override string GeneratorDescription => "projection generator"; } + diff --git a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorException.cs b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorException.cs index 3b9bc0b690..08f2b34396 100644 --- a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorException.cs @@ -2,36 +2,13 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.ProjectionGenerator.Errors; /// -/// A well known exceptions for the projection generator. +/// A well known exception for the projection generator. /// -internal sealed class WellKnownProjectionGeneratorException : Exception -{ - /// - /// Creates a new instance with the specified parameters. - /// - /// The id of the exception. - /// The exception message. - /// The inner exception. - public WellKnownProjectionGeneratorException(string id, string message, Exception? innerException) - : base(message, innerException) - { - Id = id; - } - - /// - /// Gets the id of the exception. - /// - public string Id { get; } - - /// - public override string ToString() - { - return InnerException is not null - ? $"""error {Id}: {Message} Inner exception: '{InnerException.GetType().Name}': '{InnerException.Message}'.""" - : $"""error {Id}: {Message}"""; - } -} +/// +internal sealed class WellKnownProjectionGeneratorException(string id, string message, Exception? innerException) + : WellKnownGeneratorException(id, message, innerException); diff --git a/src/WinRT.Projection.Generator/Extensions/ProjectionGeneratorExceptionExtensions.cs b/src/WinRT.Projection.Generator/Extensions/ProjectionGeneratorExceptionExtensions.cs deleted file mode 100644 index 143693992b..0000000000 --- a/src/WinRT.Projection.Generator/Extensions/ProjectionGeneratorExceptionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using WindowsRuntime.ProjectionGenerator.Errors; - -namespace WindowsRuntime.ProjectionGenerator; - -/// -/// Extensions for projection generator exceptions. -/// -internal static class ProjectionGeneratorExceptionExtensions -{ - extension(Exception exception) - { - /// - /// Gets a value indicating whether an exception is well known (and should therefore not be caught). - /// - public bool IsWellKnown => exception is OperationCanceledException or WellKnownProjectionGeneratorException; - } -} diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs index 2743360763..a98697489e 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs index bd576152ac..7738b274ca 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs @@ -7,6 +7,7 @@ using System.Linq; using AsmResolver; using AsmResolver.DotNet; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.ProjectionGenerator.Errors; using WindowsRuntime.ProjectionWriter; using WindowsRuntime.ProjectionWriter.Helpers; diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index 6d9425ba07..04d3dff603 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -6,6 +6,7 @@ using System.Threading; using ConsoleAppFramework; using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; diff --git a/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs b/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs index 731e2a129b..a5d40ab0f8 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs @@ -2,37 +2,21 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.ReferenceProjectionGenerator.Errors; /// /// An unhandled exception for the reference projection generator. /// -internal sealed class UnhandledReferenceProjectionGeneratorException : Exception +/// +internal sealed class UnhandledReferenceProjectionGeneratorException(string phase, Exception exception) + : UnhandledGeneratorException(phase, exception) { - /// - /// The phase that failed. - /// - private readonly string _phase; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The phase that failed. - /// The inner exception. - public UnhandledReferenceProjectionGeneratorException(string phase, Exception exception) - : base(null, exception) - { - _phase = phase; - } + /// + protected override string ErrorPrefix => WellKnownReferenceProjectionGeneratorExceptions.ErrorPrefix; /// - public override string ToString() - { - return - $"""error {WellKnownReferenceProjectionGeneratorExceptions.ErrorPrefix}9999: The CsWinRT reference projection generator failed with an unhandled exception """ + - $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the {_phase} phase. This might be due to an invalid """ + - $"""configuration in the current project, but the generator should still correctly identify that and fail gracefully. Please open an """ + - $"""issue at https://github.com/microsoft/CsWinRT and provide a minimal repro, if possible."""; - } + protected override string GeneratorDescription => "reference projection generator"; } + diff --git a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorException.cs b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorException.cs index 72f93c8799..d0a185b9d3 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorException.cs @@ -2,36 +2,13 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.ReferenceProjectionGenerator.Errors; /// /// A well known exception for the reference projection generator. /// -internal sealed class WellKnownReferenceProjectionGeneratorException : Exception -{ - /// - /// Creates a new instance with the specified parameters. - /// - /// The id of the exception. - /// The exception message. - /// The inner exception. - public WellKnownReferenceProjectionGeneratorException(string id, string message, Exception? innerException) - : base(message, innerException) - { - Id = id; - } - - /// - /// Gets the id of the exception. - /// - public string Id { get; } - - /// - public override string ToString() - { - return InnerException is not null - ? $"""error {Id}: {Message} Inner exception: '{InnerException.GetType().Name}': '{InnerException.Message}'.""" - : $"""error {Id}: {Message}"""; - } -} +/// +internal sealed class WellKnownReferenceProjectionGeneratorException(string id, string message, Exception? innerException) + : WellKnownGeneratorException(id, message, innerException); diff --git a/src/WinRT.Projection.Ref.Generator/Extensions/ReferenceProjectionGeneratorExceptionExtensions.cs b/src/WinRT.Projection.Ref.Generator/Extensions/ReferenceProjectionGeneratorExceptionExtensions.cs deleted file mode 100644 index a0a981c6c3..0000000000 --- a/src/WinRT.Projection.Ref.Generator/Extensions/ReferenceProjectionGeneratorExceptionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using WindowsRuntime.ReferenceProjectionGenerator.Errors; - -namespace WindowsRuntime.ReferenceProjectionGenerator; - -/// -/// Extensions for reference projection generator exceptions. -/// -internal static class ReferenceProjectionGeneratorExceptionExtensions -{ - extension(Exception exception) - { - /// - /// Gets a value indicating whether an exception is well known (and should therefore not be caught). - /// - public bool IsWellKnown => exception is OperationCanceledException or WellKnownReferenceProjectionGeneratorException; - } -} diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index 5e99b1c951..6c29efd1f0 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -7,6 +7,7 @@ using System.Threading; using ConsoleAppFramework; using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.ProjectionWriter; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ReferenceProjectionGenerator.Errors; diff --git a/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs b/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs index 96e3700bd3..be79a022cc 100644 --- a/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs +++ b/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs @@ -2,37 +2,20 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.WinMDGenerator.Errors; /// /// An unhandled exception for the WinMD generator. /// -internal sealed class UnhandledWinMDException : Exception +/// +internal sealed class UnhandledWinMDException(string phase, Exception exception) + : UnhandledGeneratorException(phase, exception) { - /// - /// The phase that failed. - /// - private readonly string _phase; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The phase that failed. - /// The inner exception. - public UnhandledWinMDException(string phase, Exception exception) - : base(null, exception) - { - _phase = phase; - } + /// + protected override string ErrorPrefix => WellKnownWinMDExceptions.ErrorPrefix; /// - public override string ToString() - { - return - $"""error {WellKnownWinMDExceptions.ErrorPrefix}9999: The CsWinRT WinMD generator failed with an unhandled exception """ + - $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the {_phase} phase. This might be due to an invalid """ + - $"""configuration in the current project, but the generator should still correctly identify that and fail gracefully. Please open an """ + - $"""issue at https://github.com/microsoft/CsWinRT and provide a minimal repro, if possible."""; - } -} \ No newline at end of file + protected override string GeneratorDescription => "WinMD generator"; +} diff --git a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs index 16ba08edcc..8dd0f0552f 100644 --- a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs +++ b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs @@ -2,36 +2,13 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.WinMDGenerator.Errors; /// /// A well known exception for the WinMD generator. /// -internal sealed class WellKnownWinMDException : Exception -{ - /// - /// Creates a new instance with the specified parameters. - /// - /// The id of the exception. - /// The exception message. - /// The inner exception. - public WellKnownWinMDException(string id, string message, Exception? innerException) - : base(message, innerException) - { - Id = id; - } - - /// - /// Gets the id of the exception. - /// - public string Id { get; } - - /// - public override string ToString() - { - return InnerException is not null - ? $"""error {Id}: {Message} Inner exception: '{InnerException.GetType().Name}': '{InnerException.Message}'.""" - : $"""error {Id}: {Message}"""; - } -} \ No newline at end of file +/// +internal sealed class WellKnownWinMDException(string id, string message, Exception? innerException) + : WellKnownGeneratorException(id, message, innerException); diff --git a/src/WinRT.WinMD.Generator/Extensions/WinMDExceptionExtensions.cs b/src/WinRT.WinMD.Generator/Extensions/WinMDExceptionExtensions.cs deleted file mode 100644 index c376c49110..0000000000 --- a/src/WinRT.WinMD.Generator/Extensions/WinMDExceptionExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using WindowsRuntime.WinMDGenerator.Errors; - -namespace WindowsRuntime.WinMDGenerator; - -/// -/// Extensions for WinMD generator exceptions. -/// -internal static class WinMDExceptionExtensions -{ - extension(Exception exception) - { - /// - /// Gets a value indicating whether an exception is well known (and should therefore not be caught). - /// - public bool IsWellKnown => exception is OperationCanceledException or WellKnownWinMDException; - } -} \ No newline at end of file diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs index 0ef8a58c56..42366e7ad4 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs @@ -6,6 +6,7 @@ using System.Threading; using ConsoleAppFramework; using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.WinMDGenerator.Errors; namespace WindowsRuntime.WinMDGenerator.Generation; From 4f58720df2bb4b955c0e19d7ed6e33c7de1fe1cc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 18:28:25 -0700 Subject: [PATCH 04/28] Phase 3: introduce IGeneratorErrorFactory shared error contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 'IGeneratorErrorFactory' (a 'static abstract' interface in 'WinRT.Generator.Cli/Errors/') with the six logical errors that every CLI generator throws today through its 'WellKnown*Exceptions' factory: - 'ResponseFileReadError(Exception)' - 'ResponseFileArgumentParsingError(string, Exception?)' - 'MalformedResponseFile()' - 'DebugReproDirectoryDoesNotExist(string)' - 'DebugReproMissingFileEntryMapping(string)' - 'DebugReproUnrecognizedFileEntry(string)' All return 'Exception', so the per-tool numeric ID, message text, and concrete 'WellKnown*Exception' subtype remain the sole responsibility of each tool's factory. This is the contract that Phase 4 (response-file plumbing) and Phase 5 (debug-repro helpers) will dispatch through, so shared code can throw via the right per-tool factory without ever knowing the IDs or message strings. Each per-tool 'WellKnown*Exceptions' shifts from 'internal static class' to 'internal sealed class' implementing 'IGeneratorErrorFactory', with a private constructor to prevent instantiation. Every factory body, error ID, message text, and concrete exception type is preserved verbatim — including the WinMD swap of 'MalformedResponseFile' (id 2) and 'ResponseFileArgumentParsingError' (id 3) versus the other four tools, and the embedded per-tool exe names in 'ResponseFileReadError'. Interop is special: its public static factories return 'WellKnownInteropException' (typed, so callers chain '.ThrowOrAttach(...)'). C# 'static abstract' interface methods don't support return-type covariance, so Interop uses explicit interface implementations that forward through the typed publics — preserving both call shapes. Validated end-to-end: ref-gen 8 files 0 diffs, WinMD 5632 bytes (16-byte MVID-only), projection 327 files 0 diffs, impl 257536 bytes 0 diffs, interop smoke-tested. All 5 build clean (0 warnings) in Release and AOT-publish cleanly for win-arm64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Errors/IGeneratorErrorFactory.cs | 44 +++++++++++++++++++ .../Errors/WellKnownImplExceptions.cs | 12 ++++- .../Errors/WellKnownInteropExceptions.cs | 44 ++++++++++++++++++- .../WellKnownProjectionGeneratorExceptions.cs | 10 ++++- ...nReferenceProjectionGeneratorExceptions.cs | 10 ++++- .../Errors/WellKnownWinMDExceptions.cs | 10 ++++- 6 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/WinRT.Generator.Cli/Errors/IGeneratorErrorFactory.cs diff --git a/src/WinRT.Generator.Cli/Errors/IGeneratorErrorFactory.cs b/src/WinRT.Generator.Cli/Errors/IGeneratorErrorFactory.cs new file mode 100644 index 0000000000..cb0d29cd92 --- /dev/null +++ b/src/WinRT.Generator.Cli/Errors/IGeneratorErrorFactory.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; + +namespace WindowsRuntime.GeneratorCli.Errors; + +/// +/// Routes shared logical errors through the per-tool well-known exception factory. +/// +/// +/// +/// Shared infrastructure (response-file parsing, debug-repro packing, etc.) is generic over an +/// implementation of this interface and reaches the per-tool factory through its static abstract +/// members. This preserves per-tool exception identity exactly: each factory continues to assign its +/// own numeric error IDs, format its own messages (including embedded tool names), and construct +/// its own concrete subtype. +/// +/// +/// Implementations must be sealed (not static) so they can participate in the +/// static abstract interface contract. The implementing type is not meant to be instantiated; +/// it is used as a type parameter to dispatch the factory call. +/// +/// +internal interface IGeneratorErrorFactory +{ + /// Some exception was thrown when trying to read the response file. + static abstract Exception ResponseFileReadError(Exception exception); + + /// Failed to parse an argument from the response file. + static abstract Exception ResponseFileArgumentParsingError(string argumentName, Exception? exception); + + /// The input response file is malformed. + static abstract Exception MalformedResponseFile(); + + /// The debug repro directory does not exist. + static abstract Exception DebugReproDirectoryDoesNotExist(string path); + + /// The debug repro contains a file entry that has no mapping. + static abstract Exception DebugReproMissingFileEntryMapping(string path); + + /// The debug repro contains a file entry that was not recognized. + static abstract Exception DebugReproUnrecognizedFileEntry(string path); +} diff --git a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs index 7b669deed5..9b9e8a3afd 100644 --- a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs +++ b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs @@ -2,19 +2,27 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.ImplGenerator.Errors; /// -/// Well known exceptions for the interop generator. +/// Well known exceptions for the impl generator. /// -internal static class WellKnownImplExceptions +internal sealed class WellKnownImplExceptions : IGeneratorErrorFactory { /// /// The prefix for all errors produced by this tool. /// public const string ErrorPrefix = "CSWINRTIMPLGEN"; + /// + /// Prevents external instantiation; this type is only used to dispatch through . + /// + private WellKnownImplExceptions() + { + } + /// /// Some exception was thrown when trying to read the response file. /// diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs index cb47d56868..c44251b95f 100644 --- a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs +++ b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs @@ -8,6 +8,7 @@ using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Cil; +using WindowsRuntime.GeneratorCli.Errors; using WindowsRuntime.InteropGenerator.Fixups; namespace WindowsRuntime.InteropGenerator.Errors; @@ -15,13 +16,54 @@ namespace WindowsRuntime.InteropGenerator.Errors; /// /// Well known exceptions for the interop generator. /// -internal static class WellKnownInteropExceptions +internal sealed class WellKnownInteropExceptions : IGeneratorErrorFactory { /// /// The prefix for all errors produced by this tool. /// public const string ErrorPrefix = "CSWINRTINTEROPGEN"; + /// + /// Prevents external instantiation; this type is only used to dispatch through . + /// + private WellKnownInteropExceptions() + { + } + + // Explicit IGeneratorErrorFactory implementations: the public static methods for these six + // shared logical errors return 'WellKnownInteropException' (so callers can chain '.ThrowOrAttach(...)'), + // but the interface contract returns 'Exception'. C# 'static abstract' interface methods don't + // support return-type covariance, so we forward through these tiny explicit shims. + static Exception IGeneratorErrorFactory.ResponseFileReadError(Exception exception) + { + return ResponseFileReadError(exception); + } + + static Exception IGeneratorErrorFactory.ResponseFileArgumentParsingError(string argumentName, Exception? exception) + { + return ResponseFileArgumentParsingError(argumentName, exception); + } + + static Exception IGeneratorErrorFactory.MalformedResponseFile() + { + return MalformedResponseFile(); + } + + static Exception IGeneratorErrorFactory.DebugReproDirectoryDoesNotExist(string path) + { + return DebugReproDirectoryDoesNotExist(path); + } + + static Exception IGeneratorErrorFactory.DebugReproMissingFileEntryMapping(string path) + { + return DebugReproMissingFileEntryMapping(path); + } + + static Exception IGeneratorErrorFactory.DebugReproUnrecognizedFileEntry(string path) + { + return DebugReproUnrecognizedFileEntry(path); + } + /// /// A runtime class name is too long. /// diff --git a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs index 9358b975c1..6a7f49e3e2 100644 --- a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs +++ b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs @@ -5,19 +5,27 @@ using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.ProjectionGenerator.Errors; /// /// Well known exceptions for the projection generator. /// -internal static class WellKnownProjectionGeneratorExceptions +internal sealed class WellKnownProjectionGeneratorExceptions : IGeneratorErrorFactory { /// /// The prefix for all errors produced by this tool. /// public const string ErrorPrefix = "CSWINRTPROJECTIONGEN"; + /// + /// Prevents external instantiation; this type is only used to dispatch through . + /// + private WellKnownProjectionGeneratorExceptions() + { + } + /// /// Some exception was thrown when trying to read the response file. /// diff --git a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs index 0d9dff9399..148ecd4892 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs @@ -2,19 +2,27 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.ReferenceProjectionGenerator.Errors; /// /// Well known exceptions for the reference projection generator. /// -internal static class WellKnownReferenceProjectionGeneratorExceptions +internal sealed class WellKnownReferenceProjectionGeneratorExceptions : IGeneratorErrorFactory { /// /// The prefix for all errors produced by this tool. /// public const string ErrorPrefix = "CSWINRTPROJECTIONREFGEN"; + /// + /// Prevents external instantiation; this type is only used to dispatch through . + /// + private WellKnownReferenceProjectionGeneratorExceptions() + { + } + /// /// Some exception was thrown when trying to read the response file. /// diff --git a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs index 83766d01cb..b155bda160 100644 --- a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs +++ b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs @@ -2,19 +2,27 @@ // Licensed under the MIT License. using System; +using WindowsRuntime.GeneratorCli.Errors; namespace WindowsRuntime.WinMDGenerator.Errors; /// /// Well-known exceptions for the WinMD generator. /// -internal static class WellKnownWinMDExceptions +internal sealed class WellKnownWinMDExceptions : IGeneratorErrorFactory { /// /// The prefix for all errors produced by this tool. /// public const string ErrorPrefix = "CSWINRTWINMDGEN"; + /// + /// Prevents external instantiation; this type is only used to dispatch through . + /// + private WellKnownWinMDExceptions() + { + } + /// /// Some exception was thrown when trying to read the response file. /// From a6a873da78b815fce0e9a2edb52fd825d092f8f9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 18:42:32 -0700 Subject: [PATCH 05/28] Phase 4: extract response-file parsing and formatting via reflection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 'ResponseFileParser' and 'ResponseFileBuilder' in 'WinRT.Generator.Cli/Parsing/', collapsing four candidates (B1 parsing core, B2 typed Get* helpers, B3 reflection lookup, C1 formatting) into one reflection-based mechanism that walks 'Type.GetProperties()' once per call. Per-property handling: - '[CommandLineArgumentName("--xxx")]' → CLI flag name. - 'required' modifier (via 'RequiredMemberAttribute') → throw 'TErr.ResponseFileArgumentParsingError(prop.Name)' if missing or parse fails. - 'string?' (via 'NullabilityInfoContext') → null on miss. - '[DefaultValue("…")]' (only on 'ProjectionGeneratorArgs.AssemblyName') → applied when missing. - 'CancellationToken'-typed property → set from parser's token argument. - 'Convert.ChangeType(string, propertyType, InvariantCulture)' for primitives ('bool', 'int'). - 'string[]' → 'Split(',', RemoveEmptyEntries | TrimEntries)'. The parser uses 'RuntimeHelpers.GetUninitializedObject' to bypass the runtime 'RequiredMemberAttribute' enforcement at construction time, then populates each public property via 'PropertyInfo.SetValue'. All AOT-safe under '[DynamicallyAccessedMembers(PublicProperties)]' on 'TArgs'. Each per-tool 'XGeneratorArgs.cs' loses its 'partial' modifier and gains three thin forwarding wrappers ('ParseFromResponseFile(string|Stream, CancellationToken)' and 'FormatToResponseFile()') so call sites are unchanged. The corresponding '*Args.Parsing.cs' and '*Args.Formatting.cs' partial files (10 files total across the 5 generators) are deleted entirely. All exceptions continue to route through the per-tool 'WellKnown*Exceptions' factory via 'TErr : IGeneratorErrorFactory', so error IDs (including WinMD's '2'/'3' swap), messages (including embedded 'cswinrtimplgen' etc. tool names), and concrete exception types are preserved bit-identically. Optional 'bool' / 'int' / 'string[]' parse failures silently fall back to defaults (matching the old 'GetOptional*' behavior); required parse failures throw (matching the old 'GetBooleanArgument' / 'GetInt32Argument' / 'GetStringArrayArgument' behavior). Validated end-to-end with debug-repro round-trips for all 5 generators: ref-gen 8/8 files 0 diffs, WinMD 5632/5632 bytes 16-byte (MVID-only) delta, projection 327/327 files 0 diffs, impl 257536/257536 bytes 0 byte diffs, interop smoke-tested. All 5 build with 0 warnings in Release and AOT-publish cleanly for win-arm64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Parsing/ResponseFileBuilder.cs | 124 +++++++ .../Parsing/ResponseFileParser.cs | 335 ++++++++++++++++++ .../ImplGeneratorArgs.Formatting.cs | 51 --- .../Generation/ImplGeneratorArgs.Parsing.cs | 200 ----------- .../Generation/ImplGeneratorArgs.cs | 38 +- .../InteropGeneratorArgs.Formatting.cs | 93 ----- .../InteropGeneratorArgs.Parsing.cs | 228 ------------ .../Generation/InteropGeneratorArgs.cs | 38 +- .../ProjectionGeneratorArgs.Formatting.cs | 70 ---- .../ProjectionGeneratorArgs.Parsing.cs | 237 ------------- .../Generation/ProjectionGeneratorArgs.cs | 40 ++- ...renceProjectionGeneratorArgs.Formatting.cs | 96 ----- ...eferenceProjectionGeneratorArgs.Parsing.cs | 219 ------------ .../ReferenceProjectionGeneratorArgs.cs | 37 +- .../WinMDGeneratorArgs.Formatting.cs | 48 --- .../Generation/WinMDGeneratorArgs.Parsing.cs | 211 ----------- .../Generation/WinMDGeneratorArgs.cs | 38 +- 17 files changed, 641 insertions(+), 1462 deletions(-) create mode 100644 src/WinRT.Generator.Cli/Parsing/ResponseFileBuilder.cs create mode 100644 src/WinRT.Generator.Cli/Parsing/ResponseFileParser.cs delete mode 100644 src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Formatting.cs delete mode 100644 src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs delete mode 100644 src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Formatting.cs delete mode 100644 src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs delete mode 100644 src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Formatting.cs delete mode 100644 src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs delete mode 100644 src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Formatting.cs delete mode 100644 src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs delete mode 100644 src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Formatting.cs delete mode 100644 src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs diff --git a/src/WinRT.Generator.Cli/Parsing/ResponseFileBuilder.cs b/src/WinRT.Generator.Cli/Parsing/ResponseFileBuilder.cs new file mode 100644 index 0000000000..cb013f90ba --- /dev/null +++ b/src/WinRT.Generator.Cli/Parsing/ResponseFileBuilder.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using WindowsRuntime.GeneratorCli.Attributes; + +namespace WindowsRuntime.GeneratorCli.Parsing; + +/// +/// Formats an args record into a response file by reflecting over its +/// []-annotated public properties. +/// +/// +/// +/// Properties are emitted in declaration order (the order returned by ), +/// one name]]>value per line. The output round-trips cleanly through +/// . +/// +/// +/// Per-property handling: +/// +/// -typed properties are skipped (they have no CLI surface). +/// Properties without are skipped. +/// values are skipped (they round-trip as "missing"). +/// Empty [] values are skipped (they round-trip as "missing" +/// to the optional-array default, matching the previous per-tool emit behavior). +/// values that are on properties without +/// are skipped (they +/// round-trip to the optional-bool default, matching the previous per-tool emit behavior). +/// All other values are formatted using : +/// strings emit as-is, arrays emit as comma-joined, primitives use . +/// +/// +/// +internal static class ResponseFileBuilder +{ + /// The required dynamic-access annotation for the TArgs type parameter. + private const DynamicallyAccessedMemberTypes ArgsAccessKinds = + DynamicallyAccessedMemberTypes.PublicProperties; + + /// + /// Formats as a response file string suitable for + /// . + /// + /// The strongly-typed args record. + /// The args instance to format. + /// The formatted response file text. + public static string Format<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs>(TArgs args) + where TArgs : class + { + StringBuilder builder = new(); + + foreach (PropertyInfo property in typeof(TArgs).GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + CommandLineArgumentNameAttribute? cliAttribute = property.GetCustomAttribute(); + + if (cliAttribute is null) + { + continue; + } + + object? value = property.GetValue(args); + + // 'null' round-trips as "missing" (the parser will apply the property's default) + if (value is null) + { + continue; + } + + // Empty arrays round-trip as "missing" too: the parser defaults 'string[]' properties to + // an empty array. Emitting "name" for an empty array would also fail the parser's + // 'IndexOf(\' \') == -1 -> MalformedResponseFile' check (after a trailing-space trim). + if (value is ICollection { Count: 0 }) + { + continue; + } + + // Optional booleans default to 'false' on parse, so emitting "name false" would be + // redundant. Required booleans always emit (both 'true' and 'false') so the parser sees + // them and doesn't reject them as missing-required. + bool isRequired = property.GetCustomAttribute() is not null; + + if (value is false && !isRequired) + { + continue; + } + + _ = builder.Append(cliAttribute.Name); + _ = builder.Append(' '); + _ = builder.AppendLine(FormatValue(value)); + } + + return builder.ToString(); + } + + /// + /// Formats a single value for inclusion in a response file line. + /// + /// The non- value to format. + /// The formatted string representation. + private static string FormatValue(object value) + { + if (value is string s) + { + return s; + } + + if (value is string[] array) + { + return string.Join(',', array); + } + + // All other primitive values use invariant culture, matching the parser's invariant + // 'Convert.ChangeType' so the round-trip is deterministic. + return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; + } +} diff --git a/src/WinRT.Generator.Cli/Parsing/ResponseFileParser.cs b/src/WinRT.Generator.Cli/Parsing/ResponseFileParser.cs new file mode 100644 index 0000000000..a52a5cb2dc --- /dev/null +++ b/src/WinRT.Generator.Cli/Parsing/ResponseFileParser.cs @@ -0,0 +1,335 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using WindowsRuntime.GeneratorCli.Attributes; +using WindowsRuntime.GeneratorCli.Errors; + +namespace WindowsRuntime.GeneratorCli.Parsing; + +/// +/// Parses a response file into a strongly-typed args record by reflecting over its +/// []-annotated public properties. +/// +/// +/// +/// The shape of the response file is one name]]>value per line, with optional +/// blank lines (the MSBuild ToolTask may emit them). The first token of each non-blank line +/// is the CLI flag name; everything after the first space (right-trimmed) is the value. +/// +/// +/// Per-property handling: +/// +/// -typed properties are set from the parser's token +/// parameter (they have no ). +/// Properties without are skipped. +/// Required properties (those with ) throw +/// +/// if the value is missing or fails to parse. +/// Optional properties default to the if +/// is present, otherwise to default(T) (with +/// [] defaulting to an empty array). +/// Value coercion is handled by +/// for primitives, with ; arrays use +/// with comma separator. +/// +/// +/// +internal static class ResponseFileParser +{ + /// The required dynamic-access annotation for the TArgs type parameter. + private const DynamicallyAccessedMemberTypes ArgsAccessKinds = + DynamicallyAccessedMemberTypes.PublicProperties; + + /// + /// Parses an instance of from a response file at the given path. + /// + /// + /// The path may be prefixed with @ (matching MSBuild's default escaping for ToolTask + /// response files), which is stripped before reading the file. + /// + /// The strongly-typed args record. Must have a public parameterless constructor surface (only public properties are inspected via reflection). + /// The per-tool well-known exception factory used to route parsing errors. + /// The path to the response file (optionally prefixed with @). + /// The cancellation token for the operation. + /// The populated instance. + public static TArgs Parse<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(string path, CancellationToken token) + where TArgs : class + where TErr : IGeneratorErrorFactory + { + // If the path is a response file, it will start with the '@' character. + // This matches the default escaping 'ToolTask' uses for response files. + if (path is ['@', .. string escapedPath]) + { + path = escapedPath; + } + + string[] lines; + + // Read all lines in the response file (each line contains a single command line argument) + try + { + lines = File.ReadAllLines(path); + } + catch (Exception e) + { + throw TErr.ResponseFileReadError(e); + } + + return ParseLines(lines, token); + } + + /// + /// Parses an instance of from a response file read from a stream. + /// + /// The strongly-typed args record. + /// The per-tool well-known exception factory used to route parsing errors. + /// The stream containing the response file content. + /// The cancellation token for the operation. + /// The populated instance. + public static TArgs Parse<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(Stream stream, CancellationToken token) + where TArgs : class + where TErr : IGeneratorErrorFactory + { + return ParseLines(File.ReadAllLines(stream), token); + } + + /// + /// Parses an instance of from the pre-split lines of a response file. + /// + /// The strongly-typed args record. + /// The per-tool well-known exception factory used to route parsing errors. + /// The lines read from the response file. + /// The cancellation token for the operation. + /// The populated instance. + public static TArgs ParseLines<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(string[] lines, CancellationToken token) + where TArgs : class + where TErr : IGeneratorErrorFactory + { + Dictionary argsMap = BuildArgsMap(lines); + + return Populate(argsMap, token); + } + + /// + /// Builds the name-to-value map from the lines of a response file. + /// + /// The per-tool well-known exception factory used to route parsing errors. + /// The lines read from the response file. + /// The resulting map. + private static Dictionary BuildArgsMap(string[] lines) + where TErr : IGeneratorErrorFactory + { + Dictionary argsMap = []; + + foreach (string line in lines) + { + string trimmedLine = line.Trim(); + + // Skip empty lines (the MSBuild ToolTask may emit blank lines) + if (trimmedLine.Length == 0) + { + continue; + } + + // Each line has the command line argument name followed by a space, and then the + // argument value. If there are no spaces on any given line, the file is malformed. + int indexOfSpace = trimmedLine.IndexOf(' '); + + if (indexOfSpace == -1) + { + throw TErr.MalformedResponseFile(); + } + + // Now we can parse the actual command line argument name and value + string argumentName = trimmedLine.AsSpan()[..indexOfSpace].ToString(); + string argumentValue = trimmedLine.AsSpan()[(indexOfSpace + 1)..].TrimEnd().ToString(); + + // We should never have duplicate commands + if (!argsMap.TryAdd(argumentName, argumentValue)) + { + throw TErr.MalformedResponseFile(); + } + } + + return argsMap; + } + + /// + /// Constructs via + /// (to bypass enforcement at construction time) and then + /// populates each public property by reflecting on its CLI attribute, nullability, default value, and type. + /// + /// The strongly-typed args record. + /// The per-tool well-known exception factory used to route parsing errors. + /// The pre-built name-to-value map. + /// The cancellation token, assigned to any -typed property on . + /// The populated instance. + [UnconditionalSuppressMessage( + "Trimming", + "IL2087:'type' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", + Justification = "GetUninitializedObject does not invoke any constructor; it allocates an instance of TArgs without running any user code, so the PublicConstructors/NonPublicConstructors annotation is not required.")] + private static TArgs Populate<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(Dictionary argsMap, CancellationToken token) + where TArgs : class + where TErr : IGeneratorErrorFactory + { + // We bypass any constructor (and the runtime's required-member enforcement) by using + // 'GetUninitializedObject'. All properties are then populated via reflection from the + // response file values, with explicit defaults applied for non-required properties. + TArgs instance = (TArgs)RuntimeHelpers.GetUninitializedObject(typeof(TArgs)); + + NullabilityInfoContext nullabilityContext = new(); + + foreach (PropertyInfo property in typeof(TArgs).GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + Type propertyType = property.PropertyType; + + // The cancellation token property is always assigned from the parser's argument, + // not from the response file (it has no '[CommandLineArgumentName]' annotation). + if (propertyType == typeof(CancellationToken)) + { + property.SetValue(instance, token); + + continue; + } + + // Properties without the CLI attribute are not part of the response file contract + CommandLineArgumentNameAttribute? cliAttribute = property.GetCustomAttribute(); + + if (cliAttribute is null) + { + continue; + } + + bool isRequired = property.GetCustomAttribute() is not null; + bool hasValue = argsMap.TryGetValue(cliAttribute.Name, out string? rawValue); + + if (!hasValue) + { + ApplyDefault(instance, property, propertyType, isRequired, nullabilityContext); + + continue; + } + + // For required values, a parse failure throws (preserving the existing behavior of + // 'GetBooleanArgument' / 'GetInt32Argument' / 'GetStringArrayArgument'). For optional + // values, a parse failure silently falls back to the default (matching the existing + // 'GetOptionalBoolArgument' / 'GetOptionalStringArrayArgument' behavior). + if (TryConvert(rawValue!, propertyType, out object? converted)) + { + property.SetValue(instance, converted); + } + else if (isRequired) + { + throw TErr.ResponseFileArgumentParsingError(property.Name, null); + } + else + { + ApplyDefault(instance, property, propertyType, isRequired: false, nullabilityContext); + } + } + + return instance; + } + + /// + /// Applies the default value for a property when the response file does not provide one. + /// + /// The per-tool well-known exception factory used to route parsing errors. + /// The args instance being populated. + /// The property being set. + /// The property's type. + /// Whether the property has . + /// The shared for nullable-reference inspection. + private static void ApplyDefault( + object instance, + PropertyInfo property, + Type propertyType, + bool isRequired, + NullabilityInfoContext nullabilityContext) + where TErr : IGeneratorErrorFactory + { + if (isRequired) + { + throw TErr.ResponseFileArgumentParsingError(property.Name, null); + } + + // '[DefaultValue("…")]' takes precedence: it lets per-tool args express initializer-style + // defaults that 'GetUninitializedObject' would otherwise skip. + DefaultValueAttribute? defaultValueAttribute = property.GetCustomAttribute(); + + if (defaultValueAttribute is not null) + { + property.SetValue(instance, defaultValueAttribute.Value); + + return; + } + + // 'string[]' properties default to an empty array (matching 'GetOptionalStringArrayArgument'). + if (propertyType == typeof(string[])) + { + property.SetValue(instance, Array.Empty()); + + return; + } + + // Nullable reference types default to null (matching 'GetNullableStringArgument'). For value + // types and non-nullable reference types we leave 'default(T)' from 'GetUninitializedObject'. + if (!propertyType.IsValueType) + { + NullabilityInfo nullability = nullabilityContext.Create(property); + + if (nullability.ReadState == NullabilityState.Nullable) + { + property.SetValue(instance, null); + } + } + } + + /// + /// Converts a raw string from the response file into the target property type. + /// + /// The raw string value from the response file. + /// The destination property type. + /// The converted value on success. + /// on success; if the value cannot be parsed for . + private static bool TryConvert(string rawValue, Type targetType, out object? converted) + { + if (targetType == typeof(string)) + { + converted = rawValue; + + return true; + } + + if (targetType == typeof(string[])) + { + converted = rawValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + return true; + } + + // For primitives ('bool', 'int', etc.) we rely on the standard 'Convert.ChangeType', which + // is AOT-safe for the built-in primitives and uses invariant culture for deterministic parsing. + try + { + converted = Convert.ChangeType(rawValue, targetType, CultureInfo.InvariantCulture); + + return true; + } + catch (Exception e) when (e is FormatException or InvalidCastException or OverflowException) + { + converted = null; + + return false; + } + } +} diff --git a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Formatting.cs b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Formatting.cs deleted file mode 100644 index 22cf0b5835..0000000000 --- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Formatting.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text; - -namespace WindowsRuntime.ImplGenerator.Generation; - -/// -internal partial class ImplGeneratorArgs -{ - /// - /// Formats the current instance into a response file text. - /// - /// The resulting response file text. - public string FormatToResponseFile() - { - StringBuilder builder = new(); - - _ = builder.Append(GetCommandLineArgumentName(nameof(ReferenceAssemblyPaths))); - _ = builder.Append(' '); - _ = builder.AppendLine(string.Join(',', ReferenceAssemblyPaths)); - - _ = builder.Append(GetCommandLineArgumentName(nameof(OutputAssemblyPath))); - _ = builder.Append(' '); - _ = builder.AppendLine(OutputAssemblyPath); - - _ = builder.Append(GetCommandLineArgumentName(nameof(GeneratedAssemblyDirectory))); - _ = builder.Append(' '); - _ = builder.AppendLine(GeneratedAssemblyDirectory); - - _ = builder.Append(GetCommandLineArgumentName(nameof(TreatWarningsAsErrors))); - _ = builder.Append(' '); - _ = builder.AppendLine(TreatWarningsAsErrors.ToString()); - - if (AssemblyOriginatorKeyFile is not null) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(AssemblyOriginatorKeyFile))); - _ = builder.Append(' '); - _ = builder.AppendLine(AssemblyOriginatorKeyFile); - } - - if (DebugReproDirectory is not null) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(DebugReproDirectory))); - _ = builder.Append(' '); - _ = builder.AppendLine(DebugReproDirectory); - } - - return builder.ToString(); - } -} diff --git a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs deleted file mode 100644 index d80b649bcb..0000000000 --- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Threading; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.ImplGenerator.Errors; -using WindowsRuntime.GeneratorCli; - -#pragma warning disable IDE0046 - -namespace WindowsRuntime.ImplGenerator.Generation; - -/// -internal partial class ImplGeneratorArgs -{ - /// - /// Parses an instance from a target response file. - /// - /// The path to the response file. - /// The token for the operation. - /// The resulting instance. - public static ImplGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) - { - // If the path is a response file, it will start with the '@' character. - // This matches the default escaping 'ToolTask' uses for response files. - if (path is ['@', .. string escapedPath]) - { - path = escapedPath; - } - - string[] lines; - - // Read all lines in the response file (each line contains a single command line argument) - try - { - lines = File.ReadAllLines(path); - } - catch (Exception e) - { - throw WellKnownImplExceptions.ResponseFileReadError(e); - } - - return ParseFromResponseFile(lines, token); - } - - /// - /// Parses an instance from a target response file. - /// - /// The stream to the response file. - /// The token for the operation. - /// The resulting instance. - public static ImplGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) - { - string[] responseArgs = File.ReadAllLines(stream); - - return ParseFromResponseFile(responseArgs, token); - } - - /// - /// Parses an instance from a target response file. - /// - /// The lines read from the response file. - /// The token for the operation. - /// The resulting instance. - private static ImplGeneratorArgs ParseFromResponseFile(string[] lines, CancellationToken token) - { - Dictionary argsMap = []; - - // Build a map with all the commands and their values - foreach (string line in lines) - { - string trimmedLine = line.Trim(); - - // Skip empty lines (the MSBuild ToolTask may emit blank lines) - if (trimmedLine.Length == 0) - { - continue; - } - - // Each line has the command line argument name followed by a space, and then the - // argument value. If there are no spaces on any given line, the file is malformed. - int indexOfSpace = trimmedLine.IndexOf(' '); - - if (indexOfSpace == -1) - { - throw WellKnownImplExceptions.MalformedResponseFile(); - } - - // Now we can parse the actual command line argument name and value - string argumentName = trimmedLine.AsSpan()[..indexOfSpace].ToString(); - string argumentValue = trimmedLine.AsSpan()[(indexOfSpace + 1)..].TrimEnd().ToString(); - - // We should never have duplicate commands - if (!argsMap.TryAdd(argumentName, argumentValue)) - { - throw WellKnownImplExceptions.MalformedResponseFile(); - } - } - - // Parse all commands to create the managed arguments to use - return new() - { - ReferenceAssemblyPaths = GetStringArrayArgument(argsMap, nameof(ReferenceAssemblyPaths)), - OutputAssemblyPath = GetStringArgument(argsMap, nameof(OutputAssemblyPath)), - GeneratedAssemblyDirectory = GetStringArgument(argsMap, nameof(GeneratedAssemblyDirectory)), - TreatWarningsAsErrors = GetBooleanArgument(argsMap, nameof(TreatWarningsAsErrors)), - AssemblyOriginatorKeyFile = GetNullableStringArgument(argsMap, nameof(AssemblyOriginatorKeyFile)), - DebugReproDirectory = GetNullableStringArgument(argsMap, nameof(DebugReproDirectory)), - Token = token - }; - } - - /// - /// Gets the command line argument name for a property. - /// - /// The target property name. - /// The command line argument name for . - public static string GetCommandLineArgumentName(string propertyName) - { - try - { - return typeof(ImplGeneratorArgs).GetProperty(propertyName)!.GetCustomAttribute()!.Name; - } - catch (Exception e) - { - throw WellKnownImplExceptions.ResponseFileArgumentParsingError(propertyName, e); - } - } - - /// - /// Parses a array argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string[] GetStringArrayArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - } - - throw WellKnownImplExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses a argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string GetStringArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - throw WellKnownImplExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses a nullable (optional) argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string? GetNullableStringArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - return null; - } - - /// - /// Parses a argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static bool GetBooleanArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - if (bool.TryParse(argumentValue, out bool parsedValue)) - { - return parsedValue; - } - } - - throw WellKnownImplExceptions.ResponseFileArgumentParsingError(propertyName); - } -} \ No newline at end of file diff --git a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs index 7ace6d93e6..b2b0939d0a 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs @@ -1,15 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.IO; using System.Threading; using WindowsRuntime.GeneratorCli.Attributes; +using WindowsRuntime.GeneratorCli.Parsing; +using WindowsRuntime.ImplGenerator.Errors; namespace WindowsRuntime.ImplGenerator.Generation; /// /// Input parameters for . /// -internal sealed partial class ImplGeneratorArgs +internal sealed class ImplGeneratorArgs { /// Gets the input .dll paths. [CommandLineArgumentName("--reference-assembly-paths")] @@ -37,4 +40,35 @@ internal sealed partial class ImplGeneratorArgs /// Gets the directory to use to place the debug repro, if requested. [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } -} \ No newline at end of file + + /// + /// Parses an instance from a response file at the given path. + /// + /// The path to the response file (optionally prefixed with @). + /// The cancellation token for the operation. + /// The resulting instance. + public static ImplGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) + { + return ResponseFileParser.Parse(path, token); + } + + /// + /// Parses an instance from a response file read from a stream. + /// + /// The stream containing the response file content. + /// The cancellation token for the operation. + /// The resulting instance. + public static ImplGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) + { + return ResponseFileParser.Parse(stream, token); + } + + /// + /// Formats the current instance into a response file text. + /// + /// The resulting response file text. + public string FormatToResponseFile() + { + return ResponseFileBuilder.Format(this); + } +} diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Formatting.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Formatting.cs deleted file mode 100644 index 5fced287dc..0000000000 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Formatting.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text; - -namespace WindowsRuntime.InteropGenerator.Generation; - -/// -internal partial class InteropGeneratorArgs -{ - /// - /// Formats the current instance into a response file text. - /// - /// The resulting response file text. - public string FormatToResponseFile() - { - StringBuilder builder = new(); - - _ = builder.Append(GetCommandLineArgumentName(nameof(ReferenceAssemblyPaths))); - _ = builder.Append(' '); - _ = builder.AppendLine(string.Join(',', ReferenceAssemblyPaths)); - - _ = builder.Append(GetCommandLineArgumentName(nameof(ImplementationAssemblyPaths))); - _ = builder.Append(' '); - _ = builder.AppendLine(string.Join(',', ImplementationAssemblyPaths)); - - _ = builder.Append(GetCommandLineArgumentName(nameof(OutputAssemblyPath))); - _ = builder.Append(' '); - _ = builder.AppendLine(OutputAssemblyPath); - - _ = builder.Append(GetCommandLineArgumentName(nameof(WinRTSdkProjectionAssemblyPath))); - _ = builder.Append(' '); - _ = builder.AppendLine(WinRTSdkProjectionAssemblyPath); - - if (WinRTSdkXamlProjectionAssemblyPath is not null) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(WinRTSdkXamlProjectionAssemblyPath))); - _ = builder.Append(' '); - _ = builder.AppendLine(WinRTSdkXamlProjectionAssemblyPath); - } - - if (WinRTProjectionAssemblyPath is not null) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(WinRTProjectionAssemblyPath))); - _ = builder.Append(' '); - _ = builder.AppendLine(WinRTProjectionAssemblyPath); - } - - if (WinRTComponentAssemblyPath is not null) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(WinRTComponentAssemblyPath))); - _ = builder.Append(' '); - _ = builder.AppendLine(WinRTComponentAssemblyPath); - } - - _ = builder.Append(GetCommandLineArgumentName(nameof(GeneratedAssemblyDirectory))); - _ = builder.Append(' '); - _ = builder.AppendLine(GeneratedAssemblyDirectory); - - _ = builder.Append(GetCommandLineArgumentName(nameof(UseWindowsUIXamlProjections))); - _ = builder.Append(' '); - _ = builder.AppendLine(UseWindowsUIXamlProjections.ToString()); - - _ = builder.Append(GetCommandLineArgumentName(nameof(ValidateWinRTRuntimeAssemblyVersion))); - _ = builder.Append(' '); - _ = builder.AppendLine(ValidateWinRTRuntimeAssemblyVersion.ToString()); - - _ = builder.Append(GetCommandLineArgumentName(nameof(ValidateWinRTRuntimeDllVersion2References))); - _ = builder.Append(' '); - _ = builder.AppendLine(ValidateWinRTRuntimeDllVersion2References.ToString()); - - _ = builder.Append(GetCommandLineArgumentName(nameof(EnableIncrementalGeneration))); - _ = builder.Append(' '); - _ = builder.AppendLine(EnableIncrementalGeneration.ToString()); - - _ = builder.Append(GetCommandLineArgumentName(nameof(TreatWarningsAsErrors))); - _ = builder.Append(' '); - _ = builder.AppendLine(TreatWarningsAsErrors.ToString()); - - _ = builder.Append(GetCommandLineArgumentName(nameof(MaxDegreesOfParallelism))); - _ = builder.Append(' '); - _ = builder.AppendLine(MaxDegreesOfParallelism.ToString()); - - if (DebugReproDirectory is not null) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(DebugReproDirectory))); - _ = builder.Append(' '); - _ = builder.AppendLine(DebugReproDirectory); - } - - return builder.ToString(); - } -} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs deleted file mode 100644 index 0763556ee3..0000000000 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.InteropGenerator.Errors; - -#pragma warning disable IDE0046 - -namespace WindowsRuntime.InteropGenerator.Generation; - -/// -internal partial class InteropGeneratorArgs -{ - /// - /// Parses an instance from a target response file. - /// - /// The path to the response file. - /// The token for the operation. - /// The resulting instance. - public static InteropGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) - { - // If the path is a response file, it will start with the '@' character. - // This matches the default escaping 'ToolTask' uses for response files. - if (path is ['@', .. string escapedPath]) - { - path = escapedPath; - } - - string[] lines; - - // Read all lines in the response file (each line contains a single command line argument) - try - { - lines = File.ReadAllLines(path); - } - catch (Exception e) - { - throw WellKnownInteropExceptions.ResponseFileReadError(e); - } - - return ParseFromResponseFile(lines, token); - } - - /// - /// Parses an instance from a target response file. - /// - /// The stream to the response file. - /// The token for the operation. - /// The resulting instance. - public static InteropGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) - { - string[] responseArgs = File.ReadAllLines(stream); - - return ParseFromResponseFile(responseArgs, token); - } - - /// - /// Parses an instance from a target response file. - /// - /// The lines read from the response file. - /// The token for the operation. - /// The resulting instance. - private static InteropGeneratorArgs ParseFromResponseFile(string[] lines, CancellationToken token) - { - Dictionary argsMap = []; - - // Build a map with all the commands and their values - foreach (string line in lines) - { - string trimmedLine = line.Trim(); - - // Skip empty lines (the MSBuild ToolTask may emit blank lines) - if (trimmedLine.Length == 0) - { - continue; - } - - // Each line has the command line argument name followed by a space, and then the - // argument value. If there are no spaces on any given line, the file is malformed. - int indexOfSpace = trimmedLine.IndexOf(' '); - - if (indexOfSpace == -1) - { - throw WellKnownInteropExceptions.MalformedResponseFile(); - } - - // Now we can parse the actual command line argument name and value - string argumentName = trimmedLine.AsSpan()[..indexOfSpace].ToString(); - string argumentValue = trimmedLine.AsSpan()[(indexOfSpace + 1)..].TrimEnd().ToString(); - - // We should never have duplicate commands - if (!argsMap.TryAdd(argumentName, argumentValue)) - { - throw WellKnownInteropExceptions.MalformedResponseFile(); - } - } - - // Parse all commands to create the managed arguments to use - return new() - { - ReferenceAssemblyPaths = GetStringArrayArgument(argsMap, nameof(ReferenceAssemblyPaths)), - ImplementationAssemblyPaths = GetStringArrayArgument(argsMap, nameof(ImplementationAssemblyPaths)), - OutputAssemblyPath = GetStringArgument(argsMap, nameof(OutputAssemblyPath)), - WinRTSdkProjectionAssemblyPath = GetStringArgument(argsMap, nameof(WinRTSdkProjectionAssemblyPath)), - WinRTSdkXamlProjectionAssemblyPath = GetNullableStringArgument(argsMap, nameof(WinRTSdkXamlProjectionAssemblyPath)), - WinRTProjectionAssemblyPath = GetNullableStringArgument(argsMap, nameof(WinRTProjectionAssemblyPath)), - WinRTComponentAssemblyPath = GetNullableStringArgument(argsMap, nameof(WinRTComponentAssemblyPath)), - GeneratedAssemblyDirectory = GetStringArgument(argsMap, nameof(GeneratedAssemblyDirectory)), - UseWindowsUIXamlProjections = GetBooleanArgument(argsMap, nameof(UseWindowsUIXamlProjections)), - ValidateWinRTRuntimeAssemblyVersion = GetBooleanArgument(argsMap, nameof(ValidateWinRTRuntimeAssemblyVersion)), - ValidateWinRTRuntimeDllVersion2References = GetBooleanArgument(argsMap, nameof(ValidateWinRTRuntimeDllVersion2References)), - EnableIncrementalGeneration = GetBooleanArgument(argsMap, nameof(EnableIncrementalGeneration)), - TreatWarningsAsErrors = GetBooleanArgument(argsMap, nameof(TreatWarningsAsErrors)), - MaxDegreesOfParallelism = GetInt32Argument(argsMap, nameof(MaxDegreesOfParallelism)), - DebugReproDirectory = GetNullableStringArgument(argsMap, nameof(DebugReproDirectory)), - Token = token - }; - } - - /// - /// Gets the command line argument name for a property. - /// - /// The target property name. - /// The command line argument name for . - public static string GetCommandLineArgumentName(string propertyName) - { - try - { - return typeof(InteropGeneratorArgs).GetProperty(propertyName)!.GetCustomAttribute()!.Name; - } - catch (Exception e) - { - throw WellKnownInteropExceptions.ResponseFileArgumentParsingError(propertyName, e); - } - } - - /// - /// Parses a array argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string[] GetStringArrayArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - } - - throw WellKnownInteropExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses a argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string GetStringArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - throw WellKnownInteropExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses a nullable (optional) argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string? GetNullableStringArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - return null; - } - - /// - /// Parses an argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static int GetInt32Argument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - if (int.TryParse(argumentValue, out int parsedValue)) - { - return parsedValue; - } - } - - throw WellKnownInteropExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses a argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static bool GetBooleanArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - if (bool.TryParse(argumentValue, out bool parsedValue)) - { - return parsedValue; - } - } - - throw WellKnownInteropExceptions.ResponseFileArgumentParsingError(propertyName); - } -} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs index b1bcb3583e..82ec1fa69a 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs @@ -1,15 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.IO; using System.Threading; using WindowsRuntime.GeneratorCli.Attributes; +using WindowsRuntime.GeneratorCli.Parsing; +using WindowsRuntime.InteropGenerator.Errors; namespace WindowsRuntime.InteropGenerator.Generation; /// /// Input parameters for . /// -internal sealed partial class InteropGeneratorArgs +internal sealed class InteropGeneratorArgs { /// Gets the input reference .dll paths. [CommandLineArgumentName("--reference-assembly-paths")] @@ -73,4 +76,35 @@ internal sealed partial class InteropGeneratorArgs /// Gets the directory to use to place the debug repro, if requested. [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } -} \ No newline at end of file + + /// + /// Parses an instance from a response file at the given path. + /// + /// The path to the response file (optionally prefixed with @). + /// The cancellation token for the operation. + /// The resulting instance. + public static InteropGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) + { + return ResponseFileParser.Parse(path, token); + } + + /// + /// Parses an instance from a response file read from a stream. + /// + /// The stream containing the response file content. + /// The cancellation token for the operation. + /// The resulting instance. + public static InteropGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) + { + return ResponseFileParser.Parse(stream, token); + } + + /// + /// Formats the current instance into a response file text. + /// + /// The resulting response file text. + public string FormatToResponseFile() + { + return ResponseFileBuilder.Format(this); + } +} diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Formatting.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Formatting.cs deleted file mode 100644 index 1010ac57ef..0000000000 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Formatting.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text; - -namespace WindowsRuntime.ProjectionGenerator.Generation; - -/// -internal partial class ProjectionGeneratorArgs -{ - /// - /// Formats the current instance into a response file text. - /// - /// The resulting response file text. - public string FormatToResponseFile() - { - StringBuilder builder = new(); - - _ = builder.Append(GetCommandLineArgumentName(nameof(ReferenceAssemblyPaths))); - _ = builder.Append(' '); - _ = builder.AppendLine(string.Join(',', ReferenceAssemblyPaths)); - - _ = builder.Append(GetCommandLineArgumentName(nameof(GeneratedAssemblyDirectory))); - _ = builder.Append(' '); - _ = builder.AppendLine(GeneratedAssemblyDirectory); - - _ = builder.Append(GetCommandLineArgumentName(nameof(WinMDPaths))); - _ = builder.Append(' '); - _ = builder.AppendLine(string.Join(',', WinMDPaths)); - - _ = builder.Append(GetCommandLineArgumentName(nameof(TargetFramework))); - _ = builder.Append(' '); - _ = builder.AppendLine(TargetFramework); - - _ = builder.Append(GetCommandLineArgumentName(nameof(WindowsMetadata))); - _ = builder.Append(' '); - _ = builder.AppendLine(WindowsMetadata); - - _ = builder.Append(GetCommandLineArgumentName(nameof(AssemblyName))); - _ = builder.Append(' '); - _ = builder.AppendLine(AssemblyName); - - if (WindowsSdkOnly) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(WindowsSdkOnly))); - _ = builder.Append(' '); - _ = builder.AppendLine(WindowsSdkOnly.ToString()); - } - - if (WindowsUIXamlProjection) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(WindowsUIXamlProjection))); - _ = builder.Append(' '); - _ = builder.AppendLine(WindowsUIXamlProjection.ToString()); - } - - _ = builder.Append(GetCommandLineArgumentName(nameof(MaxDegreesOfParallelism))); - _ = builder.Append(' '); - _ = builder.AppendLine(MaxDegreesOfParallelism.ToString()); - - if (DebugReproDirectory is not null) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(DebugReproDirectory))); - _ = builder.Append(' '); - _ = builder.AppendLine(DebugReproDirectory); - } - - return builder.ToString(); - } -} diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs deleted file mode 100644 index ebc32a13b7..0000000000 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.Parsing.cs +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.ProjectionGenerator.Errors; - -#pragma warning disable IDE0046 - -namespace WindowsRuntime.ProjectionGenerator.Generation; - -/// -internal partial class ProjectionGeneratorArgs -{ - /// - /// Parses an instance from a target response file. - /// - /// The path to the response file. - /// The token for the operation. - /// The resulting instance. - public static ProjectionGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) - { - // If the path is a response file, it will start with the '@' character. - // This matches the default escaping 'ToolTask' uses for response files. - if (path is ['@', .. string escapedPath]) - { - path = escapedPath; - } - - string[] responseArgs; - - // Read all lines in the response file (each line contains a single command line argument) - try - { - responseArgs = File.ReadAllLines(path); - } - catch (Exception e) - { - throw WellKnownProjectionGeneratorExceptions.ResponseFileReadError(e); - } - - return ParseFromResponseFile(responseArgs, token); - } - - /// - /// Parses an instance from a target response file. - /// - /// The stream to the response file. - /// The token for the operation. - /// The resulting instance. - public static ProjectionGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) - { - string[] responseArgs = File.ReadAllLines(stream); - - return ParseFromResponseFile(responseArgs, token); - } - - /// - /// Parses an instance from the lines of a response file. - /// - /// The lines read from the response file. - /// The token for the operation. - /// The resulting instance. - private static ProjectionGeneratorArgs ParseFromResponseFile(string[] lines, CancellationToken token) - { - Dictionary argsMap = []; - - // Build a map with all the commands and their values - foreach (string line in lines) - { - string trimmedLine = line.Trim(); - - // Skip empty lines (the MSBuild ToolTask may emit blank lines) - if (trimmedLine.Length == 0) - { - continue; - } - - // Each line has the command line argument name followed by a space, and then the - // argument value. If there are no spaces on any given line, the file is malformed. - int indexOfSpace = trimmedLine.IndexOf(' '); - - if (indexOfSpace == -1) - { - throw WellKnownProjectionGeneratorExceptions.MalformedResponseFile(); - } - - // Now we can parse the actual command line argument name and value - string argumentName = trimmedLine.AsSpan()[..indexOfSpace].ToString(); - string argumentValue = trimmedLine.AsSpan()[(indexOfSpace + 1)..].TrimEnd().ToString(); - - // We should never have duplicate commands - if (!argsMap.TryAdd(argumentName, argumentValue)) - { - throw WellKnownProjectionGeneratorExceptions.MalformedResponseFile(); - } - } - - // Parse all commands to create the managed arguments to use - return new() - { - ReferenceAssemblyPaths = GetStringArrayArgument(argsMap, nameof(ReferenceAssemblyPaths)), - GeneratedAssemblyDirectory = GetStringArgument(argsMap, nameof(GeneratedAssemblyDirectory)), - WinMDPaths = GetStringArrayArgument(argsMap, nameof(WinMDPaths)), - TargetFramework = GetStringArgument(argsMap, nameof(TargetFramework)), - WindowsMetadata = GetStringArgument(argsMap, nameof(WindowsMetadata)), - AssemblyName = GetOptionalStringArgument(argsMap, nameof(AssemblyName), "WinRT.Projection"), - WindowsSdkOnly = GetOptionalBoolArgument(argsMap, nameof(WindowsSdkOnly)), - WindowsUIXamlProjection = GetOptionalBoolArgument(argsMap, nameof(WindowsUIXamlProjection)), - MaxDegreesOfParallelism = GetInt32Argument(argsMap, nameof(MaxDegreesOfParallelism)), - DebugReproDirectory = GetNullableStringArgument(argsMap, nameof(DebugReproDirectory)), - Token = token - }; - } - - /// - /// Gets the command line argument name for a property. - /// - /// The target property name. - /// The command line argument name for . - public static string GetCommandLineArgumentName(string propertyName) - { - try - { - return typeof(ProjectionGeneratorArgs).GetProperty(propertyName)!.GetCustomAttribute()!.Name; - } - catch (Exception e) - { - throw WellKnownProjectionGeneratorExceptions.ResponseFileArgumentParsingError(propertyName, e); - } - } - - /// - /// Parses a array argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string[] GetStringArrayArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - } - - throw WellKnownProjectionGeneratorExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses a argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string GetStringArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - throw WellKnownProjectionGeneratorExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses an optional argument, returning a default value if not present. - /// - /// The input map with raw arguments. - /// The target property name. - /// The default value to return if the argument is not present. - /// The resulting argument, or if not found. - private static string GetOptionalStringArgument(Dictionary argsMap, string propertyName, string defaultValue) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - return defaultValue; - } - - /// - /// Parses a nullable (optional) argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument, or if not present. - private static string? GetNullableStringArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - return null; - } - - /// - /// Parses an optional argument, returning false if not present. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument, or false if not found. - private static bool GetOptionalBoolArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return bool.TryParse(argumentValue, out bool result) && result; - } - - return false; - } - - /// - /// Parses an argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static int GetInt32Argument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - if (int.TryParse(argumentValue, out int parsedValue)) - { - return parsedValue; - } - } - - throw WellKnownProjectionGeneratorExceptions.ResponseFileArgumentParsingError(propertyName); - } -} diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs index 173110eacc..05725c448f 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs @@ -1,15 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.ComponentModel; +using System.IO; using System.Threading; using WindowsRuntime.GeneratorCli.Attributes; +using WindowsRuntime.GeneratorCli.Parsing; +using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; /// /// Input parameters for . /// -internal sealed partial class ProjectionGeneratorArgs +internal sealed class ProjectionGeneratorArgs { /// Gets the input .dll paths. [CommandLineArgumentName("--reference-assembly-paths")] @@ -33,6 +37,7 @@ internal sealed partial class ProjectionGeneratorArgs /// Gets the output assembly name. Defaults to 'WinRT.Projection'. [CommandLineArgumentName("--assembly-name")] + [DefaultValue("WinRT.Projection")] public string AssemblyName { get; init; } = "WinRT.Projection"; /// @@ -60,4 +65,35 @@ internal sealed partial class ProjectionGeneratorArgs /// Gets the directory to use to place the debug repro, if requested. [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } -} \ No newline at end of file + + /// + /// Parses a instance from a response file at the given path. + /// + /// The path to the response file (optionally prefixed with @). + /// The cancellation token for the operation. + /// The resulting instance. + public static ProjectionGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) + { + return ResponseFileParser.Parse(path, token); + } + + /// + /// Parses a instance from a response file read from a stream. + /// + /// The stream containing the response file content. + /// The cancellation token for the operation. + /// The resulting instance. + public static ProjectionGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) + { + return ResponseFileParser.Parse(stream, token); + } + + /// + /// Formats the current instance into a response file text. + /// + /// The resulting response file text. + public string FormatToResponseFile() + { + return ResponseFileBuilder.Format(this); + } +} diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Formatting.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Formatting.cs deleted file mode 100644 index f61338f8ef..0000000000 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Formatting.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text; - -namespace WindowsRuntime.ReferenceProjectionGenerator.Generation; - -/// -internal partial class ReferenceProjectionGeneratorArgs -{ - /// - /// Formats the current instance into a response file text. - /// - /// The resulting response file text. - public string FormatToResponseFile() - { - StringBuilder builder = new(); - - _ = builder.Append(GetCommandLineArgumentName(nameof(InputPaths))); - _ = builder.Append(' '); - _ = builder.AppendLine(string.Join(',', InputPaths)); - - _ = builder.Append(GetCommandLineArgumentName(nameof(OutputDirectory))); - _ = builder.Append(' '); - _ = builder.AppendLine(OutputDirectory); - - _ = builder.Append(GetCommandLineArgumentName(nameof(TargetFramework))); - _ = builder.Append(' '); - _ = builder.AppendLine(TargetFramework); - - if (IncludeNamespaces.Length > 0) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(IncludeNamespaces))); - _ = builder.Append(' '); - _ = builder.AppendLine(string.Join(',', IncludeNamespaces)); - } - - if (ExcludeNamespaces.Length > 0) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(ExcludeNamespaces))); - _ = builder.Append(' '); - _ = builder.AppendLine(string.Join(',', ExcludeNamespaces)); - } - - if (AdditionExcludeNamespaces.Length > 0) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(AdditionExcludeNamespaces))); - _ = builder.Append(' '); - _ = builder.AppendLine(string.Join(',', AdditionExcludeNamespaces)); - } - - if (Verbose) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(Verbose))); - _ = builder.Append(' '); - _ = builder.AppendLine(Verbose.ToString()); - } - - if (Component) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(Component))); - _ = builder.Append(' '); - _ = builder.AppendLine(Component.ToString()); - } - - if (PublicExclusiveTo) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(PublicExclusiveTo))); - _ = builder.Append(' '); - _ = builder.AppendLine(PublicExclusiveTo.ToString()); - } - - if (IdicExclusiveTo) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(IdicExclusiveTo))); - _ = builder.Append(' '); - _ = builder.AppendLine(IdicExclusiveTo.ToString()); - } - - if (ReferenceProjection) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(ReferenceProjection))); - _ = builder.Append(' '); - _ = builder.AppendLine(ReferenceProjection.ToString()); - } - - if (DebugReproDirectory is not null) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(DebugReproDirectory))); - _ = builder.Append(' '); - _ = builder.AppendLine(DebugReproDirectory); - } - - return builder.ToString(); - } -} diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs deleted file mode 100644 index 003f296261..0000000000 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.Parsing.cs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.ReferenceProjectionGenerator.Errors; - -#pragma warning disable IDE0046 - -namespace WindowsRuntime.ReferenceProjectionGenerator.Generation; - -/// -internal partial class ReferenceProjectionGeneratorArgs -{ - /// - /// Parses an instance from a target response file. - /// - /// The path to the response file. - /// The token for the operation. - /// The resulting instance. - public static ReferenceProjectionGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) - { - // If the path is a response file, it will start with the '@' character. - // This matches the default escaping 'ToolTask' uses for response files. - if (path is ['@', .. string escapedPath]) - { - path = escapedPath; - } - - string[] responseArgs; - - // Read all lines in the response file (each line contains a single command line argument) - try - { - responseArgs = File.ReadAllLines(path); - } - catch (Exception e) - { - throw WellKnownReferenceProjectionGeneratorExceptions.ResponseFileReadError(e); - } - - return ParseFromResponseFile(responseArgs, token); - } - - /// - /// Parses an instance from a target response file. - /// - /// The stream to the response file. - /// The token for the operation. - /// The resulting instance. - public static ReferenceProjectionGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) - { - string[] responseArgs = File.ReadAllLines(stream); - - return ParseFromResponseFile(responseArgs, token); - } - - /// - /// Parses an instance from the lines of a response file. - /// - /// The lines read from the response file. - /// The token for the operation. - /// The resulting instance. - private static ReferenceProjectionGeneratorArgs ParseFromResponseFile(string[] lines, CancellationToken token) - { - Dictionary argsMap = []; - - // Build a map with all the commands and their values - foreach (string line in lines) - { - string trimmedLine = line.Trim(); - - // Skip empty lines (the MSBuild ToolTask may emit blank lines) - if (trimmedLine.Length == 0) - { - continue; - } - - // Each line has the command line argument name followed by a space, and then the - // argument value. If there are no spaces on any given line, the file is malformed. - int indexOfSpace = trimmedLine.IndexOf(' '); - - if (indexOfSpace == -1) - { - throw WellKnownReferenceProjectionGeneratorExceptions.MalformedResponseFile(); - } - - // Now we can parse the actual command line argument name and value - string argumentName = trimmedLine.AsSpan()[..indexOfSpace].ToString(); - string argumentValue = trimmedLine.AsSpan()[(indexOfSpace + 1)..].TrimEnd().ToString(); - - // We should never have duplicate commands - if (!argsMap.TryAdd(argumentName, argumentValue)) - { - throw WellKnownReferenceProjectionGeneratorExceptions.MalformedResponseFile(); - } - } - - // Parse all commands to create the managed arguments to use - return new() - { - InputPaths = GetStringArrayArgument(argsMap, nameof(InputPaths)), - OutputDirectory = GetStringArgument(argsMap, nameof(OutputDirectory)), - TargetFramework = GetStringArgument(argsMap, nameof(TargetFramework)), - IncludeNamespaces = GetOptionalStringArrayArgument(argsMap, nameof(IncludeNamespaces)), - ExcludeNamespaces = GetOptionalStringArrayArgument(argsMap, nameof(ExcludeNamespaces)), - AdditionExcludeNamespaces = GetOptionalStringArrayArgument(argsMap, nameof(AdditionExcludeNamespaces)), - Verbose = GetOptionalBoolArgument(argsMap, nameof(Verbose)), - Component = GetOptionalBoolArgument(argsMap, nameof(Component)), - PublicExclusiveTo = GetOptionalBoolArgument(argsMap, nameof(PublicExclusiveTo)), - IdicExclusiveTo = GetOptionalBoolArgument(argsMap, nameof(IdicExclusiveTo)), - ReferenceProjection = GetOptionalBoolArgument(argsMap, nameof(ReferenceProjection)), - DebugReproDirectory = GetNullableStringArgument(argsMap, nameof(DebugReproDirectory)), - Token = token - }; - } - - /// - /// Gets the command line argument name for a property. - /// - /// The target property name. - /// The command line argument name for . - public static string GetCommandLineArgumentName(string propertyName) - { - try - { - return typeof(ReferenceProjectionGeneratorArgs).GetProperty(propertyName)!.GetCustomAttribute()!.Name; - } - catch (Exception e) - { - throw WellKnownReferenceProjectionGeneratorExceptions.ResponseFileArgumentParsingError(propertyName, e); - } - } - - /// - /// Parses a required array argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string[] GetStringArrayArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - } - - throw WellKnownReferenceProjectionGeneratorExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses an optional array argument, returning an empty array if not present. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument, or an empty array if not found. - private static string[] GetOptionalStringArrayArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - } - - return []; - } - - /// - /// Parses a required argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string GetStringArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - throw WellKnownReferenceProjectionGeneratorExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses a nullable (optional) argument. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument. - private static string? GetNullableStringArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - return null; - } - - /// - /// Parses an optional argument, returning false if not present. - /// - /// The input map with raw arguments. - /// The target property name. - /// The resulting argument, or false if not found. - private static bool GetOptionalBoolArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return bool.TryParse(argumentValue, out bool result) && result; - } - - return false; - } -} diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs index 5bd0696498..5e3a2a7ea7 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs @@ -1,15 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.IO; using System.Threading; using WindowsRuntime.GeneratorCli.Attributes; +using WindowsRuntime.GeneratorCli.Parsing; +using WindowsRuntime.ReferenceProjectionGenerator.Errors; namespace WindowsRuntime.ReferenceProjectionGenerator.Generation; /// /// Input parameters for . /// -internal sealed partial class ReferenceProjectionGeneratorArgs +internal sealed class ReferenceProjectionGeneratorArgs { /// Gets the input .winmd paths (files, directories to recursively scan, or special /// tokens like "local", "sdk", "sdk+", or a version like "10.0.26100.0"). @@ -62,4 +65,36 @@ internal sealed partial class ReferenceProjectionGeneratorArgs /// Gets the directory to use to place the debug repro, if requested. [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } + + /// + /// Parses a instance from a response file at the given path. + /// + /// The path to the response file (optionally prefixed with @). + /// The cancellation token for the operation. + /// The resulting instance. + public static ReferenceProjectionGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) + { + return ResponseFileParser.Parse(path, token); + } + + /// + /// Parses a instance from a response file read from a stream. + /// + /// The stream containing the response file content. + /// The cancellation token for the operation. + /// The resulting instance. + public static ReferenceProjectionGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) + { + return ResponseFileParser.Parse(stream, token); + } + + /// + /// Formats the current instance into a response file text. + /// + /// The resulting response file text. + public string FormatToResponseFile() + { + return ResponseFileBuilder.Format(this); + } } + diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Formatting.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Formatting.cs deleted file mode 100644 index ca487eec3e..0000000000 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Formatting.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text; - -namespace WindowsRuntime.WinMDGenerator.Generation; - -/// -internal partial class WinMDGeneratorArgs -{ - /// - /// Formats the current instance into a response file text. - /// - /// The resulting response file text. - public string FormatToResponseFile() - { - StringBuilder builder = new(); - - _ = builder.Append(GetCommandLineArgumentName(nameof(InputAssemblyPath))); - _ = builder.Append(' '); - _ = builder.AppendLine(InputAssemblyPath); - - _ = builder.Append(GetCommandLineArgumentName(nameof(ReferenceAssemblyPaths))); - _ = builder.Append(' '); - _ = builder.AppendLine(string.Join(',', ReferenceAssemblyPaths)); - - _ = builder.Append(GetCommandLineArgumentName(nameof(OutputWinmdPath))); - _ = builder.Append(' '); - _ = builder.AppendLine(OutputWinmdPath); - - _ = builder.Append(GetCommandLineArgumentName(nameof(AssemblyVersion))); - _ = builder.Append(' '); - _ = builder.AppendLine(AssemblyVersion); - - _ = builder.Append(GetCommandLineArgumentName(nameof(UseWindowsUIXamlProjections))); - _ = builder.Append(' '); - _ = builder.AppendLine(UseWindowsUIXamlProjections.ToString()); - - if (DebugReproDirectory is not null) - { - _ = builder.Append(GetCommandLineArgumentName(nameof(DebugReproDirectory))); - _ = builder.Append(' '); - _ = builder.AppendLine(DebugReproDirectory); - } - - return builder.ToString(); - } -} diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs deleted file mode 100644 index 8190474e69..0000000000 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.WinMDGenerator.Errors; - -#pragma warning disable IDE0046 - -namespace WindowsRuntime.WinMDGenerator.Generation; - -/// -internal partial class WinMDGeneratorArgs -{ - /// - /// Parses a instance from a target response file. - /// - /// - /// The path may be prefixed with @ (matching the default escaping ToolTask uses - /// for response files), which is stripped before reading the file. - /// - /// The path to the response file (optionally prefixed with @). - /// The cancellation token for the operation. - /// The resulting instance. - public static WinMDGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) - { - // If the path is a response file, it will start with the '@' character. - // This matches the default escaping 'ToolTask' uses for response files. - if (path is ['@', .. string escapedPath]) - { - path = escapedPath; - } - - string[] lines; - - // Read all lines in the response file (each line contains a single command line argument) - try - { - lines = File.ReadAllLines(path); - } - catch (Exception e) - { - throw WellKnownWinMDExceptions.ResponseFileReadError(e); - } - - return ParseFromResponseFile(lines, token); - } - - /// - /// Parses a instance from a target response file. - /// - /// The stream to the response file. - /// The token for the operation. - /// The resulting instance. - public static WinMDGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) - { - string[] responseArgs = File.ReadAllLines(stream); - - return ParseFromResponseFile(responseArgs, token); - } - - /// - /// Parses a instance from the lines of a response file. - /// - /// - /// Each line in the response file contains a single command-line argument in the form - /// --argument-name value. Lines are parsed into a key-value map, and each property - /// of is populated using its . - /// Duplicate argument names cause a malformed response file error. - /// - /// The lines read from the response file. - /// The cancellation token for the operation. - /// The resulting instance. - private static WinMDGeneratorArgs ParseFromResponseFile(string[] lines, CancellationToken token) - { - Dictionary argsMap = []; - - // Build a map with all the commands and their values - foreach (string line in lines) - { - string trimmedLine = line.Trim(); - - // Skip empty lines (the MSBuild ToolTask may emit blank lines) - if (trimmedLine.Length == 0) - { - continue; - } - - // Each line has the command line argument name followed by a space, and then the - // argument value. If there are no spaces on any given line, the file is malformed. - int indexOfSpace = trimmedLine.IndexOf(' '); - - if (indexOfSpace == -1) - { - throw WellKnownWinMDExceptions.MalformedResponseFile(); - } - - // Now we can parse the actual command line argument name and value - string argumentName = trimmedLine.AsSpan()[..indexOfSpace].ToString(); - string argumentValue = trimmedLine.AsSpan()[(indexOfSpace + 1)..].TrimEnd().ToString(); - - // We should never have duplicate commands - if (!argsMap.TryAdd(argumentName, argumentValue)) - { - throw WellKnownWinMDExceptions.MalformedResponseFile(); - } - } - - // Parse all commands to create the managed arguments to use - return new() - { - InputAssemblyPath = GetStringArgument(argsMap, nameof(InputAssemblyPath)), - ReferenceAssemblyPaths = GetStringArrayArgument(argsMap, nameof(ReferenceAssemblyPaths)), - OutputWinmdPath = GetStringArgument(argsMap, nameof(OutputWinmdPath)), - AssemblyVersion = GetStringArgument(argsMap, nameof(AssemblyVersion)), - UseWindowsUIXamlProjections = GetBooleanArgument(argsMap, nameof(UseWindowsUIXamlProjections)), - DebugReproDirectory = GetNullableStringArgument(argsMap, nameof(DebugReproDirectory)), - Token = token - }; - } - - /// - /// Gets the command-line argument name for a property by reading its - /// . - /// - /// The target property name on . - /// The command-line argument name (e.g., "--input-assembly-path"). - public static string GetCommandLineArgumentName(string propertyName) - { - try - { - return typeof(WinMDGeneratorArgs).GetProperty(propertyName)!.GetCustomAttribute()!.Name; - } - catch (Exception e) - { - throw WellKnownWinMDExceptions.ResponseFileArgumentParsingError(propertyName, e); - } - } - - /// - /// Parses a comma-separated array argument from the arguments map. - /// - /// The parsed arguments map. - /// The property name to look up. - /// An array of trimmed, non-empty string values. - private static string[] GetStringArrayArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - } - - throw WellKnownWinMDExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses a argument from the arguments map. - /// - /// The parsed arguments map. - /// The property name to look up. - /// The string value of the argument. - private static string GetStringArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - throw WellKnownWinMDExceptions.ResponseFileArgumentParsingError(propertyName); - } - - /// - /// Parses a nullable (optional) argument. - /// - /// The parsed arguments map. - /// The property name to look up. - /// The resulting argument, or if not present. - private static string? GetNullableStringArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - return argumentValue; - } - - return null; - } - - /// - /// Parses a argument from the arguments map. - /// - /// The parsed arguments map. - /// The property name to look up. - /// The parsed boolean value. - private static bool GetBooleanArgument(Dictionary argsMap, string propertyName) - { - if (argsMap.TryGetValue(GetCommandLineArgumentName(propertyName), out string? argumentValue)) - { - if (bool.TryParse(argumentValue, out bool parsedValue)) - { - return parsedValue; - } - } - - throw WellKnownWinMDExceptions.ResponseFileArgumentParsingError(propertyName); - } -} \ No newline at end of file diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs index 7f6c96ad70..e89610784e 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs @@ -1,15 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.IO; using System.Threading; using WindowsRuntime.GeneratorCli.Attributes; +using WindowsRuntime.GeneratorCli.Parsing; +using WindowsRuntime.WinMDGenerator.Errors; namespace WindowsRuntime.WinMDGenerator.Generation; /// /// Input parameters for . /// -internal sealed partial class WinMDGeneratorArgs +internal sealed class WinMDGeneratorArgs { /// Gets the path to the compiled input assembly (.dll) to analyze. [CommandLineArgumentName("--input-assembly-path")] @@ -37,4 +40,35 @@ internal sealed partial class WinMDGeneratorArgs /// Gets the directory to use to place the debug repro, if requested. [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } -} \ No newline at end of file + + /// + /// Parses a instance from a response file at the given path. + /// + /// The path to the response file (optionally prefixed with @). + /// The cancellation token for the operation. + /// The resulting instance. + public static WinMDGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) + { + return ResponseFileParser.Parse(path, token); + } + + /// + /// Parses a instance from a response file read from a stream. + /// + /// The stream containing the response file content. + /// The cancellation token for the operation. + /// The resulting instance. + public static WinMDGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) + { + return ResponseFileParser.Parse(stream, token); + } + + /// + /// Formats the current instance into a response file text. + /// + /// The resulting response file text. + public string FormatToResponseFile() + { + return ResponseFileBuilder.Format(this); + } +} From 0bd144ad45f52b8ce499da128ff872779a01033e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 18:53:43 -0700 Subject: [PATCH 06/28] Phase 5: extract shared debug-repro leaf helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 'DebugReproPacker' (in 'WinRT.Generator.Cli/DebugRepro/') with the five small leaf helpers that were duplicated near-byte-identically across every '*Generator.DebugRepro.cs': - 'GetHashedFileName(string)' - Shake128 hash → '{name}_{HEX}{ext}' - 'CopyHashedFilesToDirectory(string[], string, Dictionary, CancellationToken)' - 'CopyHashedFileToDirectory(string?, string, Dictionary, CancellationToken)' (simple variant) - 'CopyPathMapToDirectory(Dictionary, string, string)' - JSON serialize via shared 'GeneratorJsonSerializerContext' - 'ExtractPathMap(ZipArchiveEntry)' - JSON deserialize via the same context Each per-tool '*Generator.DebugRepro.cs' loses its five private helper definitions and gains 'using WindowsRuntime.GeneratorCli.DebugRepro;' plus the 'DebugReproPacker.' qualifier on call sites. Unused 'using' statements (for 'System.Security.Cryptography', 'System.Text', 'System.Text.Json', 'GeneratorJsonSerializerContext', etc.) are removed. Interop keeps its rich 'CopyHashedFileToDirectory' local. That variant has reserved-DLL dedupe logic plus 'WellKnownInteropExceptions.ReservedDllOriginalPathMismatchFromDebugRepro(fileName)' on path mismatch — neither generalizable nor shared. It now delegates the hash computation to 'DebugReproPacker.GetHashedFileName'. Hashed filenames are content-addressed via Shake128 of the original file path, so cross-version replay continues to work: a '.zip' produced by an older build can be unpacked by the new build and vice versa. Validated end-to-end: ref-gen 8/8 files 0 diffs, WinMD 5632/5632 bytes 16-byte MVID-only delta, projection 327/327 files 0 diffs, impl 257536/257536 bytes 0 byte diffs, interop smoke-tested. All 5 build with 0 warnings in Release and AOT-publish cleanly for win-arm64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DebugRepro/DebugReproPacker.cs | 151 ++++++++++++++++++ .../Generation/ImplGenerator.DebugRepro.cs | 131 +-------------- .../Generation/InteropGenerator.DebugRepro.cs | 136 ++++------------ .../ProjectionGenerator.DebugRepro.cs | 105 ++---------- ...ReferenceProjectionGenerator.DebugRepro.cs | 93 +---------- .../Generation/WinMDGenerator.DebugRepro.cs | 128 +-------------- 6 files changed, 207 insertions(+), 537 deletions(-) create mode 100644 src/WinRT.Generator.Cli/DebugRepro/DebugReproPacker.cs diff --git a/src/WinRT.Generator.Cli/DebugRepro/DebugReproPacker.cs b/src/WinRT.Generator.Cli/DebugRepro/DebugReproPacker.cs new file mode 100644 index 0000000000..1e2d011da8 --- /dev/null +++ b/src/WinRT.Generator.Cli/DebugRepro/DebugReproPacker.cs @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.IO.Compression; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Threading; +using WindowsRuntime.GeneratorCli.Helpers; + +namespace WindowsRuntime.GeneratorCli.DebugRepro; + +/// +/// Leaf helpers shared across the CsWinRT CLI generators for packaging and unpacking debug repros. +/// +/// +/// Each generator's SaveDebugRepro/UnpackDebugRepro still owns the high-level orchestration +/// (which input categories exist, which subfolders are used, how the response file is re-stitched). This +/// type only owns the small, identical-across-tools building blocks: +/// +/// Hashing the original file path into a stable, collision-free file name. +/// Copying files (or a single file) into a destination directory using the hashed names. +/// Serializing / deserializing the "hashed name → original path" mapping as JSON inside the repro archive. +/// +/// +internal static class DebugReproPacker +{ + /// + /// Generates a hashed filename by appending a Shake128 hash of the original file path. + /// + /// The original file path. + /// The hashed filename in the form {name}_{HEX}{ext}. + public static string GetHashedFileName(string filePath) + { + string fileName = Path.GetFileName(Path.Normalize(filePath)); + byte[] utf8Data = Encoding.UTF8.GetBytes(filePath); + byte[] hashData = Shake128.HashData(utf8Data, outputLength: 16); + string hash = Convert.ToHexString(hashData); + + return $"{Path.GetFileNameWithoutExtension(fileName)}_{hash}{Path.GetExtension(fileName)}"; + } + + /// + /// Copies all specified files to a target folder using hashed file names, and returns the list of updated names. + /// + /// The input file paths. + /// The target directory to copy the files to. + /// A dictionary to store the original paths of the copied files (keyed by hashed file name). + /// A cancellation token to monitor for cancellation requests. + /// The list of updated hashed filenames, in the same order as . + public static List CopyHashedFilesToDirectory( + string[] filePaths, + string destinationDirectory, + Dictionary originalPaths, + CancellationToken token) + { + List updatedFileNames = []; + + foreach (string filePath in filePaths) + { + token.ThrowIfCancellationRequested(); + + string hashedName = GetHashedFileName(filePath); + string destinationPath = Path.Combine(destinationDirectory, hashedName); + + File.Copy(filePath, destinationPath, overwrite: true); + + updatedFileNames.Add(hashedName); + originalPaths.Add(hashedName, filePath); + } + + return updatedFileNames; + } + + /// + /// Copies a single specified file to a target folder using a hashed file name. + /// + /// + /// This is the simple variant used by the impl, projection, projection-ref, and WinMD generators. + /// The interop generator keeps its own variant locally (which adds reserved-DLL dedupe and throws + /// on path mismatches against the shared reference set). + /// + /// The input file path, or to skip. + /// The target directory to copy the file to. + /// A dictionary to store the original path of the copied file (keyed by hashed file name). + /// A cancellation token to monitor for cancellation requests. + /// The hashed filename, or if was . + [return: NotNullIfNotNull(nameof(filePath))] + public static string? CopyHashedFileToDirectory( + string? filePath, + string destinationDirectory, + Dictionary originalPaths, + CancellationToken token) + { + if (filePath is null) + { + return null; + } + + string hashedName = GetHashedFileName(filePath); + string destinationPath = Path.Combine(destinationDirectory, hashedName); + + File.Copy(filePath, destinationPath, overwrite: true); + + token.ThrowIfCancellationRequested(); + + originalPaths.Add(hashedName, filePath); + + return hashedName; + } + + /// + /// Serializes an input path map to a target directory as a JSON file. + /// + /// The input path map (hashed file name → original file path). + /// The target directory. + /// The name to use for the file with the serialized path map. + public static void CopyPathMapToDirectory( + Dictionary pathMap, + string destinationDirectory, + string fileName) + { + // Create the .json file with the input path map + string jsonFilePath = Path.Combine(destinationDirectory, fileName); + + using Stream jsonStream = File.Create(jsonFilePath); + + // Serialize the path map to the target file + JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); + } + + /// + /// Extracts an input path map from a .zip archive entry. + /// + /// The input path map entry. + /// The deserialized path map (hashed file name → original file path). + /// + /// The value is expected to have the content produced by calls to . + /// + public static Dictionary ExtractPathMap(ZipArchiveEntry pathMapEntry) + { + using Stream stream = pathMapEntry.Open(); + + // Load the mapping with all the original file paths for the included files + return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; + } +} diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs index 185f28dd30..5c0998e4c1 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs @@ -1,19 +1,16 @@ + // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; using System.Threading; -using WindowsRuntime.ImplGenerator.Errors; -using WindowsRuntime.GeneratorCli.Helpers; using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.DebugRepro; +using WindowsRuntime.ImplGenerator.Errors; #pragma warning disable IDE0008 @@ -63,7 +60,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) token.ThrowIfCancellationRequested(); // Load the mappings with all the original file paths for reference .dll-s - Dictionary originalReferenceDllPaths = ExtractPathMap(originalReferenceDllPathsEntry); + Dictionary originalReferenceDllPaths = DebugReproPacker.ExtractPathMap(originalReferenceDllPathsEntry); token.ThrowIfCancellationRequested(); @@ -172,12 +169,12 @@ private static void SaveDebugRepro(ImplGeneratorArgs args) // Add all reference paths with hashed names to the reference subdirectory under the // temporary directory, and store them with the updated names in a list to use to build the .rsp file. - List updatedReferenceDllNames = CopyHashedFilesToDirectory(args.ReferenceAssemblyPaths, referenceDirectory, originalReferenceDllPaths, args.Token); + List updatedReferenceDllNames = DebugReproPacker.CopyHashedFilesToDirectory(args.ReferenceAssemblyPaths, referenceDirectory, originalReferenceDllPaths, args.Token); args.Token.ThrowIfCancellationRequested(); // Hash and copy the output assembly - string outputAssemblyHashedName = CopyHashedFileToDirectory(args.OutputAssemblyPath, tempDirectory, originalReferenceDllPaths, args.Token); + string outputAssemblyHashedName = DebugReproPacker.CopyHashedFileToDirectory(args.OutputAssemblyPath, tempDirectory, originalReferenceDllPaths, args.Token); args.Token.ThrowIfCancellationRequested(); @@ -201,7 +198,7 @@ private static void SaveDebugRepro(ImplGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); // Create the .json file with the reference path map - CopyPathMapToDirectory(originalReferenceDllPaths, tempDirectory, ReferencePathMapFileName); + DebugReproPacker.CopyPathMapToDirectory(originalReferenceDllPaths, tempDirectory, ReferencePathMapFileName); args.Token.ThrowIfCancellationRequested(); @@ -217,118 +214,4 @@ private static void SaveDebugRepro(ImplGeneratorArgs args) // Clean up the temporary directory Directory.Delete(tempDirectory, recursive: true); } - - /// - /// Generates a hashed filename by appending a hash of the original filename. - /// - /// The original file path. - /// The hashed filename. - private static string GetHashedFileName(string filePath) - { - string fileName = Path.GetFileName(Path.Normalize(filePath)); - byte[] utf8Data = Encoding.UTF8.GetBytes(filePath); - byte[] hashData = Shake128.HashData(utf8Data, outputLength: 16); - string hash = Convert.ToHexString(hashData); - - return $"{Path.GetFileNameWithoutExtension(fileName)}_{hash}{Path.GetExtension(fileName)}"; - } - - /// - /// Copies all specified assemblies to a target folder, and returns the list of updated hashed filenames. - /// - /// The input assembly paths. - /// The target directory to copy the assemblies to. - /// A dictionary to store the original paths of the copied assemblies. - /// A cancellation token to monitor for cancellation requests. - /// The list of updated hashed filenames. - private static List CopyHashedFilesToDirectory( - string[] assemblyPaths, - string destinationDirectory, - Dictionary originalPaths, - CancellationToken token) - { - List updatedDllNames = []; - - foreach (string assemblyPath in assemblyPaths) - { - token.ThrowIfCancellationRequested(); - - string hashedName = GetHashedFileName(assemblyPath); - string destinationPath = Path.Combine(destinationDirectory, hashedName); - - File.Copy(assemblyPath, destinationPath, overwrite: true); - - updatedDllNames.Add(hashedName); - originalPaths.Add(hashedName, assemblyPath); - } - - return updatedDllNames; - } - - /// - /// Copies a specified assembly to a target folder. - /// - /// The input assembly paths. - /// The target directory to copy the assembly to. - /// A dictionary to store the original paths of the copied assemblies. - /// A cancellation token to monitor for cancellation requests. - /// The hashed filename. - [return: NotNullIfNotNull(nameof(assemblyPath))] - private static string? CopyHashedFileToDirectory( - string? assemblyPath, - string destinationDirectory, - Dictionary originalPaths, - CancellationToken token) - { - if (assemblyPath is null) - { - return null; - } - - string hashedName = GetHashedFileName(assemblyPath); - string destinationPath = Path.Combine(destinationDirectory, hashedName); - - File.Copy(assemblyPath, destinationPath, overwrite: true); - - token.ThrowIfCancellationRequested(); - - originalPaths.Add(hashedName, assemblyPath); - - return hashedName; - } - - /// - /// Copies an input path map to a target directory, as a serialized JSON file. - /// - /// The input path map. - /// The target directory to copy the assemblies to. - /// The name to use for the file with the serialized path map. - private static void CopyPathMapToDirectory( - Dictionary pathMap, - string destinationDirectory, - string fileName) - { - // Create the .json file with the input path map - string jsonFilePath = Path.Combine(destinationDirectory, fileName); - - using Stream jsonStream = File.Create(jsonFilePath); - - // Serialize the path map to the target file - JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); - } - - /// - /// Extracts an input path from a .zip archive entry. - /// - /// The input path map entry. - /// - /// The value is expected to have the content produced by calls to . - /// - private static Dictionary ExtractPathMap(ZipArchiveEntry pathMapEntry) - { - using Stream stream = pathMapEntry.Open(); - - // Load the mapping with all the original file paths for the included .dll-s - return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; - } } diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs index c8bd598166..7674696757 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs @@ -7,12 +7,9 @@ using System.IO; using System.IO.Compression; using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; using System.Threading; using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Helpers; +using WindowsRuntime.GeneratorCli.DebugRepro; using WindowsRuntime.InteropGenerator.Errors; #pragma warning disable IDE0008 @@ -69,8 +66,8 @@ private static string UnpackDebugRepro(string path, CancellationToken token) token.ThrowIfCancellationRequested(); // Load the mappings with all the original file paths for both reference and implementation .dll-s - Dictionary originalReferenceDllPaths = ExtractPathMap(originalReferenceDllPathsEntry); - Dictionary originalImplementationDllPaths = ExtractPathMap(originalImplementationDllPathsEntry); + Dictionary originalReferenceDllPaths = DebugReproPacker.ExtractPathMap(originalReferenceDllPathsEntry); + Dictionary originalImplementationDllPaths = DebugReproPacker.ExtractPathMap(originalImplementationDllPathsEntry); token.ThrowIfCancellationRequested(); @@ -232,8 +229,8 @@ private static void SaveDebugRepro(InteropGeneratorArgs args) // Add all reference and implementation paths with hashed names to the respective subdirectories under the // temporary directory, and store them with the updated names in a list to use to build the .rsp file. - List updatedReferenceDllNames = CopyHashedFilesToDirectory(args.ReferenceAssemblyPaths, referenceDirectory, originalReferenceDllPaths, args.Token); - List updatedImplementationDllNames = CopyHashedFilesToDirectory(args.ImplementationAssemblyPaths, implementationDirectory, originalImplementationDllPaths, args.Token); + List updatedReferenceDllNames = DebugReproPacker.CopyHashedFilesToDirectory(args.ReferenceAssemblyPaths, referenceDirectory, originalReferenceDllPaths, args.Token); + List updatedImplementationDllNames = DebugReproPacker.CopyHashedFilesToDirectory(args.ImplementationAssemblyPaths, implementationDirectory, originalImplementationDllPaths, args.Token); args.Token.ThrowIfCancellationRequested(); @@ -275,12 +272,12 @@ private static void SaveDebugRepro(InteropGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); // Create the .json file with the reference path map - CopyPathMapToDirectory(originalReferenceDllPaths, tempDirectory, ReferencePathMapFileName); + DebugReproPacker.CopyPathMapToDirectory(originalReferenceDllPaths, tempDirectory, ReferencePathMapFileName); args.Token.ThrowIfCancellationRequested(); // Do the same for the implementation path map - CopyPathMapToDirectory(originalImplementationDllPaths, tempDirectory, ImplementationPathMapFileName); + DebugReproPacker.CopyPathMapToDirectory(originalImplementationDllPaths, tempDirectory, ImplementationPathMapFileName); args.Token.ThrowIfCancellationRequested(); @@ -298,79 +295,41 @@ private static void SaveDebugRepro(InteropGeneratorArgs args) } /// - /// Generates a hashed filename by appending a hash of the original filename. + /// Copies a single specified file to a target folder, with reserved-DLL dedupe. /// - /// The original file path. - /// The hashed filename. - private static string GetHashedFileName(string filePath) - { - string fileName = Path.GetFileName(Path.Normalize(filePath)); - byte[] utf8Data = Encoding.UTF8.GetBytes(filePath); - byte[] hashData = Shake128.HashData(utf8Data, outputLength: 16); - string hash = Convert.ToHexString(hashData); - - return $"{Path.GetFileNameWithoutExtension(fileName)}_{hash}{Path.GetExtension(fileName)}"; - } - - /// - /// Copies all specified assemblies to a target folder, and returns the list of updated hashed filenames. - /// - /// The input assembly paths. - /// The target directory to copy the assemblies to. - /// A dictionary to store the original paths of the copied assemblies. - /// A cancellation token to monitor for cancellation requests. - /// The list of updated hashed filenames. - private static List CopyHashedFilesToDirectory( - string[] assemblyPaths, - string destinationDirectory, - Dictionary originalPaths, - CancellationToken token) - { - List updatedDllNames = []; - - foreach (string assemblyPath in assemblyPaths) - { - token.ThrowIfCancellationRequested(); - - string hashedName = GetHashedFileName(assemblyPath); - string destinationPath = Path.Combine(destinationDirectory, hashedName); - - File.Copy(assemblyPath, destinationPath, overwrite: true); - - updatedDllNames.Add(hashedName); - originalPaths.Add(hashedName, assemblyPath); - } - - return updatedDllNames; - } - - /// - /// Copies a specified assembly to a target folder. - /// - /// The input assembly paths. - /// The target directory to copy the assembly to. - /// A dictionary to store the original paths of the copied assemblies. + /// + /// The interop generator keeps a local variant (instead of using the shared + /// ) + /// to handle the private implementation-detail assemblies (WinRT.Sdk.Projection.dll, WinRT.Sdk.Xaml.Projection.dll, + /// WinRT.Projection.dll, WinRT.Component.dll) that are passed both via the reference set and + /// explicitly via dedicated properties. When the same path is already in the map (under the same hashed name), + /// the copy is skipped; if the hashed name collides but the original paths differ, that's a real bug and we + /// throw . + /// + /// The input file path, or to skip. + /// The target directory to copy the file to. + /// A dictionary to store the original path of the copied file (keyed by hashed file name). /// A cancellation token to monitor for cancellation requests. - /// The hashed filename. - [return: NotNullIfNotNull(nameof(assemblyPath))] + /// The hashed filename, or if was . + [return: NotNullIfNotNull(nameof(filePath))] private static string? CopyHashedFileToDirectory( - string? assemblyPath, + string? filePath, string destinationDirectory, Dictionary originalPaths, CancellationToken token) { - if (assemblyPath is null) + if (filePath is null) { return null; } - string hashedName = GetHashedFileName(assemblyPath); + string hashedName = DebugReproPacker.GetHashedFileName(filePath); // Special case for private implementation detail assemblies (e.g. 'WinRT.Projection.dll') that are // both passed via the reference set, but also explicitly as separate properties. In that case, we // expect that those should already be in the original paths at this point. So we validate that // the path actually matches, and simply do nothing if that's the case, as this is intended. - if (originalPaths.TryGetValue(hashedName, out string? originalPath) && originalPath == assemblyPath) + if (originalPaths.TryGetValue(hashedName, out string? originalPath) && originalPath == filePath) { return hashedName; } @@ -379,7 +338,7 @@ private static List CopyHashedFilesToDirectory( // different path than the one provided to the reference set, which should never happen (it's invalid). if (originalPaths.ContainsKey(hashedName)) { - string fileName = Path.GetFileName(Path.Normalize(assemblyPath)); + string fileName = Path.GetFileName(Path.Normalize(filePath)); throw WellKnownInteropExceptions.ReservedDllOriginalPathMismatchFromDebugRepro(fileName); } @@ -388,47 +347,12 @@ private static List CopyHashedFilesToDirectory( // After validating that the file is unique and should be copied, we can safely do that. We move // this operation to ensure we don't accidentally end up with duplicated .dll-s in the debug repro. - File.Copy(assemblyPath, destinationPath, overwrite: true); + File.Copy(filePath, destinationPath, overwrite: true); token.ThrowIfCancellationRequested(); - originalPaths.Add(hashedName, assemblyPath); + originalPaths.Add(hashedName, filePath); return hashedName; } - - /// - /// Copies an input path map to a target directory, as a serialized JSON file. - /// - /// The input path map. - /// The target directory to copy the assemblies to. - /// The name to use for the file with the serialized path map. - private static void CopyPathMapToDirectory( - Dictionary pathMap, - string destinationDirectory, - string fileName) - { - // Create the .json file with the input path map - string jsonFilePath = Path.Combine(destinationDirectory, fileName); - - using Stream jsonStream = File.Create(jsonFilePath); - - // Serialize the path map to the target file - JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); - } - - /// - /// Extracts an input path from a .zip archive entry. - /// - /// The input path map entry. - /// - /// The value is expected to have the content produced by calls to . - /// - private static Dictionary ExtractPathMap(ZipArchiveEntry pathMapEntry) - { - using Stream stream = pathMapEntry.Open(); - - // Load the mapping with all the original file paths for the included .dll-s - return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; - } -} \ No newline at end of file +} diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs index 65e056c026..a7882bdc4c 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs @@ -6,13 +6,10 @@ using System.IO; using System.IO.Compression; using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; using System.Threading; using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.DebugRepro; using WindowsRuntime.ProjectionGenerator.Errors; -using WindowsRuntime.GeneratorCli.Helpers; using WindowsRuntime.ProjectionWriter.Helpers; #pragma warning disable IDE0008 @@ -100,9 +97,9 @@ .. archive.Entries.Where(entry => token.ThrowIfCancellationRequested(); // Load the mappings with all the original file paths - Dictionary originalReferencePaths = ExtractPathMap(originalReferencePathsEntry); - Dictionary originalWinMDPaths = ExtractPathMap(originalWinMDPathsEntry); - Dictionary originalWindowsMetadataPaths = ExtractPathMap(originalWindowsMetadataPathsEntry); + Dictionary originalReferencePaths = DebugReproPacker.ExtractPathMap(originalReferencePathsEntry); + Dictionary originalWinMDPaths = DebugReproPacker.ExtractPathMap(originalWinMDPathsEntry); + Dictionary originalWindowsMetadataPaths = DebugReproPacker.ExtractPathMap(originalWindowsMetadataPathsEntry); token.ThrowIfCancellationRequested(); @@ -239,8 +236,8 @@ private static void SaveDebugRepro(ProjectionGeneratorArgs args) // Add all reference and .winmd paths with hashed names to the respective subdirectories under the // temporary directory, and store them with the updated names in a list to use to build the .rsp file. - List updatedReferenceNames = CopyHashedFilesToDirectory(args.ReferenceAssemblyPaths, referenceDirectory, originalReferencePaths, args.Token); - List updatedWinMDNames = CopyHashedFilesToDirectory(args.WinMDPaths, winmdDirectory, originalWinMDPaths, args.Token); + List updatedReferenceNames = DebugReproPacker.CopyHashedFilesToDirectory(args.ReferenceAssemblyPaths, referenceDirectory, originalReferencePaths, args.Token); + List updatedWinMDNames = DebugReproPacker.CopyHashedFilesToDirectory(args.WinMDPaths, winmdDirectory, originalWinMDPaths, args.Token); args.Token.ThrowIfCancellationRequested(); @@ -267,7 +264,7 @@ private static void SaveDebugRepro(ProjectionGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); // Bundle the expanded Windows metadata files into the windows-metadata subdirectory - _ = CopyHashedFilesToDirectory([.. expandedWindowsMetadataPaths], windowsMetadataDirectory, originalWindowsMetadataPaths, args.Token); + _ = DebugReproPacker.CopyHashedFilesToDirectory([.. expandedWindowsMetadataPaths], windowsMetadataDirectory, originalWindowsMetadataPaths, args.Token); args.Token.ThrowIfCancellationRequested(); @@ -297,9 +294,9 @@ private static void SaveDebugRepro(ProjectionGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); // Create the .json files with the path maps for each category - CopyPathMapToDirectory(originalReferencePaths, tempDirectory, ReferencePathMapFileName); - CopyPathMapToDirectory(originalWinMDPaths, tempDirectory, WinMDPathMapFileName); - CopyPathMapToDirectory(originalWindowsMetadataPaths, tempDirectory, WindowsMetadataPathMapFileName); + DebugReproPacker.CopyPathMapToDirectory(originalReferencePaths, tempDirectory, ReferencePathMapFileName); + DebugReproPacker.CopyPathMapToDirectory(originalWinMDPaths, tempDirectory, WinMDPathMapFileName); + DebugReproPacker.CopyPathMapToDirectory(originalWindowsMetadataPaths, tempDirectory, WindowsMetadataPathMapFileName); args.Token.ThrowIfCancellationRequested(); @@ -315,86 +312,4 @@ private static void SaveDebugRepro(ProjectionGeneratorArgs args) // Clean up the temporary directory Directory.Delete(tempDirectory, recursive: true); } - - /// - /// Generates a hashed filename by appending a hash of the original filename. - /// - /// The original file path. - /// The hashed filename. - private static string GetHashedFileName(string filePath) - { - string fileName = Path.GetFileName(Path.Normalize(filePath)); - byte[] utf8Data = Encoding.UTF8.GetBytes(filePath); - byte[] hashData = Shake128.HashData(utf8Data, outputLength: 16); - string hash = Convert.ToHexString(hashData); - - return $"{Path.GetFileNameWithoutExtension(fileName)}_{hash}{Path.GetExtension(fileName)}"; - } - - /// - /// Copies all specified files to a target folder, and returns the list of updated hashed filenames. - /// - /// The input file paths. - /// The target directory to copy the files to. - /// A dictionary to store the original paths of the copied files. - /// A cancellation token to monitor for cancellation requests. - /// The list of updated hashed filenames. - private static List CopyHashedFilesToDirectory( - string[] filePaths, - string destinationDirectory, - Dictionary originalPaths, - CancellationToken token) - { - List updatedNames = []; - - foreach (string filePath in filePaths) - { - token.ThrowIfCancellationRequested(); - - string hashedName = GetHashedFileName(filePath); - string destinationPath = Path.Combine(destinationDirectory, hashedName); - - File.Copy(filePath, destinationPath, overwrite: true); - - updatedNames.Add(hashedName); - originalPaths.Add(hashedName, filePath); - } - - return updatedNames; - } - - /// - /// Copies an input path map to a target directory, as a serialized JSON file. - /// - /// The input path map. - /// The target directory to copy the assemblies to. - /// The name to use for the file with the serialized path map. - private static void CopyPathMapToDirectory( - Dictionary pathMap, - string destinationDirectory, - string fileName) - { - // Create the .json file with the input path map - string jsonFilePath = Path.Combine(destinationDirectory, fileName); - - using Stream jsonStream = File.Create(jsonFilePath); - - // Serialize the path map to the target file - JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); - } - - /// - /// Extracts an input path from a .zip archive entry. - /// - /// The input path map entry. - /// - /// The value is expected to have the content produced by calls to . - /// - private static Dictionary ExtractPathMap(ZipArchiveEntry pathMapEntry) - { - using Stream stream = pathMapEntry.Open(); - - // Load the mapping with all the original file paths for the included assemblies - return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; - } } diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs index e6d075ebac..b2329b781c 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs @@ -6,14 +6,11 @@ using System.IO; using System.IO.Compression; using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; using System.Threading; using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.DebugRepro; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ReferenceProjectionGenerator.Errors; -using WindowsRuntime.GeneratorCli.Helpers; #pragma warning disable IDE0008 @@ -63,7 +60,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) token.ThrowIfCancellationRequested(); // Load the mappings with all the original file paths for the input .winmd files - Dictionary originalInputPaths = ExtractPathMap(originalInputPathsEntry); + Dictionary originalInputPaths = DebugReproPacker.ExtractPathMap(originalInputPathsEntry); token.ThrowIfCancellationRequested(); @@ -183,7 +180,7 @@ private static void SaveDebugRepro(ReferenceProjectionGeneratorArgs args) // Add all input paths with hashed names to the input subdirectory under the temporary // directory, and store them with the updated names in a list to use to build the .rsp file. - List updatedInputNames = CopyHashedFilesToDirectory([.. expandedInputPaths], inputDirectory, originalInputPaths, args.Token); + List updatedInputNames = DebugReproPacker.CopyHashedFilesToDirectory([.. expandedInputPaths], inputDirectory, originalInputPaths, args.Token); args.Token.ThrowIfCancellationRequested(); @@ -213,7 +210,7 @@ private static void SaveDebugRepro(ReferenceProjectionGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); // Create the .json file with the input path map - CopyPathMapToDirectory(originalInputPaths, tempDirectory, InputPathMapFileName); + DebugReproPacker.CopyPathMapToDirectory(originalInputPaths, tempDirectory, InputPathMapFileName); args.Token.ThrowIfCancellationRequested(); @@ -229,86 +226,4 @@ private static void SaveDebugRepro(ReferenceProjectionGeneratorArgs args) // Clean up the temporary directory Directory.Delete(tempDirectory, recursive: true); } - - /// - /// Generates a hashed filename by appending a hash of the original filename. - /// - /// The original file path. - /// The hashed filename. - private static string GetHashedFileName(string filePath) - { - string fileName = Path.GetFileName(Path.Normalize(filePath)); - byte[] utf8Data = Encoding.UTF8.GetBytes(filePath); - byte[] hashData = Shake128.HashData(utf8Data, outputLength: 16); - string hash = Convert.ToHexString(hashData); - - return $"{Path.GetFileNameWithoutExtension(fileName)}_{hash}{Path.GetExtension(fileName)}"; - } - - /// - /// Copies all specified files to a target folder, and returns the list of updated hashed filenames. - /// - /// The input file paths. - /// The target directory to copy the files to. - /// A dictionary to store the original paths of the copied files. - /// A cancellation token to monitor for cancellation requests. - /// The list of updated hashed filenames. - private static List CopyHashedFilesToDirectory( - string[] filePaths, - string destinationDirectory, - Dictionary originalPaths, - CancellationToken token) - { - List updatedFileNames = []; - - foreach (string filePath in filePaths) - { - token.ThrowIfCancellationRequested(); - - string hashedName = GetHashedFileName(filePath); - string destinationPath = Path.Combine(destinationDirectory, hashedName); - - File.Copy(filePath, destinationPath, overwrite: true); - - updatedFileNames.Add(hashedName); - originalPaths.Add(hashedName, filePath); - } - - return updatedFileNames; - } - - /// - /// Copies an input path map to a target directory, as a serialized JSON file. - /// - /// The input path map. - /// The target directory to copy the assemblies to. - /// The name to use for the file with the serialized path map. - private static void CopyPathMapToDirectory( - Dictionary pathMap, - string destinationDirectory, - string fileName) - { - // Create the .json file with the input path map - string jsonFilePath = Path.Combine(destinationDirectory, fileName); - - using Stream jsonStream = File.Create(jsonFilePath); - - // Serialize the path map to the target file - JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); - } - - /// - /// Extracts an input path from a .zip archive entry. - /// - /// The input path map entry. - /// - /// The value is expected to have the content produced by calls to . - /// - private static Dictionary ExtractPathMap(ZipArchiveEntry pathMapEntry) - { - using Stream stream = pathMapEntry.Open(); - - // Load the mapping with all the original file paths for the included files - return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; - } } diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs index c4d2fee556..7756150f45 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs @@ -3,17 +3,13 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Text.Json; using System.Threading; using WindowsRuntime.GeneratorCli; +using WindowsRuntime.GeneratorCli.DebugRepro; using WindowsRuntime.WinMDGenerator.Errors; -using WindowsRuntime.GeneratorCli.Helpers; #pragma warning disable IDE0008 @@ -71,7 +67,7 @@ .. archive.Entries.Where(entry => token.ThrowIfCancellationRequested(); // Load the mappings with all the original file paths for reference assemblies - Dictionary originalReferencePaths = ExtractPathMap(originalReferencePathsEntry); + Dictionary originalReferencePaths = DebugReproPacker.ExtractPathMap(originalReferencePathsEntry); token.ThrowIfCancellationRequested(); @@ -184,12 +180,12 @@ private static void SaveDebugRepro(WinMDGeneratorArgs args) // Add all reference paths with hashed names to the reference subdirectory under the // temporary directory, and store them with the updated names in a list to use to build the .rsp file. - List updatedReferenceNames = CopyHashedFilesToDirectory(args.ReferenceAssemblyPaths, referenceDirectory, originalReferencePaths, args.Token); + List updatedReferenceNames = DebugReproPacker.CopyHashedFilesToDirectory(args.ReferenceAssemblyPaths, referenceDirectory, originalReferencePaths, args.Token); args.Token.ThrowIfCancellationRequested(); // Hash and copy the input assembly to the top level temporary directory - string inputAssemblyHashedName = CopyHashedFileToDirectory(args.InputAssemblyPath, tempDirectory, originalReferencePaths, args.Token); + string inputAssemblyHashedName = DebugReproPacker.CopyHashedFileToDirectory(args.InputAssemblyPath, tempDirectory, originalReferencePaths, args.Token); args.Token.ThrowIfCancellationRequested(); @@ -213,7 +209,7 @@ private static void SaveDebugRepro(WinMDGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); // Create the .json file with the reference path map - CopyPathMapToDirectory(originalReferencePaths, tempDirectory, ReferencePathMapFileName); + DebugReproPacker.CopyPathMapToDirectory(originalReferencePaths, tempDirectory, ReferencePathMapFileName); args.Token.ThrowIfCancellationRequested(); @@ -229,118 +225,4 @@ private static void SaveDebugRepro(WinMDGeneratorArgs args) // Clean up the temporary directory Directory.Delete(tempDirectory, recursive: true); } - - /// - /// Generates a hashed filename by appending a hash of the original filename. - /// - /// The original file path. - /// The hashed filename. - private static string GetHashedFileName(string filePath) - { - string fileName = Path.GetFileName(Path.Normalize(filePath)); - byte[] utf8Data = Encoding.UTF8.GetBytes(filePath); - byte[] hashData = Shake128.HashData(utf8Data, outputLength: 16); - string hash = Convert.ToHexString(hashData); - - return $"{Path.GetFileNameWithoutExtension(fileName)}_{hash}{Path.GetExtension(fileName)}"; - } - - /// - /// Copies all specified assemblies to a target folder, and returns the list of updated hashed filenames. - /// - /// The input assembly paths. - /// The target directory to copy the assemblies to. - /// A dictionary to store the original paths of the copied assemblies. - /// A cancellation token to monitor for cancellation requests. - /// The list of updated hashed filenames. - private static List CopyHashedFilesToDirectory( - string[] assemblyPaths, - string destinationDirectory, - Dictionary originalPaths, - CancellationToken token) - { - List updatedNames = []; - - foreach (string assemblyPath in assemblyPaths) - { - token.ThrowIfCancellationRequested(); - - string hashedName = GetHashedFileName(assemblyPath); - string destinationPath = Path.Combine(destinationDirectory, hashedName); - - File.Copy(assemblyPath, destinationPath, overwrite: true); - - updatedNames.Add(hashedName); - originalPaths.Add(hashedName, assemblyPath); - } - - return updatedNames; - } - - /// - /// Copies a specified assembly to a target folder. - /// - /// The input assembly path. - /// The target directory to copy the assembly to. - /// A dictionary to store the original paths of the copied assemblies. - /// A cancellation token to monitor for cancellation requests. - /// The hashed filename. - [return: NotNullIfNotNull(nameof(assemblyPath))] - private static string? CopyHashedFileToDirectory( - string? assemblyPath, - string destinationDirectory, - Dictionary originalPaths, - CancellationToken token) - { - if (assemblyPath is null) - { - return null; - } - - string hashedName = GetHashedFileName(assemblyPath); - string destinationPath = Path.Combine(destinationDirectory, hashedName); - - File.Copy(assemblyPath, destinationPath, overwrite: true); - - token.ThrowIfCancellationRequested(); - - originalPaths.Add(hashedName, assemblyPath); - - return hashedName; - } - - /// - /// Copies an input path map to a target directory, as a serialized JSON file. - /// - /// The input path map. - /// The target directory to copy the assemblies to. - /// The name to use for the file with the serialized path map. - private static void CopyPathMapToDirectory( - Dictionary pathMap, - string destinationDirectory, - string fileName) - { - // Create the .json file with the input path map - string jsonFilePath = Path.Combine(destinationDirectory, fileName); - - using Stream jsonStream = File.Create(jsonFilePath); - - // Serialize the path map to the target file - JsonSerializer.Serialize(jsonStream, pathMap, GeneratorJsonSerializerContext.Default.DictionaryStringString); - } - - /// - /// Extracts an input path from a .zip archive entry. - /// - /// The input path map entry. - /// - /// The value is expected to have the content produced by calls to . - /// - private static Dictionary ExtractPathMap(ZipArchiveEntry pathMapEntry) - { - using Stream stream = pathMapEntry.Open(); - - // Load the mapping with all the original file paths for the included assemblies - return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; - } } From 7179aacb7e9d81ed40ef60b306fc325d33283550 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 19:06:37 -0700 Subject: [PATCH 07/28] Phase 6: extract shared Run entry-point preamble into GeneratorHost.Prepare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 'IGeneratorArgs' (a marker interface with the two well-known properties: 'Token' and 'DebugReproDirectory') and 'GeneratorHost' (in 'WinRT.Generator.Cli/'), both encapsulating the unpack-debug-repro -> parse-response-file -> save-debug-repro preamble that every generator's 'Run' method previously had inline. Each generator's 'Run' method shrinks from ~80 LOC to ~12 LOC: a single call to 'GeneratorHost.Prepare(...)' with per-tool callbacks (unpack/parse/save delegates + 'wrapUnhandled' lambda that constructs the per-tool 'Unhandled*Exception' + 'ConsoleApp.Log' for the progress messages + the tool name for the log strings). All five args records gain ': IGeneratorArgs' — no new properties are required because each already has 'public required CancellationToken Token { get; init; }' and 'public string? DebugReproDirectory { get; init; }' with matching signatures. Behavior preserved exactly: - Same phase names ('unpack-debug-repro', 'parsing', 'save-debug-repro') routed through each tool's per-tool 'Unhandled*Exception' wrapper. - Same log messages, byte-identical (the shared formatter substitutes the tool name via interpolation). - Same cancellation boundaries at each phase. - Same 'isUsingDebugRepro' flag controlling save-on-replay suppression. - Same 'DebugReproDirectory is not null' guard. - 'static (phase, e) => new Unhandled*Exception(phase, e)' lambdas — the 'static' modifier ensures no closure allocations per call. The shared 'GeneratorHost' takes 'Action log' as a parameter (instead of pulling in a ConsoleAppFramework dependency on the shared library), letting consumers pass their existing 'ConsoleApp.Log' method group. Validated end-to-end for all 5 generators: ref-gen 8/8 files 0 diffs, WinMD 5632/5632 bytes 16-byte (MVID-only) delta, projection 327/327 files 0 diffs, impl 257536/257536 bytes 0 byte diffs, interop smoke-tested. All 5 build clean (0 warnings) in Release and AOT-publish cleanly for win-arm64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/WinRT.Generator.Cli/GeneratorHost.cs | 115 ++++++++++++++++++ src/WinRT.Generator.Cli/IGeneratorArgs.cs | 24 ++++ .../Generation/ImplGenerator.cs | 69 ++--------- .../Generation/ImplGeneratorArgs.cs | 3 +- .../Generation/InteropGenerator.cs | 69 ++--------- .../Generation/InteropGeneratorArgs.cs | 3 +- .../Generation/ProjectionGenerator.cs | 69 ++--------- .../Generation/ProjectionGeneratorArgs.cs | 3 +- .../ReferenceProjectionGenerator.cs | 69 ++--------- .../ReferenceProjectionGeneratorArgs.cs | 3 +- .../Generation/WinMDGenerator.cs | 70 ++--------- .../Generation/WinMDGeneratorArgs.cs | 3 +- 12 files changed, 194 insertions(+), 306 deletions(-) create mode 100644 src/WinRT.Generator.Cli/GeneratorHost.cs create mode 100644 src/WinRT.Generator.Cli/IGeneratorArgs.cs diff --git a/src/WinRT.Generator.Cli/GeneratorHost.cs b/src/WinRT.Generator.Cli/GeneratorHost.cs new file mode 100644 index 0000000000..35d405f13f --- /dev/null +++ b/src/WinRT.Generator.Cli/GeneratorHost.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Threading; +using WindowsRuntime.GeneratorCli.Errors; + +namespace WindowsRuntime.GeneratorCli; + +/// +/// Shared Run entry-point scaffold for the CsWinRT CLI generators. +/// +/// +/// Each generator's Run method historically opened with an identical preamble: +/// +/// If the input file path looks like a .zip, unpack a debug repro and re-route to the extracted .rsp. +/// Parse the response file into a per-tool args record. +/// If DebugReproDirectory is set and we are not already replaying, save a debug repro of the current invocation. +/// +/// encapsulates that preamble. Each generator's Run now starts with a +/// single call to it; the per-tool unpack / save / parse logic is supplied via delegates so behavior +/// stays identical (same log messages, same exception phases, same per-tool unhandled exception type). +/// +internal static class GeneratorHost +{ + /// + /// Runs the shared unpack → parse → save preamble for a CsWinRT CLI generator. + /// + /// The per-tool args record (must implement ). + /// The input file path (response file or debug-repro .zip). + /// The tool name used in messages (e.g. "cswinrtimplgen"). + /// Extracts a debug-repro .zip to a temp folder and returns the rewritten response file path. + /// Parses a response file at the given path into . + /// Saves a debug repro of the current invocation (called only when is set and we are not already replaying). + /// Wraps an unexpected exception into the per-tool Unhandled*Exception with the given phase name. + /// Logs a progress message to the user (typically ConsoleApp.Log from ConsoleAppFramework). + /// The token for the operation. + /// The parsed instance. + public static TArgs Prepare( + string inputFilePath, + string toolName, + Func unpackDebugRepro, + Func parseFromResponseFile, + Action saveDebugRepro, + Func wrapUnhandled, + Action log, + CancellationToken token) + where TArgs : IGeneratorArgs + { + string responseFilePath = inputFilePath; + bool isUsingDebugRepro = false; + + // Load the debug repro to investigate with, if we have one + try + { + // If the input file path is a .zip, treat it as a debug repro and unpack + // it. Any other extension is treated as a regular response file. + if (Path.GetExtension(Path.Normalize(inputFilePath)) == ".zip") + { + log($"Unpacking input '{toolName}' debug repro"); + + isUsingDebugRepro = true; + + // If we unpacked a debug repro, we'll also replace the input file + // path with the extracted response file from the input repro. + responseFilePath = unpackDebugRepro(inputFilePath, token); + } + } + catch (Exception e) when (!e.IsWellKnown) + { + throw wrapUnhandled("unpack-debug-repro", e); + } + + token.ThrowIfCancellationRequested(); + + TArgs args; + + // Parse the actual arguments from the response file + try + { + args = parseFromResponseFile(responseFilePath, token); + } + catch (Exception e) when (!e.IsWellKnown) + { + throw wrapUnhandled("parsing", e); + } + + args.Token.ThrowIfCancellationRequested(); + + // Save a debug repro, if needed + try + { + // If no debug repro directory was provided, we have nothing to do. + // This is fully expected, it just means no debug repro is needed. + // We also skip this if we're currently processing an input debug + // repro, as there would be no point in creating a new one from that. + if (args.DebugReproDirectory is not null && !isUsingDebugRepro) + { + log($"Saving '{toolName}' debug repro"); + + saveDebugRepro(args); + } + } + catch (Exception e) when (!e.IsWellKnown) + { + throw wrapUnhandled("save-debug-repro", e); + } + + args.Token.ThrowIfCancellationRequested(); + + return args; + } +} + diff --git a/src/WinRT.Generator.Cli/IGeneratorArgs.cs b/src/WinRT.Generator.Cli/IGeneratorArgs.cs new file mode 100644 index 0000000000..ab7cf2eb4a --- /dev/null +++ b/src/WinRT.Generator.Cli/IGeneratorArgs.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading; + +namespace WindowsRuntime.GeneratorCli; + +/// +/// Common surface implemented by every per-tool args record (e.g. ImplGeneratorArgs, +/// InteropGeneratorArgs) so the shared entry-point scaffold +/// can dispatch through a couple of well-known properties. +/// +internal interface IGeneratorArgs +{ + /// + /// Gets the token for the operation. + /// + CancellationToken Token { get; } + + /// + /// Gets the directory where the debug repro should be written, if requested. + /// + string? DebugReproDirectory { get; } +} diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index 41b30d4403..9dc87e23eb 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -67,66 +67,15 @@ internal static partial class ImplGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - string responseFilePath = inputFilePath; - bool isUsingDebugRepro = false; - - // Load the debug repro to investigate with, if we have one - try - { - // If no debug repro directory was provided, we have nothing to do. - // This is fully expected, it just means no debug repro is needed. - if (Path.GetExtension(Path.Normalize(inputFilePath)) == ".zip") - { - ConsoleApp.Log("Unpacking input 'cswinrtimplgen' debug repro"); - - isUsingDebugRepro = true; - - // If we unpacked a debug repro, we'll also replace the input file - // path with the extracted response file from the input repro. - responseFilePath = UnpackDebugRepro(inputFilePath, token); - } - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledImplException("unpack-debug-repro", e); - } - - token.ThrowIfCancellationRequested(); - - ImplGeneratorArgs args; - - // Parse the actual arguments from the response file - try - { - args = ImplGeneratorArgs.ParseFromResponseFile(responseFilePath, token); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledImplException("parsing", e); - } - - args.Token.ThrowIfCancellationRequested(); - - // Save a debug repro, if needed - try - { - // If no debug repro directory was provided, we have nothing to do. - // This is fully expected, it just means no debug repro is needed. - // We also skip this if we're currently processing an input debug - // repro, as there would be no point in creating a new one from that. - if (args.DebugReproDirectory is not null && !isUsingDebugRepro) - { - ConsoleApp.Log("Saving 'cswinrtimplgen' debug repro"); - - SaveDebugRepro(args); - } - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledImplException("save-debug-repro", e); - } - - args.Token.ThrowIfCancellationRequested(); + ImplGeneratorArgs args = GeneratorHost.Prepare( + inputFilePath: inputFilePath, + toolName: "cswinrtimplgen", + unpackDebugRepro: UnpackDebugRepro, + parseFromResponseFile: ImplGeneratorArgs.ParseFromResponseFile, + saveDebugRepro: SaveDebugRepro, + wrapUnhandled: static (phase, e) => new UnhandledImplException(phase, e), + log: ConsoleApp.Log, + token: token); RuntimeContext runtimeContext; ModuleDefinition outputModule; diff --git a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs index b2b0939d0a..631b533f21 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.GeneratorCli.Attributes; using WindowsRuntime.GeneratorCli.Parsing; using WindowsRuntime.ImplGenerator.Errors; @@ -12,7 +13,7 @@ namespace WindowsRuntime.ImplGenerator.Generation; /// /// Input parameters for . /// -internal sealed class ImplGeneratorArgs +internal sealed class ImplGeneratorArgs : IGeneratorArgs { /// Gets the input .dll paths. [CommandLineArgumentName("--reference-assembly-paths")] diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs index 8b403b83d0..7277ad8743 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs @@ -24,66 +24,15 @@ internal static partial class InteropGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - string responseFilePath = inputFilePath; - bool isUsingDebugRepro = false; - - // Load the debug repro to investigate with, if we have one - try - { - // If no debug repro directory was provided, we have nothing to do. - // This is fully expected, it just means no debug repro is needed. - if (Path.GetExtension(Path.Normalize(inputFilePath)) == ".zip") - { - ConsoleApp.Log("Unpacking input 'cswinrtinteropgen' debug repro"); - - isUsingDebugRepro = true; - - // If we unpacked a debug repro, we'll also replace the input file - // path with the extracted response file from the input repro. - responseFilePath = UnpackDebugRepro(inputFilePath, token); - } - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledInteropException("unpack-debug-repro", e); - } - - token.ThrowIfCancellationRequested(); - - InteropGeneratorArgs args; - - // Parse the actual arguments from the response file - try - { - args = InteropGeneratorArgs.ParseFromResponseFile(responseFilePath, token); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledInteropException("parsing", e); - } - - args.Token.ThrowIfCancellationRequested(); - - // Save a debug repro, if needed - try - { - // If no debug repro directory was provided, we have nothing to do. - // This is fully expected, it just means no debug repro is needed. - // We also skip this if we're currently processing an input debug - // repro, as there would be no point in creating a new one from that. - if (args.DebugReproDirectory is not null && !isUsingDebugRepro) - { - ConsoleApp.Log("Saving 'cswinrtinteropgen' debug repro"); - - SaveDebugRepro(args); - } - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledInteropException("save-debug-repro", e); - } - - args.Token.ThrowIfCancellationRequested(); + InteropGeneratorArgs args = GeneratorHost.Prepare( + inputFilePath: inputFilePath, + toolName: "cswinrtinteropgen", + unpackDebugRepro: UnpackDebugRepro, + parseFromResponseFile: InteropGeneratorArgs.ParseFromResponseFile, + saveDebugRepro: SaveDebugRepro, + wrapUnhandled: static (phase, e) => new UnhandledInteropException(phase, e), + log: ConsoleApp.Log, + token: token); InteropGeneratorDiscoveryState discoveryState; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs index 82ec1fa69a..8f5e363d06 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.GeneratorCli.Attributes; using WindowsRuntime.GeneratorCli.Parsing; using WindowsRuntime.InteropGenerator.Errors; @@ -12,7 +13,7 @@ namespace WindowsRuntime.InteropGenerator.Generation; /// /// Input parameters for . /// -internal sealed class InteropGeneratorArgs +internal sealed class InteropGeneratorArgs : IGeneratorArgs { /// Gets the input reference .dll paths. [CommandLineArgumentName("--reference-assembly-paths")] diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index 04d3dff603..bf5faade49 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -23,66 +23,15 @@ internal static partial class ProjectionGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - string responseFilePath = inputFilePath; - bool isUsingDebugRepro = false; - - // Load the debug repro to investigate with, if we have one - try - { - // If no debug repro directory was provided, we have nothing to do. - // This is fully expected, it just means no debug repro is needed. - if (Path.GetExtension(Path.Normalize(inputFilePath)) == ".zip") - { - ConsoleApp.Log("Unpacking input 'cswinrtprojectiongen' debug repro"); - - isUsingDebugRepro = true; - - // If we unpacked a debug repro, we'll also replace the input file - // path with the extracted response file from the input repro. - responseFilePath = UnpackDebugRepro(inputFilePath, token); - } - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledProjectionGeneratorException("unpack-debug-repro", e); - } - - token.ThrowIfCancellationRequested(); - - ProjectionGeneratorArgs args; - - // Parse the actual arguments from the response file - try - { - args = ProjectionGeneratorArgs.ParseFromResponseFile(responseFilePath, token); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledProjectionGeneratorException("parsing", e); - } - - args.Token.ThrowIfCancellationRequested(); - - // Save a debug repro, if needed - try - { - // If no debug repro directory was provided, we have nothing to do. - // This is fully expected, it just means no debug repro is needed. - // We also skip this if we're currently processing an input debug - // repro, as there would be no point in creating a new one from that. - if (args.DebugReproDirectory is not null && !isUsingDebugRepro) - { - ConsoleApp.Log("Saving 'cswinrtprojectiongen' debug repro"); - - SaveDebugRepro(args); - } - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledProjectionGeneratorException("save-debug-repro", e); - } - - args.Token.ThrowIfCancellationRequested(); + ProjectionGeneratorArgs args = GeneratorHost.Prepare( + inputFilePath: inputFilePath, + toolName: "cswinrtprojectiongen", + unpackDebugRepro: UnpackDebugRepro, + parseFromResponseFile: ProjectionGeneratorArgs.ParseFromResponseFile, + saveDebugRepro: SaveDebugRepro, + wrapUnhandled: static (phase, e) => new UnhandledProjectionGeneratorException(phase, e), + log: ConsoleApp.Log, + token: token); ProjectionGeneratorProcessingState processingState; diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs index 05725c448f..2b62005b12 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.IO; using System.Threading; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.GeneratorCli.Attributes; using WindowsRuntime.GeneratorCli.Parsing; using WindowsRuntime.ProjectionGenerator.Errors; @@ -13,7 +14,7 @@ namespace WindowsRuntime.ProjectionGenerator.Generation; /// /// Input parameters for . /// -internal sealed class ProjectionGeneratorArgs +internal sealed class ProjectionGeneratorArgs : IGeneratorArgs { /// Gets the input .dll paths. [CommandLineArgumentName("--reference-assembly-paths")] diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index 6c29efd1f0..a12d457ad0 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -28,66 +28,15 @@ internal static partial class ReferenceProjectionGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - string responseFilePath = inputFilePath; - bool isUsingDebugRepro = false; - - // Load the debug repro to investigate with, if we have one - try - { - // If no debug repro directory was provided, we have nothing to do. - // This is fully expected, it just means no debug repro is needed. - if (Path.GetExtension(Path.Normalize(inputFilePath)) == ".zip") - { - ConsoleApp.Log("Unpacking input 'cswinrtprojectionrefgen' debug repro"); - - isUsingDebugRepro = true; - - // If we unpacked a debug repro, we'll also replace the input file - // path with the extracted response file from the input repro. - responseFilePath = UnpackDebugRepro(inputFilePath, token); - } - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledReferenceProjectionGeneratorException("unpack-debug-repro", e); - } - - token.ThrowIfCancellationRequested(); - - ReferenceProjectionGeneratorArgs args; - - // Parse the actual arguments from the response file - try - { - args = ReferenceProjectionGeneratorArgs.ParseFromResponseFile(responseFilePath, token); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledReferenceProjectionGeneratorException("parsing", e); - } - - args.Token.ThrowIfCancellationRequested(); - - // Save a debug repro, if needed - try - { - // If no debug repro directory was provided, we have nothing to do. - // This is fully expected, it just means no debug repro is needed. - // We also skip this if we're currently processing an input debug - // repro, as there would be no point in creating a new one from that. - if (args.DebugReproDirectory is not null && !isUsingDebugRepro) - { - ConsoleApp.Log("Saving 'cswinrtprojectionrefgen' debug repro"); - - SaveDebugRepro(args); - } - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledReferenceProjectionGeneratorException("save-debug-repro", e); - } - - args.Token.ThrowIfCancellationRequested(); + ReferenceProjectionGeneratorArgs args = GeneratorHost.Prepare( + inputFilePath: inputFilePath, + toolName: "cswinrtprojectionrefgen", + unpackDebugRepro: UnpackDebugRepro, + parseFromResponseFile: ReferenceProjectionGeneratorArgs.ParseFromResponseFile, + saveDebugRepro: SaveDebugRepro, + wrapUnhandled: static (phase, e) => new UnhandledReferenceProjectionGeneratorException(phase, e), + log: ConsoleApp.Log, + token: token); // Validate the target framework. CsWinRT 3.0 requires .NET 10 or later. if (!string.IsNullOrEmpty(args.TargetFramework) && !args.TargetFramework.StartsWith("net10.0", StringComparison.Ordinal)) diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs index 5e3a2a7ea7..7fcc4ff175 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.GeneratorCli.Attributes; using WindowsRuntime.GeneratorCli.Parsing; using WindowsRuntime.ReferenceProjectionGenerator.Errors; @@ -12,7 +13,7 @@ namespace WindowsRuntime.ReferenceProjectionGenerator.Generation; /// /// Input parameters for . /// -internal sealed class ReferenceProjectionGeneratorArgs +internal sealed class ReferenceProjectionGeneratorArgs : IGeneratorArgs { /// Gets the input .winmd paths (files, directories to recursively scan, or special /// tokens like "local", "sdk", "sdk+", or a version like "10.0.26100.0"). diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs index 42366e7ad4..5136966af6 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.IO; using System.Threading; using ConsoleAppFramework; using WindowsRuntime.GeneratorCli; @@ -39,66 +38,15 @@ internal static partial class WinMDGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - string responseFilePath = inputFilePath; - bool isUsingDebugRepro = false; - - // Load the debug repro to investigate with, if we have one - try - { - // If no debug repro directory was provided, we have nothing to do. - // This is fully expected, it just means no debug repro is needed. - if (Path.GetExtension(Path.Normalize(inputFilePath)) == ".zip") - { - ConsoleApp.Log("Unpacking input 'cswinrtwinmdgen' debug repro"); - - isUsingDebugRepro = true; - - // If we unpacked a debug repro, we'll also replace the input file - // path with the extracted response file from the input repro. - responseFilePath = UnpackDebugRepro(inputFilePath, token); - } - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledWinMDException("unpack-debug-repro", e); - } - - token.ThrowIfCancellationRequested(); - - WinMDGeneratorArgs args; - - // Parse the arguments from the response file - try - { - args = WinMDGeneratorArgs.ParseFromResponseFile(responseFilePath, token); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledWinMDException("parsing", e); - } - - token.ThrowIfCancellationRequested(); - - // Save a debug repro, if needed - try - { - // If no debug repro directory was provided, we have nothing to do. - // This is fully expected, it just means no debug repro is needed. - // We also skip this if we're currently processing an input debug - // repro, as there would be no point in creating a new one from that. - if (args.DebugReproDirectory is not null && !isUsingDebugRepro) - { - ConsoleApp.Log("Saving 'cswinrtwinmdgen' debug repro"); - - SaveDebugRepro(args); - } - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledWinMDException("save-debug-repro", e); - } - - args.Token.ThrowIfCancellationRequested(); + WinMDGeneratorArgs args = GeneratorHost.Prepare( + inputFilePath: inputFilePath, + toolName: "cswinrtwinmdgen", + unpackDebugRepro: UnpackDebugRepro, + parseFromResponseFile: WinMDGeneratorArgs.ParseFromResponseFile, + saveDebugRepro: SaveDebugRepro, + wrapUnhandled: static (phase, e) => new UnhandledWinMDException(phase, e), + log: ConsoleApp.Log, + token: token); // Discover the types to process WinMDGeneratorDiscoveryState discoveryState; diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs index e89610784e..e52d74502f 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading; +using WindowsRuntime.GeneratorCli; using WindowsRuntime.GeneratorCli.Attributes; using WindowsRuntime.GeneratorCli.Parsing; using WindowsRuntime.WinMDGenerator.Errors; @@ -12,7 +13,7 @@ namespace WindowsRuntime.WinMDGenerator.Generation; /// /// Input parameters for . /// -internal sealed class WinMDGeneratorArgs +internal sealed class WinMDGeneratorArgs : IGeneratorArgs { /// Gets the path to the compiled input assembly (.dll) to analyze. [CommandLineArgumentName("--input-assembly-path")] From 848cd1feb4c4464614cac84d64104ac86522bcbe Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 22:37:56 -0700 Subject: [PATCH 08/28] Rename WinRT.Generator.Cli to WinRT.Generator.Core Move shared generator infrastructure into a core project. Several source files were relocated from src/WinRT.Generator.Cli/* to src/WinRT.Generator.Core/*, the project file was renamed and its metadata updated (Description, AssemblyTitle, RootNamespace, removal of AssemblyName), dependent generator project references were updated to point at WinRT.Generator.Core.csproj, and the solution file was adjusted to reference the new project path. --- .../Attributes/CommandLineArgumentNameAttribute.cs | 0 .../DebugRepro/DebugReproPacker.cs | 0 .../Errors/GeneratorExceptionExtensions.cs | 0 .../Errors/IGeneratorErrorFactory.cs | 0 .../Errors/UnhandledGeneratorException.cs | 0 .../Errors/WellKnownGeneratorException.cs | 0 .../Extensions/FileExtensions.cs | 0 .../Extensions/PathExtensions.cs | 0 .../GeneratorHost.cs | 0 .../Helpers/GeneratorJsonSerializerContext.cs | 0 .../IGeneratorArgs.cs | 0 .../Parsing/ResponseFileBuilder.cs | 0 .../Parsing/ResponseFileParser.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../WinRT.Generator.Core.csproj} | 14 +++++--------- .../WinRT.Impl.Generator.csproj | 2 +- .../WinRT.Interop.Generator.csproj | 2 +- .../WinRT.Projection.Generator.csproj | 2 +- .../WinRT.Projection.Ref.Generator.csproj | 2 +- .../WinRT.WinMD.Generator.csproj | 2 +- src/cswinrt.slnx | 2 +- 21 files changed, 11 insertions(+), 15 deletions(-) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Attributes/CommandLineArgumentNameAttribute.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/DebugRepro/DebugReproPacker.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Errors/GeneratorExceptionExtensions.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Errors/IGeneratorErrorFactory.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Errors/UnhandledGeneratorException.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Errors/WellKnownGeneratorException.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Extensions/FileExtensions.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Extensions/PathExtensions.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/GeneratorHost.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Helpers/GeneratorJsonSerializerContext.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/IGeneratorArgs.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Parsing/ResponseFileBuilder.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Parsing/ResponseFileParser.cs (100%) rename src/{WinRT.Generator.Cli => WinRT.Generator.Core}/Properties/AssemblyInfo.cs (100%) rename src/{WinRT.Generator.Cli/WinRT.Generator.Cli.csproj => WinRT.Generator.Core/WinRT.Generator.Core.csproj} (68%) diff --git a/src/WinRT.Generator.Cli/Attributes/CommandLineArgumentNameAttribute.cs b/src/WinRT.Generator.Core/Attributes/CommandLineArgumentNameAttribute.cs similarity index 100% rename from src/WinRT.Generator.Cli/Attributes/CommandLineArgumentNameAttribute.cs rename to src/WinRT.Generator.Core/Attributes/CommandLineArgumentNameAttribute.cs diff --git a/src/WinRT.Generator.Cli/DebugRepro/DebugReproPacker.cs b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs similarity index 100% rename from src/WinRT.Generator.Cli/DebugRepro/DebugReproPacker.cs rename to src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs diff --git a/src/WinRT.Generator.Cli/Errors/GeneratorExceptionExtensions.cs b/src/WinRT.Generator.Core/Errors/GeneratorExceptionExtensions.cs similarity index 100% rename from src/WinRT.Generator.Cli/Errors/GeneratorExceptionExtensions.cs rename to src/WinRT.Generator.Core/Errors/GeneratorExceptionExtensions.cs diff --git a/src/WinRT.Generator.Cli/Errors/IGeneratorErrorFactory.cs b/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs similarity index 100% rename from src/WinRT.Generator.Cli/Errors/IGeneratorErrorFactory.cs rename to src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs diff --git a/src/WinRT.Generator.Cli/Errors/UnhandledGeneratorException.cs b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs similarity index 100% rename from src/WinRT.Generator.Cli/Errors/UnhandledGeneratorException.cs rename to src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs diff --git a/src/WinRT.Generator.Cli/Errors/WellKnownGeneratorException.cs b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorException.cs similarity index 100% rename from src/WinRT.Generator.Cli/Errors/WellKnownGeneratorException.cs rename to src/WinRT.Generator.Core/Errors/WellKnownGeneratorException.cs diff --git a/src/WinRT.Generator.Cli/Extensions/FileExtensions.cs b/src/WinRT.Generator.Core/Extensions/FileExtensions.cs similarity index 100% rename from src/WinRT.Generator.Cli/Extensions/FileExtensions.cs rename to src/WinRT.Generator.Core/Extensions/FileExtensions.cs diff --git a/src/WinRT.Generator.Cli/Extensions/PathExtensions.cs b/src/WinRT.Generator.Core/Extensions/PathExtensions.cs similarity index 100% rename from src/WinRT.Generator.Cli/Extensions/PathExtensions.cs rename to src/WinRT.Generator.Core/Extensions/PathExtensions.cs diff --git a/src/WinRT.Generator.Cli/GeneratorHost.cs b/src/WinRT.Generator.Core/GeneratorHost.cs similarity index 100% rename from src/WinRT.Generator.Cli/GeneratorHost.cs rename to src/WinRT.Generator.Core/GeneratorHost.cs diff --git a/src/WinRT.Generator.Cli/Helpers/GeneratorJsonSerializerContext.cs b/src/WinRT.Generator.Core/Helpers/GeneratorJsonSerializerContext.cs similarity index 100% rename from src/WinRT.Generator.Cli/Helpers/GeneratorJsonSerializerContext.cs rename to src/WinRT.Generator.Core/Helpers/GeneratorJsonSerializerContext.cs diff --git a/src/WinRT.Generator.Cli/IGeneratorArgs.cs b/src/WinRT.Generator.Core/IGeneratorArgs.cs similarity index 100% rename from src/WinRT.Generator.Cli/IGeneratorArgs.cs rename to src/WinRT.Generator.Core/IGeneratorArgs.cs diff --git a/src/WinRT.Generator.Cli/Parsing/ResponseFileBuilder.cs b/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs similarity index 100% rename from src/WinRT.Generator.Cli/Parsing/ResponseFileBuilder.cs rename to src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs diff --git a/src/WinRT.Generator.Cli/Parsing/ResponseFileParser.cs b/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs similarity index 100% rename from src/WinRT.Generator.Cli/Parsing/ResponseFileParser.cs rename to src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs diff --git a/src/WinRT.Generator.Cli/Properties/AssemblyInfo.cs b/src/WinRT.Generator.Core/Properties/AssemblyInfo.cs similarity index 100% rename from src/WinRT.Generator.Cli/Properties/AssemblyInfo.cs rename to src/WinRT.Generator.Core/Properties/AssemblyInfo.cs diff --git a/src/WinRT.Generator.Cli/WinRT.Generator.Cli.csproj b/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj similarity index 68% rename from src/WinRT.Generator.Cli/WinRT.Generator.Cli.csproj rename to src/WinRT.Generator.Core/WinRT.Generator.Core.csproj index ac750676c2..55ac42ef9a 100644 --- a/src/WinRT.Generator.Cli/WinRT.Generator.Cli.csproj +++ b/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj @@ -8,8 +8,8 @@ true - C#/WinRT Generator CLI shared infrastructure v$(VersionString) - C#/WinRT Generator CLI shared infrastructure v$(VersionString) + C#/WinRT Generator core shared infrastructure v$(VersionString) + C#/WinRT Generator core shared infrastructure v$(VersionString) Copyright (c) Microsoft Corporation. All rights reserved. $(AssemblyVersionNumber) $(VersionNumber) @@ -17,13 +17,10 @@ true - - WindowsRuntime.GeneratorCli + + WindowsRuntime.Generator - - WinRT.Generator.Cli - - + true true latest @@ -32,5 +29,4 @@ strict true - diff --git a/src/WinRT.Impl.Generator/WinRT.Impl.Generator.csproj b/src/WinRT.Impl.Generator/WinRT.Impl.Generator.csproj index 41670c9cf9..c34119d260 100644 --- a/src/WinRT.Impl.Generator/WinRT.Impl.Generator.csproj +++ b/src/WinRT.Impl.Generator/WinRT.Impl.Generator.csproj @@ -56,7 +56,7 @@ - + diff --git a/src/WinRT.Interop.Generator/WinRT.Interop.Generator.csproj b/src/WinRT.Interop.Generator/WinRT.Interop.Generator.csproj index 9c7335aa9c..f3a7ff2e70 100644 --- a/src/WinRT.Interop.Generator/WinRT.Interop.Generator.csproj +++ b/src/WinRT.Interop.Generator/WinRT.Interop.Generator.csproj @@ -84,7 +84,7 @@ - + diff --git a/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj b/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj index 499db8b939..0068c222ce 100644 --- a/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj +++ b/src/WinRT.Projection.Generator/WinRT.Projection.Generator.csproj @@ -54,7 +54,7 @@ - + diff --git a/src/WinRT.Projection.Ref.Generator/WinRT.Projection.Ref.Generator.csproj b/src/WinRT.Projection.Ref.Generator/WinRT.Projection.Ref.Generator.csproj index 7cca4ba52e..6f68a999b9 100644 --- a/src/WinRT.Projection.Ref.Generator/WinRT.Projection.Ref.Generator.csproj +++ b/src/WinRT.Projection.Ref.Generator/WinRT.Projection.Ref.Generator.csproj @@ -51,7 +51,7 @@ - + diff --git a/src/WinRT.WinMD.Generator/WinRT.WinMD.Generator.csproj b/src/WinRT.WinMD.Generator/WinRT.WinMD.Generator.csproj index 812b791760..b2bb0f6dff 100644 --- a/src/WinRT.WinMD.Generator/WinRT.WinMD.Generator.csproj +++ b/src/WinRT.WinMD.Generator/WinRT.WinMD.Generator.csproj @@ -56,7 +56,7 @@ - + diff --git a/src/cswinrt.slnx b/src/cswinrt.slnx index fda1706ba4..8d8143675a 100644 --- a/src/cswinrt.slnx +++ b/src/cswinrt.slnx @@ -416,7 +416,7 @@ - + From 0533632df7bcc3bbffe2b8479b823124b65713f0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 22:38:55 -0700 Subject: [PATCH 09/28] Rename GeneratorCli namespace to Generator Refactor namespaces and usings: replace WindowsRuntime.GeneratorCli.* with WindowsRuntime.Generator.* (and related sub-namespaces like Attributes, DebugRepro, Errors, Helpers, Parsing, Extensions) across the core library and all generator projects (Impl, Interop, Projection, ReferenceProjection, WinMD). This unifies naming/imports across many files; no behavioral logic changes were made. --- .../Attributes/CommandLineArgumentNameAttribute.cs | 2 +- src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs | 4 ++-- .../Errors/GeneratorExceptionExtensions.cs | 2 +- src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs | 2 +- .../Errors/UnhandledGeneratorException.cs | 2 +- .../Errors/WellKnownGeneratorException.cs | 2 +- src/WinRT.Generator.Core/Extensions/FileExtensions.cs | 2 +- src/WinRT.Generator.Core/Extensions/PathExtensions.cs | 2 +- src/WinRT.Generator.Core/GeneratorHost.cs | 4 ++-- .../Helpers/GeneratorJsonSerializerContext.cs | 2 +- src/WinRT.Generator.Core/IGeneratorArgs.cs | 2 +- src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs | 4 ++-- src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs | 6 +++--- src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs | 2 +- src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs | 2 +- src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs | 2 +- .../Generation/ImplGenerator.DebugRepro.cs | 4 ++-- src/WinRT.Impl.Generator/Generation/ImplGenerator.cs | 4 ++-- src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs | 6 +++--- .../Builders/IgnoresAccessChecksToBuilder.cs | 2 +- .../Builders/WindowsRuntimeTypeHierarchyBuilder.cs | 2 +- .../Errors/UnhandledInteropException.cs | 2 +- .../Errors/WellKnownInteropException.cs | 2 +- .../Errors/WellKnownInteropExceptions.cs | 2 +- .../Generation/InteropGenerator.DebugRepro.cs | 4 ++-- .../Generation/InteropGenerator.Discover.cs | 2 +- .../Generation/InteropGenerator.Emit.cs | 4 ++-- src/WinRT.Interop.Generator/Generation/InteropGenerator.cs | 4 ++-- .../Generation/InteropGeneratorArgs.cs | 6 +++--- .../Errors/UnhandledProjectionGeneratorException.cs | 2 +- .../Errors/WellKnownProjectionGeneratorException.cs | 2 +- .../Errors/WellKnownProjectionGeneratorExceptions.cs | 2 +- .../Generation/ProjectionGenerator.DebugRepro.cs | 4 ++-- .../Generation/ProjectionGenerator.Emit.cs | 2 +- .../Generation/ProjectionGenerator.Generate.cs | 2 +- .../Generation/ProjectionGenerator.cs | 4 ++-- .../Generation/ProjectionGeneratorArgs.cs | 6 +++--- .../UnhandledReferenceProjectionGeneratorException.cs | 2 +- .../WellKnownReferenceProjectionGeneratorException.cs | 2 +- .../WellKnownReferenceProjectionGeneratorExceptions.cs | 2 +- .../Generation/ReferenceProjectionGenerator.DebugRepro.cs | 4 ++-- .../Generation/ReferenceProjectionGenerator.cs | 4 ++-- .../Generation/ReferenceProjectionGeneratorArgs.cs | 6 +++--- src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs | 2 +- src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs | 2 +- .../Errors/WellKnownWinMDExceptions.cs | 2 +- .../Generation/WinMDGenerator.DebugRepro.cs | 4 ++-- src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs | 4 ++-- src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs | 6 +++--- 49 files changed, 75 insertions(+), 75 deletions(-) diff --git a/src/WinRT.Generator.Core/Attributes/CommandLineArgumentNameAttribute.cs b/src/WinRT.Generator.Core/Attributes/CommandLineArgumentNameAttribute.cs index 80e905933c..ca36c6346f 100644 --- a/src/WinRT.Generator.Core/Attributes/CommandLineArgumentNameAttribute.cs +++ b/src/WinRT.Generator.Core/Attributes/CommandLineArgumentNameAttribute.cs @@ -3,7 +3,7 @@ using System; -namespace WindowsRuntime.GeneratorCli.Attributes; +namespace WindowsRuntime.Generator.Attributes; /// /// An attribute indicating the name of a given command line argument. diff --git a/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs index 1e2d011da8..db68eaef15 100644 --- a/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs +++ b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs @@ -10,9 +10,9 @@ using System.Text; using System.Text.Json; using System.Threading; -using WindowsRuntime.GeneratorCli.Helpers; +using WindowsRuntime.Generator.Helpers; -namespace WindowsRuntime.GeneratorCli.DebugRepro; +namespace WindowsRuntime.Generator.DebugRepro; /// /// Leaf helpers shared across the CsWinRT CLI generators for packaging and unpacking debug repros. diff --git a/src/WinRT.Generator.Core/Errors/GeneratorExceptionExtensions.cs b/src/WinRT.Generator.Core/Errors/GeneratorExceptionExtensions.cs index f2f645dd17..ab7cc82890 100644 --- a/src/WinRT.Generator.Core/Errors/GeneratorExceptionExtensions.cs +++ b/src/WinRT.Generator.Core/Errors/GeneratorExceptionExtensions.cs @@ -3,7 +3,7 @@ using System; -namespace WindowsRuntime.GeneratorCli.Errors; +namespace WindowsRuntime.Generator.Errors; /// /// Shared extensions for CsWinRT CLI generator exceptions. diff --git a/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs b/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs index cb0d29cd92..21f31ef861 100644 --- a/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs +++ b/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs @@ -3,7 +3,7 @@ using System; -namespace WindowsRuntime.GeneratorCli.Errors; +namespace WindowsRuntime.Generator.Errors; /// /// Routes shared logical errors through the per-tool well-known exception factory. diff --git a/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs index a6f8a8656d..10baf76686 100644 --- a/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs +++ b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs @@ -3,7 +3,7 @@ using System; -namespace WindowsRuntime.GeneratorCli.Errors; +namespace WindowsRuntime.Generator.Errors; /// /// An unhandled exception for a CsWinRT CLI generator. diff --git a/src/WinRT.Generator.Core/Errors/WellKnownGeneratorException.cs b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorException.cs index c95874623c..d65c0e9016 100644 --- a/src/WinRT.Generator.Core/Errors/WellKnownGeneratorException.cs +++ b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorException.cs @@ -3,7 +3,7 @@ using System; -namespace WindowsRuntime.GeneratorCli.Errors; +namespace WindowsRuntime.Generator.Errors; /// /// A well-known exception for a CsWinRT CLI generator. diff --git a/src/WinRT.Generator.Core/Extensions/FileExtensions.cs b/src/WinRT.Generator.Core/Extensions/FileExtensions.cs index 8429bb01b6..02ac39d6fc 100644 --- a/src/WinRT.Generator.Core/Extensions/FileExtensions.cs +++ b/src/WinRT.Generator.Core/Extensions/FileExtensions.cs @@ -5,7 +5,7 @@ using System.IO; using System.Text; -namespace WindowsRuntime.GeneratorCli; +namespace WindowsRuntime.Generator; /// /// Extensions for . diff --git a/src/WinRT.Generator.Core/Extensions/PathExtensions.cs b/src/WinRT.Generator.Core/Extensions/PathExtensions.cs index 3f468411fc..b4afd3b725 100644 --- a/src/WinRT.Generator.Core/Extensions/PathExtensions.cs +++ b/src/WinRT.Generator.Core/Extensions/PathExtensions.cs @@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; -namespace WindowsRuntime.GeneratorCli; +namespace WindowsRuntime.Generator; /// /// Extensions for the type. diff --git a/src/WinRT.Generator.Core/GeneratorHost.cs b/src/WinRT.Generator.Core/GeneratorHost.cs index 35d405f13f..ba60c250f7 100644 --- a/src/WinRT.Generator.Core/GeneratorHost.cs +++ b/src/WinRT.Generator.Core/GeneratorHost.cs @@ -4,9 +4,9 @@ using System; using System.IO; using System.Threading; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; -namespace WindowsRuntime.GeneratorCli; +namespace WindowsRuntime.Generator; /// /// Shared Run entry-point scaffold for the CsWinRT CLI generators. diff --git a/src/WinRT.Generator.Core/Helpers/GeneratorJsonSerializerContext.cs b/src/WinRT.Generator.Core/Helpers/GeneratorJsonSerializerContext.cs index 4ab8c7431f..88ea3b6d25 100644 --- a/src/WinRT.Generator.Core/Helpers/GeneratorJsonSerializerContext.cs +++ b/src/WinRT.Generator.Core/Helpers/GeneratorJsonSerializerContext.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace WindowsRuntime.GeneratorCli.Helpers; +namespace WindowsRuntime.Generator.Helpers; /// /// A for types used across the CsWinRT CLI generators. diff --git a/src/WinRT.Generator.Core/IGeneratorArgs.cs b/src/WinRT.Generator.Core/IGeneratorArgs.cs index ab7cf2eb4a..34c2a1be87 100644 --- a/src/WinRT.Generator.Core/IGeneratorArgs.cs +++ b/src/WinRT.Generator.Core/IGeneratorArgs.cs @@ -3,7 +3,7 @@ using System.Threading; -namespace WindowsRuntime.GeneratorCli; +namespace WindowsRuntime.Generator; /// /// Common surface implemented by every per-tool args record (e.g. ImplGeneratorArgs, diff --git a/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs b/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs index cb013f90ba..00e2699e39 100644 --- a/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs +++ b/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs @@ -9,9 +9,9 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading; -using WindowsRuntime.GeneratorCli.Attributes; +using WindowsRuntime.Generator.Attributes; -namespace WindowsRuntime.GeneratorCli.Parsing; +namespace WindowsRuntime.Generator.Parsing; /// /// Formats an args record into a response file by reflecting over its diff --git a/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs b/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs index a52a5cb2dc..f635d005de 100644 --- a/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs +++ b/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs @@ -10,10 +10,10 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Attributes; +using WindowsRuntime.Generator.Errors; -namespace WindowsRuntime.GeneratorCli.Parsing; +namespace WindowsRuntime.Generator.Parsing; /// /// Parses a response file into a strongly-typed args record by reflecting over its diff --git a/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs b/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs index 4180d465a9..4d1f987fba 100644 --- a/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs +++ b/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.ImplGenerator.Errors; diff --git a/src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs b/src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs index 26211d7680..7a2be1f116 100644 --- a/src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs +++ b/src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.ImplGenerator.Errors; diff --git a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs index 9b9e8a3afd..960ee08993 100644 --- a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs +++ b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.ImplGenerator.Errors; diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs index 5c0998e4c1..7b2c0ad0c0 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs @@ -8,8 +8,8 @@ using System.IO.Compression; using System.Linq; using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.DebugRepro; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.DebugRepro; using WindowsRuntime.ImplGenerator.Errors; #pragma warning disable IDE0008 diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index 9dc87e23eb..14833e3dcc 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -17,8 +17,8 @@ using AsmResolver.PE; using AsmResolver.PE.DotNet.StrongName; using ConsoleAppFramework; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.ImplGenerator.Errors; using WindowsRuntime.ImplGenerator.References; diff --git a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs index 631b533f21..2113e33040 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs @@ -3,9 +3,9 @@ using System.IO; using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.GeneratorCli.Parsing; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Attributes; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ImplGenerator.Errors; namespace WindowsRuntime.ImplGenerator.Generation; diff --git a/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs b/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs index 190e1e6e7c..59c480334e 100644 --- a/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs +++ b/src/WinRT.Interop.Generator/Builders/IgnoresAccessChecksToBuilder.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.IO; using AsmResolver.DotNet; -using WindowsRuntime.GeneratorCli; +using WindowsRuntime.Generator; using WindowsRuntime.InteropGenerator.Factories; using WindowsRuntime.InteropGenerator.References; diff --git a/src/WinRT.Interop.Generator/Builders/WindowsRuntimeTypeHierarchyBuilder.cs b/src/WinRT.Interop.Generator/Builders/WindowsRuntimeTypeHierarchyBuilder.cs index b8b8b156ff..aabdfe8ed1 100644 --- a/src/WinRT.Interop.Generator/Builders/WindowsRuntimeTypeHierarchyBuilder.cs +++ b/src/WinRT.Interop.Generator/Builders/WindowsRuntimeTypeHierarchyBuilder.cs @@ -15,7 +15,7 @@ using AsmResolver.PE.DotNet.Metadata.Tables; using CommunityToolkit.HighPerformance; using CommunityToolkit.HighPerformance.Buffers; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.References; using static AsmResolver.PE.DotNet.Cil.CilOpCodes; diff --git a/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs index 42100e1489..d42e6216d4 100644 --- a/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs +++ b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.InteropGenerator.Errors; diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs index 0b51a63386..d5676957f8 100644 --- a/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs +++ b/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs @@ -6,7 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using System.Text; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.InteropGenerator.Errors; diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs index c44251b95f..3f9e6c721f 100644 --- a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs +++ b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs @@ -8,7 +8,7 @@ using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Cil; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.InteropGenerator.Fixups; namespace WindowsRuntime.InteropGenerator.Errors; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs index 7674696757..f7358bd46e 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs @@ -8,8 +8,8 @@ using System.IO.Compression; using System.Linq; using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.DebugRepro; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.DebugRepro; using WindowsRuntime.InteropGenerator.Errors; #pragma warning disable IDE0008 diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index 1f8fb5870c..1608a1ed40 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -10,7 +10,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.PE; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.InteropGenerator.Discovery; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Models; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs index 95ac0a289f..24da3772da 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs @@ -8,8 +8,8 @@ using System.Linq; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.InteropGenerator.Builders; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Fixups; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs index 7277ad8743..c9168f1b4e 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs @@ -5,8 +5,8 @@ using System.IO; using System.Threading; using ConsoleAppFramework; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.References; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs index 8f5e363d06..71760b6d3e 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs @@ -3,9 +3,9 @@ using System.IO; using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.GeneratorCli.Parsing; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Attributes; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.InteropGenerator.Errors; namespace WindowsRuntime.InteropGenerator.Generation; diff --git a/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs b/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs index 2dd6341434..75bff109be 100644 --- a/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.ProjectionGenerator.Errors; diff --git a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorException.cs b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorException.cs index 08f2b34396..64d32601ac 100644 --- a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorException.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.ProjectionGenerator.Errors; diff --git a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs index 6a7f49e3e2..5a6c98cc04 100644 --- a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs +++ b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.ProjectionGenerator.Errors; diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs index a7882bdc4c..11ee418cf1 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs @@ -7,8 +7,8 @@ using System.IO.Compression; using System.Linq; using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.DebugRepro; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.DebugRepro; using WindowsRuntime.ProjectionGenerator.Errors; using WindowsRuntime.ProjectionWriter.Helpers; diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs index a98697489e..71ad974fbe 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs @@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs index 7738b274ca..3b2e71e2f5 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs @@ -7,7 +7,7 @@ using System.Linq; using AsmResolver; using AsmResolver.DotNet; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.ProjectionGenerator.Errors; using WindowsRuntime.ProjectionWriter; using WindowsRuntime.ProjectionWriter.Helpers; diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index bf5faade49..d9100ecaab 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -5,8 +5,8 @@ using System.IO; using System.Threading; using ConsoleAppFramework; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs index 2b62005b12..9f05e0f1eb 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs @@ -4,9 +4,9 @@ using System.ComponentModel; using System.IO; using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.GeneratorCli.Parsing; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Attributes; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; diff --git a/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs b/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs index a5d40ab0f8..7c46c60ce8 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.ReferenceProjectionGenerator.Errors; diff --git a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorException.cs b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorException.cs index d0a185b9d3..8f66cfa80f 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorException.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.ReferenceProjectionGenerator.Errors; diff --git a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs index 148ecd4892..b5ae41ee1b 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.ReferenceProjectionGenerator.Errors; diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs index b2329b781c..4fa7a171f2 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs @@ -7,8 +7,8 @@ using System.IO.Compression; using System.Linq; using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.DebugRepro; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.DebugRepro; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ReferenceProjectionGenerator.Errors; diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index a12d457ad0..b89fffe4ba 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -6,8 +6,8 @@ using System.IO; using System.Threading; using ConsoleAppFramework; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.ProjectionWriter; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ReferenceProjectionGenerator.Errors; diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs index 7fcc4ff175..067dbb6bd5 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs @@ -3,9 +3,9 @@ using System.IO; using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.GeneratorCli.Parsing; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Attributes; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ReferenceProjectionGenerator.Errors; namespace WindowsRuntime.ReferenceProjectionGenerator.Generation; diff --git a/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs b/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs index be79a022cc..7faae43b4c 100644 --- a/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs +++ b/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.WinMDGenerator.Errors; diff --git a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs index 8dd0f0552f..2ccce2e233 100644 --- a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs +++ b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.WinMDGenerator.Errors; diff --git a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs index b155bda160..58a0678f00 100644 --- a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs +++ b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using System; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator.Errors; namespace WindowsRuntime.WinMDGenerator.Errors; diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs index 7756150f45..f70bcea95c 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs @@ -7,8 +7,8 @@ using System.IO.Compression; using System.Linq; using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.DebugRepro; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.DebugRepro; using WindowsRuntime.WinMDGenerator.Errors; #pragma warning disable IDE0008 diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs index 5136966af6..13d419494d 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs @@ -4,8 +4,8 @@ using System; using System.Threading; using ConsoleAppFramework; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Errors; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.WinMDGenerator.Errors; namespace WindowsRuntime.WinMDGenerator.Generation; diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs index e52d74502f..2cf21fae65 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs @@ -3,9 +3,9 @@ using System.IO; using System.Threading; -using WindowsRuntime.GeneratorCli; -using WindowsRuntime.GeneratorCli.Attributes; -using WindowsRuntime.GeneratorCli.Parsing; +using WindowsRuntime.Generator; +using WindowsRuntime.Generator.Attributes; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.WinMDGenerator.Errors; namespace WindowsRuntime.WinMDGenerator.Generation; From cece6be719f6e4d355107e2a946352310a798c25 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 22:47:24 -0700 Subject: [PATCH 10/28] Use for generator args docs Replace explicit XML comments with on the Token and DebugReproDirectory properties to inherit documentation and reduce duplication. Applied to generator argument classes: ImplGeneratorArgs, InteropGeneratorArgs, ProjectionGeneratorArgs, ReferenceProjectionGeneratorArgs, and WinMDGeneratorArgs (five files). --- src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs | 4 ++-- .../Generation/InteropGeneratorArgs.cs | 4 ++-- .../Generation/ProjectionGeneratorArgs.cs | 4 ++-- .../Generation/ReferenceProjectionGeneratorArgs.cs | 4 ++-- src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs index 2113e33040..edbd0cbc85 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs @@ -35,10 +35,10 @@ internal sealed class ImplGeneratorArgs : IGeneratorArgs [CommandLineArgumentName("--assembly-originator-key-file")] public string? AssemblyOriginatorKeyFile { get; init; } - /// Gets the token for the operation. + /// public required CancellationToken Token { get; init; } - /// Gets the directory to use to place the debug repro, if requested. + /// [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs index 71760b6d3e..2597cb9ab2 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs @@ -71,10 +71,10 @@ internal sealed class InteropGeneratorArgs : IGeneratorArgs [CommandLineArgumentName("--max-degrees-of-parallelism")] public required int MaxDegreesOfParallelism { get; init; } - /// Gets the token for the operation. + /// public required CancellationToken Token { get; init; } - /// Gets the directory to use to place the debug repro, if requested. + /// [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs index 9f05e0f1eb..0b7dd8ba7f 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs @@ -60,10 +60,10 @@ internal sealed class ProjectionGeneratorArgs : IGeneratorArgs [CommandLineArgumentName("--max-degrees-of-parallelism")] public required int MaxDegreesOfParallelism { get; init; } - /// Gets the token for the operation. + /// public required CancellationToken Token { get; init; } - /// Gets the directory to use to place the debug repro, if requested. + /// [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs index 067dbb6bd5..2d7c486307 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs @@ -60,10 +60,10 @@ internal sealed class ReferenceProjectionGeneratorArgs : IGeneratorArgs [CommandLineArgumentName("--reference-projection")] public bool ReferenceProjection { get; init; } - /// Gets the token for the operation. + /// public required CancellationToken Token { get; init; } - /// Gets the directory to use to place the debug repro, if requested. + /// [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs index 2cf21fae65..264797ac7b 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs @@ -35,10 +35,10 @@ internal sealed class WinMDGeneratorArgs : IGeneratorArgs [CommandLineArgumentName("--use-windows-ui-xaml-projections")] public required bool UseWindowsUIXamlProjections { get; init; } - /// Gets the token for the operation. + /// public required CancellationToken Token { get; init; } - /// Gets the directory to use to place the debug repro, if requested. + /// [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } From 0c0b5d4e92bdf895ef4eab255fc8e0fce7dfe2b2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 23:06:36 -0700 Subject: [PATCH 11/28] Remove ParseFromResponseFile/FormatToResponseFile wrappers from args types Each of the 5 generator '*Args' types had three thin one-liner wrappers ('ParseFromResponseFile(string)', 'ParseFromResponseFile(Stream)', 'FormatToResponseFile()') that forwarded straight to the shared 'ResponseFileParser.Parse' and 'ResponseFileBuilder.Format' APIs. They contributed more XML doc lines than actual code, with no behavioral value beyond closure over the per-tool 'WellKnown*Exceptions' factory. Delete the 15 wrappers (3 per args type) and inline all call sites: * '*Generator.cs' 'parseFromResponseFile:' callback now uses the parser method group directly ('ResponseFileParser.Parse') instead of forwarding through the args type. * '*Generator.DebugRepro.cs' stream-parse call sites use the same parser method directly, and format call sites use 'ResponseFileBuilder.Format' directly. * Unused 'System.IO', 'WindowsRuntime.Generator.Parsing' and per-tool 'Errors' usings removed from each '*Args.cs'; the parsing using is added to the DebugRepro and Generator files that now need it. * Repoint the broken XML cref in 'WinMDGenerator' and update the MSBuild comment in 'WinRT.Internal.csproj' that named the deleted method. Net diff: 15 files, +52 / -207 LOC. All 5 generators still build with zero warnings (Release) and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Generation/ImplGenerator.DebugRepro.cs | 11 +++--- .../Generation/ImplGenerator.cs | 3 +- .../Generation/ImplGeneratorArgs.cs | 35 +------------------ src/WinRT.Internal/WinRT.Internal.csproj | 5 +-- .../Generation/InteropGenerator.DebugRepro.cs | 11 +++--- .../Generation/InteropGenerator.cs | 3 +- .../Generation/InteropGeneratorArgs.cs | 35 +------------------ .../ProjectionGenerator.DebugRepro.cs | 11 +++--- .../Generation/ProjectionGenerator.cs | 3 +- .../Generation/ProjectionGeneratorArgs.cs | 35 +------------------ ...ReferenceProjectionGenerator.DebugRepro.cs | 11 +++--- .../ReferenceProjectionGenerator.cs | 3 +- .../ReferenceProjectionGeneratorArgs.cs | 35 +------------------ .../Generation/WinMDGenerator.DebugRepro.cs | 11 +++--- .../Generation/WinMDGenerator.cs | 5 +-- .../Generation/WinMDGeneratorArgs.cs | 35 +------------------ 16 files changed, 49 insertions(+), 203 deletions(-) diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs index 7b2c0ad0c0..7027c9802d 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs @@ -10,6 +10,7 @@ using System.Threading; using WindowsRuntime.Generator; using WindowsRuntime.Generator.DebugRepro; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ImplGenerator.Errors; #pragma warning disable IDE0008 @@ -54,7 +55,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) // Parse the debug repro .rsp file using (Stream stream = responseFileEntry.Open()) { - args = ImplGeneratorArgs.ParseFromResponseFile(stream, token); + args = ResponseFileParser.Parse(stream, token); } token.ThrowIfCancellationRequested(); @@ -115,7 +116,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) token.ThrowIfCancellationRequested(); // Prepare the .rsp file with all updated arguments - string rspText = new ImplGeneratorArgs + string rspText = ResponseFileBuilder.Format(new ImplGeneratorArgs { ReferenceAssemblyPaths = [.. referencePaths], OutputAssemblyPath = outputAssemblyPath!, @@ -124,7 +125,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) AssemblyOriginatorKeyFile = args.AssemblyOriginatorKeyFile, DebugReproDirectory = null, Token = CancellationToken.None - }.FormatToResponseFile(); + }); // Create the actual .rsp file string rspFilePath = Path.Combine(tempDirectory, "cswinrtimplgen.rsp"); @@ -179,7 +180,7 @@ private static void SaveDebugRepro(ImplGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); // Prepare the .rsp file with all updated arguments - string rspText = new ImplGeneratorArgs + string rspText = ResponseFileBuilder.Format(new ImplGeneratorArgs { ReferenceAssemblyPaths = [.. updatedReferenceDllNames], OutputAssemblyPath = outputAssemblyHashedName, @@ -188,7 +189,7 @@ private static void SaveDebugRepro(ImplGeneratorArgs args) AssemblyOriginatorKeyFile = args.AssemblyOriginatorKeyFile, DebugReproDirectory = args.DebugReproDirectory, Token = CancellationToken.None - }.FormatToResponseFile(); + }); // Create the actual .rsp file string rspFilePath = Path.Combine(tempDirectory, "cswinrtimplgen.rsp"); diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index 14833e3dcc..a6e2e389a7 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -19,6 +19,7 @@ using ConsoleAppFramework; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ImplGenerator.Errors; using WindowsRuntime.ImplGenerator.References; @@ -71,7 +72,7 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) inputFilePath: inputFilePath, toolName: "cswinrtimplgen", unpackDebugRepro: UnpackDebugRepro, - parseFromResponseFile: ImplGeneratorArgs.ParseFromResponseFile, + parseFromResponseFile: ResponseFileParser.Parse, saveDebugRepro: SaveDebugRepro, wrapUnhandled: static (phase, e) => new UnhandledImplException(phase, e), log: ConsoleApp.Log, diff --git a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs index edbd0cbc85..d85ec1dd2e 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs @@ -1,12 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.IO; using System.Threading; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Attributes; -using WindowsRuntime.Generator.Parsing; -using WindowsRuntime.ImplGenerator.Errors; namespace WindowsRuntime.ImplGenerator.Generation; @@ -41,35 +38,5 @@ internal sealed class ImplGeneratorArgs : IGeneratorArgs /// [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } - - /// - /// Parses an instance from a response file at the given path. - /// - /// The path to the response file (optionally prefixed with @). - /// The cancellation token for the operation. - /// The resulting instance. - public static ImplGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) - { - return ResponseFileParser.Parse(path, token); - } - - /// - /// Parses an instance from a response file read from a stream. - /// - /// The stream containing the response file content. - /// The cancellation token for the operation. - /// The resulting instance. - public static ImplGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) - { - return ResponseFileParser.Parse(stream, token); - } - - /// - /// Formats the current instance into a response file text. - /// - /// The resulting response file text. - public string FormatToResponseFile() - { - return ResponseFileBuilder.Format(this); - } } + diff --git a/src/WinRT.Internal/WinRT.Internal.csproj b/src/WinRT.Internal/WinRT.Internal.csproj index b8f65df35d..cf114c6f0e 100644 --- a/src/WinRT.Internal/WinRT.Internal.csproj +++ b/src/WinRT.Internal/WinRT.Internal.csproj @@ -135,8 +135,9 @@ <_CsWinRTInternalWinMDInputAssemblyPath>@(IntermediateAssembly->'%(FullPath)') diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs index f7358bd46e..e5309cf44c 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs @@ -10,6 +10,7 @@ using System.Threading; using WindowsRuntime.Generator; using WindowsRuntime.Generator.DebugRepro; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.InteropGenerator.Errors; #pragma warning disable IDE0008 @@ -60,7 +61,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) // Parse the debug repro .rsp file using (Stream stream = responseFileEntry.Open()) { - args = InteropGeneratorArgs.ParseFromResponseFile(stream, token); + args = ResponseFileParser.Parse(stream, token); } token.ThrowIfCancellationRequested(); @@ -163,7 +164,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) token.ThrowIfCancellationRequested(); // Prepare the .rsp file with all updated arguments - string rspText = new InteropGeneratorArgs + string rspText = ResponseFileBuilder.Format(new InteropGeneratorArgs { ReferenceAssemblyPaths = [.. referencePaths], ImplementationAssemblyPaths = [.. implementationPaths], @@ -181,7 +182,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) MaxDegreesOfParallelism = args.MaxDegreesOfParallelism, DebugReproDirectory = null, Token = CancellationToken.None - }.FormatToResponseFile(); + }); // Create the actual .rsp file string rspFilePath = Path.Combine(tempDirectory, "cswinrtinteropgen.rsp"); @@ -244,7 +245,7 @@ private static void SaveDebugRepro(InteropGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); // Prepare the .rsp file with all updated arguments - string rspText = new InteropGeneratorArgs + string rspText = ResponseFileBuilder.Format(new InteropGeneratorArgs { ReferenceAssemblyPaths = [.. updatedReferenceDllNames], ImplementationAssemblyPaths = [.. updatedImplementationDllNames], @@ -262,7 +263,7 @@ private static void SaveDebugRepro(InteropGeneratorArgs args) MaxDegreesOfParallelism = args.MaxDegreesOfParallelism, DebugReproDirectory = args.DebugReproDirectory, Token = CancellationToken.None - }.FormatToResponseFile(); + }); // Create the actual .rsp file string rspFilePath = Path.Combine(tempDirectory, "cswinrtinteropgen.rsp"); diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs index c9168f1b4e..44693e753d 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs @@ -7,6 +7,7 @@ using ConsoleAppFramework; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.References; @@ -28,7 +29,7 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) inputFilePath: inputFilePath, toolName: "cswinrtinteropgen", unpackDebugRepro: UnpackDebugRepro, - parseFromResponseFile: InteropGeneratorArgs.ParseFromResponseFile, + parseFromResponseFile: ResponseFileParser.Parse, saveDebugRepro: SaveDebugRepro, wrapUnhandled: static (phase, e) => new UnhandledInteropException(phase, e), log: ConsoleApp.Log, diff --git a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs index 2597cb9ab2..baf3434fed 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs @@ -1,12 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.IO; using System.Threading; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Attributes; -using WindowsRuntime.Generator.Parsing; -using WindowsRuntime.InteropGenerator.Errors; namespace WindowsRuntime.InteropGenerator.Generation; @@ -77,35 +74,5 @@ internal sealed class InteropGeneratorArgs : IGeneratorArgs /// [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } - - /// - /// Parses an instance from a response file at the given path. - /// - /// The path to the response file (optionally prefixed with @). - /// The cancellation token for the operation. - /// The resulting instance. - public static InteropGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) - { - return ResponseFileParser.Parse(path, token); - } - - /// - /// Parses an instance from a response file read from a stream. - /// - /// The stream containing the response file content. - /// The cancellation token for the operation. - /// The resulting instance. - public static InteropGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) - { - return ResponseFileParser.Parse(stream, token); - } - - /// - /// Formats the current instance into a response file text. - /// - /// The resulting response file text. - public string FormatToResponseFile() - { - return ResponseFileBuilder.Format(this); - } } + diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs index 11ee418cf1..22d3816e1a 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs @@ -9,6 +9,7 @@ using System.Threading; using WindowsRuntime.Generator; using WindowsRuntime.Generator.DebugRepro; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ProjectionGenerator.Errors; using WindowsRuntime.ProjectionWriter.Helpers; @@ -91,7 +92,7 @@ .. archive.Entries.Where(entry => // Parse the debug repro .rsp file using (Stream stream = responseFileEntry.Open()) { - args = ProjectionGeneratorArgs.ParseFromResponseFile(stream, token); + args = ResponseFileParser.Parse(stream, token); } token.ThrowIfCancellationRequested(); @@ -172,7 +173,7 @@ .. archive.Entries.Where(entry => // Prepare the .rsp file with all updated arguments. The 'WindowsMetadata' value points at the // bundled folder, which the writer scans recursively to pick up all the .winmd files it contains. - string rspText = new ProjectionGeneratorArgs + string rspText = ResponseFileBuilder.Format(new ProjectionGeneratorArgs { ReferenceAssemblyPaths = [.. referencePaths], GeneratedAssemblyDirectory = tempDirectory, @@ -185,7 +186,7 @@ .. archive.Entries.Where(entry => MaxDegreesOfParallelism = args.MaxDegreesOfParallelism, DebugReproDirectory = null, Token = CancellationToken.None - }.FormatToResponseFile(); + }); // Create the actual .rsp file string rspFilePath = Path.Combine(tempDirectory, "cswinrtprojectiongen.rsp"); @@ -271,7 +272,7 @@ private static void SaveDebugRepro(ProjectionGeneratorArgs args) // Prepare the .rsp file with all updated arguments. The 'WindowsMetadata' value is just the // subfolder name (relative path); the replay run resolves it to an absolute path inside its // own temporary unpack directory, since the original 'DebugReproDirectory' may not exist there. - string rspText = new ProjectionGeneratorArgs + string rspText = ResponseFileBuilder.Format(new ProjectionGeneratorArgs { ReferenceAssemblyPaths = [.. updatedReferenceNames], GeneratedAssemblyDirectory = args.GeneratedAssemblyDirectory, @@ -284,7 +285,7 @@ private static void SaveDebugRepro(ProjectionGeneratorArgs args) MaxDegreesOfParallelism = args.MaxDegreesOfParallelism, DebugReproDirectory = args.DebugReproDirectory, Token = CancellationToken.None - }.FormatToResponseFile(); + }); // Create the actual .rsp file string rspFilePath = Path.Combine(tempDirectory, "cswinrtprojectiongen.rsp"); diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index d9100ecaab..4844f07463 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -7,6 +7,7 @@ using ConsoleAppFramework; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; @@ -27,7 +28,7 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) inputFilePath: inputFilePath, toolName: "cswinrtprojectiongen", unpackDebugRepro: UnpackDebugRepro, - parseFromResponseFile: ProjectionGeneratorArgs.ParseFromResponseFile, + parseFromResponseFile: ResponseFileParser.Parse, saveDebugRepro: SaveDebugRepro, wrapUnhandled: static (phase, e) => new UnhandledProjectionGeneratorException(phase, e), log: ConsoleApp.Log, diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs index 0b7dd8ba7f..e01fb8d7fa 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs @@ -2,12 +2,9 @@ // Licensed under the MIT License. using System.ComponentModel; -using System.IO; using System.Threading; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Attributes; -using WindowsRuntime.Generator.Parsing; -using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; @@ -66,35 +63,5 @@ internal sealed class ProjectionGeneratorArgs : IGeneratorArgs /// [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } - - /// - /// Parses a instance from a response file at the given path. - /// - /// The path to the response file (optionally prefixed with @). - /// The cancellation token for the operation. - /// The resulting instance. - public static ProjectionGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) - { - return ResponseFileParser.Parse(path, token); - } - - /// - /// Parses a instance from a response file read from a stream. - /// - /// The stream containing the response file content. - /// The cancellation token for the operation. - /// The resulting instance. - public static ProjectionGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) - { - return ResponseFileParser.Parse(stream, token); - } - - /// - /// Formats the current instance into a response file text. - /// - /// The resulting response file text. - public string FormatToResponseFile() - { - return ResponseFileBuilder.Format(this); - } } + diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs index 4fa7a171f2..1358d044e9 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs @@ -9,6 +9,7 @@ using System.Threading; using WindowsRuntime.Generator; using WindowsRuntime.Generator.DebugRepro; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ReferenceProjectionGenerator.Errors; @@ -54,7 +55,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) // Parse the debug repro .rsp file using (Stream stream = responseFileEntry.Open()) { - args = ReferenceProjectionGeneratorArgs.ParseFromResponseFile(stream, token); + args = ResponseFileParser.Parse(stream, token); } token.ThrowIfCancellationRequested(); @@ -107,7 +108,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) token.ThrowIfCancellationRequested(); // Prepare the .rsp file with all updated arguments - string rspText = new ReferenceProjectionGeneratorArgs + string rspText = ResponseFileBuilder.Format(new ReferenceProjectionGeneratorArgs { InputPaths = [.. inputPaths], OutputDirectory = tempDirectory, @@ -122,7 +123,7 @@ private static string UnpackDebugRepro(string path, CancellationToken token) ReferenceProjection = args.ReferenceProjection, DebugReproDirectory = null, Token = CancellationToken.None - }.FormatToResponseFile(); + }); // Create the actual .rsp file string rspFilePath = Path.Combine(tempDirectory, "cswinrtprojectionrefgen.rsp"); @@ -185,7 +186,7 @@ private static void SaveDebugRepro(ReferenceProjectionGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); // Prepare the .rsp file with all updated arguments - string rspText = new ReferenceProjectionGeneratorArgs + string rspText = ResponseFileBuilder.Format(new ReferenceProjectionGeneratorArgs { InputPaths = [.. updatedInputNames], OutputDirectory = args.OutputDirectory, @@ -200,7 +201,7 @@ private static void SaveDebugRepro(ReferenceProjectionGeneratorArgs args) ReferenceProjection = args.ReferenceProjection, DebugReproDirectory = args.DebugReproDirectory, Token = CancellationToken.None - }.FormatToResponseFile(); + }); // Create the actual .rsp file string rspFilePath = Path.Combine(tempDirectory, "cswinrtprojectionrefgen.rsp"); diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index b89fffe4ba..990f39a1ef 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -8,6 +8,7 @@ using ConsoleAppFramework; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ProjectionWriter; using WindowsRuntime.ProjectionWriter.Helpers; using WindowsRuntime.ReferenceProjectionGenerator.Errors; @@ -32,7 +33,7 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) inputFilePath: inputFilePath, toolName: "cswinrtprojectionrefgen", unpackDebugRepro: UnpackDebugRepro, - parseFromResponseFile: ReferenceProjectionGeneratorArgs.ParseFromResponseFile, + parseFromResponseFile: ResponseFileParser.Parse, saveDebugRepro: SaveDebugRepro, wrapUnhandled: static (phase, e) => new UnhandledReferenceProjectionGeneratorException(phase, e), log: ConsoleApp.Log, diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs index 2d7c486307..508ed77844 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs @@ -1,12 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.IO; using System.Threading; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Attributes; -using WindowsRuntime.Generator.Parsing; -using WindowsRuntime.ReferenceProjectionGenerator.Errors; namespace WindowsRuntime.ReferenceProjectionGenerator.Generation; @@ -66,36 +63,6 @@ internal sealed class ReferenceProjectionGeneratorArgs : IGeneratorArgs /// [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } - - /// - /// Parses a instance from a response file at the given path. - /// - /// The path to the response file (optionally prefixed with @). - /// The cancellation token for the operation. - /// The resulting instance. - public static ReferenceProjectionGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) - { - return ResponseFileParser.Parse(path, token); - } - - /// - /// Parses a instance from a response file read from a stream. - /// - /// The stream containing the response file content. - /// The cancellation token for the operation. - /// The resulting instance. - public static ReferenceProjectionGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) - { - return ResponseFileParser.Parse(stream, token); - } - - /// - /// Formats the current instance into a response file text. - /// - /// The resulting response file text. - public string FormatToResponseFile() - { - return ResponseFileBuilder.Format(this); - } } + diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs index f70bcea95c..49834c4124 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs @@ -9,6 +9,7 @@ using System.Threading; using WindowsRuntime.Generator; using WindowsRuntime.Generator.DebugRepro; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.WinMDGenerator.Errors; #pragma warning disable IDE0008 @@ -61,7 +62,7 @@ .. archive.Entries.Where(entry => // Parse the debug repro .rsp file using (Stream stream = responseFileEntry.Open()) { - args = WinMDGeneratorArgs.ParseFromResponseFile(stream, token); + args = ResponseFileParser.Parse(stream, token); } token.ThrowIfCancellationRequested(); @@ -126,7 +127,7 @@ .. archive.Entries.Where(entry => string outputWinmdPath = Path.Combine(tempDirectory, originalOutputName); // Prepare the .rsp file with all updated arguments - string rspText = new WinMDGeneratorArgs + string rspText = ResponseFileBuilder.Format(new WinMDGeneratorArgs { InputAssemblyPath = inputAssemblyPath!, ReferenceAssemblyPaths = [.. referencePaths], @@ -135,7 +136,7 @@ .. archive.Entries.Where(entry => UseWindowsUIXamlProjections = args.UseWindowsUIXamlProjections, DebugReproDirectory = null, Token = CancellationToken.None - }.FormatToResponseFile(); + }); // Create the actual .rsp file string rspFilePath = Path.Combine(tempDirectory, "cswinrtwinmdgen.rsp"); @@ -190,7 +191,7 @@ private static void SaveDebugRepro(WinMDGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); // Prepare the .rsp file with all updated arguments - string rspText = new WinMDGeneratorArgs + string rspText = ResponseFileBuilder.Format(new WinMDGeneratorArgs { InputAssemblyPath = inputAssemblyHashedName, ReferenceAssemblyPaths = [.. updatedReferenceNames], @@ -199,7 +200,7 @@ private static void SaveDebugRepro(WinMDGeneratorArgs args) UseWindowsUIXamlProjections = args.UseWindowsUIXamlProjections, DebugReproDirectory = args.DebugReproDirectory, Token = CancellationToken.None - }.FormatToResponseFile(); + }); // Create the actual .rsp file string rspFilePath = Path.Combine(tempDirectory, "cswinrtwinmdgen.rsp"); diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs index 13d419494d..8b850ce782 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs @@ -6,6 +6,7 @@ using ConsoleAppFramework; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.Parsing; using WindowsRuntime.WinMDGenerator.Errors; namespace WindowsRuntime.WinMDGenerator.Generation; @@ -24,7 +25,7 @@ namespace WindowsRuntime.WinMDGenerator.Generation; /// The generation process runs in three phases: /// /// -/// Parse: Read arguments from the response file via . +/// Parse: Read arguments from the response file via . /// Discover: Load the input assembly and discover public types via . /// Generate: Transform discovered types and write the WinMD file via . /// @@ -42,7 +43,7 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) inputFilePath: inputFilePath, toolName: "cswinrtwinmdgen", unpackDebugRepro: UnpackDebugRepro, - parseFromResponseFile: WinMDGeneratorArgs.ParseFromResponseFile, + parseFromResponseFile: ResponseFileParser.Parse, saveDebugRepro: SaveDebugRepro, wrapUnhandled: static (phase, e) => new UnhandledWinMDException(phase, e), log: ConsoleApp.Log, diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs index 264797ac7b..07dcf7b211 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs @@ -1,12 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.IO; using System.Threading; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Attributes; -using WindowsRuntime.Generator.Parsing; -using WindowsRuntime.WinMDGenerator.Errors; namespace WindowsRuntime.WinMDGenerator.Generation; @@ -41,35 +38,5 @@ internal sealed class WinMDGeneratorArgs : IGeneratorArgs /// [CommandLineArgumentName("--debug-repro-directory")] public string? DebugReproDirectory { get; init; } - - /// - /// Parses a instance from a response file at the given path. - /// - /// The path to the response file (optionally prefixed with @). - /// The cancellation token for the operation. - /// The resulting instance. - public static WinMDGeneratorArgs ParseFromResponseFile(string path, CancellationToken token) - { - return ResponseFileParser.Parse(path, token); - } - - /// - /// Parses a instance from a response file read from a stream. - /// - /// The stream containing the response file content. - /// The cancellation token for the operation. - /// The resulting instance. - public static WinMDGeneratorArgs ParseFromResponseFile(Stream stream, CancellationToken token) - { - return ResponseFileParser.Parse(stream, token); - } - - /// - /// Formats the current instance into a response file text. - /// - /// The resulting response file text. - public string FormatToResponseFile() - { - return ResponseFileBuilder.Format(this); - } } + From ec962edbcf44b13bbf78a6469616c38804df487e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 8 Jun 2026 23:12:36 -0700 Subject: [PATCH 12/28] Always quote the phase name in unhandled generator messages 'UnhandledGeneratorException' had a 'QuotePhaseInMessage' virtual hook defaulting to false, overridden to true only by 'UnhandledInteropException', which forced the 'ToString' implementation to first format the phase via a conditional and then build the final message by concatenating four '$"""..."""' raw interpolated string fragments. The behavioral discrepancy between tools was almost certainly an accident (both pieces of code predate the shared base class). Remove the hook and always wrap the phase name in single quotes for every generator. With the conditional gone, the entire message collapses into a single regular interpolated string and the override is no longer needed. All 5 generators build clean and pass debug-repro validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Errors/UnhandledGeneratorException.cs | 18 +++--------------- .../Errors/UnhandledInteropException.cs | 3 --- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs index 10baf76686..929afaccc9 100644 --- a/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs +++ b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs @@ -11,9 +11,7 @@ namespace WindowsRuntime.Generator.Errors; /// /// Each per-tool unhandled exception inherits from this type and provides its /// and so that the standardized -/// message remains tool-specific. -/// defaults to ; the interop generator overrides it to -/// (its historical message wraps the phase name in single quotes). +/// message remains tool-specific. /// /// The phase that failed. /// The inner exception. @@ -31,24 +29,14 @@ internal abstract class UnhandledGeneratorException(string phase, Exception exce /// protected abstract string GeneratorDescription { get; } - /// - /// Gets a value indicating whether the phase name should be wrapped in single quotes in the message. - /// - /// - /// Defaults to . The interop generator historically wraps the phase name - /// in single quotes and overrides this to to preserve that behavior. - /// - protected virtual bool QuotePhaseInMessage => false; - /// public override string ToString() { - string formattedPhase = QuotePhaseInMessage ? $"'{phase}'" : phase; - return $"""error {ErrorPrefix}9999: The CsWinRT {GeneratorDescription} failed with an unhandled exception """ + - $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the {formattedPhase} phase. This might be due to an invalid """ + + $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the '{phase}' phase. This might be due to an invalid """ + $"""configuration in the current project, but the generator should still correctly identify that and fail gracefully. Please open an """ + $"""issue at https://github.com/microsoft/CsWinRT and provide a minimal repro, if possible."""; } } + diff --git a/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs index d42e6216d4..c4b0ee30bf 100644 --- a/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs +++ b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs @@ -18,7 +18,4 @@ internal sealed class UnhandledInteropException(string phase, Exception exceptio /// protected override string GeneratorDescription => "interop generator"; - - /// - protected override bool QuotePhaseInMessage => true; } From a5a1ff3efee5c2deea5778ac8ad063818a62584d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 12:07:22 -0700 Subject: [PATCH 13/28] Delete dead SignatureComparerExtensions copies in Impl + Projection 'WinRT.Impl.Generator' and 'WinRT.Projection.Generator' each ship a copy of 'SignatureComparerExtensions' that has zero call sites in their own project. The real consumer is 'WinRT.Interop.Generator', which has its own (larger, strict-superset) copy at 'src/WinRT.Interop.Generator/Extensions/SignatureComparerExtensions.cs'. The two dead copies appear to have been left behind during earlier refactors. Delete them. No behavioral change is possible since they had no callers; the Interop copy is untouched and continues to serve its local consumers. Net diff: -141 LOC across 2 files. Both projects build with 0 warnings in Release. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Extensions/SignatureComparerExtensions.cs | 70 ------------------- .../Extensions/SignatureComparerExtensions.cs | 70 ------------------- 2 files changed, 140 deletions(-) delete mode 100644 src/WinRT.Impl.Generator/Extensions/SignatureComparerExtensions.cs delete mode 100644 src/WinRT.Projection.Generator/Extensions/SignatureComparerExtensions.cs diff --git a/src/WinRT.Impl.Generator/Extensions/SignatureComparerExtensions.cs b/src/WinRT.Impl.Generator/Extensions/SignatureComparerExtensions.cs deleted file mode 100644 index c1aa401f6a..0000000000 --- a/src/WinRT.Impl.Generator/Extensions/SignatureComparerExtensions.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using AsmResolver.DotNet.Signatures; - -namespace WindowsRuntime.ImplGenerator; - -/// -/// Extensions for . -/// -internal static class SignatureComparerExtensions -{ -#pragma warning disable IDE0052 // TODO: remove this once Roslyn bug is fixed - /// - /// Backing field for . - /// - private static readonly SignatureComparer IgnoreVersion = new(SignatureComparisonFlags.VersionAgnostic); -#pragma warning restore IDE0052 - - extension(SignatureComparer comparer) - { - /// - /// An immutable default instance of , with . - /// - public static SignatureComparer IgnoreVersion => IgnoreVersion; - - /// - /// Creates an instance for a pair of values. - /// - /// The resulting instance. - public IEqualityComparer<(TypeSignature, TypeSignature)> MakeValueTupleComparer() - { - return new SignatureValueTupleComparer(comparer); - } - } -} - -/// -/// An for a pair of values. -/// -file sealed class SignatureValueTupleComparer : IEqualityComparer<(TypeSignature, TypeSignature)> -{ - /// - /// The wrapped instance used for comparison. - /// - private readonly SignatureComparer _comparer; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The instance to wrap. - public SignatureValueTupleComparer(SignatureComparer comparer) - { - _comparer = comparer; - } - - /// - public bool Equals((TypeSignature, TypeSignature) x, (TypeSignature, TypeSignature) y) - { - return _comparer.Equals(x.Item1, y.Item1) && _comparer.Equals(x.Item2, y.Item2); - } - - /// - public int GetHashCode((TypeSignature, TypeSignature) obj) - { - return HashCode.Combine(_comparer.GetHashCode(obj.Item1), _comparer.GetHashCode(obj.Item2)); - } -} \ No newline at end of file diff --git a/src/WinRT.Projection.Generator/Extensions/SignatureComparerExtensions.cs b/src/WinRT.Projection.Generator/Extensions/SignatureComparerExtensions.cs deleted file mode 100644 index 82670098d4..0000000000 --- a/src/WinRT.Projection.Generator/Extensions/SignatureComparerExtensions.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using AsmResolver.DotNet.Signatures; - -namespace WindowsRuntime.ProjectionGenerator; - -/// -/// Extensions for . -/// -internal static class SignatureComparerExtensions -{ -#pragma warning disable IDE0052 // TODO: remove this once Roslyn bug is fixed - /// - /// Backing field for . - /// - private static readonly SignatureComparer IgnoreVersion = new(SignatureComparisonFlags.VersionAgnostic); -#pragma warning restore IDE0052 - - extension(SignatureComparer comparer) - { - /// - /// An immutable default instance of , with . - /// - public static SignatureComparer IgnoreVersion => IgnoreVersion; - - /// - /// Creates an instance for a pair of values. - /// - /// The resulting instance. - public IEqualityComparer<(TypeSignature, TypeSignature)> MakeValueTupleComparer() - { - return new SignatureValueTupleComparer(comparer); - } - } -} - -/// -/// An for a pair of values. -/// -file sealed class SignatureValueTupleComparer : IEqualityComparer<(TypeSignature, TypeSignature)> -{ - /// - /// The wrapped instance used for comparison. - /// - private readonly SignatureComparer _comparer; - - /// - /// Creates a new instance with the specified parameters. - /// - /// The instance to wrap. - public SignatureValueTupleComparer(SignatureComparer comparer) - { - _comparer = comparer; - } - - /// - public bool Equals((TypeSignature, TypeSignature) x, (TypeSignature, TypeSignature) y) - { - return _comparer.Equals(x.Item1, y.Item1) && _comparer.Equals(x.Item2, y.Item2); - } - - /// - public int GetHashCode((TypeSignature, TypeSignature) obj) - { - return HashCode.Combine(_comparer.GetHashCode(obj.Item1), _comparer.GetHashCode(obj.Item2)); - } -} From c7e62425a0f387620e9bd185056ea89fe9e45565 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 12:16:54 -0700 Subject: [PATCH 14/28] Add debug-repro orchestration helpers to DebugReproPacker Extend 'DebugReproPacker' with three orchestration helpers that capture the preamble/tail boilerplate that all 5 generators' 'SaveDebugRepro' and 'UnpackDebugRepro' methods used to repeat verbatim: * 'BeginSave(directory, toolName, archiveFileName)' validates the user-provided directory exists (throws the per-tool 'TError.DebugReproDirectoryDoesNotExist' via the shared 'IGeneratorErrorFactory' contract), builds the target '.zip' path and creates a fresh '{toolName}-debug-repro-{Guid}' staging dir. * 'FinalizeSave(tempDirectory, zipPath)' deletes any pre-existing archive, zips the staging dir, and deletes the staging dir. * 'CreateUnpackTempDirectory(toolName)' creates a fresh '{toolName}-debug-repro-unpack-{Guid}' directory. Each '*Generator.DebugRepro.cs' now calls these helpers in place of the inlined boilerplate. Per-tool concerns stay per-tool: subdirectory layout, file-copy strategy, '.rsp' build, and JSON path-map writes are all unchanged. Per-tool exception identity is fully preserved because every 'BeginSave' call binds to the tool's own 'WellKnown*Exceptions' static-abstract factory. Net diff: 6 files, +103 / -140 LOC (-37 LOC). All 5 generators build clean and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DebugRepro/DebugReproPacker.cs | 73 +++++++++++++++++++ .../Generation/ImplGenerator.DebugRepro.cs | 34 ++------- .../Generation/InteropGenerator.DebugRepro.cs | 34 ++------- .../ProjectionGenerator.DebugRepro.cs | 34 ++------- ...ReferenceProjectionGenerator.DebugRepro.cs | 34 ++------- .../Generation/WinMDGenerator.DebugRepro.cs | 34 ++------- 6 files changed, 103 insertions(+), 140 deletions(-) diff --git a/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs index db68eaef15..d706c93b87 100644 --- a/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs +++ b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs @@ -10,6 +10,7 @@ using System.Text; using System.Text.Json; using System.Threading; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.Generator.Helpers; namespace WindowsRuntime.Generator.DebugRepro; @@ -148,4 +149,76 @@ public static Dictionary ExtractPathMap(ZipArchiveEntry pathMapE // Load the mapping with all the original file paths for the included files return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; } + + /// + /// Prepares the staging directory and target archive path for a debug repro save operation. + /// + /// The per-tool error factory used to throw if does not exist. + /// The user-provided directory where the resulting .zip archive will be written. Must already exist. + /// The CLI tool name (e.g. "cswinrtimplgen"), used as the prefix of the staging directory. + /// The file name of the resulting .zip archive (e.g. "impl-debug-repro.zip"). + /// A pair containing the freshly-created staging directory and the absolute path of the target archive. + /// Thrown via if does not exist. + public static (string TempDirectory, string ZipPath) BeginSave( + string debugReproDirectory, + string toolName, + string archiveFileName) + where TError : IGeneratorErrorFactory + { + // The target folder must exist + if (!Directory.Exists(debugReproDirectory)) + { + throw TError.DebugReproDirectoryDoesNotExist(debugReproDirectory); + } + + // Path for the ZIP archive + string zipPath = Path.Combine(debugReproDirectory, archiveFileName); + + // Create a temporary directory to stage files for the ZIP + string tempFolderName = $"{toolName}-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; + string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); + + _ = Directory.CreateDirectory(tempDirectory); + + return (tempDirectory, zipPath); + } + + /// + /// Finalizes a debug repro save by zipping the staging directory into the target archive and deleting the staging directory. + /// + /// The staging directory previously returned by . + /// The absolute path of the target .zip archive, previously returned by . + /// + /// If a file already exists at , it is deleted before the new archive is created. + /// + public static void FinalizeSave(string tempDirectory, string zipPath) + { + // Delete the previous file, if it exists + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } + + // Create the actual .zip file in the target directory + ZipFile.CreateFromDirectory(tempDirectory, zipPath); + + // Clean up the temporary directory + Directory.Delete(tempDirectory, recursive: true); + } + + /// + /// Creates a freshly-named temporary directory for unpacking a debug repro .zip archive. + /// + /// The CLI tool name (e.g. "cswinrtimplgen"), used as the prefix of the directory. + /// The absolute path of the created directory. + public static string CreateUnpackTempDirectory(string toolName) + { + // Create a temporary directory to extract the files from the debug repro + string tempFolderName = $"{toolName}-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; + string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); + + _ = Directory.CreateDirectory(tempDirectory); + + return tempDirectory; + } } diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs index 7027c9802d..dea139fcf1 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs @@ -33,11 +33,7 @@ internal static partial class ImplGenerator /// The path to the resulting response file to use. private static string UnpackDebugRepro(string path, CancellationToken token) { - // Create a temporary directory to extract the files from the debug repro - string tempFolderName = $"cswinrtimplgen-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); - - _ = Directory.CreateDirectory(tempDirectory); + string tempDirectory = DebugReproPacker.CreateUnpackTempDirectory("cswinrtimplgen"); token.ThrowIfCancellationRequested(); @@ -148,21 +144,13 @@ private static void SaveDebugRepro(ImplGeneratorArgs args) return; } - // The target folder must exist - if (!Directory.Exists(args.DebugReproDirectory)) - { - throw WellKnownImplExceptions.DebugReproDirectoryDoesNotExist(args.DebugReproDirectory); - } - - // Path for the ZIP archive - string zipPath = Path.Combine(args.DebugReproDirectory, "impl-debug-repro.zip"); + (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave( + args.DebugReproDirectory, + toolName: "cswinrtimplgen", + archiveFileName: "impl-debug-repro.zip"); - // Create a temporary directory to stage files for the ZIP - string tempFolderName = $"cswinrtimplgen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); string referenceDirectory = Path.Combine(tempDirectory, "reference"); - _ = Directory.CreateDirectory(tempDirectory); _ = Directory.CreateDirectory(referenceDirectory); // Map with all the original paths @@ -203,16 +191,6 @@ private static void SaveDebugRepro(ImplGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); - // Delete the previous file, if it exists - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - // Create the actual .zip file in the target directory - ZipFile.CreateFromDirectory(tempDirectory, zipPath); - - // Clean up the temporary directory - Directory.Delete(tempDirectory, recursive: true); + DebugReproPacker.FinalizeSave(tempDirectory, zipPath); } } diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs index e5309cf44c..f39a75b95b 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs @@ -38,11 +38,7 @@ internal partial class InteropGenerator /// The path to the resulting response file to use. private static string UnpackDebugRepro(string path, CancellationToken token) { - // Create a temporary directory to extract the files from the debug repro - string tempFolderName = $"cswinrtinteropgen-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); - - _ = Directory.CreateDirectory(tempDirectory); + string tempDirectory = DebugReproPacker.CreateUnpackTempDirectory("cswinrtinteropgen"); token.ThrowIfCancellationRequested(); @@ -205,22 +201,14 @@ private static void SaveDebugRepro(InteropGeneratorArgs args) return; } - // The target folder must exist - if (!Directory.Exists(args.DebugReproDirectory)) - { - throw WellKnownInteropExceptions.DebugReproDirectoryDoesNotExist(args.DebugReproDirectory); - } - - // Path for the ZIP archive - string zipPath = Path.Combine(args.DebugReproDirectory, "interop-debug-repro.zip"); + (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave( + args.DebugReproDirectory, + toolName: "cswinrtinteropgen", + archiveFileName: "interop-debug-repro.zip"); - // Create a temporary directory to stage files for the ZIP - string tempFolderName = $"cswinrtinteropgen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); string referenceDirectory = Path.Combine(tempDirectory, "reference"); string implementationDirectory = Path.Combine(tempDirectory, "implementation"); - _ = Directory.CreateDirectory(tempDirectory); _ = Directory.CreateDirectory(referenceDirectory); _ = Directory.CreateDirectory(implementationDirectory); @@ -282,17 +270,7 @@ private static void SaveDebugRepro(InteropGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); - // Delete the previous file, if it exists - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - // Create the actual .zip file in the target directory - ZipFile.CreateFromDirectory(tempDirectory, zipPath); - - // Clean up the temporary directory - Directory.Delete(tempDirectory, recursive: true); + DebugReproPacker.FinalizeSave(tempDirectory, zipPath); } /// diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs index 22d3816e1a..1e784160d3 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs @@ -60,11 +60,7 @@ internal static partial class ProjectionGenerator /// The path to the resulting response file to use. private static string UnpackDebugRepro(string path, CancellationToken token) { - // Create a temporary directory to extract the files from the debug repro - string tempFolderName = $"cswinrtprojectiongen-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); - - _ = Directory.CreateDirectory(tempDirectory); + string tempDirectory = DebugReproPacker.CreateUnpackTempDirectory("cswinrtprojectiongen"); token.ThrowIfCancellationRequested(); @@ -209,23 +205,15 @@ private static void SaveDebugRepro(ProjectionGeneratorArgs args) return; } - // The target folder must exist - if (!Directory.Exists(args.DebugReproDirectory)) - { - throw WellKnownProjectionGeneratorExceptions.DebugReproDirectoryDoesNotExist(args.DebugReproDirectory); - } - - // Path for the ZIP archive - string zipPath = Path.Combine(args.DebugReproDirectory, "projection-debug-repro.zip"); + (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave( + args.DebugReproDirectory, + toolName: "cswinrtprojectiongen", + archiveFileName: "projection-debug-repro.zip"); - // Create a temporary directory to stage files for the ZIP - string tempFolderName = $"cswinrtprojectiongen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); string referenceDirectory = Path.Combine(tempDirectory, ReferenceSubfolder); string winmdDirectory = Path.Combine(tempDirectory, WinMDSubfolder); string windowsMetadataDirectory = Path.Combine(tempDirectory, WindowsMetadataSubfolder); - _ = Directory.CreateDirectory(tempDirectory); _ = Directory.CreateDirectory(referenceDirectory); _ = Directory.CreateDirectory(winmdDirectory); _ = Directory.CreateDirectory(windowsMetadataDirectory); @@ -301,16 +289,6 @@ private static void SaveDebugRepro(ProjectionGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); - // Delete the previous file, if it exists - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - // Create the actual .zip file in the target directory - ZipFile.CreateFromDirectory(tempDirectory, zipPath); - - // Clean up the temporary directory - Directory.Delete(tempDirectory, recursive: true); + DebugReproPacker.FinalizeSave(tempDirectory, zipPath); } } diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs index 1358d044e9..1c08ac14e1 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs @@ -33,11 +33,7 @@ internal static partial class ReferenceProjectionGenerator /// The path to the resulting response file to use. private static string UnpackDebugRepro(string path, CancellationToken token) { - // Create a temporary directory to extract the files from the debug repro - string tempFolderName = $"cswinrtprojectionrefgen-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); - - _ = Directory.CreateDirectory(tempDirectory); + string tempDirectory = DebugReproPacker.CreateUnpackTempDirectory("cswinrtprojectionrefgen"); token.ThrowIfCancellationRequested(); @@ -146,21 +142,13 @@ private static void SaveDebugRepro(ReferenceProjectionGeneratorArgs args) return; } - // The target folder must exist - if (!Directory.Exists(args.DebugReproDirectory)) - { - throw WellKnownReferenceProjectionGeneratorExceptions.DebugReproDirectoryDoesNotExist(args.DebugReproDirectory); - } - - // Path for the ZIP archive - string zipPath = Path.Combine(args.DebugReproDirectory, "ref-projection-debug-repro.zip"); + (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave( + args.DebugReproDirectory, + toolName: "cswinrtprojectionrefgen", + archiveFileName: "ref-projection-debug-repro.zip"); - // Create a temporary directory to stage files for the ZIP - string tempFolderName = $"cswinrtprojectionrefgen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); string inputDirectory = Path.Combine(tempDirectory, "input"); - _ = Directory.CreateDirectory(tempDirectory); _ = Directory.CreateDirectory(inputDirectory); // Expand all input paths (which may be file paths, directories to recursively scan, or @@ -215,16 +203,6 @@ private static void SaveDebugRepro(ReferenceProjectionGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); - // Delete the previous file, if it exists - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - // Create the actual .zip file in the target directory - ZipFile.CreateFromDirectory(tempDirectory, zipPath); - - // Clean up the temporary directory - Directory.Delete(tempDirectory, recursive: true); + DebugReproPacker.FinalizeSave(tempDirectory, zipPath); } } diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs index 49834c4124..4f2ea5783e 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs @@ -32,11 +32,7 @@ internal static partial class WinMDGenerator /// The path to the resulting response file to use. private static string UnpackDebugRepro(string path, CancellationToken token) { - // Create a temporary directory to extract the files from the debug repro - string tempFolderName = $"cswinrtwinmdgen-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); - - _ = Directory.CreateDirectory(tempDirectory); + string tempDirectory = DebugReproPacker.CreateUnpackTempDirectory("cswinrtwinmdgen"); token.ThrowIfCancellationRequested(); @@ -159,21 +155,13 @@ private static void SaveDebugRepro(WinMDGeneratorArgs args) return; } - // The target folder must exist - if (!Directory.Exists(args.DebugReproDirectory)) - { - throw WellKnownWinMDExceptions.DebugReproDirectoryDoesNotExist(args.DebugReproDirectory); - } - - // Path for the ZIP archive - string zipPath = Path.Combine(args.DebugReproDirectory, "winmd-debug-repro.zip"); + (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave( + args.DebugReproDirectory, + toolName: "cswinrtwinmdgen", + archiveFileName: "winmd-debug-repro.zip"); - // Create a temporary directory to stage files for the ZIP - string tempFolderName = $"cswinrtwinmdgen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); string referenceDirectory = Path.Combine(tempDirectory, "reference"); - _ = Directory.CreateDirectory(tempDirectory); _ = Directory.CreateDirectory(referenceDirectory); // Map with all the original paths @@ -214,16 +202,6 @@ private static void SaveDebugRepro(WinMDGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); - // Delete the previous file, if it exists - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - // Create the actual .zip file in the target directory - ZipFile.CreateFromDirectory(tempDirectory, zipPath); - - // Clean up the temporary directory - Directory.Delete(tempDirectory, recursive: true); + DebugReproPacker.FinalizeSave(tempDirectory, zipPath); } } From a8917ecc0ac73950a8c6dbb33ac9fb441439bcf2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 12:24:07 -0700 Subject: [PATCH 15/28] Move RuntimeContextExtensions.LoadModule to Core 'Impl', 'Interop', and 'WinMD' each shipped their own copy of 'RuntimeContextExtensions.LoadModule', with bodies that are character-for-character identical (only the receiver parameter name differed). Impl had only the 'PEImage' overload, Interop had only the 'string' overload, WinMD had both. Consolidate to a single file under 'WinRT.Generator.Core' that carries both overloads. All three consumer projects pick up the overload they already used; no behavioral change. This adds an 'AsmResolver.DotNet' package reference to 'WinRT.Generator.Core' (all 5 generators already reference AsmResolver directly). Net diff: 6 files, +63 / -126 LOC (-63 LOC). All 5 generators build with 0 warnings and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Extensions/RuntimeContextExtensions.cs | 2 +- .../WinRT.Generator.Core.csproj | 4 ++ .../Extensions/RuntimeContextExtensions.cs | 40 ------------------- .../Generation/ImplGenerator.cs | 1 + .../Extensions/RuntimeContextExtensions.cs | 39 ------------------ .../Generation/InteropGenerator.Discover.cs | 1 + .../Generation/WinMDGenerator.Discover.cs | 1 + 7 files changed, 8 insertions(+), 80 deletions(-) rename src/{WinRT.WinMD.Generator => WinRT.Generator.Core}/Extensions/RuntimeContextExtensions.cs (98%) delete mode 100644 src/WinRT.Impl.Generator/Extensions/RuntimeContextExtensions.cs delete mode 100644 src/WinRT.Interop.Generator/Extensions/RuntimeContextExtensions.cs diff --git a/src/WinRT.WinMD.Generator/Extensions/RuntimeContextExtensions.cs b/src/WinRT.Generator.Core/Extensions/RuntimeContextExtensions.cs similarity index 98% rename from src/WinRT.WinMD.Generator/Extensions/RuntimeContextExtensions.cs rename to src/WinRT.Generator.Core/Extensions/RuntimeContextExtensions.cs index 8794f0c543..7123d56211 100644 --- a/src/WinRT.WinMD.Generator/Extensions/RuntimeContextExtensions.cs +++ b/src/WinRT.Generator.Core/Extensions/RuntimeContextExtensions.cs @@ -7,7 +7,7 @@ #pragma warning disable IDE0046 -namespace WindowsRuntime.WinMDGenerator; +namespace WindowsRuntime.Generator.Extensions; /// /// Extensions for the type. diff --git a/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj b/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj index 55ac42ef9a..e2c8a96a3f 100644 --- a/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj +++ b/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj @@ -29,4 +29,8 @@ strict true + + + + diff --git a/src/WinRT.Impl.Generator/Extensions/RuntimeContextExtensions.cs b/src/WinRT.Impl.Generator/Extensions/RuntimeContextExtensions.cs deleted file mode 100644 index 55b50ba06f..0000000000 --- a/src/WinRT.Impl.Generator/Extensions/RuntimeContextExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using AsmResolver.DotNet; -using AsmResolver.PE; - -#pragma warning disable IDE0046 - -namespace WindowsRuntime.ImplGenerator; - -/// -/// Extensions for the type. -/// -internal static class RuntimeContextExtensions -{ - extension(RuntimeContext signature) - { - /// - /// Loads a .NET module into the context from the provided input image. - /// - /// The image containing the .NET metadata. - /// The module. - /// Occurs when the image does not contain a valid .NET metadata directory. - public ModuleDefinition LoadModule(PEImage peImage) - { - AssemblyDefinition assemblyDefinition = signature.LoadAssembly(peImage); - - // Every valid .NET assembly will always have exactly one module. In practice, we should - // never encounter an assembly with zero or more than one module, but we can still check - // and ensure that this is the case, just to guard against malformed .NET assemblies too. - if (assemblyDefinition.Modules is not [ModuleDefinition moduleDefinition]) - { - throw new BadImageFormatException(); - } - - return moduleDefinition; - } - } -} \ No newline at end of file diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index a6e2e389a7..da8c3be7a9 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -19,6 +19,7 @@ using ConsoleAppFramework; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.Extensions; using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ImplGenerator.Errors; using WindowsRuntime.ImplGenerator.References; diff --git a/src/WinRT.Interop.Generator/Extensions/RuntimeContextExtensions.cs b/src/WinRT.Interop.Generator/Extensions/RuntimeContextExtensions.cs deleted file mode 100644 index 05b9c3833f..0000000000 --- a/src/WinRT.Interop.Generator/Extensions/RuntimeContextExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using AsmResolver.DotNet; - -#pragma warning disable IDE0046 - -namespace WindowsRuntime.InteropGenerator; - -/// -/// Extensions for the type. -/// -internal static class RuntimeContextExtensions -{ - extension(RuntimeContext signature) - { - /// - /// Loads a .NET module into the context from the provided input file. - /// - /// The file path to the input executable to load. - /// The module. - /// Occurs when the image does not contain a valid .NET metadata directory. - public ModuleDefinition LoadModule(string filePath) - { - AssemblyDefinition assemblyDefinition = signature.LoadAssembly(filePath); - - // Every valid .NET assembly will always have exactly one module. In practice, we should - // never encounter an assembly with zero or more than one module, but we can still check - // and ensure that this is the case, just to guard against malformed .NET assemblies too. - if (assemblyDefinition.Modules is not [ModuleDefinition moduleDefinition]) - { - throw new BadImageFormatException(); - } - - return moduleDefinition; - } - } -} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index 1608a1ed40..c2444f365a 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -11,6 +11,7 @@ using AsmResolver.DotNet.Signatures; using AsmResolver.PE; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.Extensions; using WindowsRuntime.InteropGenerator.Discovery; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Models; diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.Discover.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.Discover.cs index ea927ce18b..86ab36b8ec 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.Discover.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.Discover.cs @@ -6,6 +6,7 @@ using System.Linq; using AsmResolver.DotNet; using AsmResolver.PE; +using WindowsRuntime.Generator.Extensions; using WindowsRuntime.WinMDGenerator.Discovery; using WindowsRuntime.WinMDGenerator.Errors; From 10ba86136497f230f61821a37dc907f485b5a44a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 12:35:54 -0700 Subject: [PATCH 16/28] Add GeneratorPhaseRunner to wrap per-phase try/catch + log Every generator's 'Run' method historically opens with a single 'GeneratorHost.Prepare' call (which handles the shared unpack/parse/save preamble) and then proceeds through a series of phases (discovery, processing, emit, ...). Each phase was wrapped in an identical: try { ConsoleApp.Log("..."); DoPhase(args); } catch (Exception e) when (!e.IsWellKnown) { throw new UnhandledXxxException("phase-name", e); } That is now expressed as a single 'runner.RunPhase' call. The 'GeneratorPhaseRunner' readonly struct captures the per-tool 'wrapUnhandled' + 'log' delegates once (it is returned bound to them by 'GeneratorHost.Prepare') and offers four overloads ('Action' / 'Func', each with or without a log message). All four invariably route through the per-tool 'wrapUnhandled' delegate so each tool keeps throwing its own 'UnhandledXxxException' with the right phase name. 'GeneratorHost.Prepare' now returns '(TArgs Args, GeneratorPhaseRunner Runner)' instead of just 'TArgs'. The 5 'Run' methods deconstruct the tuple and use the runner for each phase. Impl's 'LoadOutputModule(args, out runtimeContext, out outputModule)' becomes a tuple-return 'LoadOutputModule(args) -> (RuntimeContext, ModuleDefinition)' so it fits the 'Func' overload cleanly. 'ThrowIfCancellationRequested' calls stay per-tool at the call sites (the pattern isn't uniform: some phases use 'args.Token', WinMD uses 'token', and the last phase typically has no check). ReferenceProjection's second phase wraps to a well-known 'CsWinRTProcessError' rather than the 'Unhandled' factory, so it's left as an inline try/catch. Net diff across modified files: -84 LOC. Including the new 'GeneratorPhaseRunner.cs' (~123 LOC, mostly XML docs), the overall file diff is +39 LOC but with a much lower density of error-handling boilerplate at the per-tool call sites. All 5 generators build with 0 warnings and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/WinRT.Generator.Core/GeneratorHost.cs | 18 ++- .../GeneratorPhaseRunner.cs | 126 ++++++++++++++++++ .../Generation/ImplGenerator.cs | 71 +++------- .../Generation/InteropGenerator.cs | 41 ++---- .../Generation/ProjectionGenerator.cs | 57 +++----- .../ReferenceProjectionGenerator.cs | 19 +-- .../Generation/WinMDGenerator.cs | 34 ++--- 7 files changed, 204 insertions(+), 162 deletions(-) create mode 100644 src/WinRT.Generator.Core/GeneratorPhaseRunner.cs diff --git a/src/WinRT.Generator.Core/GeneratorHost.cs b/src/WinRT.Generator.Core/GeneratorHost.cs index ba60c250f7..94b1d09b13 100644 --- a/src/WinRT.Generator.Core/GeneratorHost.cs +++ b/src/WinRT.Generator.Core/GeneratorHost.cs @@ -21,6 +21,14 @@ namespace WindowsRuntime.Generator; /// encapsulates that preamble. Each generator's Run now starts with a /// single call to it; the per-tool unpack / save / parse logic is supplied via delegates so behavior /// stays identical (same log messages, same exception phases, same per-tool unhandled exception type). +/// +/// The same Run methods then proceed through a series of phases (loading, processing, emit, ...), +/// each wrapped in an identical try/catch that re-throws as the per-tool +/// Unhandled*Exception. additionally returns a +/// bound to the same per-tool wrapUnhandled and log +/// delegates so each phase can be expressed as a single +/// call instead of a hand-written try/catch. +/// /// internal static class GeneratorHost { @@ -36,8 +44,11 @@ internal static class GeneratorHost /// Wraps an unexpected exception into the per-tool Unhandled*Exception with the given phase name. /// Logs a progress message to the user (typically ConsoleApp.Log from ConsoleAppFramework). /// The token for the operation. - /// The parsed instance. - public static TArgs Prepare( + /// + /// A pair containing the parsed instance and a + /// pre-bound to and for use by subsequent phases. + /// + public static (TArgs Args, GeneratorPhaseRunner Runner) Prepare( string inputFilePath, string toolName, Func unpackDebugRepro, @@ -109,7 +120,8 @@ public static TArgs Prepare( args.Token.ThrowIfCancellationRequested(); - return args; + return (args, new GeneratorPhaseRunner(wrapUnhandled, log)); } } + diff --git a/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs b/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs new file mode 100644 index 0000000000..8e71fb5e3d --- /dev/null +++ b/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using WindowsRuntime.Generator.Errors; + +namespace WindowsRuntime.Generator; + +/// +/// Runs the body of a single generator phase, wrapping unexpected exceptions in the per-tool +/// Unhandled*Exception and optionally logging a progress message before the body runs. +/// +/// +/// Each generator's Run method historically wrapped every phase (loading, processing, emit, ...) +/// in an identical try/catch that re-threw as the per-tool Unhandled*Exception +/// (with the phase name as the constructor argument). captures +/// the per-tool wrapUnhandled and log delegates once (it is returned bound to them by +/// ) and lets each phase call site collapse to a single +/// (or overload) invocation. Per-tool exception identity +/// is fully preserved because the original wrapUnhandled delegate is invoked unchanged. +/// +internal readonly struct GeneratorPhaseRunner +{ + /// + /// The per-tool wrapUnhandled delegate. + /// + private readonly Func _wrapUnhandled; + + /// + /// The per-tool progress logger. + /// + private readonly Action _log; + + /// + /// Creates a new bound to the given per-tool delegates. + /// + /// Wraps an unexpected exception into the per-tool Unhandled*Exception with the given phase name. + /// Logs a progress message to the user (typically ConsoleApp.Log from ConsoleAppFramework). + internal GeneratorPhaseRunner(Func wrapUnhandled, Action log) + { + _wrapUnhandled = wrapUnhandled; + _log = log; + } + + /// + /// Runs , wrapping any unexpected exception in the per-tool + /// Unhandled*Exception with as the phase tag. + /// + /// The phase name used by the per-tool Unhandled*Exception. + /// The body of the phase to run. + public void RunPhase(string phaseName, Action body) + { + try + { + body(); + } + catch (Exception e) when (!e.IsWellKnown) + { + throw _wrapUnhandled(phaseName, e); + } + } + + /// + /// Logs and then runs , wrapping any + /// unexpected exception in the per-tool Unhandled*Exception with + /// as the phase tag. + /// + /// The phase name used by the per-tool Unhandled*Exception. + /// The progress message to log before the body runs. + /// The body of the phase to run. + public void RunPhase(string phaseName, string logMessage, Action body) + { + try + { + _log(logMessage); + body(); + } + catch (Exception e) when (!e.IsWellKnown) + { + throw _wrapUnhandled(phaseName, e); + } + } + + /// + /// Runs and returns its result, wrapping any unexpected exception + /// in the per-tool Unhandled*Exception with as the phase tag. + /// + /// The result type of . + /// The phase name used by the per-tool Unhandled*Exception. + /// The body of the phase to run. + /// The value returned by . + public T RunPhase(string phaseName, Func body) + { + try + { + return body(); + } + catch (Exception e) when (!e.IsWellKnown) + { + throw _wrapUnhandled(phaseName, e); + } + } + + /// + /// Logs , then runs and returns its result, + /// wrapping any unexpected exception in the per-tool Unhandled*Exception with + /// as the phase tag. + /// + /// The result type of . + /// The phase name used by the per-tool Unhandled*Exception. + /// The progress message to log before the body runs. + /// The body of the phase to run. + /// The value returned by . + public T RunPhase(string phaseName, string logMessage, Func body) + { + try + { + _log(logMessage); + return body(); + } + catch (Exception e) when (!e.IsWellKnown) + { + throw _wrapUnhandled(phaseName, e); + } + } +} diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index da8c3be7a9..1a6149aaee 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -69,7 +69,7 @@ internal static partial class ImplGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - ImplGeneratorArgs args = GeneratorHost.Prepare( + (ImplGeneratorArgs args, GeneratorPhaseRunner runner) = GeneratorHost.Prepare( inputFilePath: inputFilePath, toolName: "cswinrtimplgen", unpackDebugRepro: UnpackDebugRepro, @@ -79,67 +79,38 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) log: ConsoleApp.Log, token: token); - RuntimeContext runtimeContext; - ModuleDefinition outputModule; - // Initialize the assembly resolver and load the output module - try - { - LoadOutputModule(args, out runtimeContext, out outputModule); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledImplException("loading", e); - } + (RuntimeContext runtimeContext, ModuleDefinition outputModule) = runner.RunPhase( + phaseName: "loading", + body: () => LoadOutputModule(args)); args.Token.ThrowIfCancellationRequested(); - ModuleDefinition implModule; - // Define the impl module to emit - try - { - implModule = DefineImplModule(runtimeContext, outputModule); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledImplException("loading", e); - } + ModuleDefinition implModule = runner.RunPhase( + phaseName: "loading", + body: () => DefineImplModule(runtimeContext, outputModule)); args.Token.ThrowIfCancellationRequested(); // Emit all necessary IL code in the impl module - try + runner.RunPhase(phaseName: "generation", body: () => { EmitAssemblyAttributes(outputModule, implModule); EmitTypeForwards(outputModule, implModule); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledImplException("generation", e); - } + }); args.Token.ThrowIfCancellationRequested(); // Write the module to disk with all the generated contents - try - { - WriteImplModuleToDisk(args, outputModule, implModule); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledImplException("emit", e); - } + runner.RunPhase( + phaseName: "emit", + body: () => WriteImplModuleToDisk(args, outputModule, implModule)); // Signs the module on disk, if needed - try - { - SignImplModuleOnDisk(args, outputModule); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledImplException("sign", e); - } + runner.RunPhase( + phaseName: "sign", + body: () => SignImplModuleOnDisk(args, outputModule)); // Notify the user that generation was successful ConsoleApp.Log($"Impl code generated -> {Path.Combine(args.GeneratedAssemblyDirectory, implModule.Name!)}"); @@ -149,12 +120,8 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) /// Loads the output assembly being produced. /// /// The arguments for this invocation. - /// The instance in use. - /// The loaded for the output assembly. - private static void LoadOutputModule( - ImplGeneratorArgs args, - out RuntimeContext runtimeContext, - out ModuleDefinition outputModule) + /// The instance in use and the loaded for the output assembly. + private static (RuntimeContext RuntimeContext, ModuleDefinition OutputModule) LoadOutputModule(ImplGeneratorArgs args) { PEImage outputAssemblyImage; @@ -179,12 +146,12 @@ private static void LoadOutputModule( PathAssemblyResolver assemblyResolver = new(args.ReferenceAssemblyPaths); // Initialize the runtime context (this will be reused to allow caching) - runtimeContext = new RuntimeContext(targetRuntime, assemblyResolver); + RuntimeContext runtimeContext = new(targetRuntime, assemblyResolver); // Try to load the .dll at the current path try { - outputModule = runtimeContext.LoadModule(outputAssemblyImage); + return (runtimeContext, runtimeContext.LoadModule(outputAssemblyImage)); } catch (Exception e) { diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs index 44693e753d..8e3631caf5 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs @@ -1,12 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.IO; using System.Threading; using ConsoleAppFramework; using WindowsRuntime.Generator; -using WindowsRuntime.Generator.Errors; using WindowsRuntime.Generator.Parsing; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.References; @@ -25,7 +23,7 @@ internal static partial class InteropGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - InteropGeneratorArgs args = GeneratorHost.Prepare( + (InteropGeneratorArgs args, GeneratorPhaseRunner runner) = GeneratorHost.Prepare( inputFilePath: inputFilePath, toolName: "cswinrtinteropgen", unpackDebugRepro: UnpackDebugRepro, @@ -35,36 +33,19 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) log: ConsoleApp.Log, token: token); - InteropGeneratorDiscoveryState discoveryState; - - // Wrap the actual logic, to ensure that we're only ever throwing an exception that will result - // in either graceful cancellation, or a well formatted error message. The 'ConsoleApp' code is - // taking care of passing the exception 'ToString()' result to the output buffer, so we want all - // exceptions that can reach that path to have our custom formatting implementation there. - try - { - ConsoleApp.Log($"Processing {args.ReferenceAssemblyPaths.Length + args.ImplementationAssemblyPaths.Length + 1} module(s)"); - - discoveryState = Discover(args); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledInteropException("discovery", e); - } + // Discover the types to process + InteropGeneratorDiscoveryState discoveryState = runner.RunPhase( + phaseName: "discovery", + logMessage: $"Processing {args.ReferenceAssemblyPaths.Length + args.ImplementationAssemblyPaths.Length + 1} module(s)", + body: () => Discover(args)); args.Token.ThrowIfCancellationRequested(); - // Same thing for the emit phase - try - { - ConsoleApp.Log("Generating interop code"); - - Emit(args, discoveryState); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledInteropException("emit", e); - } + // Emit the resulting interop assembly + runner.RunPhase( + phaseName: "emit", + logMessage: "Generating interop code", + body: () => Emit(args, discoveryState)); // Notify the user that generation was successful ConsoleApp.Log($"Interop code generated -> {Path.Combine(args.GeneratedAssemblyDirectory, InteropNames.WindowsRuntimeInteropDllName)}"); diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index 4844f07463..658d491144 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -1,12 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.IO; using System.Threading; using ConsoleAppFramework; using WindowsRuntime.Generator; -using WindowsRuntime.Generator.Errors; using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ProjectionGenerator.Errors; @@ -24,7 +22,7 @@ internal static partial class ProjectionGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - ProjectionGeneratorArgs args = GeneratorHost.Prepare( + (ProjectionGeneratorArgs args, GeneratorPhaseRunner runner) = GeneratorHost.Prepare( inputFilePath: inputFilePath, toolName: "cswinrtprojectiongen", unpackDebugRepro: UnpackDebugRepro, @@ -34,27 +32,18 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) log: ConsoleApp.Log, token: token); - ProjectionGeneratorProcessingState processingState; - - // Process all .winmd references - try - { - // Show the appropriate message to inform users of what this generator is doing, - // based on the input arguments. If we don't have precompiled projections, this - // tool might run up to 3 times during builds, so this helps make things clearer. - ConsoleApp.Log(args switch + // Process all .winmd references. Show the appropriate message to inform users of what this + // generator is doing, based on the input arguments. If we don't have precompiled projections, + // this tool might run up to 3 times during builds, so this helps make things clearer. + ProjectionGeneratorProcessingState processingState = runner.RunPhase( + phaseName: "processing", + logMessage: args switch { { WindowsSdkOnly: true, WindowsUIXamlProjection: false } => "Processing Windows SDK .winmd references", { WindowsSdkOnly: true, WindowsUIXamlProjection: true } => "Processing 'Windows.UI.Xaml' .winmd references", _ => $"Processing {args.WinMDPaths.Length} .winmd reference(s)" - }); - - processingState = ProcessReferences(args); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledProjectionGeneratorException("processing", e); - } + }, + body: () => ProcessReferences(args)); args.Token.ThrowIfCancellationRequested(); @@ -66,30 +55,18 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) } // Invoke the projection writer (in-process) to generate the projection sources - try - { - ConsoleApp.Log("Generating projection code"); - - GenerateSources(processingState); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledProjectionGeneratorException("source-generation", e); - } + runner.RunPhase( + phaseName: "source-generation", + logMessage: "Generating projection code", + body: () => GenerateSources(processingState)); args.Token.ThrowIfCancellationRequested(); // Invoke Roslyn to compile the generated sources into 'WinRT.Projection.dll' - try - { - ConsoleApp.Log("Compiling projection code"); - - Emit(args, processingState); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledProjectionGeneratorException("emit", e); - } + runner.RunPhase( + phaseName: "emit", + logMessage: "Compiling projection code", + body: () => Emit(args, processingState)); // Notify the user that generation was successful ConsoleApp.Log($"Projection code generated -> {Path.Combine(args.GeneratedAssemblyDirectory, args.AssemblyName)}.dll"); diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index 990f39a1ef..505f2d4abe 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -29,7 +29,7 @@ internal static partial class ReferenceProjectionGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - ReferenceProjectionGeneratorArgs args = GeneratorHost.Prepare( + (ReferenceProjectionGeneratorArgs args, GeneratorPhaseRunner runner) = GeneratorHost.Prepare( inputFilePath: inputFilePath, toolName: "cswinrtprojectionrefgen", unpackDebugRepro: UnpackDebugRepro, @@ -46,20 +46,15 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) } // Build the writer options from the parsed arguments - ProjectionWriterOptions options; - - try - { - options = BuildWriterOptions(args); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledReferenceProjectionGeneratorException("processing", e); - } + ProjectionWriterOptions options = runner.RunPhase( + phaseName: "processing", + body: () => BuildWriterOptions(args)); args.Token.ThrowIfCancellationRequested(); - // Invoke the projection writer (in-process) to generate the projection sources + // Invoke the projection writer (in-process) to generate the projection sources. We can't + // route this through the shared 'runner.RunPhase' helper because we wrap the exception + // into a well-known 'CsWinRTProcessError' rather than the per-tool 'Unhandled' factory. try { ConsoleApp.Log($"Generating reference projection sources -> {options.OutputFolder}"); diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs index 8b850ce782..293858a395 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Threading; using ConsoleAppFramework; using WindowsRuntime.Generator; -using WindowsRuntime.Generator.Errors; using WindowsRuntime.Generator.Parsing; using WindowsRuntime.WinMDGenerator.Errors; @@ -39,7 +37,7 @@ internal static partial class WinMDGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - WinMDGeneratorArgs args = GeneratorHost.Prepare( + (WinMDGeneratorArgs args, GeneratorPhaseRunner runner) = GeneratorHost.Prepare( inputFilePath: inputFilePath, toolName: "cswinrtwinmdgen", unpackDebugRepro: UnpackDebugRepro, @@ -50,32 +48,18 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) token: token); // Discover the types to process - WinMDGeneratorDiscoveryState discoveryState; - - try - { - ConsoleApp.Log($"Processing assembly: '{System.IO.Path.GetFileName(args.InputAssemblyPath)}'"); - - discoveryState = Discover(args); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledWinMDException("discovery", e); - } + WinMDGeneratorDiscoveryState discoveryState = runner.RunPhase( + phaseName: "discovery", + logMessage: $"Processing assembly: '{System.IO.Path.GetFileName(args.InputAssemblyPath)}'", + body: () => Discover(args)); token.ThrowIfCancellationRequested(); // Generate and write the .winmd file - try - { - ConsoleApp.Log($"Defining {discoveryState.PublicTypes.Count} authored type(s)"); - - Generate(args, discoveryState); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledWinMDException("generation", e); - } + runner.RunPhase( + phaseName: "generation", + logMessage: $"Defining {discoveryState.PublicTypes.Count} authored type(s)", + body: () => Generate(args, discoveryState)); ConsoleApp.Log($"Windows Runtime assembly (.winmd) generated -> {args.OutputWinmdPath}"); } From 785a0329abc910d853cabf0bdf311a852c730377 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 12:44:45 -0700 Subject: [PATCH 17/28] Centralize IGeneratorErrorFactory message strings in WellKnownGeneratorMessages Each per-tool 'WellKnown*Exceptions' factory implements the same 6 logical errors from 'IGeneratorErrorFactory' with byte-identical message text (5 of 6 strings are character-for-character identical across all 5 tools; the sixth, 'ResponseFileReadError', only varies by the embedded tool name). Move the message templates to a new shared 'WellKnownGeneratorMessages' class in 'WinRT.Generator.Core' and have each per-tool factory call through to it. Per-tool error ID prefixes (e.g. 'CSWINRTIMPLGEN0001' vs 'CSWINRTINTEROPGEN0028'), per-tool concrete exception types ('WellKnownImplException' / 'WellKnownInteropException' / ...) and per-tool numeric IDs are all unchanged. While here, replace the per-tool '' doc on each of these 6 methods with ''. The per-tool summaries were byte-identical to the interface summaries, so this is no info loss; it also avoids future drift. Net diff: 6 files (5 modified + 1 new), -60 LOC across the per-tool factories and +70 LOC for the new central messages file. The real win is the divergence-protection: any future message tweak now lives in one place. All 5 generators build clean and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Errors/WellKnownGeneratorMessages.cs | 71 +++++++++++++++++++ .../Errors/WellKnownImplExceptions.cs | 36 ++++------ .../Errors/WellKnownInteropExceptions.cs | 36 ++++------ .../WellKnownProjectionGeneratorExceptions.cs | 36 ++++------ ...nReferenceProjectionGeneratorExceptions.cs | 36 ++++------ .../Errors/WellKnownWinMDExceptions.cs | 36 ++++------ 6 files changed, 131 insertions(+), 120 deletions(-) create mode 100644 src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessages.cs diff --git a/src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessages.cs b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessages.cs new file mode 100644 index 0000000000..8404f0a29b --- /dev/null +++ b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessages.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WindowsRuntime.Generator.Errors; + +/// +/// Shared message templates for the well-known logical errors defined by . +/// +/// +/// Each per-tool WellKnown*Exceptions factory uses these helpers to format its own +/// instance of every method, ensuring that the message +/// text stays identical across all generators while the per-tool error ID prefix (e.g. +/// CSWINRTIMPLGEN) and concrete exception type are still chosen per-tool. +/// +internal static class WellKnownGeneratorMessages +{ + /// + /// Builds the message for . + /// + /// The CLI tool name embedded in the message (e.g. "cswinrtimplgen"). + /// The formatted error message. + public static string ResponseFileReadError(string toolName) + { + return $"Failed to read the response file to run '{toolName}'."; + } + + /// + /// Builds the message for . + /// + /// The name of the response-file argument that failed to parse. + /// The formatted error message. + public static string ResponseFileArgumentParsingError(string argumentName) + { + return $"Failed to parse argument '{argumentName}' from response file."; + } + + /// + /// The message for . + /// + public const string MalformedResponseFile = "The response file is malformed and contains invalid content."; + + /// + /// Builds the message for . + /// + /// The directory path that does not exist. + /// The formatted error message. + public static string DebugReproDirectoryDoesNotExist(string path) + { + return $"The debug repro directory '{path}' does not exist."; + } + + /// + /// Builds the message for . + /// + /// The debug-repro file entry path that has no mapping. + /// The formatted error message. + public static string DebugReproMissingFileEntryMapping(string path) + { + return $"The debug repro file entry with path '{path}' is missing its assembly path mapping."; + } + + /// + /// Builds the message for . + /// + /// The debug-repro file entry path that was not recognized. + /// The formatted error message. + public static string DebugReproUnrecognizedFileEntry(string path) + { + return $"The debug repro file entry with path '{path}' was not recognized."; + } +} diff --git a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs index 960ee08993..ef3a92458c 100644 --- a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs +++ b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs @@ -23,28 +23,22 @@ private WellKnownImplExceptions() { } - /// - /// Some exception was thrown when trying to read the response file. - /// + /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, "Failed to read the response file to run 'cswinrtimplgen'.", exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError("cswinrtimplgen"), exception); } - /// - /// Failed to parse an argument from the response file. - /// + /// public static Exception ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) { - return Exception(2, $"Failed to parse argument '{argumentName}' from response file.", exception); + return Exception(2, WellKnownGeneratorMessages.ResponseFileArgumentParsingError(argumentName), exception); } - /// - /// The input response file is malformed. - /// + /// public static Exception MalformedResponseFile() { - return Exception(3, "The response file is malformed and contains invalid content."); + return Exception(3, WellKnownGeneratorMessages.MalformedResponseFile); } /// @@ -103,28 +97,22 @@ public static Exception SignDllError(Exception exception) return Exception(10, "Failed to sign the impl .dll on disk.", exception); } - /// - /// The debug repro directory does not exist. - /// + /// public static Exception DebugReproDirectoryDoesNotExist(string path) { - return Exception(11, $"The debug repro directory '{path}' does not exist."); + return Exception(11, WellKnownGeneratorMessages.DebugReproDirectoryDoesNotExist(path)); } - /// - /// The debug repro contains a file entry that has no mapping. - /// + /// public static Exception DebugReproMissingFileEntryMapping(string path) { - return Exception(12, $"The debug repro file entry with path '{path}' is missing its assembly path mapping."); + return Exception(12, WellKnownGeneratorMessages.DebugReproMissingFileEntryMapping(path)); } - /// - /// The debug repro contains a file entry that was not recognized. - /// + /// public static Exception DebugReproUnrecognizedFileEntry(string path) { - return Exception(13, $"The debug repro file entry with path '{path}' was not recognized."); + return Exception(13, WellKnownGeneratorMessages.DebugReproUnrecognizedFileEntry(path)); } /// diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs index 3f9e6c721f..6043cc31fd 100644 --- a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs +++ b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs @@ -284,36 +284,28 @@ public static WellKnownInteropException IDictionary2TypeCodeGenerationError(Type return Exception(27, $"Failed to generate marshalling code for 'IDictionary' type '{dictionaryType}'.", exception); } - /// - /// Some exception was thrown when trying to read the response file. - /// + /// public static WellKnownInteropException ResponseFileReadError(Exception exception) { - return Exception(28, "Failed to read the response file to run 'cswinrtinteropgen'.", exception); + return Exception(28, WellKnownGeneratorMessages.ResponseFileReadError("cswinrtinteropgen"), exception); } - /// - /// Failed to parse an argument from the response file. - /// + /// public static WellKnownInteropException ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) { - return Exception(29, $"Failed to parse argument '{argumentName}' from response file.", exception); + return Exception(29, WellKnownGeneratorMessages.ResponseFileArgumentParsingError(argumentName), exception); } - /// - /// The input response file is malformed. - /// + /// public static WellKnownInteropException MalformedResponseFile() { - return Exception(30, "The response file is malformed and contains invalid content."); + return Exception(30, WellKnownGeneratorMessages.MalformedResponseFile); } - /// - /// The debug repro directory does not exist. - /// + /// public static WellKnownInteropException DebugReproDirectoryDoesNotExist(string path) { - return Exception(31, $"The debug repro directory '{path}' does not exist."); + return Exception(31, WellKnownGeneratorMessages.DebugReproDirectoryDoesNotExist(path)); } /// @@ -788,20 +780,16 @@ public static WellKnownInteropException ReservedDllOriginalPathMismatchFromDebug return Exception(88, $"The reserved '{dllName}' assembly has a mismatching path with the item supplied via '$(ReferencePath)': the debug repro cannot be generated."); } - /// - /// The debug repro contains a file entry that has no mapping. - /// + /// public static WellKnownInteropException DebugReproMissingFileEntryMapping(string path) { - return Exception(89, $"The debug repro file entry with path '{path}' is missing its assembly path mapping."); + return Exception(89, WellKnownGeneratorMessages.DebugReproMissingFileEntryMapping(path)); } - /// - /// The debug repro contains a file entry that was not recognized. - /// + /// public static WellKnownInteropException DebugReproUnrecognizedFileEntry(string path) { - return Exception(90, $"The debug repro file entry with path '{path}' was not recognized."); + return Exception(90, WellKnownGeneratorMessages.DebugReproUnrecognizedFileEntry(path)); } /// diff --git a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs index 5a6c98cc04..4bde0b5e20 100644 --- a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs +++ b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs @@ -26,28 +26,22 @@ private WellKnownProjectionGeneratorExceptions() { } - /// - /// Some exception was thrown when trying to read the response file. - /// + /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, "Failed to read the response file to run 'cswinrtprojectiongen'.", exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError("cswinrtprojectiongen"), exception); } - /// - /// Failed to parse an argument from the response file. - /// + /// public static Exception ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) { - return Exception(2, $"Failed to parse argument '{argumentName}' from response file.", exception); + return Exception(2, WellKnownGeneratorMessages.ResponseFileArgumentParsingError(argumentName), exception); } - /// - /// The input response file is malformed. - /// + /// public static Exception MalformedResponseFile() { - return Exception(3, "The response file is malformed and contains invalid content."); + return Exception(3, WellKnownGeneratorMessages.MalformedResponseFile); } /// @@ -100,28 +94,22 @@ public static Exception CsWinRTProcessError(int exitCode, Exception exception) return Exception(8, $"The projection writer failed during source generation (exit code {exitCode}).", exception); } - /// - /// The debug repro directory does not exist. - /// + /// public static Exception DebugReproDirectoryDoesNotExist(string path) { - return Exception(9, $"The debug repro directory '{path}' does not exist."); + return Exception(9, WellKnownGeneratorMessages.DebugReproDirectoryDoesNotExist(path)); } - /// - /// The debug repro contains a file entry that has no mapping. - /// + /// public static Exception DebugReproMissingFileEntryMapping(string path) { - return Exception(10, $"The debug repro file entry with path '{path}' is missing its assembly path mapping."); + return Exception(10, WellKnownGeneratorMessages.DebugReproMissingFileEntryMapping(path)); } - /// - /// The debug repro contains a file entry that was not recognized. - /// + /// public static Exception DebugReproUnrecognizedFileEntry(string path) { - return Exception(11, $"The debug repro file entry with path '{path}' was not recognized."); + return Exception(11, WellKnownGeneratorMessages.DebugReproUnrecognizedFileEntry(path)); } /// diff --git a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs index b5ae41ee1b..9e232becc0 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs @@ -23,28 +23,22 @@ private WellKnownReferenceProjectionGeneratorExceptions() { } - /// - /// Some exception was thrown when trying to read the response file. - /// + /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, "Failed to read the response file to run 'cswinrtprojectionrefgen'.", exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError("cswinrtprojectionrefgen"), exception); } - /// - /// Failed to parse an argument from the response file. - /// + /// public static Exception ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) { - return Exception(2, $"Failed to parse argument '{argumentName}' from response file.", exception); + return Exception(2, WellKnownGeneratorMessages.ResponseFileArgumentParsingError(argumentName), exception); } - /// - /// The input response file is malformed. - /// + /// public static Exception MalformedResponseFile() { - return Exception(3, "The response file is malformed and contains invalid content."); + return Exception(3, WellKnownGeneratorMessages.MalformedResponseFile); } /// @@ -63,28 +57,22 @@ public static Exception CsWinRTProcessError(Exception exception) return Exception(5, "The projection writer failed during source generation.", exception); } - /// - /// The debug repro directory does not exist. - /// + /// public static Exception DebugReproDirectoryDoesNotExist(string path) { - return Exception(6, $"The debug repro directory '{path}' does not exist."); + return Exception(6, WellKnownGeneratorMessages.DebugReproDirectoryDoesNotExist(path)); } - /// - /// The debug repro contains a file entry that has no mapping. - /// + /// public static Exception DebugReproMissingFileEntryMapping(string path) { - return Exception(7, $"The debug repro file entry with path '{path}' is missing its assembly path mapping."); + return Exception(7, WellKnownGeneratorMessages.DebugReproMissingFileEntryMapping(path)); } - /// - /// The debug repro contains a file entry that was not recognized. - /// + /// public static Exception DebugReproUnrecognizedFileEntry(string path) { - return Exception(8, $"The debug repro file entry with path '{path}' was not recognized."); + return Exception(8, WellKnownGeneratorMessages.DebugReproUnrecognizedFileEntry(path)); } /// diff --git a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs index 58a0678f00..3ec0e57344 100644 --- a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs +++ b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs @@ -23,28 +23,22 @@ private WellKnownWinMDExceptions() { } - /// - /// Some exception was thrown when trying to read the response file. - /// + /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, "Failed to read the response file to run 'cswinrtwinmdgen'.", exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError("cswinrtwinmdgen"), exception); } - /// - /// The input response file is malformed. - /// + /// public static Exception MalformedResponseFile() { - return Exception(2, "The response file is malformed and contains invalid content."); + return Exception(2, WellKnownGeneratorMessages.MalformedResponseFile); } - /// - /// Failed to parse an argument from the response file. - /// + /// public static Exception ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) { - return Exception(3, $"Failed to parse argument '{argumentName}' from response file.", exception); + return Exception(3, WellKnownGeneratorMessages.ResponseFileArgumentParsingError(argumentName), exception); } /// @@ -79,28 +73,22 @@ public static Exception InputAssemblyRuntimeVersionNotFound(string path) return Exception(7, $"Failed to probe the .NET runtime version from the input assembly '{path}'."); } - /// - /// The debug repro directory does not exist. - /// + /// public static Exception DebugReproDirectoryDoesNotExist(string path) { - return Exception(8, $"The debug repro directory '{path}' does not exist."); + return Exception(8, WellKnownGeneratorMessages.DebugReproDirectoryDoesNotExist(path)); } - /// - /// The debug repro contains a file entry that has no mapping. - /// + /// public static Exception DebugReproMissingFileEntryMapping(string path) { - return Exception(9, $"The debug repro file entry with path '{path}' is missing its assembly path mapping."); + return Exception(9, WellKnownGeneratorMessages.DebugReproMissingFileEntryMapping(path)); } - /// - /// The debug repro contains a file entry that was not recognized. - /// + /// public static Exception DebugReproUnrecognizedFileEntry(string path) { - return Exception(10, $"The debug repro file entry with path '{path}' was not recognized."); + return Exception(10, WellKnownGeneratorMessages.DebugReproUnrecognizedFileEntry(path)); } /// From e044e48a776dc66a20208382d0ec63786ae14b42 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 12:50:56 -0700 Subject: [PATCH 18/28] Consolidate MvidGenerator + IncrementalHashExtensions in Core Both Impl and Interop shipped their own 'MvidGenerator' with complementary overloads ('CreateMvid(Guid, Guid)' for Impl; 'CreateMvid(params IEnumerable)' for Interop). Interop's overload also depended on a local 'IncrementalHashExtensions.AppendData(Stream)' extension whose only consumer was that one method. Move both 'MvidGenerator' overloads into a single file in 'WinRT.Generator.Core' and relocate 'IncrementalHashExtensions' alongside it. Each consumer continues to call the exact overload it already used, with no behavioral change. The impl-gen end-to-end output is byte-identical (257536 bytes, 0 byte diffs) which proves the 'Guid+Guid' MVID computation is preserved exactly. Net diff: 5 files changed, 2 new + 3 deleted. All 5 generators build with 0 warnings and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Extensions/IncrementalHashExtensions.cs | 4 +-- .../Helpers/MvidGenerator.cs | 26 +++++++++++++- .../Generation/ImplGenerator.cs | 1 + .../Helpers/MvidGenerator.cs | 36 ------------------- .../Generation/InteropGenerator.Emit.cs | 2 +- 5 files changed, 29 insertions(+), 40 deletions(-) rename src/{WinRT.Interop.Generator => WinRT.Generator.Core}/Extensions/IncrementalHashExtensions.cs (95%) rename src/{WinRT.Interop.Generator => WinRT.Generator.Core}/Helpers/MvidGenerator.cs (57%) delete mode 100644 src/WinRT.Impl.Generator/Helpers/MvidGenerator.cs diff --git a/src/WinRT.Interop.Generator/Extensions/IncrementalHashExtensions.cs b/src/WinRT.Generator.Core/Extensions/IncrementalHashExtensions.cs similarity index 95% rename from src/WinRT.Interop.Generator/Extensions/IncrementalHashExtensions.cs rename to src/WinRT.Generator.Core/Extensions/IncrementalHashExtensions.cs index ed75d20673..1db570f31a 100644 --- a/src/WinRT.Interop.Generator/Extensions/IncrementalHashExtensions.cs +++ b/src/WinRT.Generator.Core/Extensions/IncrementalHashExtensions.cs @@ -5,7 +5,7 @@ using System.IO; using System.Security.Cryptography; -namespace WindowsRuntime.InteropGenerator; +namespace WindowsRuntime.Generator.Extensions; /// /// Extensions for the type. @@ -34,4 +34,4 @@ public void AppendData(Stream stream) ArrayPool.Shared.Return(buffer); } } -} \ No newline at end of file +} diff --git a/src/WinRT.Interop.Generator/Helpers/MvidGenerator.cs b/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs similarity index 57% rename from src/WinRT.Interop.Generator/Helpers/MvidGenerator.cs rename to src/WinRT.Generator.Core/Helpers/MvidGenerator.cs index d738973660..200483f064 100644 --- a/src/WinRT.Interop.Generator/Helpers/MvidGenerator.cs +++ b/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs @@ -6,14 +6,38 @@ using System.IO; using System.Linq; using System.Security.Cryptography; +using WindowsRuntime.Generator.Extensions; -namespace WindowsRuntime.InteropGenerator.Helpers; +namespace WindowsRuntime.Generator.Helpers; /// /// A generator for MVIDs for .NET modules. /// internal static class MvidGenerator { + /// + /// Generates a deterministic MVID based on two input IIDs. + /// + /// The first IID to combine. + /// The second IID to combine. + /// The resulting MVID. + public static Guid CreateMvid(Guid left, Guid right) + { + Span input = stackalloc byte[32]; + + // Write the two IIDs in sequence + _ = left.TryWriteBytes(input, bigEndian: true, out _); + _ = right.TryWriteBytes(input[16..], bigEndian: true, out _); + + Span hash = stackalloc byte[SHA1.HashSizeInBytes]; + + // Hash the two IIDs together (the order matters) + _ = SHA1.HashData(input, hash); + + // Create the final MVID from the first 16 bytes of the hash + return new(hash[..16]); + } + /// /// Generates a deterministic MVID based on a set of input assemblies. /// diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index 1a6149aaee..41ef267294 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -20,6 +20,7 @@ using WindowsRuntime.Generator; using WindowsRuntime.Generator.Errors; using WindowsRuntime.Generator.Extensions; +using WindowsRuntime.Generator.Helpers; using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ImplGenerator.Errors; using WindowsRuntime.ImplGenerator.References; diff --git a/src/WinRT.Impl.Generator/Helpers/MvidGenerator.cs b/src/WinRT.Impl.Generator/Helpers/MvidGenerator.cs deleted file mode 100644 index 4a87706e0c..0000000000 --- a/src/WinRT.Impl.Generator/Helpers/MvidGenerator.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Security.Cryptography; - -namespace WindowsRuntime.ImplGenerator; - -/// -/// A generator for MVIDs for .NET modules. -/// -internal static class MvidGenerator -{ - /// - /// Generates a deterministic MVID based on two input IIDs. - /// - /// The first IID to combine. - /// The second IID to combine. - /// The resulting MVID. - public static Guid CreateMvid(Guid left, Guid right) - { - Span input = stackalloc byte[32]; - - // Write the two IIDs in sequence - _ = left.TryWriteBytes(input, bigEndian: true, out _); - _ = right.TryWriteBytes(input[16..], bigEndian: true, out _); - - Span hash = stackalloc byte[SHA1.HashSizeInBytes]; - - // Hash the two IIDs together (the order matters) - _ = SHA1.HashData(input, hash); - - // Create the final MVID from the first 16 bytes of the hash - return new(hash[..16]); - } -} diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs index 24da3772da..012efd323f 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs @@ -10,10 +10,10 @@ using AsmResolver.DotNet.Signatures; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.Helpers; using WindowsRuntime.InteropGenerator.Builders; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Fixups; -using WindowsRuntime.InteropGenerator.Helpers; using WindowsRuntime.InteropGenerator.Models; using WindowsRuntime.InteropGenerator.References; using WindowsRuntime.InteropGenerator.Rewriters; From a9322b00b2b8b0595008f758fddf35b70931dc78 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 13:02:14 -0700 Subject: [PATCH 19/28] Consolidate WellKnownPublicKeys + WellKnownPublicKeyTokens in Core The 160-byte CsWinRT 3.0 strong-name public keys lived in several inconsistent places across the generators: * 'ImplValues.PublicKeyData' (Impl) - the SDK projection key, used 3x, but the type/property name suggested it was a generic 'PublicKey'. * 'InteropValues.WindowsSdkProjectionPublicKey[Data]' (Interop) - the same SDK projection key, dead code (0 callers). * 'InteropValues.CsWinRTPublicKey[Data]' (Interop) - the real CsWinRT runtime key, used 1x. * 'ProjectionGenerator.Emit.CsWinRTPublicKey' (Projection) - the SDK projection key again, used 1x, but misnamed as the CsWinRT key. The const-string forms ('PublicKey', 'CsWinRTPublicKey', 'WindowsSdkProjectionPublicKey') had 0 callers anywhere - leftover dead code. The same 'B5FC90E7...' byte sequence appeared 3 times, which is exactly the kind of duplication that risks accidental drift. Consolidate into a single 'WellKnownPublicKeys' in 'WinRT.Generator.Core': * 'WindowsSdkProjection' - the precompiled SDK projection assembly key (used by Impl forwarder refs and by Projection's Roslyn delay-signing). * 'CsWinRT' - the real CsWinRT 3.0 runtime release key (used by Interop when emitting refs to 'WinRT.Runtime.dll'). Each live caller is repointed; Projection wraps the 'byte[]' as 'ImmutableArray' on the fly via 'ImmutableCollectionsMarshal' for Roslyn's API. The dead-code declarations are deleted, including the now-empty 'ImplValues' and 'InteropValues' files entirely. The public key tokens ('Interop\WellKnownPublicKeyTokens.cs' with 6 entries + 'WinMD\WellKnownPublicKeyTokens.cs' with 1 entry) are unioned into 'WinRT.Generator.Core\References\WellKnownPublicKeyTokens.cs' (7 entries total: MSCorLib + the 6 from Interop). Net diff: 10 modified files + 2 new + 4 deleted (16 files total), -22 LOC. Removes the dead 'WindowsSdkProjectionPublicKey[Data]' declarations from 'InteropValues' (~31 LOC), fixes the 'PublicKey'/'CsWinRTPublicKey'/'WindowsSdkProjection' naming inconsistency, and centralizes the canonical byte sequences in one file. The byte-identical impl-gen output is the strongest possible confirmation that the consolidated keys match the originals exactly. All 5 generators build with 0 warnings and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../References/WellKnownPublicKeyTokens.cs | 22 +++++++++----- .../References/WellKnownPublicKeys.cs | 24 +++++++++++++++ .../Generation/ImplGenerator.cs | 8 ++--- .../References/ImplValues.cs | 20 ------------- .../Generation/InteropGenerator.Discover.cs | 3 +- .../References/InteropReferences.cs | 1 + .../References/InteropValues.cs | 30 ------------------- .../Generation/ProjectionGenerator.Emit.cs | 10 ++----- .../References/WellKnownPublicKeyTokens.cs | 15 ---------- .../Writers/WinMDWriter.TypeMapping.cs | 2 +- .../Writers/WinMDWriter.cs | 2 +- 11 files changed, 50 insertions(+), 87 deletions(-) rename src/{WinRT.Interop.Generator => WinRT.Generator.Core}/References/WellKnownPublicKeyTokens.cs (60%) create mode 100644 src/WinRT.Generator.Core/References/WellKnownPublicKeys.cs delete mode 100644 src/WinRT.Impl.Generator/References/ImplValues.cs delete mode 100644 src/WinRT.Interop.Generator/References/InteropValues.cs delete mode 100644 src/WinRT.WinMD.Generator/References/WellKnownPublicKeyTokens.cs diff --git a/src/WinRT.Interop.Generator/References/WellKnownPublicKeyTokens.cs b/src/WinRT.Generator.Core/References/WellKnownPublicKeyTokens.cs similarity index 60% rename from src/WinRT.Interop.Generator/References/WellKnownPublicKeyTokens.cs rename to src/WinRT.Generator.Core/References/WellKnownPublicKeyTokens.cs index a9f34cc3da..7247409794 100644 --- a/src/WinRT.Interop.Generator/References/WellKnownPublicKeyTokens.cs +++ b/src/WinRT.Generator.Core/References/WellKnownPublicKeyTokens.cs @@ -1,35 +1,41 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace WindowsRuntime.InteropGenerator.References; +namespace WindowsRuntime.Generator.References; /// -/// Well known public key tokens. +/// Well known public key tokens for the framework and CsWinRT assemblies referenced by the +/// CsWinRT CLI generators. /// internal static class WellKnownPublicKeyTokens { /// - /// The public key data for System.Memory.dll. + /// The public key token for mscorlib (b77a5c561934e089). + /// + public static readonly byte[] MSCorLib = [0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89]; + + /// + /// The public key token for System.Memory.dll. /// public static readonly byte[] SystemMemory = [0xCC, 0x7B, 0x13, 0xFF, 0xCD, 0x2D, 0xDD, 0x51]; /// - /// The public key data for System.ObjectModel.dll. + /// The public key token for System.ObjectModel.dll. /// public static readonly byte[] SystemObjectModel = [0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A]; /// - /// The public key data for System.Runtime.InteropServices.dll. + /// The public key token for System.Runtime.InteropServices.dll. /// public static readonly byte[] SystemRuntimeInteropServices = [0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A]; /// - /// The public key data for System.Numerics.Vectors.dll. + /// The public key token for System.Numerics.Vectors.dll. /// public static readonly byte[] SystemNumericsVectors = [0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A]; /// - /// The public key data for System.Threading.dll. + /// The public key token for System.Threading.dll. /// public static readonly byte[] SystemThreading = [0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A]; @@ -37,4 +43,4 @@ internal static class WellKnownPublicKeyTokens /// The public key token for CsWinRT assemblies (31bf3856ad364e35). /// public static readonly byte[] CsWinRT = [0x31, 0xBF, 0x38, 0x56, 0xAD, 0x36, 0x4E, 0x35]; -} \ No newline at end of file +} diff --git a/src/WinRT.Generator.Core/References/WellKnownPublicKeys.cs b/src/WinRT.Generator.Core/References/WellKnownPublicKeys.cs new file mode 100644 index 0000000000..73e159760b --- /dev/null +++ b/src/WinRT.Generator.Core/References/WellKnownPublicKeys.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WindowsRuntime.Generator.References; + +/// +/// The 160-byte strong-name public keys consumed by the CsWinRT CLI generators when emitting +/// assembly references or signing generated assemblies. +/// +internal static class WellKnownPublicKeys +{ + /// + /// The public key used by the precompiled Windows SDK projection assemblies (WinRT.Sdk.Projection.dll, + /// WinRT.Sdk.Xaml.Projection.dll) and the merged projection assembly (WinRT.Projection.dll) that + /// the projection generator produces at app build time. The impl generator stamps this same key on the + /// AssemblyReference entries it emits in the forwarder .dll. + /// + public static readonly byte[] WindowsSdkProjection = [0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xB5, 0xFC, 0x90, 0xE7, 0x02, 0x7F, 0x67, 0x87, 0x1E, 0x77, 0x3A, 0x8F, 0xDE, 0x89, 0x38, 0xC8, 0x1D, 0xD4, 0x02, 0xBA, 0x65, 0xB9, 0x20, 0x1D, 0x60, 0x59, 0x3E, 0x96, 0xC4, 0x92, 0x65, 0x1E, 0x88, 0x9C, 0xC1, 0x3F, 0x14, 0x15, 0xEB, 0xB5, 0x3F, 0xAC, 0x11, 0x31, 0xAE, 0x0B, 0xD3, 0x33, 0xC5, 0xEE, 0x60, 0x21, 0x67, 0x2D, 0x97, 0x18, 0xEA, 0x31, 0xA8, 0xAE, 0xBD, 0x0D, 0xA0, 0x07, 0x2F, 0x25, 0xD8, 0x7D, 0xBA, 0x6F, 0xC9, 0x0F, 0xFD, 0x59, 0x8E, 0xD4, 0xDA, 0x35, 0xE4, 0x4C, 0x39, 0x8C, 0x45, 0x43, 0x07, 0xE8, 0xE3, 0x3B, 0x84, 0x26, 0x14, 0x3D, 0xAE, 0xC9, 0xF5, 0x96, 0x83, 0x6F, 0x97, 0xC8, 0xF7, 0x47, 0x50, 0xE5, 0x97, 0x5C, 0x64, 0xE2, 0x18, 0x9F, 0x45, 0xDE, 0xF4, 0x6B, 0x2A, 0x2B, 0x12, 0x47, 0xAD, 0xC3, 0x65, 0x2B, 0xF5, 0xC3, 0x08, 0x05, 0x5D, 0xA9]; + + /// + /// The public key for the real CsWinRT 3.0 runtime release assemblies (e.g. WinRT.Runtime.dll). + /// + public static readonly byte[] CsWinRT = [0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x65, 0x32, 0x43, 0xC2, 0xEC, 0x7B, 0x83, 0x00, 0x92, 0x93, 0xB0, 0xE7, 0x98, 0x19, 0x3A, 0x8B, 0xB1, 0x03, 0x90, 0xB5, 0xA1, 0x2A, 0x3F, 0x2D, 0x5C, 0x58, 0x28, 0x2D, 0x71, 0x29, 0xDA, 0xD2, 0xD0, 0xC0, 0x12, 0x75, 0x53, 0x29, 0xC1, 0xA4, 0x51, 0x73, 0xE1, 0xAC, 0x9B, 0x8B, 0x4C, 0x0A, 0x4E, 0x07, 0x82, 0x30, 0xBD, 0xD5, 0xE8, 0xCE, 0x39, 0x32, 0x08, 0x3B, 0x09, 0x89, 0x2D, 0x82, 0x8E, 0x4B, 0x18, 0xAF, 0x00, 0xCE, 0x74, 0x6B, 0xCC, 0x99, 0x4D, 0xAD, 0x06, 0xAC, 0xEC, 0x3E, 0x69, 0x79, 0x3E, 0x75, 0xF8, 0xD2, 0xCC, 0x8E, 0x77, 0xF4, 0x46, 0x68, 0x55, 0x0C, 0xA9, 0xB8, 0x3D, 0xD6, 0x48, 0x2D, 0xA9, 0xAA, 0x4D, 0x2E, 0x8B, 0xCF, 0x2E, 0x17, 0x93, 0xCF, 0x84, 0xC4, 0x95, 0x34, 0x6B, 0x1B, 0x99, 0xEB, 0x99, 0x76, 0x7F, 0xB4, 0x12, 0x46, 0x67, 0x2E, 0x7C, 0xF0]; +} diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index 41ef267294..10b86b510c 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -22,8 +22,8 @@ using WindowsRuntime.Generator.Extensions; using WindowsRuntime.Generator.Helpers; using WindowsRuntime.Generator.Parsing; +using WindowsRuntime.Generator.References; using WindowsRuntime.ImplGenerator.Errors; -using WindowsRuntime.ImplGenerator.References; namespace WindowsRuntime.ImplGenerator.Generation; @@ -245,7 +245,7 @@ private static void EmitTypeForwards(ModuleDefinition inputModule, ModuleDefinit // will always have a version number equal or higher than this, so it will load correctly. AssemblyReference sdkProjectionAssembly = new("WinRT.Sdk.Projection"u8, new Version(0, 0, 0, 0)) { - PublicKeyOrToken = ImplValues.PublicKeyData, + PublicKeyOrToken = WellKnownPublicKeys.WindowsSdkProjection, HasPublicKey = true }; @@ -253,7 +253,7 @@ private static void EmitTypeForwards(ModuleDefinition inputModule, ModuleDefinit // This is only used when the option to use Windows UI Xaml projections is enabled. AssemblyReference sdkXamlProjectionAssembly = new("WinRT.Sdk.Xaml.Projection"u8, new Version(0, 0, 0, 0)) { - PublicKeyOrToken = ImplValues.PublicKeyData, + PublicKeyOrToken = WellKnownPublicKeys.WindowsSdkProjection, HasPublicKey = true }; @@ -261,7 +261,7 @@ private static void EmitTypeForwards(ModuleDefinition inputModule, ModuleDefinit // Unlike the implementation .dll for the Windows SDK however, this .dll is created on the fly. AssemblyReference projectionAssembly = new("WinRT.Projection"u8, new Version(0, 0, 0, 0)) { - PublicKeyOrToken = ImplValues.PublicKeyData, + PublicKeyOrToken = WellKnownPublicKeys.WindowsSdkProjection, HasPublicKey = true }; diff --git a/src/WinRT.Impl.Generator/References/ImplValues.cs b/src/WinRT.Impl.Generator/References/ImplValues.cs deleted file mode 100644 index e29b8828de..0000000000 --- a/src/WinRT.Impl.Generator/References/ImplValues.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WindowsRuntime.ImplGenerator.References; - -/// -/// Well known impl values (constants). -/// -internal static class ImplValues -{ - /// - /// The public key for CsWinRT assemblies. - /// - public const string PublicKey = "0024000004800000940000000602000000240000525341310004000001000100B5FC90E7027F67871E773A8FDE8938C81DD402BA65B9201D60593E96C492651E889CC13F1415EBB53FAC1131AE0BD333C5EE6021672D9718EA31A8AEBD0DA0072F25D87DBA6FC90FFD598ED4DA35E44C398C454307E8E33B8426143DAEC9F596836F97C8F74750E5975C64E2189F45DEF46B2A2B1247ADC3652BF5C308055DA9"; - - /// - /// The public key data for CsWinRT assemblies. - /// - public static readonly byte[] PublicKeyData = [0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xB5, 0xFC, 0x90, 0xE7, 0x02, 0x7F, 0x67, 0x87, 0x1E, 0x77, 0x3A, 0x8F, 0xDE, 0x89, 0x38, 0xC8, 0x1D, 0xD4, 0x02, 0xBA, 0x65, 0xB9, 0x20, 0x1D, 0x60, 0x59, 0x3E, 0x96, 0xC4, 0x92, 0x65, 0x1E, 0x88, 0x9C, 0xC1, 0x3F, 0x14, 0x15, 0xEB, 0xB5, 0x3F, 0xAC, 0x11, 0x31, 0xAE, 0x0B, 0xD3, 0x33, 0xC5, 0xEE, 0x60, 0x21, 0x67, 0x2D, 0x97, 0x18, 0xEA, 0x31, 0xA8, 0xAE, 0xBD, 0x0D, 0xA0, 0x07, 0x2F, 0x25, 0xD8, 0x7D, 0xBA, 0x6F, 0xC9, 0x0F, 0xFD, 0x59, 0x8E, 0xD4, 0xDA, 0x35, 0xE4, 0x4C, 0x39, 0x8C, 0x45, 0x43, 0x07, 0xE8, 0xE3, 0x3B, 0x84, 0x26, 0x14, 0x3D, 0xAE, 0xC9, 0xF5, 0x96, 0x83, 0x6F, 0x97, 0xC8, 0xF7, 0x47, 0x50, 0xE5, 0x97, 0x5C, 0x64, 0xE2, 0x18, 0x9F, 0x45, 0xDE, 0xF4, 0x6B, 0x2A, 0x2B, 0x12, 0x47, 0xAD, 0xC3, 0x65, 0x2B, 0xF5, 0xC3, 0x08, 0x05, 0x5D, 0xA9]; -} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index c2444f365a..42760ba3c2 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -12,6 +12,7 @@ using AsmResolver.PE; using WindowsRuntime.Generator.Errors; using WindowsRuntime.Generator.Extensions; +using WindowsRuntime.Generator.References; using WindowsRuntime.InteropGenerator.Discovery; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Models; @@ -557,7 +558,7 @@ private static InteropReferences CreateDiscoveryInteropReferences(ModuleDefiniti AssemblyReference windowsRuntimeAssembly = new("WinRT.Runtime"u8, windowsRuntimeVersion) { // Set the public keys, as it's needed to ensure references compare as equals as expected - PublicKeyOrToken = InteropValues.CsWinRTPublicKeyData, + PublicKeyOrToken = WellKnownPublicKeys.CsWinRT, HasPublicKey = true }; diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index fb8e570927..77c1e2ed9d 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -4,6 +4,7 @@ using System; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.Generator.References; using WindowsRuntime.InteropGenerator.Factories; #pragma warning disable IDE0032 diff --git a/src/WinRT.Interop.Generator/References/InteropValues.cs b/src/WinRT.Interop.Generator/References/InteropValues.cs deleted file mode 100644 index a74b9748d5..0000000000 --- a/src/WinRT.Interop.Generator/References/InteropValues.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WindowsRuntime.InteropGenerator.References; - -/// -/// Well known interop values (constants). -/// -internal static class InteropValues -{ - /// - /// The public key for CsWinRT assemblies. - /// - public const string CsWinRTPublicKey = "0024000004800000940000000602000000240000525341310004000001000100653243C2EC7B83009293B0E798193A8BB10390B5A12A3F2D5C58282D7129DAD2D0C012755329C1A45173E1AC9B8B4C0A4E078230BDD5E8CE3932083B09892D828E4B18AF00CE746BCC994DAD06ACEC3E69793E75F8D2CC8E77F44668550CA9B83DD6482DA9AA4D2E8BCF2E1793CF84C495346B1B99EB99767FB41246672E7CF0"; - - /// - /// The public key for Windows SDK projection assemblies. - /// - public const string WindowsSdkProjectionPublicKey = "0024000004800000940000000602000000240000525341310004000001000100B5FC90E7027F67871E773A8FDE8938C81DD402BA65B9201D60593E96C492651E889CC13F1415EBB53FAC1131AE0BD333C5EE6021672D9718EA31A8AEBD0DA0072F25D87DBA6FC90FFD598ED4DA35E44C398C454307E8E33B8426143DAEC9F596836F97C8F74750E5975C64E2189F45DEF46B2A2B1247ADC3652BF5C308055DA9"; - - /// - /// The public key data for CsWinRT assemblies. - /// - public static readonly byte[] CsWinRTPublicKeyData = [0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x65, 0x32, 0x43, 0xC2, 0xEC, 0x7B, 0x83, 0x00, 0x92, 0x93, 0xB0, 0xE7, 0x98, 0x19, 0x3A, 0x8B, 0xB1, 0x03, 0x90, 0xB5, 0xA1, 0x2A, 0x3F, 0x2D, 0x5C, 0x58, 0x28, 0x2D, 0x71, 0x29, 0xDA, 0xD2, 0xD0, 0xC0, 0x12, 0x75, 0x53, 0x29, 0xC1, 0xA4, 0x51, 0x73, 0xE1, 0xAC, 0x9B, 0x8B, 0x4C, 0x0A, 0x4E, 0x07, 0x82, 0x30, 0xBD, 0xD5, 0xE8, 0xCE, 0x39, 0x32, 0x08, 0x3B, 0x09, 0x89, 0x2D, 0x82, 0x8E, 0x4B, 0x18, 0xAF, 0x00, 0xCE, 0x74, 0x6B, 0xCC, 0x99, 0x4D, 0xAD, 0x06, 0xAC, 0xEC, 0x3E, 0x69, 0x79, 0x3E, 0x75, 0xF8, 0xD2, 0xCC, 0x8E, 0x77, 0xF4, 0x46, 0x68, 0x55, 0x0C, 0xA9, 0xB8, 0x3D, 0xD6, 0x48, 0x2D, 0xA9, 0xAA, 0x4D, 0x2E, 0x8B, 0xCF, 0x2E, 0x17, 0x93, 0xCF, 0x84, 0xC4, 0x95, 0x34, 0x6B, 0x1B, 0x99, 0xEB, 0x99, 0x76, 0x7F, 0xB4, 0x12, 0x46, 0x67, 0x2E, 0x7C, 0xF0]; - - /// - /// The public key data for Windows SDK projection assemblies. - /// - public static readonly byte[] WindowsSdkProjectionPublicKeyData = [0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xB5, 0xFC, 0x90, 0xE7, 0x02, 0x7F, 0x67, 0x87, 0x1E, 0x77, 0x3A, 0x8F, 0xDE, 0x89, 0x38, 0xC8, 0x1D, 0xD4, 0x02, 0xBA, 0x65, 0xB9, 0x20, 0x1D, 0x60, 0x59, 0x3E, 0x96, 0xC4, 0x92, 0x65, 0x1E, 0x88, 0x9C, 0xC1, 0x3F, 0x14, 0x15, 0xEB, 0xB5, 0x3F, 0xAC, 0x11, 0x31, 0xAE, 0x0B, 0xD3, 0x33, 0xC5, 0xEE, 0x60, 0x21, 0x67, 0x2D, 0x97, 0x18, 0xEA, 0x31, 0xA8, 0xAE, 0xBD, 0x0D, 0xA0, 0x07, 0x2F, 0x25, 0xD8, 0x7D, 0xBA, 0x6F, 0xC9, 0x0F, 0xFD, 0x59, 0x8E, 0xD4, 0xDA, 0x35, 0xE4, 0x4C, 0x39, 0x8C, 0x45, 0x43, 0x07, 0xE8, 0xE3, 0x3B, 0x84, 0x26, 0x14, 0x3D, 0xAE, 0xC9, 0xF5, 0x96, 0x83, 0x6F, 0x97, 0xC8, 0xF7, 0x47, 0x50, 0xE5, 0x97, 0x5C, 0x64, 0xE2, 0x18, 0x9F, 0x45, 0xDE, 0xF4, 0x6B, 0x2A, 0x2B, 0x12, 0x47, 0xAD, 0xC3, 0x65, 0x2B, 0xF5, 0xC3, 0x08, 0x05, 0x5D, 0xA9]; -} \ No newline at end of file diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs index 71ad974fbe..506a93f8f8 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs @@ -3,13 +3,14 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; +using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.References; using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; @@ -17,11 +18,6 @@ namespace WindowsRuntime.ProjectionGenerator.Generation; /// internal partial class ProjectionGenerator { - /// - /// The public key for CsWinRT assemblies, used for delay signing projection DLLs. - /// - private static readonly ImmutableArray CsWinRTPublicKey = [0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xB5, 0xFC, 0x90, 0xE7, 0x02, 0x7F, 0x67, 0x87, 0x1E, 0x77, 0x3A, 0x8F, 0xDE, 0x89, 0x38, 0xC8, 0x1D, 0xD4, 0x02, 0xBA, 0x65, 0xB9, 0x20, 0x1D, 0x60, 0x59, 0x3E, 0x96, 0xC4, 0x92, 0x65, 0x1E, 0x88, 0x9C, 0xC1, 0x3F, 0x14, 0x15, 0xEB, 0xB5, 0x3F, 0xAC, 0x11, 0x31, 0xAE, 0x0B, 0xD3, 0x33, 0xC5, 0xEE, 0x60, 0x21, 0x67, 0x2D, 0x97, 0x18, 0xEA, 0x31, 0xA8, 0xAE, 0xBD, 0x0D, 0xA0, 0x07, 0x2F, 0x25, 0xD8, 0x7D, 0xBA, 0x6F, 0xC9, 0x0F, 0xFD, 0x59, 0x8E, 0xD4, 0xDA, 0x35, 0xE4, 0x4C, 0x39, 0x8C, 0x45, 0x43, 0x07, 0xE8, 0xE3, 0x3B, 0x84, 0x26, 0x14, 0x3D, 0xAE, 0xC9, 0xF5, 0x96, 0x83, 0x6F, 0x97, 0xC8, 0xF7, 0x47, 0x50, 0xE5, 0x97, 0x5C, 0x64, 0xE2, 0x18, 0x9F, 0x45, 0xDE, 0xF4, 0x6B, 0x2A, 0x2B, 0x12, 0x47, 0xAD, 0xC3, 0x65, 0x2B, 0xF5, 0xC3, 0x08, 0x05, 0x5D, 0xA9]; - /// /// Runs the emit logic for the generator. /// @@ -68,7 +64,7 @@ private static void Emit(ProjectionGeneratorArgs args, ProjectionGeneratorProces allowUnsafe: true, optimizationLevel: OptimizationLevel.Release, deterministic: true, - cryptoPublicKey: CsWinRTPublicKey, + cryptoPublicKey: ImmutableCollectionsMarshal.AsImmutableArray(WellKnownPublicKeys.WindowsSdkProjection), delaySign: true, generalDiagnosticOption: ReportDiagnostic.Info)); } diff --git a/src/WinRT.WinMD.Generator/References/WellKnownPublicKeyTokens.cs b/src/WinRT.WinMD.Generator/References/WellKnownPublicKeyTokens.cs deleted file mode 100644 index 77897462dc..0000000000 --- a/src/WinRT.WinMD.Generator/References/WellKnownPublicKeyTokens.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WindowsRuntime.WinMDGenerator.References; - -/// -/// Well known public key tokens. -/// -internal static class WellKnownPublicKeyTokens -{ - /// - /// The public key data for mscorlib. - /// - public static readonly byte[] MSCorLib = [0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89]; -} \ No newline at end of file diff --git a/src/WinRT.WinMD.Generator/Writers/WinMDWriter.TypeMapping.cs b/src/WinRT.WinMD.Generator/Writers/WinMDWriter.TypeMapping.cs index 4412429c39..07a492aa10 100644 --- a/src/WinRT.WinMD.Generator/Writers/WinMDWriter.TypeMapping.cs +++ b/src/WinRT.WinMD.Generator/Writers/WinMDWriter.TypeMapping.cs @@ -6,9 +6,9 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; +using WindowsRuntime.Generator.References; using WindowsRuntime.InteropGenerator.References; using WindowsRuntime.WinMDGenerator.Models; -using WindowsRuntime.WinMDGenerator.References; using AssemblyAttributes = AsmResolver.PE.DotNet.Metadata.Tables.AssemblyAttributes; #pragma warning disable IDE0072 diff --git a/src/WinRT.WinMD.Generator/Writers/WinMDWriter.cs b/src/WinRT.WinMD.Generator/Writers/WinMDWriter.cs index bd6ee8f52e..e7af3b363f 100644 --- a/src/WinRT.WinMD.Generator/Writers/WinMDWriter.cs +++ b/src/WinRT.WinMD.Generator/Writers/WinMDWriter.cs @@ -4,11 +4,11 @@ using System; using System.Collections.Generic; using AsmResolver.DotNet; +using WindowsRuntime.Generator.References; using WindowsRuntime.InteropGenerator.References; using WindowsRuntime.WinMDGenerator.Errors; using WindowsRuntime.WinMDGenerator.Helpers; using WindowsRuntime.WinMDGenerator.Models; -using WindowsRuntime.WinMDGenerator.References; using AssemblyAttributes = AsmResolver.PE.DotNet.Metadata.Tables.AssemblyAttributes; #pragma warning disable IDE0046 From d93c8c725fb55f673c2688058df4f5f004166436 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 13:19:26 -0700 Subject: [PATCH 20/28] Make GeneratorPhaseRunner generic in TArgs and capture the args itself 'GeneratorHost.Prepare' previously returned a tuple '(TArgs Args, GeneratorPhaseRunner Runner)', and each per-tool 'Run' method captured the 'args' local in a closure for every phase body ('runner.RunPhase("phase", () => DoPhase(args, ...))'). Make 'GeneratorPhaseRunner' generic in 'TArgs', have it capture the parsed args directly, and pass them to every body delegate. The 'RunPhase' overloads now take 'Action' / 'Func' instead of 'Action' / 'Func'. Per-tool exception identity is fully preserved because the per-tool 'wrapUnhandled' delegate is still invoked unchanged. Phase bodies that only need 'args' now bind to a method group (e.g. 'body: Discover' instead of 'body: () => Discover(args)') and allocate zero closures per invocation. Five phases across the five generators benefit from this: WinMD 'Discover', Interop 'Discover', Projection 'ProcessReferences', ProjectionRef 'BuildWriterOptions', and Impl 'LoadOutputModule'. The remaining eight phases (which need additional state besides 'args') still use lambdas, but with 'args' as an explicit parameter rather than a captured local. Rename 'GeneratorHost.Prepare' to 'GeneratorHost.CreateRunner' since the method now exclusively returns the runner (no longer a preamble-only operation that needs a separate "prepare" verb). Net diff: 7 files, +83 / -67 LOC. All 5 generators build with 0 warnings and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/WinRT.Generator.Core/GeneratorHost.cs | 27 +++++----- .../GeneratorPhaseRunner.cs | 53 ++++++++++++------- .../Generation/ImplGenerator.cs | 20 +++---- .../Generation/InteropGenerator.cs | 12 ++--- .../Generation/ProjectionGenerator.cs | 18 +++---- .../ReferenceProjectionGenerator.cs | 10 ++-- .../Generation/WinMDGenerator.cs | 10 ++-- 7 files changed, 83 insertions(+), 67 deletions(-) diff --git a/src/WinRT.Generator.Core/GeneratorHost.cs b/src/WinRT.Generator.Core/GeneratorHost.cs index 94b1d09b13..e8f543eeaf 100644 --- a/src/WinRT.Generator.Core/GeneratorHost.cs +++ b/src/WinRT.Generator.Core/GeneratorHost.cs @@ -18,22 +18,24 @@ namespace WindowsRuntime.Generator; /// Parse the response file into a per-tool args record. /// If DebugReproDirectory is set and we are not already replaying, save a debug repro of the current invocation. /// -/// encapsulates that preamble. Each generator's Run now starts with a -/// single call to it; the per-tool unpack / save / parse logic is supplied via delegates so behavior -/// stays identical (same log messages, same exception phases, same per-tool unhandled exception type). +/// encapsulates that preamble. Each generator's Run now starts with +/// a single call to it; the per-tool unpack / save / parse logic is supplied via delegates so behavior stays +/// identical (same log messages, same exception phases, same per-tool unhandled exception type). /// /// The same Run methods then proceed through a series of phases (loading, processing, emit, ...), /// each wrapped in an identical try/catch that re-throws as the per-tool -/// Unhandled*Exception. additionally returns a -/// bound to the same per-tool wrapUnhandled and log -/// delegates so each phase can be expressed as a single -/// call instead of a hand-written try/catch. +/// Unhandled*Exception. returns a +/// bound to the parsed args plus the same per-tool +/// wrapUnhandled and log delegates, so each phase can be expressed as a single +/// call instead of a +/// hand-written try/catch. /// /// internal static class GeneratorHost { /// - /// Runs the shared unpack → parse → save preamble for a CsWinRT CLI generator. + /// Runs the shared unpack → parse → save preamble for a CsWinRT CLI generator and returns a + /// ready to drive the remaining per-tool phases. /// /// The per-tool args record (must implement ). /// The input file path (response file or debug-repro .zip). @@ -45,10 +47,10 @@ internal static class GeneratorHost /// Logs a progress message to the user (typically ConsoleApp.Log from ConsoleAppFramework). /// The token for the operation. /// - /// A pair containing the parsed instance and a - /// pre-bound to and for use by subsequent phases. + /// A pre-bound to the parsed args, + /// and for use by subsequent phases. /// - public static (TArgs Args, GeneratorPhaseRunner Runner) Prepare( + public static GeneratorPhaseRunner CreateRunner( string inputFilePath, string toolName, Func unpackDebugRepro, @@ -120,8 +122,9 @@ public static (TArgs Args, GeneratorPhaseRunner Runner) Prepare( args.Token.ThrowIfCancellationRequested(); - return (args, new GeneratorPhaseRunner(wrapUnhandled, log)); + return new GeneratorPhaseRunner(args, wrapUnhandled, log); } } + diff --git a/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs b/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs index 8e71fb5e3d..cc151243d6 100644 --- a/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs +++ b/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs @@ -10,16 +10,21 @@ namespace WindowsRuntime.Generator; /// Runs the body of a single generator phase, wrapping unexpected exceptions in the per-tool /// Unhandled*Exception and optionally logging a progress message before the body runs. /// +/// The per-tool args record (must implement ). /// /// Each generator's Run method historically wrapped every phase (loading, processing, emit, ...) /// in an identical try/catch that re-threw as the per-tool Unhandled*Exception -/// (with the phase name as the constructor argument). captures -/// the per-tool wrapUnhandled and log delegates once (it is returned bound to them by -/// ) and lets each phase call site collapse to a single -/// (or overload) invocation. Per-tool exception identity -/// is fully preserved because the original wrapUnhandled delegate is invoked unchanged. +/// (with the phase name as the constructor argument). captures +/// the parsed instance plus the per-tool wrapUnhandled and log +/// delegates (it is returned bound to all three by ) and +/// lets each phase call site collapse to a single (or +/// overload) invocation. The captured is forwarded to every body delegate, +/// so phases that only depend on it can be expressed as a static lambda (or method group) with +/// zero per-call allocations. Per-tool exception identity is fully preserved because the original +/// wrapUnhandled delegate is invoked unchanged. /// -internal readonly struct GeneratorPhaseRunner +internal readonly struct GeneratorPhaseRunner + where TArgs : IGeneratorArgs { /// /// The per-tool wrapUnhandled delegate. @@ -32,27 +37,34 @@ internal readonly struct GeneratorPhaseRunner private readonly Action _log; /// - /// Creates a new bound to the given per-tool delegates. + /// Creates a new bound to the given args and per-tool delegates. /// + /// The parsed per-tool args, forwarded to every body. /// Wraps an unexpected exception into the per-tool Unhandled*Exception with the given phase name. /// Logs a progress message to the user (typically ConsoleApp.Log from ConsoleAppFramework). - internal GeneratorPhaseRunner(Func wrapUnhandled, Action log) + internal GeneratorPhaseRunner(TArgs args, Func wrapUnhandled, Action log) { + Args = args; _wrapUnhandled = wrapUnhandled; _log = log; } + /// + /// Gets the parsed per-tool args, forwarded to every body. + /// + public TArgs Args { get; } + /// /// Runs , wrapping any unexpected exception in the per-tool /// Unhandled*Exception with as the phase tag. /// /// The phase name used by the per-tool Unhandled*Exception. - /// The body of the phase to run. - public void RunPhase(string phaseName, Action body) + /// The body of the phase to run. The captured is forwarded as its argument. + public void RunPhase(string phaseName, Action body) { try { - body(); + body(Args); } catch (Exception e) when (!e.IsWellKnown) { @@ -67,13 +79,13 @@ public void RunPhase(string phaseName, Action body) /// /// The phase name used by the per-tool Unhandled*Exception. /// The progress message to log before the body runs. - /// The body of the phase to run. - public void RunPhase(string phaseName, string logMessage, Action body) + /// The body of the phase to run. The captured is forwarded as its argument. + public void RunPhase(string phaseName, string logMessage, Action body) { try { _log(logMessage); - body(); + body(Args); } catch (Exception e) when (!e.IsWellKnown) { @@ -87,13 +99,13 @@ public void RunPhase(string phaseName, string logMessage, Action body) /// /// The result type of . /// The phase name used by the per-tool Unhandled*Exception. - /// The body of the phase to run. + /// The body of the phase to run. The captured is forwarded as its argument. /// The value returned by . - public T RunPhase(string phaseName, Func body) + public T RunPhase(string phaseName, Func body) { try { - return body(); + return body(Args); } catch (Exception e) when (!e.IsWellKnown) { @@ -109,14 +121,14 @@ public T RunPhase(string phaseName, Func body) /// The result type of . /// The phase name used by the per-tool Unhandled*Exception. /// The progress message to log before the body runs. - /// The body of the phase to run. + /// The body of the phase to run. The captured is forwarded as its argument. /// The value returned by . - public T RunPhase(string phaseName, string logMessage, Func body) + public T RunPhase(string phaseName, string logMessage, Func body) { try { _log(logMessage); - return body(); + return body(Args); } catch (Exception e) when (!e.IsWellKnown) { @@ -124,3 +136,4 @@ public T RunPhase(string phaseName, string logMessage, Func body) } } } + diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index 10b86b510c..e855b52c90 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -70,7 +70,7 @@ internal static partial class ImplGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - (ImplGeneratorArgs args, GeneratorPhaseRunner runner) = GeneratorHost.Prepare( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtimplgen", unpackDebugRepro: UnpackDebugRepro, @@ -83,38 +83,38 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) // Initialize the assembly resolver and load the output module (RuntimeContext runtimeContext, ModuleDefinition outputModule) = runner.RunPhase( phaseName: "loading", - body: () => LoadOutputModule(args)); + body: LoadOutputModule); - args.Token.ThrowIfCancellationRequested(); + runner.Args.Token.ThrowIfCancellationRequested(); // Define the impl module to emit ModuleDefinition implModule = runner.RunPhase( phaseName: "loading", - body: () => DefineImplModule(runtimeContext, outputModule)); + body: _ => DefineImplModule(runtimeContext, outputModule)); - args.Token.ThrowIfCancellationRequested(); + runner.Args.Token.ThrowIfCancellationRequested(); // Emit all necessary IL code in the impl module - runner.RunPhase(phaseName: "generation", body: () => + runner.RunPhase(phaseName: "generation", body: _ => { EmitAssemblyAttributes(outputModule, implModule); EmitTypeForwards(outputModule, implModule); }); - args.Token.ThrowIfCancellationRequested(); + runner.Args.Token.ThrowIfCancellationRequested(); // Write the module to disk with all the generated contents runner.RunPhase( phaseName: "emit", - body: () => WriteImplModuleToDisk(args, outputModule, implModule)); + body: args => WriteImplModuleToDisk(args, outputModule, implModule)); // Signs the module on disk, if needed runner.RunPhase( phaseName: "sign", - body: () => SignImplModuleOnDisk(args, outputModule)); + body: args => SignImplModuleOnDisk(args, outputModule)); // Notify the user that generation was successful - ConsoleApp.Log($"Impl code generated -> {Path.Combine(args.GeneratedAssemblyDirectory, implModule.Name!)}"); + ConsoleApp.Log($"Impl code generated -> {Path.Combine(runner.Args.GeneratedAssemblyDirectory, implModule.Name!)}"); } /// diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs index 8e3631caf5..b2298b6089 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs @@ -23,7 +23,7 @@ internal static partial class InteropGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - (InteropGeneratorArgs args, GeneratorPhaseRunner runner) = GeneratorHost.Prepare( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtinteropgen", unpackDebugRepro: UnpackDebugRepro, @@ -36,18 +36,18 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) // Discover the types to process InteropGeneratorDiscoveryState discoveryState = runner.RunPhase( phaseName: "discovery", - logMessage: $"Processing {args.ReferenceAssemblyPaths.Length + args.ImplementationAssemblyPaths.Length + 1} module(s)", - body: () => Discover(args)); + logMessage: $"Processing {runner.Args.ReferenceAssemblyPaths.Length + runner.Args.ImplementationAssemblyPaths.Length + 1} module(s)", + body: Discover); - args.Token.ThrowIfCancellationRequested(); + runner.Args.Token.ThrowIfCancellationRequested(); // Emit the resulting interop assembly runner.RunPhase( phaseName: "emit", logMessage: "Generating interop code", - body: () => Emit(args, discoveryState)); + body: args => Emit(args, discoveryState)); // Notify the user that generation was successful - ConsoleApp.Log($"Interop code generated -> {Path.Combine(args.GeneratedAssemblyDirectory, InteropNames.WindowsRuntimeInteropDllName)}"); + ConsoleApp.Log($"Interop code generated -> {Path.Combine(runner.Args.GeneratedAssemblyDirectory, InteropNames.WindowsRuntimeInteropDllName)}"); } } \ No newline at end of file diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index 658d491144..6c9f164f65 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -22,7 +22,7 @@ internal static partial class ProjectionGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - (ProjectionGeneratorArgs args, GeneratorPhaseRunner runner) = GeneratorHost.Prepare( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtprojectiongen", unpackDebugRepro: UnpackDebugRepro, @@ -37,15 +37,15 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) // this tool might run up to 3 times during builds, so this helps make things clearer. ProjectionGeneratorProcessingState processingState = runner.RunPhase( phaseName: "processing", - logMessage: args switch + logMessage: runner.Args switch { { WindowsSdkOnly: true, WindowsUIXamlProjection: false } => "Processing Windows SDK .winmd references", { WindowsSdkOnly: true, WindowsUIXamlProjection: true } => "Processing 'Windows.UI.Xaml' .winmd references", - _ => $"Processing {args.WinMDPaths.Length} .winmd reference(s)" + _ => $"Processing {runner.Args.WinMDPaths.Length} .winmd reference(s)" }, - body: () => ProcessReferences(args)); + body: ProcessReferences); - args.Token.ThrowIfCancellationRequested(); + runner.Args.Token.ThrowIfCancellationRequested(); // If no types were found to project (e.g., component mode with no component references), // skip the source generation and emit phases entirely (no .dll will be produced at all). @@ -58,17 +58,17 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) runner.RunPhase( phaseName: "source-generation", logMessage: "Generating projection code", - body: () => GenerateSources(processingState)); + body: _ => GenerateSources(processingState)); - args.Token.ThrowIfCancellationRequested(); + runner.Args.Token.ThrowIfCancellationRequested(); // Invoke Roslyn to compile the generated sources into 'WinRT.Projection.dll' runner.RunPhase( phaseName: "emit", logMessage: "Compiling projection code", - body: () => Emit(args, processingState)); + body: args => Emit(args, processingState)); // Notify the user that generation was successful - ConsoleApp.Log($"Projection code generated -> {Path.Combine(args.GeneratedAssemblyDirectory, args.AssemblyName)}.dll"); + ConsoleApp.Log($"Projection code generated -> {Path.Combine(runner.Args.GeneratedAssemblyDirectory, runner.Args.AssemblyName)}.dll"); } } \ No newline at end of file diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index 505f2d4abe..ad6d877859 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -29,7 +29,7 @@ internal static partial class ReferenceProjectionGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - (ReferenceProjectionGeneratorArgs args, GeneratorPhaseRunner runner) = GeneratorHost.Prepare( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtprojectionrefgen", unpackDebugRepro: UnpackDebugRepro, @@ -40,17 +40,17 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) token: token); // Validate the target framework. CsWinRT 3.0 requires .NET 10 or later. - if (!string.IsNullOrEmpty(args.TargetFramework) && !args.TargetFramework.StartsWith("net10.0", StringComparison.Ordinal)) + if (!string.IsNullOrEmpty(runner.Args.TargetFramework) && !runner.Args.TargetFramework.StartsWith("net10.0", StringComparison.Ordinal)) { - throw WellKnownReferenceProjectionGeneratorExceptions.UnsupportedTargetFramework(args.TargetFramework); + throw WellKnownReferenceProjectionGeneratorExceptions.UnsupportedTargetFramework(runner.Args.TargetFramework); } // Build the writer options from the parsed arguments ProjectionWriterOptions options = runner.RunPhase( phaseName: "processing", - body: () => BuildWriterOptions(args)); + body: BuildWriterOptions); - args.Token.ThrowIfCancellationRequested(); + runner.Args.Token.ThrowIfCancellationRequested(); // Invoke the projection writer (in-process) to generate the projection sources. We can't // route this through the shared 'runner.RunPhase' helper because we wrap the exception diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs index 293858a395..298a1c683e 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs @@ -37,7 +37,7 @@ internal static partial class WinMDGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - (WinMDGeneratorArgs args, GeneratorPhaseRunner runner) = GeneratorHost.Prepare( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtwinmdgen", unpackDebugRepro: UnpackDebugRepro, @@ -50,8 +50,8 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) // Discover the types to process WinMDGeneratorDiscoveryState discoveryState = runner.RunPhase( phaseName: "discovery", - logMessage: $"Processing assembly: '{System.IO.Path.GetFileName(args.InputAssemblyPath)}'", - body: () => Discover(args)); + logMessage: $"Processing assembly: '{System.IO.Path.GetFileName(runner.Args.InputAssemblyPath)}'", + body: Discover); token.ThrowIfCancellationRequested(); @@ -59,8 +59,8 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) runner.RunPhase( phaseName: "generation", logMessage: $"Defining {discoveryState.PublicTypes.Count} authored type(s)", - body: () => Generate(args, discoveryState)); + body: args => Generate(args, discoveryState)); - ConsoleApp.Log($"Windows Runtime assembly (.winmd) generated -> {args.OutputWinmdPath}"); + ConsoleApp.Log($"Windows Runtime assembly (.winmd) generated -> {runner.Args.OutputWinmdPath}"); } } \ No newline at end of file From adcf776634e3f56cf6f748dbee051e7c4d3c94bb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 14:06:06 -0700 Subject: [PATCH 21/28] Simplify response-file parsing and messages Refactor response-file parsing and related generator messaging: - Consolidate and simplify WellKnownGeneratorMessages (make ResponseFileReadError a constant and clean up XML docs) and update per-tool exception factories to use the new message form. - Restructure ResponseFileParser: split line parsing into a map builder and a Populate method, adjust DynamicallyAccessedMembers annotations, remove nullable-inspection/NullabilityInfo usage, and simplify default-application logic. - Tidy ResponseFileBuilder formatting and annotations; minor comment and formatting cleanups. - Remove an unused PathExtensions.Normalize(ReadOnlySpan) overload and add small GeneratorHost/GeneratorPhaseRunner code cleanups (target-typed new, pragma for warnings, reorder assignments). These changes reduce duplication, simplify trimming/reflection annotations for the linker, and clarify parsing behavior without changing external behavior. --- .../DebugRepro/DebugReproPacker.cs | 10 +- .../Errors/IGeneratorErrorFactory.cs | 14 +-- .../Errors/WellKnownGeneratorMessages.cs | 35 ++----- .../Extensions/PathExtensions.cs | 15 --- src/WinRT.Generator.Core/GeneratorHost.cs | 27 +---- .../GeneratorPhaseRunner.cs | 44 ++------- src/WinRT.Generator.Core/IGeneratorArgs.cs | 6 +- .../Parsing/ResponseFileBuilder.cs | 44 ++------- .../Parsing/ResponseFileParser.cs | 98 ++++--------------- .../References/WellKnownPublicKeyTokens.cs | 5 +- .../References/WellKnownPublicKeys.cs | 6 +- .../Errors/WellKnownImplExceptions.cs | 2 +- .../Errors/WellKnownInteropExceptions.cs | 2 +- .../WellKnownProjectionGeneratorExceptions.cs | 2 +- ...nReferenceProjectionGeneratorExceptions.cs | 2 +- .../Errors/WellKnownWinMDExceptions.cs | 2 +- 16 files changed, 63 insertions(+), 251 deletions(-) diff --git a/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs index d706c93b87..ba0da678e9 100644 --- a/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs +++ b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs @@ -19,14 +19,8 @@ namespace WindowsRuntime.Generator.DebugRepro; /// Leaf helpers shared across the CsWinRT CLI generators for packaging and unpacking debug repros. /// /// -/// Each generator's SaveDebugRepro/UnpackDebugRepro still owns the high-level orchestration -/// (which input categories exist, which subfolders are used, how the response file is re-stitched). This -/// type only owns the small, identical-across-tools building blocks: -/// -/// Hashing the original file path into a stable, collision-free file name. -/// Copying files (or a single file) into a destination directory using the hashed names. -/// Serializing / deserializing the "hashed name → original path" mapping as JSON inside the repro archive. -/// +/// Each generator still owns the high-level orchestration (which input categories exist, which subfolders are +/// used, how the response file is re-stitched). This type only owns identical helpers that can be reused. /// internal static class DebugReproPacker { diff --git a/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs b/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs index 21f31ef861..8906ab8c63 100644 --- a/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs +++ b/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs @@ -9,18 +9,10 @@ namespace WindowsRuntime.Generator.Errors; /// Routes shared logical errors through the per-tool well-known exception factory. /// /// -/// /// Shared infrastructure (response-file parsing, debug-repro packing, etc.) is generic over an -/// implementation of this interface and reaches the per-tool factory through its static abstract -/// members. This preserves per-tool exception identity exactly: each factory continues to assign its -/// own numeric error IDs, format its own messages (including embedded tool names), and construct -/// its own concrete subtype. -/// -/// -/// Implementations must be sealed (not static) so they can participate in the -/// static abstract interface contract. The implementing type is not meant to be instantiated; -/// it is used as a type parameter to dispatch the factory call. -/// +/// implementation of this interface to preserve per-tool exception identity exactly: each factory +/// continues to assign its own numeric error IDs, format its own messages (including embedded tool names), +/// and construct its own concrete subtype. /// internal interface IGeneratorErrorFactory { diff --git a/src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessages.cs b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessages.cs index 8404f0a29b..03e5bee363 100644 --- a/src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessages.cs +++ b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessages.cs @@ -14,56 +14,35 @@ namespace WindowsRuntime.Generator.Errors; /// internal static class WellKnownGeneratorMessages { - /// - /// Builds the message for . - /// - /// The CLI tool name embedded in the message (e.g. "cswinrtimplgen"). - /// The formatted error message. - public static string ResponseFileReadError(string toolName) - { - return $"Failed to read the response file to run '{toolName}'."; - } + /// + public const string ResponseFileReadError = "Failed to read the response file (e.g. it may be missing or not accessible)."; - /// - /// Builds the message for . - /// + /// /// The name of the response-file argument that failed to parse. - /// The formatted error message. public static string ResponseFileArgumentParsingError(string argumentName) { return $"Failed to parse argument '{argumentName}' from response file."; } - /// - /// The message for . - /// + /// public const string MalformedResponseFile = "The response file is malformed and contains invalid content."; - /// - /// Builds the message for . - /// + /// /// The directory path that does not exist. - /// The formatted error message. public static string DebugReproDirectoryDoesNotExist(string path) { return $"The debug repro directory '{path}' does not exist."; } - /// - /// Builds the message for . - /// + /// /// The debug-repro file entry path that has no mapping. - /// The formatted error message. public static string DebugReproMissingFileEntryMapping(string path) { return $"The debug repro file entry with path '{path}' is missing its assembly path mapping."; } - /// - /// Builds the message for . - /// + /// /// The debug-repro file entry path that was not recognized. - /// The formatted error message. public static string DebugReproUnrecognizedFileEntry(string path) { return $"The debug repro file entry with path '{path}' was not recognized."; diff --git a/src/WinRT.Generator.Core/Extensions/PathExtensions.cs b/src/WinRT.Generator.Core/Extensions/PathExtensions.cs index b4afd3b725..fcb37d6130 100644 --- a/src/WinRT.Generator.Core/Extensions/PathExtensions.cs +++ b/src/WinRT.Generator.Core/Extensions/PathExtensions.cs @@ -36,21 +36,6 @@ internal static class PathExtensions return path?.Replace('\\', '/'); } - /// - public static ReadOnlySpan Normalize(ReadOnlySpan path) - { - if (OperatingSystem.IsWindows()) - { - return path; - } - - char[] buffer = new char[path.Length]; - - path.Replace(buffer, '\\', '/'); - - return buffer; - } - /// /// Checks whether a given path represents a file or folder contained within a folder with a given name. /// diff --git a/src/WinRT.Generator.Core/GeneratorHost.cs b/src/WinRT.Generator.Core/GeneratorHost.cs index e8f543eeaf..27df49e2d1 100644 --- a/src/WinRT.Generator.Core/GeneratorHost.cs +++ b/src/WinRT.Generator.Core/GeneratorHost.cs @@ -11,30 +11,10 @@ namespace WindowsRuntime.Generator; /// /// Shared Run entry-point scaffold for the CsWinRT CLI generators. /// -/// -/// Each generator's Run method historically opened with an identical preamble: -/// -/// If the input file path looks like a .zip, unpack a debug repro and re-route to the extracted .rsp. -/// Parse the response file into a per-tool args record. -/// If DebugReproDirectory is set and we are not already replaying, save a debug repro of the current invocation. -/// -/// encapsulates that preamble. Each generator's Run now starts with -/// a single call to it; the per-tool unpack / save / parse logic is supplied via delegates so behavior stays -/// identical (same log messages, same exception phases, same per-tool unhandled exception type). -/// -/// The same Run methods then proceed through a series of phases (loading, processing, emit, ...), -/// each wrapped in an identical try/catch that re-throws as the per-tool -/// Unhandled*Exception. returns a -/// bound to the parsed args plus the same per-tool -/// wrapUnhandled and log delegates, so each phase can be expressed as a single -/// call instead of a -/// hand-written try/catch. -/// -/// internal static class GeneratorHost { /// - /// Runs the shared unpack → parse → save preamble for a CsWinRT CLI generator and returns a + /// Runs the shared unpack, parse, save preamble for a CsWinRT CLI generator and returns a /// ready to drive the remaining per-tool phases. /// /// The per-tool args record (must implement ). @@ -122,9 +102,6 @@ public static GeneratorPhaseRunner CreateRunner( args.Token.ThrowIfCancellationRequested(); - return new GeneratorPhaseRunner(args, wrapUnhandled, log); + return new(args, wrapUnhandled, log); } } - - - diff --git a/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs b/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs index cc151243d6..af038e4428 100644 --- a/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs +++ b/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs @@ -4,6 +4,8 @@ using System; using WindowsRuntime.Generator.Errors; +#pragma warning disable CS1573 + namespace WindowsRuntime.Generator; /// @@ -11,18 +13,6 @@ namespace WindowsRuntime.Generator; /// Unhandled*Exception and optionally logging a progress message before the body runs. /// /// The per-tool args record (must implement ). -/// -/// Each generator's Run method historically wrapped every phase (loading, processing, emit, ...) -/// in an identical try/catch that re-threw as the per-tool Unhandled*Exception -/// (with the phase name as the constructor argument). captures -/// the parsed instance plus the per-tool wrapUnhandled and log -/// delegates (it is returned bound to all three by ) and -/// lets each phase call site collapse to a single (or -/// overload) invocation. The captured is forwarded to every body delegate, -/// so phases that only depend on it can be expressed as a static lambda (or method group) with -/// zero per-call allocations. Per-tool exception identity is fully preserved because the original -/// wrapUnhandled delegate is invoked unchanged. -/// internal readonly struct GeneratorPhaseRunner where TArgs : IGeneratorArgs { @@ -44,9 +34,10 @@ internal readonly struct GeneratorPhaseRunner /// Logs a progress message to the user (typically ConsoleApp.Log from ConsoleAppFramework). internal GeneratorPhaseRunner(TArgs args, Func wrapUnhandled, Action log) { - Args = args; _wrapUnhandled = wrapUnhandled; _log = log; + + Args = args; } /// @@ -72,19 +63,14 @@ public void RunPhase(string phaseName, Action body) } } - /// - /// Logs and then runs , wrapping any - /// unexpected exception in the per-tool Unhandled*Exception with - /// as the phase tag. - /// - /// The phase name used by the per-tool Unhandled*Exception. + /// /// The progress message to log before the body runs. - /// The body of the phase to run. The captured is forwarded as its argument. public void RunPhase(string phaseName, string logMessage, Action body) { try { _log(logMessage); + body(Args); } catch (Exception e) when (!e.IsWellKnown) @@ -93,13 +79,7 @@ public void RunPhase(string phaseName, string logMessage, Action body) } } - /// - /// Runs and returns its result, wrapping any unexpected exception - /// in the per-tool Unhandled*Exception with as the phase tag. - /// - /// The result type of . - /// The phase name used by the per-tool Unhandled*Exception. - /// The body of the phase to run. The captured is forwarded as its argument. + /// /// The value returned by . public T RunPhase(string phaseName, Func body) { @@ -113,21 +93,15 @@ public T RunPhase(string phaseName, Func body) } } - /// - /// Logs , then runs and returns its result, - /// wrapping any unexpected exception in the per-tool Unhandled*Exception with - /// as the phase tag. - /// - /// The result type of . - /// The phase name used by the per-tool Unhandled*Exception. + /// /// The progress message to log before the body runs. - /// The body of the phase to run. The captured is forwarded as its argument. /// The value returned by . public T RunPhase(string phaseName, string logMessage, Func body) { try { _log(logMessage); + return body(Args); } catch (Exception e) when (!e.IsWellKnown) diff --git a/src/WinRT.Generator.Core/IGeneratorArgs.cs b/src/WinRT.Generator.Core/IGeneratorArgs.cs index 34c2a1be87..f6bf0ed5c8 100644 --- a/src/WinRT.Generator.Core/IGeneratorArgs.cs +++ b/src/WinRT.Generator.Core/IGeneratorArgs.cs @@ -6,14 +6,12 @@ namespace WindowsRuntime.Generator; /// -/// Common surface implemented by every per-tool args record (e.g. ImplGeneratorArgs, -/// InteropGeneratorArgs) so the shared entry-point scaffold -/// can dispatch through a couple of well-known properties. +/// Common surface implemented by every per-tool arguments record. /// internal interface IGeneratorArgs { /// - /// Gets the token for the operation. + /// Gets the cancellation token for the generator invocation. /// CancellationToken Token { get; } diff --git a/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs b/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs index 00e2699e39..fa90f69710 100644 --- a/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs +++ b/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs @@ -8,51 +8,22 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; -using System.Threading; using WindowsRuntime.Generator.Attributes; namespace WindowsRuntime.Generator.Parsing; /// -/// Formats an args record into a response file by reflecting over its -/// []-annotated public properties. +/// Helper to format argument objects into response files. /// -/// -/// -/// Properties are emitted in declaration order (the order returned by ), -/// one name]]>value per line. The output round-trips cleanly through -/// . -/// -/// -/// Per-property handling: -/// -/// -typed properties are skipped (they have no CLI surface). -/// Properties without are skipped. -/// values are skipped (they round-trip as "missing"). -/// Empty [] values are skipped (they round-trip as "missing" -/// to the optional-array default, matching the previous per-tool emit behavior). -/// values that are on properties without -/// are skipped (they -/// round-trip to the optional-bool default, matching the previous per-tool emit behavior). -/// All other values are formatted using : -/// strings emit as-is, arrays emit as comma-joined, primitives use . -/// -/// -/// internal static class ResponseFileBuilder { - /// The required dynamic-access annotation for the TArgs type parameter. - private const DynamicallyAccessedMemberTypes ArgsAccessKinds = - DynamicallyAccessedMemberTypes.PublicProperties; - /// - /// Formats as a response file string suitable for - /// . + /// Formats as a response file string. /// /// The strongly-typed args record. /// The args instance to format. /// The formatted response file text. - public static string Format<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs>(TArgs args) + public static string Format<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TArgs>(TArgs args) where TArgs : class { StringBuilder builder = new(); @@ -83,8 +54,8 @@ internal static class ResponseFileBuilder } // Optional booleans default to 'false' on parse, so emitting "name false" would be - // redundant. Required booleans always emit (both 'true' and 'false') so the parser sees - // them and doesn't reject them as missing-required. + // redundant. Required booleans always emit (both 'true' and 'false') so the parser + // sees them and doesn't reject them as missing-required. bool isRequired = property.GetCustomAttribute() is not null; if (value is false && !isRequired) @@ -112,13 +83,14 @@ private static string FormatValue(object value) return s; } + // String values are formatted as comma-separated lists, without brackets or spaces if (value is string[] array) { return string.Join(',', array); } - // All other primitive values use invariant culture, matching the parser's invariant - // 'Convert.ChangeType' so the round-trip is deterministic. + // All other primitive values use invariant culture, matching the parser's + // invariant 'Convert.ChangeType' so the round-trip is deterministic. return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; } } diff --git a/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs b/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs index f635d005de..25a2bc7a6b 100644 --- a/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs +++ b/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs @@ -16,52 +16,25 @@ namespace WindowsRuntime.Generator.Parsing; /// -/// Parses a response file into a strongly-typed args record by reflecting over its -/// []-annotated public properties. +/// Helper to parse response files into argument objects. /// -/// -/// -/// The shape of the response file is one name]]>value per line, with optional -/// blank lines (the MSBuild ToolTask may emit them). The first token of each non-blank line -/// is the CLI flag name; everything after the first space (right-trimmed) is the value. -/// -/// -/// Per-property handling: -/// -/// -typed properties are set from the parser's token -/// parameter (they have no ). -/// Properties without are skipped. -/// Required properties (those with ) throw -/// -/// if the value is missing or fails to parse. -/// Optional properties default to the if -/// is present, otherwise to default(T) (with -/// [] defaulting to an empty array). -/// Value coercion is handled by -/// for primitives, with ; arrays use -/// with comma separator. -/// -/// -/// internal static class ResponseFileParser { - /// The required dynamic-access annotation for the TArgs type parameter. - private const DynamicallyAccessedMemberTypes ArgsAccessKinds = - DynamicallyAccessedMemberTypes.PublicProperties; - /// /// Parses an instance of from a response file at the given path. /// /// - /// The path may be prefixed with @ (matching MSBuild's default escaping for ToolTask - /// response files), which is stripped before reading the file. + /// The path may be prefixed with @ (matching MSBuild's default escaping + /// for ToolTask response files), which is stripped before reading the file. /// /// The strongly-typed args record. Must have a public parameterless constructor surface (only public properties are inspected via reflection). /// The per-tool well-known exception factory used to route parsing errors. /// The path to the response file (optionally prefixed with @). /// The cancellation token for the operation. /// The populated instance. - public static TArgs Parse<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(string path, CancellationToken token) + public static TArgs Parse<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] TArgs, TErr>( + string path, + CancellationToken token) where TArgs : class where TErr : IGeneratorErrorFactory { @@ -84,7 +57,9 @@ internal static class ResponseFileParser throw TErr.ResponseFileReadError(e); } - return ParseLines(lines, token); + Dictionary argsMap = BuildArgsMap(lines); + + return Populate(argsMap, token); } /// @@ -95,25 +70,14 @@ internal static class ResponseFileParser /// The stream containing the response file content. /// The cancellation token for the operation. /// The populated instance. - public static TArgs Parse<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(Stream stream, CancellationToken token) + public static TArgs Parse<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] TArgs, TErr>( + Stream stream, + CancellationToken token) where TArgs : class where TErr : IGeneratorErrorFactory { - return ParseLines(File.ReadAllLines(stream), token); - } + string[] lines = File.ReadAllLines(stream); - /// - /// Parses an instance of from the pre-split lines of a response file. - /// - /// The strongly-typed args record. - /// The per-tool well-known exception factory used to route parsing errors. - /// The lines read from the response file. - /// The cancellation token for the operation. - /// The populated instance. - public static TArgs ParseLines<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(string[] lines, CancellationToken token) - where TArgs : class - where TErr : IGeneratorErrorFactory - { Dictionary argsMap = BuildArgsMap(lines); return Populate(argsMap, token); @@ -164,20 +128,16 @@ private static Dictionary BuildArgsMap(string[] lines) } /// - /// Constructs via - /// (to bypass enforcement at construction time) and then - /// populates each public property by reflecting on its CLI attribute, nullability, default value, and type. + /// Populates an arguments object with the provided parsed values. /// /// The strongly-typed args record. /// The per-tool well-known exception factory used to route parsing errors. /// The pre-built name-to-value map. /// The cancellation token, assigned to any -typed property on . /// The populated instance. - [UnconditionalSuppressMessage( - "Trimming", - "IL2087:'type' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", - Justification = "GetUninitializedObject does not invoke any constructor; it allocates an instance of TArgs without running any user code, so the PublicConstructors/NonPublicConstructors annotation is not required.")] - private static TArgs Populate<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(Dictionary argsMap, CancellationToken token) + private static TArgs Populate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] TArgs, TErr>( + Dictionary argsMap, + CancellationToken token) where TArgs : class where TErr : IGeneratorErrorFactory { @@ -186,8 +146,6 @@ private static Dictionary BuildArgsMap(string[] lines) // response file values, with explicit defaults applied for non-required properties. TArgs instance = (TArgs)RuntimeHelpers.GetUninitializedObject(typeof(TArgs)); - NullabilityInfoContext nullabilityContext = new(); - foreach (PropertyInfo property in typeof(TArgs).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { Type propertyType = property.PropertyType; @@ -214,7 +172,7 @@ private static Dictionary BuildArgsMap(string[] lines) if (!hasValue) { - ApplyDefault(instance, property, propertyType, isRequired, nullabilityContext); + ApplyDefault(instance, property, propertyType, isRequired); continue; } @@ -233,7 +191,7 @@ private static Dictionary BuildArgsMap(string[] lines) } else { - ApplyDefault(instance, property, propertyType, isRequired: false, nullabilityContext); + ApplyDefault(instance, property, propertyType, isRequired: false); } } @@ -248,13 +206,11 @@ private static Dictionary BuildArgsMap(string[] lines) /// The property being set. /// The property's type. /// Whether the property has . - /// The shared for nullable-reference inspection. private static void ApplyDefault( object instance, PropertyInfo property, Type propertyType, - bool isRequired, - NullabilityInfoContext nullabilityContext) + bool isRequired) where TErr : IGeneratorErrorFactory { if (isRequired) @@ -263,7 +219,7 @@ private static void ApplyDefault( } // '[DefaultValue("…")]' takes precedence: it lets per-tool args express initializer-style - // defaults that 'GetUninitializedObject' would otherwise skip. + // defaults that 'GetUninitializedObject' would otherwise skip (initializers aren't needed). DefaultValueAttribute? defaultValueAttribute = property.GetCustomAttribute(); if (defaultValueAttribute is not null) @@ -280,18 +236,6 @@ private static void ApplyDefault( return; } - - // Nullable reference types default to null (matching 'GetNullableStringArgument'). For value - // types and non-nullable reference types we leave 'default(T)' from 'GetUninitializedObject'. - if (!propertyType.IsValueType) - { - NullabilityInfo nullability = nullabilityContext.Create(property); - - if (nullability.ReadState == NullabilityState.Nullable) - { - property.SetValue(instance, null); - } - } } /// diff --git a/src/WinRT.Generator.Core/References/WellKnownPublicKeyTokens.cs b/src/WinRT.Generator.Core/References/WellKnownPublicKeyTokens.cs index 7247409794..0942d8ec00 100644 --- a/src/WinRT.Generator.Core/References/WellKnownPublicKeyTokens.cs +++ b/src/WinRT.Generator.Core/References/WellKnownPublicKeyTokens.cs @@ -4,13 +4,12 @@ namespace WindowsRuntime.Generator.References; /// -/// Well known public key tokens for the framework and CsWinRT assemblies referenced by the -/// CsWinRT CLI generators. +/// Well-known public key tokens for assemblies used by the CsWinRT generators. /// internal static class WellKnownPublicKeyTokens { /// - /// The public key token for mscorlib (b77a5c561934e089). + /// The public key token for mscorlib. /// public static readonly byte[] MSCorLib = [0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89]; diff --git a/src/WinRT.Generator.Core/References/WellKnownPublicKeys.cs b/src/WinRT.Generator.Core/References/WellKnownPublicKeys.cs index 73e159760b..1c755b9d73 100644 --- a/src/WinRT.Generator.Core/References/WellKnownPublicKeys.cs +++ b/src/WinRT.Generator.Core/References/WellKnownPublicKeys.cs @@ -4,16 +4,14 @@ namespace WindowsRuntime.Generator.References; /// -/// The 160-byte strong-name public keys consumed by the CsWinRT CLI generators when emitting -/// assembly references or signing generated assemblies. +/// Well-known public keys for assemblies used by the CsWinRT generators. /// internal static class WellKnownPublicKeys { /// /// The public key used by the precompiled Windows SDK projection assemblies (WinRT.Sdk.Projection.dll, /// WinRT.Sdk.Xaml.Projection.dll) and the merged projection assembly (WinRT.Projection.dll) that - /// the projection generator produces at app build time. The impl generator stamps this same key on the - /// AssemblyReference entries it emits in the forwarder .dll. + /// the projection generator produces at app build time. /// public static readonly byte[] WindowsSdkProjection = [0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xB5, 0xFC, 0x90, 0xE7, 0x02, 0x7F, 0x67, 0x87, 0x1E, 0x77, 0x3A, 0x8F, 0xDE, 0x89, 0x38, 0xC8, 0x1D, 0xD4, 0x02, 0xBA, 0x65, 0xB9, 0x20, 0x1D, 0x60, 0x59, 0x3E, 0x96, 0xC4, 0x92, 0x65, 0x1E, 0x88, 0x9C, 0xC1, 0x3F, 0x14, 0x15, 0xEB, 0xB5, 0x3F, 0xAC, 0x11, 0x31, 0xAE, 0x0B, 0xD3, 0x33, 0xC5, 0xEE, 0x60, 0x21, 0x67, 0x2D, 0x97, 0x18, 0xEA, 0x31, 0xA8, 0xAE, 0xBD, 0x0D, 0xA0, 0x07, 0x2F, 0x25, 0xD8, 0x7D, 0xBA, 0x6F, 0xC9, 0x0F, 0xFD, 0x59, 0x8E, 0xD4, 0xDA, 0x35, 0xE4, 0x4C, 0x39, 0x8C, 0x45, 0x43, 0x07, 0xE8, 0xE3, 0x3B, 0x84, 0x26, 0x14, 0x3D, 0xAE, 0xC9, 0xF5, 0x96, 0x83, 0x6F, 0x97, 0xC8, 0xF7, 0x47, 0x50, 0xE5, 0x97, 0x5C, 0x64, 0xE2, 0x18, 0x9F, 0x45, 0xDE, 0xF4, 0x6B, 0x2A, 0x2B, 0x12, 0x47, 0xAD, 0xC3, 0x65, 0x2B, 0xF5, 0xC3, 0x08, 0x05, 0x5D, 0xA9]; diff --git a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs index ef3a92458c..ab965be045 100644 --- a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs +++ b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs @@ -26,7 +26,7 @@ private WellKnownImplExceptions() /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError("cswinrtimplgen"), exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, exception); } /// diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs index 6043cc31fd..6df46e720a 100644 --- a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs +++ b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs @@ -287,7 +287,7 @@ public static WellKnownInteropException IDictionary2TypeCodeGenerationError(Type /// public static WellKnownInteropException ResponseFileReadError(Exception exception) { - return Exception(28, WellKnownGeneratorMessages.ResponseFileReadError("cswinrtinteropgen"), exception); + return Exception(28, WellKnownGeneratorMessages.ResponseFileReadError, exception); } /// diff --git a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs index 4bde0b5e20..4ac475f319 100644 --- a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs +++ b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs @@ -29,7 +29,7 @@ private WellKnownProjectionGeneratorExceptions() /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError("cswinrtprojectiongen"), exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, exception); } /// diff --git a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs index 9e232becc0..aa3d8014d6 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs @@ -26,7 +26,7 @@ private WellKnownReferenceProjectionGeneratorExceptions() /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError("cswinrtprojectionrefgen"), exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, exception); } /// diff --git a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs index 3ec0e57344..d958c0c7a9 100644 --- a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs +++ b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs @@ -26,7 +26,7 @@ private WellKnownWinMDExceptions() /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError("cswinrtwinmdgen"), exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, exception); } /// From 79f3f181d17901a89f95262a02dc6b646f50182d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 14:28:46 -0700 Subject: [PATCH 22/28] Standardize generator naming and runner calls Rename the UnhandledGeneratorException property from GeneratorDescription to GeneratorName and update the standardized ToString message to use the new name ("The CsWinRT {GeneratorName} generator..."). Adjust all per-tool unhandled exception types to provide GeneratorName values. Remove explicit generic type args from several GeneratorHost.CreateRunner calls (relying on inference). Also apply minor XML-doc and cref cleanups (simplify exception/type cref qualifications and projection writer cref) and a small doc fix in WindowsRuntimeExceptionExtensions. --- .../Errors/UnhandledGeneratorException.cs | 9 ++++----- .../Errors/UnhandledImplException.cs | 2 +- src/WinRT.Impl.Generator/Generation/ImplGenerator.cs | 2 +- .../Errors/UnhandledInteropException.cs | 2 +- .../Generation/InteropGenerator.cs | 2 +- .../References/WellKnownInterfaceIIDs.cs | 2 +- .../Errors/UnhandledProjectionGeneratorException.cs | 2 +- .../Generation/ProjectionGenerator.cs | 2 +- .../Generation/ProjectionGeneratorProcessingState.cs | 4 ++-- .../UnhandledReferenceProjectionGeneratorException.cs | 2 +- .../Generation/ReferenceProjectionGenerator.cs | 2 +- .../Properties/WindowsRuntimeExceptionExtensions.cs | 2 +- .../Errors/UnhandledWinMDException.cs | 2 +- src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs | 2 +- 14 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs index 929afaccc9..3b51d8ad6b 100644 --- a/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs +++ b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs @@ -10,7 +10,7 @@ namespace WindowsRuntime.Generator.Errors; /// /// /// Each per-tool unhandled exception inherits from this type and provides its -/// and so that the standardized +/// and so that the standardized /// message remains tool-specific. /// /// The phase that failed. @@ -24,16 +24,15 @@ internal abstract class UnhandledGeneratorException(string phase, Exception exce protected abstract string ErrorPrefix { get; } /// - /// Gets the description of the generator used in the standard message - /// (e.g. "impl generator", "interop generator", "WinMD generator"). + /// Gets the name of the generator used in the standard message. /// - protected abstract string GeneratorDescription { get; } + protected abstract string GeneratorName { get; } /// public override string ToString() { return - $"""error {ErrorPrefix}9999: The CsWinRT {GeneratorDescription} failed with an unhandled exception """ + + $"""error {ErrorPrefix}9999: The CsWinRT {GeneratorName} generator failed with an unhandled exception """ + $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the '{phase}' phase. This might be due to an invalid """ + $"""configuration in the current project, but the generator should still correctly identify that and fail gracefully. Please open an """ + $"""issue at https://github.com/microsoft/CsWinRT and provide a minimal repro, if possible."""; diff --git a/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs b/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs index 4d1f987fba..af5926b763 100644 --- a/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs +++ b/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs @@ -17,5 +17,5 @@ internal sealed class UnhandledImplException(string phase, Exception exception) protected override string ErrorPrefix => WellKnownImplExceptions.ErrorPrefix; /// - protected override string GeneratorDescription => "impl generator"; + protected override string GeneratorName => "impl"; } diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index e855b52c90..cf6e97c87f 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -70,7 +70,7 @@ internal static partial class ImplGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtimplgen", unpackDebugRepro: UnpackDebugRepro, diff --git a/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs index c4b0ee30bf..972d473b14 100644 --- a/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs +++ b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs @@ -17,5 +17,5 @@ internal sealed class UnhandledInteropException(string phase, Exception exceptio protected override string ErrorPrefix => WellKnownInteropExceptions.ErrorPrefix; /// - protected override string GeneratorDescription => "interop generator"; + protected override string GeneratorName => "interop"; } diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs index b2298b6089..9b51554ae9 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs @@ -23,7 +23,7 @@ internal static partial class InteropGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtinteropgen", unpackDebugRepro: UnpackDebugRepro, diff --git a/src/WinRT.Interop.Generator/References/WellKnownInterfaceIIDs.cs b/src/WinRT.Interop.Generator/References/WellKnownInterfaceIIDs.cs index 4d56b5bdfd..fafda0e0fb 100644 --- a/src/WinRT.Interop.Generator/References/WellKnownInterfaceIIDs.cs +++ b/src/WinRT.Interop.Generator/References/WellKnownInterfaceIIDs.cs @@ -65,7 +65,7 @@ internal static class WellKnownInterfaceIIDs /// Whether to use Windows.UI.Xaml projections. /// The instance to use. /// The for the get_IID_... method for . - /// + /// /// /// The types handled by this method should be kept in sync with /// and diff --git a/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs b/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs index 75bff109be..da7ea08ff4 100644 --- a/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs @@ -17,6 +17,6 @@ internal sealed class UnhandledProjectionGeneratorException(string phase, Except protected override string ErrorPrefix => WellKnownProjectionGeneratorExceptions.ErrorPrefix; /// - protected override string GeneratorDescription => "projection generator"; + protected override string GeneratorName => "projection"; } diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index 6c9f164f65..7f871b28d3 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -22,7 +22,7 @@ internal static partial class ProjectionGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtprojectiongen", unpackDebugRepro: UnpackDebugRepro, diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs index fd1ede400c..b4c09a58bb 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs @@ -10,7 +10,7 @@ namespace WindowsRuntime.ProjectionGenerator.Generation; /// /// The path to the folder where sources will be generated. /// The reference assembly paths excluding projection assemblies. -/// The options to pass to . +/// The options to pass to . /// Whether any types were found to project. internal sealed class ProjectionGeneratorProcessingState( string sourcesFolder, @@ -29,7 +29,7 @@ internal sealed class ProjectionGeneratorProcessingState( public string[] ReferencesWithoutProjections { get; } = referencesWithoutProjections; /// - /// Gets the options used to invoke . + /// Gets the options used to invoke . /// public ProjectionWriterOptions WriterOptions { get; } = writerOptions; diff --git a/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs b/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs index 7c46c60ce8..1bf7f1f132 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs @@ -17,6 +17,6 @@ internal sealed class UnhandledReferenceProjectionGeneratorException(string phas protected override string ErrorPrefix => WellKnownReferenceProjectionGeneratorExceptions.ErrorPrefix; /// - protected override string GeneratorDescription => "reference projection generator"; + protected override string GeneratorName => "reference projection"; } diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index ad6d877859..3e7f7026c9 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -29,7 +29,7 @@ internal static partial class ReferenceProjectionGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtprojectionrefgen", unpackDebugRepro: UnpackDebugRepro, diff --git a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs index 06a163f49d..b86a3c1360 100644 --- a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs +++ b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs @@ -1106,7 +1106,7 @@ public static ArgumentException GetInvalidSeekOriginException(string? paramName) extension(UnauthorizedAccessException) { /// - /// Throws an indicating that the internal buffer of a cannot be accessed. + /// Throws an indicating that the internal buffer of a cannot be accessed. /// /// Always thrown. [DoesNotReturn] diff --git a/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs b/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs index 7faae43b4c..9002ee8c21 100644 --- a/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs +++ b/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs @@ -17,5 +17,5 @@ internal sealed class UnhandledWinMDException(string phase, Exception exception) protected override string ErrorPrefix => WellKnownWinMDExceptions.ErrorPrefix; /// - protected override string GeneratorDescription => "WinMD generator"; + protected override string GeneratorName => "WinMD"; } diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs index 298a1c683e..188d64c068 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs @@ -37,7 +37,7 @@ internal static partial class WinMDGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtwinmdgen", unpackDebugRepro: UnpackDebugRepro, From d124065a6f8fde24f3fb08698bcd87417fd6a256 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 14:30:35 -0700 Subject: [PATCH 23/28] Move InternalsVisibleTo declarations from AssemblyInfo.cs to .csproj The 'InternalsVisibleTo' items in 'src/WinRT.Generator.Core/Properties/AssemblyInfo.cs' are emitted by the .NET SDK directly from MSBuild '' items when they're declared in the project file, so there is no need for a hand-written 'AssemblyInfo.cs' to host them. Move the 5 entries (one per consuming generator) into 'WinRT.Generator.Core.csproj' and delete the now-empty 'Properties/AssemblyInfo.cs' (plus its empty parent folder). All 5 generators build with 0 warnings, confirming the SDK-emitted attributes still grant the same internals access. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/WinRT.Generator.Core/Properties/AssemblyInfo.cs | 10 ---------- src/WinRT.Generator.Core/WinRT.Generator.Core.csproj | 8 ++++++++ .../Generation/ReferenceProjectionGenerator.cs | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) delete mode 100644 src/WinRT.Generator.Core/Properties/AssemblyInfo.cs diff --git a/src/WinRT.Generator.Core/Properties/AssemblyInfo.cs b/src/WinRT.Generator.Core/Properties/AssemblyInfo.cs deleted file mode 100644 index 41f427dd1d..0000000000 --- a/src/WinRT.Generator.Core/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("cswinrtimplgen")] -[assembly: InternalsVisibleTo("cswinrtinteropgen")] -[assembly: InternalsVisibleTo("cswinrtprojectiongen")] -[assembly: InternalsVisibleTo("cswinrtprojectionrefgen")] -[assembly: InternalsVisibleTo("cswinrtwinmdgen")] diff --git a/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj b/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj index e2c8a96a3f..5e0d083a0e 100644 --- a/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj +++ b/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj @@ -33,4 +33,12 @@ + + + + + + + + diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index 3e7f7026c9..0fa795e2c8 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -39,7 +39,7 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) log: ConsoleApp.Log, token: token); - // Validate the target framework. CsWinRT 3.0 requires .NET 10 or later. + // Validate the target framework. CsWinRT 3.0 requires .NET 10 or later if (!string.IsNullOrEmpty(runner.Args.TargetFramework) && !runner.Args.TargetFramework.StartsWith("net10.0", StringComparison.Ordinal)) { throw WellKnownReferenceProjectionGeneratorExceptions.UnsupportedTargetFramework(runner.Args.TargetFramework); From 7a598dcd5bb612af96cce2bd896c46d0a85de70c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 14:31:19 -0700 Subject: [PATCH 24/28] Remove redundant global/System qualifiers Simplify references by removing unnecessary qualification prefixes: replace System.StringComparison with StringComparison and global::WindowsRuntime.ProjectionWriter.ProjectionWriter.Run with ProjectionWriter.ProjectionWriter.Run. This is a code cleanup across generator tasks (RunCsWinRTWinMDGenerator.cs, ProjectionGenerator.Generate.cs, ReferenceProjectionGenerator.cs) with no behavior change. --- src/WinRT.Generator.Tasks/RunCsWinRTWinMDGenerator.cs | 2 +- .../Generation/ProjectionGenerator.Generate.cs | 2 +- .../Generation/ReferenceProjectionGenerator.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/WinRT.Generator.Tasks/RunCsWinRTWinMDGenerator.cs b/src/WinRT.Generator.Tasks/RunCsWinRTWinMDGenerator.cs index a5c3aa5ded..64817c827c 100644 --- a/src/WinRT.Generator.Tasks/RunCsWinRTWinMDGenerator.cs +++ b/src/WinRT.Generator.Tasks/RunCsWinRTWinMDGenerator.cs @@ -126,7 +126,7 @@ protected override string GenerateFullPathToTool() string? effectiveArchitecture = CsWinRTToolsArchitecture; // Special case for when 'AnyCPU' is specified (mostly for testing scenarios). - if (effectiveArchitecture?.Equals("AnyCPU", System.StringComparison.OrdinalIgnoreCase) is true) + if (effectiveArchitecture?.Equals("AnyCPU", StringComparison.OrdinalIgnoreCase) is true) { return Path.Combine(CsWinRTToolsDirectory!, ToolName); } diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs index 3b2e71e2f5..58e6fba9ee 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs @@ -67,7 +67,7 @@ private static void GenerateSources(ProjectionGeneratorProcessingState processin // Invoke the projection writer in-process via its public C# API. try { - global::WindowsRuntime.ProjectionWriter.ProjectionWriter.Run(processingState.WriterOptions); + ProjectionWriter.ProjectionWriter.Run(processingState.WriterOptions); } catch (Exception e) when (!e.IsWellKnown) { diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index 0fa795e2c8..f221165bc7 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -59,7 +59,7 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) { ConsoleApp.Log($"Generating reference projection sources -> {options.OutputFolder}"); - global::WindowsRuntime.ProjectionWriter.ProjectionWriter.Run(options); + ProjectionWriter.ProjectionWriter.Run(options); } catch (Exception e) when (!e.IsWellKnown) { From afef5793463cdcb01b21b6981f90ad74826f2402 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 14:33:40 -0700 Subject: [PATCH 25/28] Use primary constructor for GeneratorPhaseRunner Convert GeneratorPhaseRunner to use C# primary-constructor syntax, removing explicit private fields and the manual constructor. Replace uses of _wrapUnhandled and _log with the positional parameters wrapUnhandled and log, and make Args return the positional args field. Add XML param docs for the new parameters. This is a refactor to reduce boilerplate without changing behavior. --- .../GeneratorPhaseRunner.cs | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs b/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs index af038e4428..4e9b26594b 100644 --- a/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs +++ b/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs @@ -13,37 +13,19 @@ namespace WindowsRuntime.Generator; /// Unhandled*Exception and optionally logging a progress message before the body runs. /// /// The per-tool args record (must implement ). -internal readonly struct GeneratorPhaseRunner +/// The parsed per-tool args, forwarded to every body. +/// Wraps an unexpected exception into the per-tool Unhandled*Exception with the given phase name. +/// Logs a progress message to the user (typically ConsoleApp.Log from ConsoleAppFramework). +internal readonly struct GeneratorPhaseRunner( + TArgs args, + Func wrapUnhandled, + Action log) where TArgs : IGeneratorArgs { - /// - /// The per-tool wrapUnhandled delegate. - /// - private readonly Func _wrapUnhandled; - - /// - /// The per-tool progress logger. - /// - private readonly Action _log; - - /// - /// Creates a new bound to the given args and per-tool delegates. - /// - /// The parsed per-tool args, forwarded to every body. - /// Wraps an unexpected exception into the per-tool Unhandled*Exception with the given phase name. - /// Logs a progress message to the user (typically ConsoleApp.Log from ConsoleAppFramework). - internal GeneratorPhaseRunner(TArgs args, Func wrapUnhandled, Action log) - { - _wrapUnhandled = wrapUnhandled; - _log = log; - - Args = args; - } - /// /// Gets the parsed per-tool args, forwarded to every body. /// - public TArgs Args { get; } + public TArgs Args => args; /// /// Runs , wrapping any unexpected exception in the per-tool @@ -59,7 +41,7 @@ public void RunPhase(string phaseName, Action body) } catch (Exception e) when (!e.IsWellKnown) { - throw _wrapUnhandled(phaseName, e); + throw wrapUnhandled(phaseName, e); } } @@ -69,13 +51,13 @@ public void RunPhase(string phaseName, string logMessage, Action body) { try { - _log(logMessage); + log(logMessage); body(Args); } catch (Exception e) when (!e.IsWellKnown) { - throw _wrapUnhandled(phaseName, e); + throw wrapUnhandled(phaseName, e); } } @@ -89,7 +71,7 @@ public T RunPhase(string phaseName, Func body) } catch (Exception e) when (!e.IsWellKnown) { - throw _wrapUnhandled(phaseName, e); + throw wrapUnhandled(phaseName, e); } } @@ -100,13 +82,13 @@ public T RunPhase(string phaseName, string logMessage, Func body) { try { - _log(logMessage); + log(logMessage); return body(Args); } catch (Exception e) when (!e.IsWellKnown) { - throw _wrapUnhandled(phaseName, e); + throw wrapUnhandled(phaseName, e); } } } From 6276d75540c227705455daacc53ac75873c9f829 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 14:43:16 -0700 Subject: [PATCH 26/28] Auto-call ThrowIfCancellationRequested after each RunPhase body Every per-tool 'Run' method previously placed a manual 'args.Token.ThrowIfCancellationRequested()' between consecutive phases - 8 explicit calls across the 5 generators. Bake that check into 'GeneratorPhaseRunner.RunPhase' (all 4 overloads): after the body completes successfully, the runner calls 'args.Token.ThrowIfCancellationRequested()' before returning. The check sits OUTSIDE the 'try'/'catch' so the resulting 'OperationCanceledException' propagates without going through the per-tool 'wrapUnhandled' delegate, which is already a no-op for it ('OperationCanceledException' is in 'IsWellKnown'). Per-tool 'Run' methods drop the 8 redundant 'runner.Args.Token.ThrowIfCancellationRequested()' calls; the last phase of each generator now does one harmless extra cancellation check before the success log message. Net diff: 6 files, +18 / -18 LOC. All 5 generators build with 0 warnings and pass end-to-end debug-repro validation (ref-gen: 8/8 0 diffs, WinMD: 16-byte MVID-only delta, proj-gen: 327/327 0 diffs, impl-gen: 0 byte diffs, interop-gen: smoke OK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../GeneratorPhaseRunner.cs | 20 +++++++++++++++++-- .../Generation/ImplGenerator.cs | 6 ------ .../Generation/InteropGenerator.cs | 2 -- .../Generation/ProjectionGenerator.cs | 4 ---- .../ReferenceProjectionGenerator.cs | 2 -- .../Generation/WinMDGenerator.cs | 2 -- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs b/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs index 4e9b26594b..58b3cf1299 100644 --- a/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs +++ b/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs @@ -43,6 +43,8 @@ public void RunPhase(string phaseName, Action body) { throw wrapUnhandled(phaseName, e); } + + args.Token.ThrowIfCancellationRequested(); } /// @@ -59,20 +61,28 @@ public void RunPhase(string phaseName, string logMessage, Action body) { throw wrapUnhandled(phaseName, e); } + + args.Token.ThrowIfCancellationRequested(); } /// /// The value returned by . public T RunPhase(string phaseName, Func body) { + T result; + try { - return body(Args); + result = body(Args); } catch (Exception e) when (!e.IsWellKnown) { throw wrapUnhandled(phaseName, e); } + + args.Token.ThrowIfCancellationRequested(); + + return result; } /// @@ -80,16 +90,22 @@ public T RunPhase(string phaseName, Func body) /// The value returned by . public T RunPhase(string phaseName, string logMessage, Func body) { + T result; + try { log(logMessage); - return body(Args); + result = body(Args); } catch (Exception e) when (!e.IsWellKnown) { throw wrapUnhandled(phaseName, e); } + + args.Token.ThrowIfCancellationRequested(); + + return result; } } diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index cf6e97c87f..331c19a05d 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -85,15 +85,11 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) phaseName: "loading", body: LoadOutputModule); - runner.Args.Token.ThrowIfCancellationRequested(); - // Define the impl module to emit ModuleDefinition implModule = runner.RunPhase( phaseName: "loading", body: _ => DefineImplModule(runtimeContext, outputModule)); - runner.Args.Token.ThrowIfCancellationRequested(); - // Emit all necessary IL code in the impl module runner.RunPhase(phaseName: "generation", body: _ => { @@ -101,8 +97,6 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) EmitTypeForwards(outputModule, implModule); }); - runner.Args.Token.ThrowIfCancellationRequested(); - // Write the module to disk with all the generated contents runner.RunPhase( phaseName: "emit", diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs index 9b51554ae9..7ec2f9c60c 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs @@ -39,8 +39,6 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) logMessage: $"Processing {runner.Args.ReferenceAssemblyPaths.Length + runner.Args.ImplementationAssemblyPaths.Length + 1} module(s)", body: Discover); - runner.Args.Token.ThrowIfCancellationRequested(); - // Emit the resulting interop assembly runner.RunPhase( phaseName: "emit", diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index 7f871b28d3..7bb3a9ffb1 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -45,8 +45,6 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) }, body: ProcessReferences); - runner.Args.Token.ThrowIfCancellationRequested(); - // If no types were found to project (e.g., component mode with no component references), // skip the source generation and emit phases entirely (no .dll will be produced at all). if (!processingState.HasTypesToProject) @@ -60,8 +58,6 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) logMessage: "Generating projection code", body: _ => GenerateSources(processingState)); - runner.Args.Token.ThrowIfCancellationRequested(); - // Invoke Roslyn to compile the generated sources into 'WinRT.Projection.dll' runner.RunPhase( phaseName: "emit", diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index f221165bc7..a242cbcd8e 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -50,8 +50,6 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) phaseName: "processing", body: BuildWriterOptions); - runner.Args.Token.ThrowIfCancellationRequested(); - // Invoke the projection writer (in-process) to generate the projection sources. We can't // route this through the shared 'runner.RunPhase' helper because we wrap the exception // into a well-known 'CsWinRTProcessError' rather than the per-tool 'Unhandled' factory. diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs index 188d64c068..1f71951433 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs @@ -53,8 +53,6 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) logMessage: $"Processing assembly: '{System.IO.Path.GetFileName(runner.Args.InputAssemblyPath)}'", body: Discover); - token.ThrowIfCancellationRequested(); - // Generate and write the .winmd file runner.RunPhase( phaseName: "generation", From 6a21afd016b82ec3cd7ce48492b93fcaec411887 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 9 Jun 2026 14:44:15 -0700 Subject: [PATCH 27/28] Rename file to GeneratorPhaseRunner{TArgs}.cs Rename src/WinRT.Generator.Core/GeneratorPhaseRunner.cs to src/WinRT.Generator.Core/GeneratorPhaseRunner{TArgs}.cs. No source changes were made; this update clarifies the file name to reflect the generic TArgs type parameter. --- .../{GeneratorPhaseRunner.cs => GeneratorPhaseRunner{TArgs}.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/WinRT.Generator.Core/{GeneratorPhaseRunner.cs => GeneratorPhaseRunner{TArgs}.cs} (100%) diff --git a/src/WinRT.Generator.Core/GeneratorPhaseRunner.cs b/src/WinRT.Generator.Core/GeneratorPhaseRunner{TArgs}.cs similarity index 100% rename from src/WinRT.Generator.Core/GeneratorPhaseRunner.cs rename to src/WinRT.Generator.Core/GeneratorPhaseRunner{TArgs}.cs From c5e03594f8a597c41620af82f001efdd5851bd4d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 13 Jun 2026 11:42:33 -0700 Subject: [PATCH 28/28] Add CodeQL annotations & clarify IID comments Add CodeQL [SM02196] explanatory comments to MvidGenerator to indicate the SHA1-based MVID/hash buffer is fully populated and that the hash is not used for authentication. Also refine punctuation. In WinMDWriter.Attributes, correct and clarify the comment to state that Windows Runtime uses UUID v5 SHA1 to generate IIDs for parameterized types (instead of saying GUIDs). These changes document security rationale for SHA1 usage and silence static analysis warnings. --- src/WinRT.Generator.Core/Helpers/MvidGenerator.cs | 8 ++++++-- .../Writers/WinMDWriter.Attributes.cs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs b/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs index 200483f064..b3f32689b8 100644 --- a/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs +++ b/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs @@ -29,9 +29,11 @@ public static Guid CreateMvid(Guid left, Guid right) _ = left.TryWriteBytes(input, bigEndian: true, out _); _ = right.TryWriteBytes(input[16..], bigEndian: true, out _); + // CodeQL [SM02196] We'll fill the entire buffer during hashing (see below) Span hash = stackalloc byte[SHA1.HashSizeInBytes]; - // Hash the two IIDs together (the order matters) + // Hash the two IIDs together (the order matters). + // CodeQL [SM02196] This hash is only used as MVID for the assembly, not for authentication. _ = SHA1.HashData(input, hash); // Create the final MVID from the first 16 bytes of the hash @@ -45,6 +47,7 @@ public static Guid CreateMvid(Guid left, Guid right) /// The resulting MVID. public static Guid CreateMvid(params IEnumerable assemblyPaths) { + // CodeQL [SM02196] This hash is only used as MVID for the assembly, not for authentication. using IncrementalHash hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); // Process all input assemblies to compute the MVID @@ -55,9 +58,10 @@ public static Guid CreateMvid(params IEnumerable assemblyPaths) hasher.AppendData(stream); } + // CodeQL [SM02196] We'll fill the entire buffer during hashing (see below) Span hash = stackalloc byte[SHA1.HashSizeInBytes]; - // Write the final combined hash + // Write the final combined hash. _ = hasher.GetCurrentHash(hash); // Create the final MVID from the first 16 bytes of the hash diff --git a/src/WinRT.WinMD.Generator/Writers/WinMDWriter.Attributes.cs b/src/WinRT.WinMD.Generator/Writers/WinMDWriter.Attributes.cs index 9cafa77b75..764cc80001 100644 --- a/src/WinRT.WinMD.Generator/Writers/WinMDWriter.Attributes.cs +++ b/src/WinRT.WinMD.Generator/Writers/WinMDWriter.Attributes.cs @@ -66,7 +66,7 @@ private void AddGuidAttributeFromName(TypeDefinition outputType, string name) Span hash = stackalloc byte[SHA1.HashSizeInBytes]; // Hash the data (we know we'll always fully fill the buffer). - // CodeQL [SM02196] Windows Runtime uses UUID v5 SHA1 to generate Guids for parameterized types. + // CodeQL [SM02196] Windows Runtime uses UUID v5 SHA1 to generate IIDs for parameterized types. _ = SHA1.HashData(span[..writtenNumberOfBytes], hash); // Return the rented array to the pool, if we have one