diff --git a/src/WinRT.Impl.Generator/Attributes/CommandLineArgumentNameAttribute.cs b/src/WinRT.Generator.Core/Attributes/CommandLineArgumentNameAttribute.cs
similarity index 91%
rename from src/WinRT.Impl.Generator/Attributes/CommandLineArgumentNameAttribute.cs
rename to src/WinRT.Generator.Core/Attributes/CommandLineArgumentNameAttribute.cs
index 95d0ace705..ca36c6346f 100644
--- a/src/WinRT.Impl.Generator/Attributes/CommandLineArgumentNameAttribute.cs
+++ b/src/WinRT.Generator.Core/Attributes/CommandLineArgumentNameAttribute.cs
@@ -3,7 +3,7 @@
using System;
-namespace WindowsRuntime.ImplGenerator.Attributes;
+namespace WindowsRuntime.Generator.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.Generator.Core/DebugRepro/DebugReproPacker.cs b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs
new file mode 100644
index 0000000000..ba0da678e9
--- /dev/null
+++ b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs
@@ -0,0 +1,218 @@
+// 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.Generator.Errors;
+using WindowsRuntime.Generator.Helpers;
+
+namespace WindowsRuntime.Generator.DebugRepro;
+
+///
+/// Leaf helpers shared across the CsWinRT CLI generators for packaging and unpacking debug repros.
+///
+///
+/// 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
+{
+ ///
+ /// 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)!;
+ }
+
+ ///
+ /// 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/Extensions/ImplExceptionExtensions.cs b/src/WinRT.Generator.Core/Errors/GeneratorExceptionExtensions.cs
similarity index 65%
rename from src/WinRT.Impl.Generator/Extensions/ImplExceptionExtensions.cs
rename to src/WinRT.Generator.Core/Errors/GeneratorExceptionExtensions.cs
index 9bf6d91b12..ab7cc82890 100644
--- a/src/WinRT.Impl.Generator/Extensions/ImplExceptionExtensions.cs
+++ b/src/WinRT.Generator.Core/Errors/GeneratorExceptionExtensions.cs
@@ -2,20 +2,19 @@
// Licensed under the MIT License.
using System;
-using WindowsRuntime.ImplGenerator.Errors;
-namespace WindowsRuntime.ImplGenerator;
+namespace WindowsRuntime.Generator.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.Core/Errors/IGeneratorErrorFactory.cs b/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs
new file mode 100644
index 0000000000..8906ab8c63
--- /dev/null
+++ b/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+
+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 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
+{
+ /// 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.Generator.Core/Errors/UnhandledGeneratorException.cs b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs
new file mode 100644
index 0000000000..3b51d8ad6b
--- /dev/null
+++ b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+
+namespace WindowsRuntime.Generator.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.
+///
+/// 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 name of the generator used in the standard message.
+ ///
+ protected abstract string GeneratorName { get; }
+
+ ///
+ public override string ToString()
+ {
+ return
+ $"""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.Generator.Core/Errors/WellKnownGeneratorException.cs b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorException.cs
new file mode 100644
index 0000000000..d65c0e9016
--- /dev/null
+++ b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorException.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+
+namespace WindowsRuntime.Generator.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.Generator.Core/Errors/WellKnownGeneratorMessages.cs b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessages.cs
new file mode 100644
index 0000000000..03e5bee363
--- /dev/null
+++ b/src/WinRT.Generator.Core/Errors/WellKnownGeneratorMessages.cs
@@ -0,0 +1,50 @@
+// 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
+{
+ ///
+ public const string ResponseFileReadError = "Failed to read the response file (e.g. it may be missing or not accessible).";
+
+ ///
+ /// The name of the response-file argument that failed to parse.
+ public static string ResponseFileArgumentParsingError(string argumentName)
+ {
+ return $"Failed to parse argument '{argumentName}' from response file.";
+ }
+
+ ///
+ public const string MalformedResponseFile = "The response file is malformed and contains invalid content.";
+
+ ///
+ /// The directory path that does not exist.
+ public static string DebugReproDirectoryDoesNotExist(string path)
+ {
+ return $"The debug repro directory '{path}' does not exist.";
+ }
+
+ ///
+ /// The debug-repro file entry path that has no mapping.
+ public static string DebugReproMissingFileEntryMapping(string path)
+ {
+ return $"The debug repro file entry with path '{path}' is missing its assembly path mapping.";
+ }
+
+ ///
+ /// The debug-repro file entry path that was not recognized.
+ public static string DebugReproUnrecognizedFileEntry(string path)
+ {
+ return $"The debug repro file entry with path '{path}' was not recognized.";
+ }
+}
diff --git a/src/WinRT.Interop.Generator/Extensions/FileExtensions.cs b/src/WinRT.Generator.Core/Extensions/FileExtensions.cs
similarity index 95%
rename from src/WinRT.Interop.Generator/Extensions/FileExtensions.cs
rename to src/WinRT.Generator.Core/Extensions/FileExtensions.cs
index 46869482ef..02ac39d6fc 100644
--- a/src/WinRT.Interop.Generator/Extensions/FileExtensions.cs
+++ b/src/WinRT.Generator.Core/Extensions/FileExtensions.cs
@@ -5,7 +5,7 @@
using System.IO;
using System.Text;
-namespace WindowsRuntime.InteropGenerator;
+namespace WindowsRuntime.Generator;
///
/// 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/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/Extensions/PathExtensions.cs b/src/WinRT.Generator.Core/Extensions/PathExtensions.cs
similarity index 73%
rename from src/WinRT.Interop.Generator/Extensions/PathExtensions.cs
rename to src/WinRT.Generator.Core/Extensions/PathExtensions.cs
index fac234101c..fcb37d6130 100644
--- a/src/WinRT.Interop.Generator/Extensions/PathExtensions.cs
+++ b/src/WinRT.Generator.Core/Extensions/PathExtensions.cs
@@ -5,7 +5,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
-namespace WindowsRuntime.InteropGenerator;
+namespace WindowsRuntime.Generator;
///
/// 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;
@@ -35,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.
///
@@ -61,4 +47,4 @@ public static bool IsWithinDirectoryName(ReadOnlySpan path, ReadOnlySpan
/// Extensions for the type.
diff --git a/src/WinRT.Generator.Core/GeneratorHost.cs b/src/WinRT.Generator.Core/GeneratorHost.cs
new file mode 100644
index 0000000000..27df49e2d1
--- /dev/null
+++ b/src/WinRT.Generator.Core/GeneratorHost.cs
@@ -0,0 +1,107 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.IO;
+using System.Threading;
+using WindowsRuntime.Generator.Errors;
+
+namespace WindowsRuntime.Generator;
+
+///
+/// Shared Run entry-point scaffold for the CsWinRT CLI generators.
+///
+internal static class GeneratorHost
+{
+ ///
+ /// 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).
+ /// 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.
+ ///
+ /// A pre-bound to the parsed args,
+ /// and for use by subsequent phases.
+ ///
+ public static GeneratorPhaseRunner CreateRunner(
+ 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 new(args, wrapUnhandled, log);
+ }
+}
diff --git a/src/WinRT.Generator.Core/GeneratorPhaseRunner{TArgs}.cs b/src/WinRT.Generator.Core/GeneratorPhaseRunner{TArgs}.cs
new file mode 100644
index 0000000000..58b3cf1299
--- /dev/null
+++ b/src/WinRT.Generator.Core/GeneratorPhaseRunner{TArgs}.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using WindowsRuntime.Generator.Errors;
+
+#pragma warning disable CS1573
+
+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 ).
+/// 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
+{
+ ///
+ /// Gets the parsed per-tool args, forwarded to every body.
+ ///
+ public TArgs Args => args;
+
+ ///
+ /// 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. The captured is forwarded as its argument.
+ public void RunPhase(string phaseName, Action body)
+ {
+ try
+ {
+ body(Args);
+ }
+ catch (Exception e) when (!e.IsWellKnown)
+ {
+ throw wrapUnhandled(phaseName, e);
+ }
+
+ args.Token.ThrowIfCancellationRequested();
+ }
+
+ ///
+ /// The progress message to log before the body runs.
+ public void RunPhase(string phaseName, string logMessage, Action body)
+ {
+ try
+ {
+ log(logMessage);
+
+ body(Args);
+ }
+ catch (Exception e) when (!e.IsWellKnown)
+ {
+ throw wrapUnhandled(phaseName, e);
+ }
+
+ args.Token.ThrowIfCancellationRequested();
+ }
+
+ ///
+ /// The value returned by .
+ public T RunPhase(string phaseName, Func body)
+ {
+ T result;
+
+ try
+ {
+ result = body(Args);
+ }
+ catch (Exception e) when (!e.IsWellKnown)
+ {
+ throw wrapUnhandled(phaseName, e);
+ }
+
+ args.Token.ThrowIfCancellationRequested();
+
+ return result;
+ }
+
+ ///
+ /// The progress message to log before the body runs.
+ /// The value returned by .
+ public T RunPhase(string phaseName, string logMessage, Func body)
+ {
+ T result;
+
+ try
+ {
+ log(logMessage);
+
+ 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/Helpers/ImplGeneratorJsonSerializerContext.cs b/src/WinRT.Generator.Core/Helpers/GeneratorJsonSerializerContext.cs
similarity index 56%
rename from src/WinRT.Impl.Generator/Helpers/ImplGeneratorJsonSerializerContext.cs
rename to src/WinRT.Generator.Core/Helpers/GeneratorJsonSerializerContext.cs
index bdff83c9f8..88ea3b6d25 100644
--- a/src/WinRT.Impl.Generator/Helpers/ImplGeneratorJsonSerializerContext.cs
+++ b/src/WinRT.Generator.Core/Helpers/GeneratorJsonSerializerContext.cs
@@ -4,11 +4,11 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace WindowsRuntime.ImplGenerator.Helpers;
+namespace WindowsRuntime.Generator.Helpers;
///
-/// A for types used in the impl generator.
+/// A for types used across the CsWinRT CLI generators.
///
[JsonSerializable(typeof(Dictionary))]
[JsonSourceGenerationOptions(WriteIndented = true)]
-internal sealed partial class ImplGeneratorJsonSerializerContext : JsonSerializerContext;
+internal sealed partial class GeneratorJsonSerializerContext : JsonSerializerContext;
diff --git a/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs b/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs
new file mode 100644
index 0000000000..b3f32689b8
--- /dev/null
+++ b/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using WindowsRuntime.Generator.Extensions;
+
+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 _);
+
+ // 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).
+ // 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
+ return new(hash[..16]);
+ }
+
+ ///
+ /// Generates a deterministic MVID based on a set of input assemblies.
+ ///
+ /// The input paths of all assemblies being processed.
+ /// 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
+ foreach (string assemblyPath in assemblyPaths.Order())
+ {
+ using FileStream stream = File.OpenRead(assemblyPath);
+
+ 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.
+ _ = hasher.GetCurrentHash(hash);
+
+ // Create the final MVID from the first 16 bytes of the hash
+ return new(hash[..16]);
+ }
+}
diff --git a/src/WinRT.Generator.Core/IGeneratorArgs.cs b/src/WinRT.Generator.Core/IGeneratorArgs.cs
new file mode 100644
index 0000000000..f6bf0ed5c8
--- /dev/null
+++ b/src/WinRT.Generator.Core/IGeneratorArgs.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Threading;
+
+namespace WindowsRuntime.Generator;
+
+///
+/// Common surface implemented by every per-tool arguments record.
+///
+internal interface IGeneratorArgs
+{
+ ///
+ /// Gets the cancellation token for the generator invocation.
+ ///
+ CancellationToken Token { get; }
+
+ ///
+ /// Gets the directory where the debug repro should be written, if requested.
+ ///
+ string? DebugReproDirectory { get; }
+}
diff --git a/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs b/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs
new file mode 100644
index 0000000000..fa90f69710
--- /dev/null
+++ b/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs
@@ -0,0 +1,96 @@
+// 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 WindowsRuntime.Generator.Attributes;
+
+namespace WindowsRuntime.Generator.Parsing;
+
+///
+/// Helper to format argument objects into response files.
+///
+internal static class ResponseFileBuilder
+{
+ ///
+ /// 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(DynamicallyAccessedMemberTypes.PublicProperties)] 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;
+ }
+
+ // 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.
+ 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
new file mode 100644
index 0000000000..25a2bc7a6b
--- /dev/null
+++ b/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs
@@ -0,0 +1,279 @@
+// 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.Generator.Attributes;
+using WindowsRuntime.Generator.Errors;
+
+namespace WindowsRuntime.Generator.Parsing;
+
+///
+/// Helper to parse response files into argument objects.
+///
+internal static class ResponseFileParser
+{
+ ///
+ /// 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(DynamicallyAccessedMemberTypes.AllConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] 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);
+ }
+
+ Dictionary argsMap = BuildArgsMap(lines);
+
+ return Populate(argsMap, 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(DynamicallyAccessedMemberTypes.AllConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] TArgs, TErr>(
+ Stream stream,
+ CancellationToken token)
+ where TArgs : class
+ where TErr : IGeneratorErrorFactory
+ {
+ string[] lines = File.ReadAllLines(stream);
+
+ 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;
+ }
+
+ ///
+ /// 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.
+ private static TArgs Populate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] 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));
+
+ 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);
+
+ 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);
+ }
+ }
+
+ 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 .
+ private static void ApplyDefault(
+ object instance,
+ PropertyInfo property,
+ Type propertyType,
+ bool isRequired)
+ 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 (initializers aren't needed).
+ 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;
+ }
+ }
+
+ ///
+ /// 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.Interop.Generator/References/WellKnownPublicKeyTokens.cs b/src/WinRT.Generator.Core/References/WellKnownPublicKeyTokens.cs
similarity index 62%
rename from src/WinRT.Interop.Generator/References/WellKnownPublicKeyTokens.cs
rename to src/WinRT.Generator.Core/References/WellKnownPublicKeyTokens.cs
index a9f34cc3da..0942d8ec00 100644
--- a/src/WinRT.Interop.Generator/References/WellKnownPublicKeyTokens.cs
+++ b/src/WinRT.Generator.Core/References/WellKnownPublicKeyTokens.cs
@@ -1,35 +1,40 @@
// 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 assemblies used by the CsWinRT generators.
///
internal static class WellKnownPublicKeyTokens
{
///
- /// The public key data for System.Memory.dll.
+ /// The public key token for mscorlib.
+ ///
+ 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 +42,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..1c755b9d73
--- /dev/null
+++ b/src/WinRT.Generator.Core/References/WellKnownPublicKeys.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace WindowsRuntime.Generator.References;
+
+///
+/// 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.
+ ///
+ 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.Generator.Core/WinRT.Generator.Core.csproj b/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj
new file mode 100644
index 0000000000..5e0d083a0e
--- /dev/null
+++ b/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj
@@ -0,0 +1,44 @@
+
+
+ net10.0
+ 14.0
+ enable
+ true
+ true
+ true
+
+
+ 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)
+
+
+ true
+
+
+ WindowsRuntime.Generator
+
+
+ true
+ true
+ latest
+ latest-all
+ true
+ strict
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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.Impl.Generator/Errors/UnhandledImplException.cs b/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs
index cd391f8b66..af5926b763 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.Generator.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 GeneratorName => "impl";
+}
diff --git a/src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs b/src/WinRT.Impl.Generator/Errors/WellKnownImplException.cs
index 7bd5e646e0..7a2be1f116 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.Generator.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/Errors/WellKnownImplExceptions.cs b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs
index 7b669deed5..ab965be045 100644
--- a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs
+++ b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs
@@ -2,13 +2,14 @@
// Licensed under the MIT License.
using System;
+using WindowsRuntime.Generator.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.
@@ -16,27 +17,28 @@ internal static class WellKnownImplExceptions
public const string ErrorPrefix = "CSWINRTIMPLGEN";
///
- /// Some exception was thrown when trying to read the response file.
+ /// Prevents external instantiation; this type is only used to dispatch through .
///
+ private WellKnownImplExceptions()
+ {
+ }
+
+ ///
public static Exception ResponseFileReadError(Exception exception)
{
- return Exception(1, "Failed to read the response file to run 'cswinrtimplgen'.", exception);
+ return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, 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);
}
///
@@ -95,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.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/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.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs
index eac02ae9f4..dea139fcf1 100644
--- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs
+++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs
@@ -1,19 +1,17 @@
+
// 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.Generator;
+using WindowsRuntime.Generator.DebugRepro;
+using WindowsRuntime.Generator.Parsing;
using WindowsRuntime.ImplGenerator.Errors;
-using WindowsRuntime.ImplGenerator.Helpers;
-using WindowsRuntime.InteropGenerator;
#pragma warning disable IDE0008
@@ -35,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();
@@ -57,13 +51,13 @@ 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();
// Load the mappings with all the original file paths for reference .dll-s
- Dictionary originalReferenceDllPaths = ExtractPathMap(originalReferenceDllPathsEntry);
+ Dictionary originalReferenceDllPaths = DebugReproPacker.ExtractPathMap(originalReferenceDllPathsEntry);
token.ThrowIfCancellationRequested();
@@ -118,7 +112,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!,
@@ -127,7 +121,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");
@@ -150,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);
- }
+ (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave(
+ args.DebugReproDirectory,
+ toolName: "cswinrtimplgen",
+ archiveFileName: "impl-debug-repro.zip");
- // Path for the ZIP archive
- string zipPath = Path.Combine(args.DebugReproDirectory, "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
@@ -172,17 +158,17 @@ 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();
// Prepare the .rsp file with all updated arguments
- string rspText = new ImplGeneratorArgs
+ string rspText = ResponseFileBuilder.Format(new ImplGeneratorArgs
{
ReferenceAssemblyPaths = [.. updatedReferenceDllNames],
OutputAssemblyPath = outputAssemblyHashedName,
@@ -191,7 +177,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");
@@ -201,134 +187,10 @@ 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();
- // 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);
- }
-
- ///
- /// 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, ImplGeneratorJsonSerializerContext.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, ImplGeneratorJsonSerializerContext.Default.DictionaryStringString)!;
+ DebugReproPacker.FinalizeSave(tempDirectory, zipPath);
}
}
diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs
index 0f9854159b..331c19a05d 100644
--- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs
+++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs
@@ -17,9 +17,13 @@
using AsmResolver.PE;
using AsmResolver.PE.DotNet.StrongName;
using ConsoleAppFramework;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.Errors;
+using WindowsRuntime.Generator.Extensions;
+using WindowsRuntime.Generator.Helpers;
+using WindowsRuntime.Generator.Parsing;
+using WindowsRuntime.Generator.References;
using WindowsRuntime.ImplGenerator.Errors;
-using WindowsRuntime.ImplGenerator.References;
-using WindowsRuntime.InteropGenerator;
namespace WindowsRuntime.ImplGenerator.Generation;
@@ -66,143 +70,53 @@ 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();
-
- RuntimeContext runtimeContext;
- ModuleDefinition outputModule;
+ GeneratorPhaseRunner runner = GeneratorHost.CreateRunner(
+ inputFilePath: inputFilePath,
+ toolName: "cswinrtimplgen",
+ unpackDebugRepro: UnpackDebugRepro,
+ parseFromResponseFile: ResponseFileParser.Parse,
+ saveDebugRepro: SaveDebugRepro,
+ wrapUnhandled: static (phase, e) => new UnhandledImplException(phase, e),
+ log: ConsoleApp.Log,
+ token: token);
// 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);
- }
-
- args.Token.ThrowIfCancellationRequested();
-
- ModuleDefinition implModule;
+ (RuntimeContext runtimeContext, ModuleDefinition outputModule) = runner.RunPhase(
+ phaseName: "loading",
+ body: LoadOutputModule);
// Define the impl module to emit
- try
- {
- implModule = DefineImplModule(runtimeContext, outputModule);
- }
- catch (Exception e) when (!e.IsWellKnown)
- {
- throw new UnhandledImplException("loading", e);
- }
-
- args.Token.ThrowIfCancellationRequested();
+ ModuleDefinition implModule = runner.RunPhase(
+ phaseName: "loading",
+ body: _ => DefineImplModule(runtimeContext, outputModule));
// 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: args => 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: 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!)}");
}
///
/// 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;
@@ -227,12 +141,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)
{
@@ -325,7 +239,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
};
@@ -333,7 +247,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
};
@@ -341,7 +255,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/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 27fd5e5115..0000000000
--- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.Parsing.cs
+++ /dev/null
@@ -1,194 +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.ImplGenerator.Attributes;
-using WindowsRuntime.ImplGenerator.Errors;
-using WindowsRuntime.InteropGenerator;
-
-#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();
-
- // 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 be772b2086..d85ec1dd2e 100644
--- a/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs
+++ b/src/WinRT.Impl.Generator/Generation/ImplGeneratorArgs.cs
@@ -2,14 +2,15 @@
// Licensed under the MIT License.
using System.Threading;
-using WindowsRuntime.ImplGenerator.Attributes;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.Attributes;
namespace WindowsRuntime.ImplGenerator.Generation;
///
/// Input parameters for .
///
-internal sealed partial class ImplGeneratorArgs
+internal sealed class ImplGeneratorArgs : IGeneratorArgs
{
/// Gets the input .dll paths.
[CommandLineArgumentName("--reference-assembly-paths")]
@@ -31,10 +32,11 @@ internal sealed partial class ImplGeneratorArgs
[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; }
-}
\ No newline at end of file
+}
+
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.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.Impl.Generator/WinRT.Impl.Generator.csproj b/src/WinRT.Impl.Generator/WinRT.Impl.Generator.csproj
index e37bc9a077..c34119d260 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.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/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..59c480334e 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.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 26b86be38f..aabdfe8ed1 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.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 b279abc9aa..972d473b14 100644
--- a/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs
+++ b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs
@@ -2,37 +2,20 @@
// Licensed under the MIT License.
using System;
+using WindowsRuntime.Generator.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;
-
- ///
- /// 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 ErrorPrefix => WellKnownInteropExceptions.ErrorPrefix;
///
- 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 string GeneratorName => "interop";
+}
diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropException.cs
index 2d7ea67300..d5676957f8 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.Generator.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/Errors/WellKnownInteropExceptions.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs
index cb47d56868..6df46e720a 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.Generator.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.
///
@@ -242,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, 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));
}
///
@@ -746,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.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/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.DebugRepro.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs
index 22396c11b1..f39a75b95b 100644
--- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs
+++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs
@@ -7,12 +7,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.Generator;
+using WindowsRuntime.Generator.DebugRepro;
+using WindowsRuntime.Generator.Parsing;
using WindowsRuntime.InteropGenerator.Errors;
-using WindowsRuntime.InteropGenerator.Helpers;
#pragma warning disable IDE0008
@@ -39,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();
@@ -62,14 +57,14 @@ 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();
// 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();
@@ -165,7 +160,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],
@@ -183,7 +178,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");
@@ -206,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);
@@ -231,8 +218,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();
@@ -246,7 +233,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],
@@ -264,7 +251,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");
@@ -274,102 +261,54 @@ 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();
- // 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);
- }
-
- ///
- /// 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;
+ DebugReproPacker.FinalizeSave(tempDirectory, zipPath);
}
///
- /// Copies a specified assembly to a target folder.
+ /// Copies a single specified file to a target folder, with reserved-DLL dedupe.
///
- /// 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;
}
@@ -378,7 +317,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);
}
@@ -387,47 +326,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, InteropGeneratorJsonSerializerContext.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, InteropGeneratorJsonSerializerContext.Default.DictionaryStringString)!;
- }
-}
\ 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..42760ba3c2 100644
--- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs
+++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs
@@ -10,6 +10,9 @@
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
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;
@@ -555,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/Generation/InteropGenerator.Emit.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs
index 6817914a14..012efd323f 100644
--- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs
+++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs
@@ -8,10 +8,12 @@
using System.Linq;
using AsmResolver.DotNet;
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;
diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs
index f330be463d..7ec2f9c60c 100644
--- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs
+++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs
@@ -1,10 +1,11 @@
// 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.Parsing;
using WindowsRuntime.InteropGenerator.Errors;
using WindowsRuntime.InteropGenerator.References;
@@ -22,99 +23,29 @@ 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();
-
- 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);
- }
-
- 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);
- }
+ GeneratorPhaseRunner runner = GeneratorHost.CreateRunner(
+ inputFilePath: inputFilePath,
+ toolName: "cswinrtinteropgen",
+ unpackDebugRepro: UnpackDebugRepro,
+ parseFromResponseFile: ResponseFileParser.Parse,
+ saveDebugRepro: SaveDebugRepro,
+ wrapUnhandled: static (phase, e) => new UnhandledInteropException(phase, e),
+ log: ConsoleApp.Log,
+ token: token);
+
+ // Discover the types to process
+ InteropGeneratorDiscoveryState discoveryState = runner.RunPhase(
+ phaseName: "discovery",
+ logMessage: $"Processing {runner.Args.ReferenceAssemblyPaths.Length + runner.Args.ImplementationAssemblyPaths.Length + 1} module(s)",
+ body: Discover);
+
+ // Emit the resulting interop assembly
+ runner.RunPhase(
+ phaseName: "emit",
+ logMessage: "Generating interop code",
+ 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.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 89e4559ac4..0000000000
--- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.Parsing.cs
+++ /dev/null
@@ -1,221 +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.InteropGenerator.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();
-
- // 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 0384a4f8cd..baf3434fed 100644
--- a/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs
+++ b/src/WinRT.Interop.Generator/Generation/InteropGeneratorArgs.cs
@@ -2,14 +2,15 @@
// Licensed under the MIT License.
using System.Threading;
-using WindowsRuntime.InteropGenerator.Attributes;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.Attributes;
namespace WindowsRuntime.InteropGenerator.Generation;
///
/// Input parameters for .
///
-internal sealed partial class InteropGeneratorArgs
+internal sealed class InteropGeneratorArgs : IGeneratorArgs
{
/// Gets the input reference .dll paths.
[CommandLineArgumentName("--reference-assembly-paths")]
@@ -67,10 +68,11 @@ internal sealed partial class InteropGeneratorArgs
[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; }
-}
\ No newline at end of file
+}
+
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/Helpers/MvidGenerator.cs b/src/WinRT.Interop.Generator/Helpers/MvidGenerator.cs
deleted file mode 100644
index d738973660..0000000000
--- a/src/WinRT.Interop.Generator/Helpers/MvidGenerator.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Security.Cryptography;
-
-namespace WindowsRuntime.InteropGenerator.Helpers;
-
-///
-/// A generator for MVIDs for .NET modules.
-///
-internal static class MvidGenerator
-{
- ///
- /// Generates a deterministic MVID based on a set of input assemblies.
- ///
- /// The input paths of all assemblies being processed.
- /// The resulting MVID.
- public static Guid CreateMvid(params IEnumerable assemblyPaths)
- {
- using IncrementalHash hasher = IncrementalHash.CreateHash(HashAlgorithmName.SHA1);
-
- // Process all input assemblies to compute the MVID
- foreach (string assemblyPath in assemblyPaths.Order())
- {
- using FileStream stream = File.OpenRead(assemblyPath);
-
- hasher.AppendData(stream);
- }
-
- Span hash = stackalloc byte[SHA1.HashSizeInBytes];
-
- // Write the final combined hash
- _ = hasher.GetCurrentHash(hash);
-
- // Create the final MVID from the first 16 bytes of the hash
- return new(hash[..16]);
- }
-}
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.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.Interop.Generator/WinRT.Interop.Generator.csproj b/src/WinRT.Interop.Generator/WinRT.Interop.Generator.csproj
index 062188b98b..f3a7ff2e70 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/Errors/UnhandledProjectionGeneratorException.cs b/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs
index de0df58c60..da7ea08ff4 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.Generator.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 GeneratorName => "projection";
}
+
diff --git a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorException.cs b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorException.cs
index 3b9bc0b690..64d32601ac 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.Generator.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/Errors/WellKnownProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs
index 9358b975c1..4ac475f319 100644
--- a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs
+++ b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs
@@ -5,13 +5,14 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
+using WindowsRuntime.Generator.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.
@@ -19,27 +20,28 @@ internal static class WellKnownProjectionGeneratorExceptions
public const string ErrorPrefix = "CSWINRTPROJECTIONGEN";
///
- /// Some exception was thrown when trying to read the response file.
+ /// Prevents external instantiation; this type is only used to dispatch through .
///
+ private WellKnownProjectionGeneratorExceptions()
+ {
+ }
+
+ ///
public static Exception ResponseFileReadError(Exception exception)
{
- return Exception(1, "Failed to read the response file to run 'cswinrtprojectiongen'.", exception);
+ return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, 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);
}
///
@@ -92,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.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/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));
- }
-}
diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs
index 949fbc53bb..1e784160d3 100644
--- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs
+++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs
@@ -6,13 +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.InteropGenerator;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.DebugRepro;
+using WindowsRuntime.Generator.Parsing;
using WindowsRuntime.ProjectionGenerator.Errors;
-using WindowsRuntime.ProjectionGenerator.Helpers;
using WindowsRuntime.ProjectionWriter.Helpers;
#pragma warning disable IDE0008
@@ -62,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();
@@ -94,15 +88,15 @@ .. 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();
// 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();
@@ -175,7 +169,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,
@@ -188,7 +182,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");
@@ -211,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);
- }
+ (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave(
+ args.DebugReproDirectory,
+ toolName: "cswinrtprojectiongen",
+ archiveFileName: "projection-debug-repro.zip");
- // Path for the ZIP archive
- string zipPath = Path.Combine(args.DebugReproDirectory, "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);
@@ -239,8 +225,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,14 +253,14 @@ 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();
// 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,
@@ -287,7 +273,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");
@@ -297,104 +283,12 @@ 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();
- // 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);
- }
-
- ///
- /// 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, ProjectionGeneratorJsonSerializerContext.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, ProjectionGeneratorJsonSerializerContext.Default.DictionaryStringString)!;
+ DebugReproPacker.FinalizeSave(tempDirectory, zipPath);
}
}
diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs
index 2743360763..506a93f8f8 100644
--- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs
+++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs
@@ -3,12 +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;
@@ -16,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.
///
@@ -67,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.Projection.Generator/Generation/ProjectionGenerator.Generate.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs
index bd576152ac..58e6fba9ee 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.Generator.Errors;
using WindowsRuntime.ProjectionGenerator.Errors;
using WindowsRuntime.ProjectionWriter;
using WindowsRuntime.ProjectionWriter.Helpers;
@@ -66,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.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs
index 019a2077eb..7bb3a9ffb1 100644
--- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs
+++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs
@@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System;
using System.IO;
using System.Threading;
using ConsoleAppFramework;
-using WindowsRuntime.InteropGenerator;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.Parsing;
using WindowsRuntime.ProjectionGenerator.Errors;
namespace WindowsRuntime.ProjectionGenerator.Generation;
@@ -22,90 +22,28 @@ 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();
-
- 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
+ GeneratorPhaseRunner runner = GeneratorHost.CreateRunner(
+ inputFilePath: inputFilePath,
+ toolName: "cswinrtprojectiongen",
+ unpackDebugRepro: UnpackDebugRepro,
+ parseFromResponseFile: ResponseFileParser.Parse,
+ saveDebugRepro: SaveDebugRepro,
+ wrapUnhandled: static (phase, e) => new UnhandledProjectionGeneratorException(phase, e),
+ log: ConsoleApp.Log,
+ token: token);
+
+ // 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: 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)"
- });
-
- processingState = ProcessReferences(args);
- }
- catch (Exception e) when (!e.IsWellKnown)
- {
- throw new UnhandledProjectionGeneratorException("processing", e);
- }
-
- args.Token.ThrowIfCancellationRequested();
+ _ => $"Processing {runner.Args.WinMDPaths.Length} .winmd reference(s)"
+ },
+ body: ProcessReferences);
// 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).
@@ -115,32 +53,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);
- }
-
- args.Token.ThrowIfCancellationRequested();
+ runner.RunPhase(
+ phaseName: "source-generation",
+ logMessage: "Generating projection code",
+ body: _ => GenerateSources(processingState));
// 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: 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.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 2f5011d5e9..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.InteropGenerator;
-using WindowsRuntime.ProjectionGenerator.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 054ddea620..e01fb8d7fa 100644
--- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs
+++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorArgs.cs
@@ -1,15 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.ComponentModel;
using System.Threading;
-using WindowsRuntime.ProjectionGenerator.Attributes;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.Attributes;
namespace WindowsRuntime.ProjectionGenerator.Generation;
///
/// Input parameters for .
///
-internal sealed partial class ProjectionGeneratorArgs
+internal sealed class ProjectionGeneratorArgs : IGeneratorArgs
{
/// Gets the input .dll paths.
[CommandLineArgumentName("--reference-assembly-paths")]
@@ -33,6 +35,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";
///
@@ -54,10 +57,11 @@ internal sealed partial class ProjectionGeneratorArgs
[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; }
-}
\ No newline at end of file
+}
+
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.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..0068c222ce 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/Errors/UnhandledReferenceProjectionGeneratorException.cs b/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs
index 731e2a129b..1bf7f1f132 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.Generator.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 GeneratorName => "reference projection";
}
+
diff --git a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorException.cs b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorException.cs
index 72f93c8799..8f66cfa80f 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.Generator.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/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs
index 0d9dff9399..aa3d8014d6 100644
--- a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs
+++ b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs
@@ -2,13 +2,14 @@
// Licensed under the MIT License.
using System;
+using WindowsRuntime.Generator.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.
@@ -16,27 +17,28 @@ internal static class WellKnownReferenceProjectionGeneratorExceptions
public const string ErrorPrefix = "CSWINRTPROJECTIONREFGEN";
///
- /// Some exception was thrown when trying to read the response file.
+ /// Prevents external instantiation; this type is only used to dispatch through .
///
+ private WellKnownReferenceProjectionGeneratorExceptions()
+ {
+ }
+
+ ///
public static Exception ResponseFileReadError(Exception exception)
{
- return Exception(1, "Failed to read the response file to run 'cswinrtprojectionrefgen'.", exception);
+ return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, 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);
}
///
@@ -55,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.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.DebugRepro.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs
index d3d507f95d..1c08ac14e1 100644
--- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs
+++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs
@@ -6,14 +6,12 @@
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.InteropGenerator;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.DebugRepro;
+using WindowsRuntime.Generator.Parsing;
using WindowsRuntime.ProjectionWriter.Helpers;
using WindowsRuntime.ReferenceProjectionGenerator.Errors;
-using WindowsRuntime.ReferenceProjectionGenerator.Helpers;
#pragma warning disable IDE0008
@@ -35,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();
@@ -57,13 +51,13 @@ 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();
// 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();
@@ -110,7 +104,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,
@@ -125,7 +119,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");
@@ -148,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);
- }
+ (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave(
+ args.DebugReproDirectory,
+ toolName: "cswinrtprojectionrefgen",
+ archiveFileName: "ref-projection-debug-repro.zip");
- // Path for the ZIP archive
- string zipPath = Path.Combine(args.DebugReproDirectory, "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
@@ -183,12 +169,12 @@ 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();
// Prepare the .rsp file with all updated arguments
- string rspText = new ReferenceProjectionGeneratorArgs
+ string rspText = ResponseFileBuilder.Format(new ReferenceProjectionGeneratorArgs
{
InputPaths = [.. updatedInputNames],
OutputDirectory = args.OutputDirectory,
@@ -203,7 +189,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");
@@ -213,102 +199,10 @@ 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();
- // 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);
- }
-
- ///
- /// 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, ReferenceProjectionGeneratorJsonSerializerContext.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, ReferenceProjectionGeneratorJsonSerializerContext.Default.DictionaryStringString)!;
+ DebugReproPacker.FinalizeSave(tempDirectory, zipPath);
}
}
diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs
index 5156adf288..a242cbcd8e 100644
--- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs
+++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs
@@ -6,7 +6,9 @@
using System.IO;
using System.Threading;
using ConsoleAppFramework;
-using WindowsRuntime.InteropGenerator;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.Errors;
+using WindowsRuntime.Generator.Parsing;
using WindowsRuntime.ProjectionWriter;
using WindowsRuntime.ProjectionWriter.Helpers;
using WindowsRuntime.ReferenceProjectionGenerator.Errors;
@@ -27,93 +29,35 @@ 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)
+ GeneratorPhaseRunner runner = GeneratorHost.CreateRunner(
+ inputFilePath: inputFilePath,
+ toolName: "cswinrtprojectionrefgen",
+ unpackDebugRepro: UnpackDebugRepro,
+ parseFromResponseFile: ResponseFileParser.Parse,
+ 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(runner.Args.TargetFramework) && !runner.Args.TargetFramework.StartsWith("net10.0", StringComparison.Ordinal))
{
- 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();
-
- // Validate the target framework. CsWinRT 3.0 requires .NET 10 or later.
- if (!string.IsNullOrEmpty(args.TargetFramework) && !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;
-
- try
- {
- options = BuildWriterOptions(args);
- }
- catch (Exception e) when (!e.IsWellKnown)
- {
- throw new UnhandledReferenceProjectionGeneratorException("processing", e);
- }
-
- args.Token.ThrowIfCancellationRequested();
+ ProjectionWriterOptions options = runner.RunPhase(
+ phaseName: "processing",
+ body: BuildWriterOptions);
- // 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}");
- global::WindowsRuntime.ProjectionWriter.ProjectionWriter.Run(options);
+ ProjectionWriter.ProjectionWriter.Run(options);
}
catch (Exception e) when (!e.IsWellKnown)
{
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 c3eebe8810..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.InteropGenerator;
-using WindowsRuntime.ReferenceProjectionGenerator.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 53f6dedea8..508ed77844 100644
--- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs
+++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGeneratorArgs.cs
@@ -2,14 +2,15 @@
// Licensed under the MIT License.
using System.Threading;
-using WindowsRuntime.ReferenceProjectionGenerator.Attributes;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.Attributes;
namespace WindowsRuntime.ReferenceProjectionGenerator.Generation;
///
/// Input parameters for .
///
-internal sealed partial 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").
@@ -56,10 +57,12 @@ internal sealed partial class ReferenceProjectionGeneratorArgs
[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.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..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,11 +51,7 @@
-
-
-
-
-
+
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/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/Errors/UnhandledWinMDException.cs b/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs
index 96e3700bd3..9002ee8c21 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.Generator.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 GeneratorName => "WinMD";
+}
diff --git a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDException.cs
index 16ba08edcc..2ccce2e233 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.Generator.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/Errors/WellKnownWinMDExceptions.cs b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs
index 83766d01cb..d958c0c7a9 100644
--- a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs
+++ b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs
@@ -2,13 +2,14 @@
// Licensed under the MIT License.
using System;
+using WindowsRuntime.Generator.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.
@@ -16,27 +17,28 @@ internal static class WellKnownWinMDExceptions
public const string ErrorPrefix = "CSWINRTWINMDGEN";
///
- /// Some exception was thrown when trying to read the response file.
+ /// Prevents external instantiation; this type is only used to dispatch through .
///
+ private WellKnownWinMDExceptions()
+ {
+ }
+
+ ///
public static Exception ResponseFileReadError(Exception exception)
{
- return Exception(1, "Failed to read the response file to run 'cswinrtwinmdgen'.", exception);
+ return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, 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);
}
///
@@ -71,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));
}
///
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.DebugRepro.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs
index d7e1814d1a..4f2ea5783e 100644
--- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs
+++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs
@@ -3,17 +3,14 @@
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.InteropGenerator;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.DebugRepro;
+using WindowsRuntime.Generator.Parsing;
using WindowsRuntime.WinMDGenerator.Errors;
-using WindowsRuntime.WinMDGenerator.Helpers;
#pragma warning disable IDE0008
@@ -35,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();
@@ -65,13 +58,13 @@ .. 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();
// Load the mappings with all the original file paths for reference assemblies
- Dictionary originalReferencePaths = ExtractPathMap(originalReferencePathsEntry);
+ Dictionary originalReferencePaths = DebugReproPacker.ExtractPathMap(originalReferencePathsEntry);
token.ThrowIfCancellationRequested();
@@ -130,7 +123,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],
@@ -139,7 +132,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");
@@ -162,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
@@ -184,17 +169,17 @@ 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();
// Prepare the .rsp file with all updated arguments
- string rspText = new WinMDGeneratorArgs
+ string rspText = ResponseFileBuilder.Format(new WinMDGeneratorArgs
{
InputAssemblyPath = inputAssemblyHashedName,
ReferenceAssemblyPaths = [.. updatedReferenceNames],
@@ -203,7 +188,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");
@@ -213,134 +198,10 @@ 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();
- // 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);
- }
-
- ///
- /// 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, WinMDGeneratorJsonSerializerContext.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, WinMDGeneratorJsonSerializerContext.Default.DictionaryStringString)!;
+ DebugReproPacker.FinalizeSave(tempDirectory, zipPath);
}
}
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;
diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs
index 875770bde0..1f71951433 100644
--- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs
+++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs
@@ -1,11 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System;
-using System.IO;
using System.Threading;
using ConsoleAppFramework;
-using WindowsRuntime.InteropGenerator;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.Parsing;
using WindowsRuntime.WinMDGenerator.Errors;
namespace WindowsRuntime.WinMDGenerator.Generation;
@@ -24,7 +23,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 .
///
@@ -38,95 +37,28 @@ 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();
+ GeneratorPhaseRunner runner = GeneratorHost.CreateRunner(
+ inputFilePath: inputFilePath,
+ toolName: "cswinrtwinmdgen",
+ unpackDebugRepro: UnpackDebugRepro,
+ parseFromResponseFile: ResponseFileParser.Parse,
+ saveDebugRepro: SaveDebugRepro,
+ wrapUnhandled: static (phase, e) => new UnhandledWinMDException(phase, e),
+ log: ConsoleApp.Log,
+ 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);
- }
-
- token.ThrowIfCancellationRequested();
+ WinMDGeneratorDiscoveryState discoveryState = runner.RunPhase(
+ phaseName: "discovery",
+ logMessage: $"Processing assembly: '{System.IO.Path.GetFileName(runner.Args.InputAssemblyPath)}'",
+ body: Discover);
// 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: 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
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 351029ab5a..0000000000
--- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.Parsing.cs
+++ /dev/null
@@ -1,210 +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.InteropGenerator;
-using WindowsRuntime.WinMDGenerator.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();
-
- if (string.IsNullOrEmpty(trimmedLine))
- {
- 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 c686f116f0..07dcf7b211 100644
--- a/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs
+++ b/src/WinRT.WinMD.Generator/Generation/WinMDGeneratorArgs.cs
@@ -2,14 +2,15 @@
// Licensed under the MIT License.
using System.Threading;
-using WindowsRuntime.WinMDGenerator.Attributes;
+using WindowsRuntime.Generator;
+using WindowsRuntime.Generator.Attributes;
namespace WindowsRuntime.WinMDGenerator.Generation;
///
/// Input parameters for .
///
-internal sealed partial class WinMDGeneratorArgs
+internal sealed class WinMDGeneratorArgs : IGeneratorArgs
{
/// Gets the path to the compiled input assembly (.dll) to analyze.
[CommandLineArgumentName("--input-assembly-path")]
@@ -31,10 +32,11 @@ internal sealed partial class WinMDGeneratorArgs
[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; }
-}
\ No newline at end of file
+}
+
diff --git a/src/WinRT.WinMD.Generator/Helpers/WinMDGeneratorJsonSerializerContext.cs b/src/WinRT.WinMD.Generator/Helpers/WinMDGeneratorJsonSerializerContext.cs
deleted file mode 100644
index a6a1422219..0000000000
--- a/src/WinRT.WinMD.Generator/Helpers/WinMDGeneratorJsonSerializerContext.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.WinMDGenerator.Helpers;
-
-///
-/// A for types used in the WinMD generator.
-///
-[JsonSerializable(typeof(Dictionary))]
-[JsonSourceGenerationOptions(WriteIndented = true)]
-internal sealed partial class WinMDGeneratorJsonSerializerContext : JsonSerializerContext;
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/WinRT.WinMD.Generator.csproj b/src/WinRT.WinMD.Generator/WinRT.WinMD.Generator.csproj
index 0d13c5831c..b2bb0f6dff 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/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
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
diff --git a/src/cswinrt.slnx b/src/cswinrt.slnx
index e62de46e37..8d8143675a 100644
--- a/src/cswinrt.slnx
+++ b/src/cswinrt.slnx
@@ -416,6 +416,7 @@
+