diff --git a/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs index db68eaef15..ba0da678e9 100644 --- a/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs +++ b/src/WinRT.Generator.Core/DebugRepro/DebugReproPacker.cs @@ -10,6 +10,7 @@ using System.Text; using System.Text.Json; using System.Threading; +using WindowsRuntime.Generator.Errors; using WindowsRuntime.Generator.Helpers; namespace WindowsRuntime.Generator.DebugRepro; @@ -18,14 +19,8 @@ namespace WindowsRuntime.Generator.DebugRepro; /// Leaf helpers shared across the CsWinRT CLI generators for packaging and unpacking debug repros. /// /// -/// Each generator's SaveDebugRepro/UnpackDebugRepro still owns the high-level orchestration -/// (which input categories exist, which subfolders are used, how the response file is re-stitched). This -/// type only owns the small, identical-across-tools building blocks: -/// -/// Hashing the original file path into a stable, collision-free file name. -/// Copying files (or a single file) into a destination directory using the hashed names. -/// Serializing / deserializing the "hashed name → original path" mapping as JSON inside the repro archive. -/// +/// Each generator still owns the high-level orchestration (which input categories exist, which subfolders are +/// used, how the response file is re-stitched). This type only owns identical helpers that can be reused. /// internal static class DebugReproPacker { @@ -148,4 +143,76 @@ public static Dictionary ExtractPathMap(ZipArchiveEntry pathMapE // Load the mapping with all the original file paths for the included files return JsonSerializer.Deserialize(stream, GeneratorJsonSerializerContext.Default.DictionaryStringString)!; } + + /// + /// Prepares the staging directory and target archive path for a debug repro save operation. + /// + /// The per-tool error factory used to throw if does not exist. + /// The user-provided directory where the resulting .zip archive will be written. Must already exist. + /// The CLI tool name (e.g. "cswinrtimplgen"), used as the prefix of the staging directory. + /// The file name of the resulting .zip archive (e.g. "impl-debug-repro.zip"). + /// A pair containing the freshly-created staging directory and the absolute path of the target archive. + /// Thrown via if does not exist. + public static (string TempDirectory, string ZipPath) BeginSave( + string debugReproDirectory, + string toolName, + string archiveFileName) + where TError : IGeneratorErrorFactory + { + // The target folder must exist + if (!Directory.Exists(debugReproDirectory)) + { + throw TError.DebugReproDirectoryDoesNotExist(debugReproDirectory); + } + + // Path for the ZIP archive + string zipPath = Path.Combine(debugReproDirectory, archiveFileName); + + // Create a temporary directory to stage files for the ZIP + string tempFolderName = $"{toolName}-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; + string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); + + _ = Directory.CreateDirectory(tempDirectory); + + return (tempDirectory, zipPath); + } + + /// + /// Finalizes a debug repro save by zipping the staging directory into the target archive and deleting the staging directory. + /// + /// The staging directory previously returned by . + /// The absolute path of the target .zip archive, previously returned by . + /// + /// If a file already exists at , it is deleted before the new archive is created. + /// + public static void FinalizeSave(string tempDirectory, string zipPath) + { + // Delete the previous file, if it exists + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } + + // Create the actual .zip file in the target directory + ZipFile.CreateFromDirectory(tempDirectory, zipPath); + + // Clean up the temporary directory + Directory.Delete(tempDirectory, recursive: true); + } + + /// + /// Creates a freshly-named temporary directory for unpacking a debug repro .zip archive. + /// + /// The CLI tool name (e.g. "cswinrtimplgen"), used as the prefix of the directory. + /// The absolute path of the created directory. + public static string CreateUnpackTempDirectory(string toolName) + { + // Create a temporary directory to extract the files from the debug repro + string tempFolderName = $"{toolName}-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; + string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); + + _ = Directory.CreateDirectory(tempDirectory); + + return tempDirectory; + } } diff --git a/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs b/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs index 21f31ef861..8906ab8c63 100644 --- a/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs +++ b/src/WinRT.Generator.Core/Errors/IGeneratorErrorFactory.cs @@ -9,18 +9,10 @@ namespace WindowsRuntime.Generator.Errors; /// Routes shared logical errors through the per-tool well-known exception factory. /// /// -/// /// Shared infrastructure (response-file parsing, debug-repro packing, etc.) is generic over an -/// implementation of this interface and reaches the per-tool factory through its static abstract -/// members. This preserves per-tool exception identity exactly: each factory continues to assign its -/// own numeric error IDs, format its own messages (including embedded tool names), and construct -/// its own concrete subtype. -/// -/// -/// Implementations must be sealed (not static) so they can participate in the -/// static abstract interface contract. The implementing type is not meant to be instantiated; -/// it is used as a type parameter to dispatch the factory call. -/// +/// implementation of this interface to preserve per-tool exception identity exactly: each factory +/// continues to assign its own numeric error IDs, format its own messages (including embedded tool names), +/// and construct its own concrete subtype. /// internal interface IGeneratorErrorFactory { diff --git a/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs index 929afaccc9..3b51d8ad6b 100644 --- a/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs +++ b/src/WinRT.Generator.Core/Errors/UnhandledGeneratorException.cs @@ -10,7 +10,7 @@ namespace WindowsRuntime.Generator.Errors; /// /// /// Each per-tool unhandled exception inherits from this type and provides its -/// and so that the standardized +/// and so that the standardized /// message remains tool-specific. /// /// The phase that failed. @@ -24,16 +24,15 @@ internal abstract class UnhandledGeneratorException(string phase, Exception exce protected abstract string ErrorPrefix { get; } /// - /// Gets the description of the generator used in the standard message - /// (e.g. "impl generator", "interop generator", "WinMD generator"). + /// Gets the name of the generator used in the standard message. /// - protected abstract string GeneratorDescription { get; } + protected abstract string GeneratorName { get; } /// public override string ToString() { return - $"""error {ErrorPrefix}9999: The CsWinRT {GeneratorDescription} failed with an unhandled exception """ + + $"""error {ErrorPrefix}9999: The CsWinRT {GeneratorName} generator failed with an unhandled exception """ + $"""('{InnerException!.GetType().Name}': '{InnerException!.Message}') during the '{phase}' phase. This might be due to an invalid """ + $"""configuration in the current project, but the generator should still correctly identify that and fail gracefully. Please open an """ + $"""issue at https://github.com/microsoft/CsWinRT and provide a minimal repro, if possible."""; diff --git a/src/WinRT.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/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.Generator.Core/Extensions/PathExtensions.cs b/src/WinRT.Generator.Core/Extensions/PathExtensions.cs index b4afd3b725..fcb37d6130 100644 --- a/src/WinRT.Generator.Core/Extensions/PathExtensions.cs +++ b/src/WinRT.Generator.Core/Extensions/PathExtensions.cs @@ -36,21 +36,6 @@ internal static class PathExtensions return path?.Replace('\\', '/'); } - /// - public static ReadOnlySpan Normalize(ReadOnlySpan path) - { - if (OperatingSystem.IsWindows()) - { - return path; - } - - char[] buffer = new char[path.Length]; - - path.Replace(buffer, '\\', '/'); - - return buffer; - } - /// /// Checks whether a given path represents a file or folder contained within a folder with a given name. /// diff --git a/src/WinRT.WinMD.Generator/Extensions/RuntimeContextExtensions.cs b/src/WinRT.Generator.Core/Extensions/RuntimeContextExtensions.cs similarity index 98% rename from src/WinRT.WinMD.Generator/Extensions/RuntimeContextExtensions.cs rename to src/WinRT.Generator.Core/Extensions/RuntimeContextExtensions.cs index 8794f0c543..7123d56211 100644 --- a/src/WinRT.WinMD.Generator/Extensions/RuntimeContextExtensions.cs +++ b/src/WinRT.Generator.Core/Extensions/RuntimeContextExtensions.cs @@ -7,7 +7,7 @@ #pragma warning disable IDE0046 -namespace WindowsRuntime.WinMDGenerator; +namespace WindowsRuntime.Generator.Extensions; /// /// Extensions for the type. diff --git a/src/WinRT.Generator.Core/GeneratorHost.cs b/src/WinRT.Generator.Core/GeneratorHost.cs index ba60c250f7..27df49e2d1 100644 --- a/src/WinRT.Generator.Core/GeneratorHost.cs +++ b/src/WinRT.Generator.Core/GeneratorHost.cs @@ -11,21 +11,11 @@ namespace WindowsRuntime.Generator; /// /// Shared Run entry-point scaffold for the CsWinRT CLI generators. /// -/// -/// Each generator's Run method historically opened with an identical preamble: -/// -/// If the input file path looks like a .zip, unpack a debug repro and re-route to the extracted .rsp. -/// Parse the response file into a per-tool args record. -/// If DebugReproDirectory is set and we are not already replaying, save a debug repro of the current invocation. -/// -/// encapsulates that preamble. Each generator's Run now starts with a -/// single call to it; the per-tool unpack / save / parse logic is supplied via delegates so behavior -/// stays identical (same log messages, same exception phases, same per-tool unhandled exception type). -/// internal static class GeneratorHost { /// - /// Runs the shared unpack → parse → save preamble for a CsWinRT CLI generator. + /// Runs the shared unpack, parse, save preamble for a CsWinRT CLI generator and returns a + /// ready to drive the remaining per-tool phases. /// /// The per-tool args record (must implement ). /// The input file path (response file or debug-repro .zip). @@ -36,8 +26,11 @@ internal static class GeneratorHost /// Wraps an unexpected exception into the per-tool Unhandled*Exception with the given phase name. /// Logs a progress message to the user (typically ConsoleApp.Log from ConsoleAppFramework). /// The token for the operation. - /// The parsed instance. - public static TArgs Prepare( + /// + /// A pre-bound to the parsed args, + /// and for use by subsequent phases. + /// + public static GeneratorPhaseRunner CreateRunner( string inputFilePath, string toolName, Func unpackDebugRepro, @@ -109,7 +102,6 @@ public static TArgs Prepare( args.Token.ThrowIfCancellationRequested(); - return args; + 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.Interop.Generator/Helpers/MvidGenerator.cs b/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs similarity index 57% rename from src/WinRT.Interop.Generator/Helpers/MvidGenerator.cs rename to src/WinRT.Generator.Core/Helpers/MvidGenerator.cs index d738973660..200483f064 100644 --- a/src/WinRT.Interop.Generator/Helpers/MvidGenerator.cs +++ b/src/WinRT.Generator.Core/Helpers/MvidGenerator.cs @@ -6,14 +6,38 @@ using System.IO; using System.Linq; using System.Security.Cryptography; +using WindowsRuntime.Generator.Extensions; -namespace WindowsRuntime.InteropGenerator.Helpers; +namespace WindowsRuntime.Generator.Helpers; /// /// A generator for MVIDs for .NET modules. /// internal static class MvidGenerator { + /// + /// Generates a deterministic MVID based on two input IIDs. + /// + /// The first IID to combine. + /// The second IID to combine. + /// The resulting MVID. + public static Guid CreateMvid(Guid left, Guid right) + { + Span input = stackalloc byte[32]; + + // Write the two IIDs in sequence + _ = left.TryWriteBytes(input, bigEndian: true, out _); + _ = right.TryWriteBytes(input[16..], bigEndian: true, out _); + + Span hash = stackalloc byte[SHA1.HashSizeInBytes]; + + // Hash the two IIDs together (the order matters) + _ = SHA1.HashData(input, hash); + + // Create the final MVID from the first 16 bytes of the hash + return new(hash[..16]); + } + /// /// Generates a deterministic MVID based on a set of input assemblies. /// diff --git a/src/WinRT.Generator.Core/IGeneratorArgs.cs b/src/WinRT.Generator.Core/IGeneratorArgs.cs index 34c2a1be87..f6bf0ed5c8 100644 --- a/src/WinRT.Generator.Core/IGeneratorArgs.cs +++ b/src/WinRT.Generator.Core/IGeneratorArgs.cs @@ -6,14 +6,12 @@ namespace WindowsRuntime.Generator; /// -/// Common surface implemented by every per-tool args record (e.g. ImplGeneratorArgs, -/// InteropGeneratorArgs) so the shared entry-point scaffold -/// can dispatch through a couple of well-known properties. +/// Common surface implemented by every per-tool arguments record. /// internal interface IGeneratorArgs { /// - /// Gets the token for the operation. + /// Gets the cancellation token for the generator invocation. /// CancellationToken Token { get; } diff --git a/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs b/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs index 00e2699e39..fa90f69710 100644 --- a/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs +++ b/src/WinRT.Generator.Core/Parsing/ResponseFileBuilder.cs @@ -8,51 +8,22 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; -using System.Threading; using WindowsRuntime.Generator.Attributes; namespace WindowsRuntime.Generator.Parsing; /// -/// Formats an args record into a response file by reflecting over its -/// []-annotated public properties. +/// Helper to format argument objects into response files. /// -/// -/// -/// Properties are emitted in declaration order (the order returned by ), -/// one name]]>value per line. The output round-trips cleanly through -/// . -/// -/// -/// Per-property handling: -/// -/// -typed properties are skipped (they have no CLI surface). -/// Properties without are skipped. -/// values are skipped (they round-trip as "missing"). -/// Empty [] values are skipped (they round-trip as "missing" -/// to the optional-array default, matching the previous per-tool emit behavior). -/// values that are on properties without -/// are skipped (they -/// round-trip to the optional-bool default, matching the previous per-tool emit behavior). -/// All other values are formatted using : -/// strings emit as-is, arrays emit as comma-joined, primitives use . -/// -/// -/// internal static class ResponseFileBuilder { - /// The required dynamic-access annotation for the TArgs type parameter. - private const DynamicallyAccessedMemberTypes ArgsAccessKinds = - DynamicallyAccessedMemberTypes.PublicProperties; - /// - /// Formats as a response file string suitable for - /// . + /// Formats as a response file string. /// /// The strongly-typed args record. /// The args instance to format. /// The formatted response file text. - public static string Format<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs>(TArgs args) + public static string Format<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TArgs>(TArgs args) where TArgs : class { StringBuilder builder = new(); @@ -83,8 +54,8 @@ internal static class ResponseFileBuilder } // Optional booleans default to 'false' on parse, so emitting "name false" would be - // redundant. Required booleans always emit (both 'true' and 'false') so the parser sees - // them and doesn't reject them as missing-required. + // redundant. Required booleans always emit (both 'true' and 'false') so the parser + // sees them and doesn't reject them as missing-required. bool isRequired = property.GetCustomAttribute() is not null; if (value is false && !isRequired) @@ -112,13 +83,14 @@ private static string FormatValue(object value) return s; } + // String values are formatted as comma-separated lists, without brackets or spaces if (value is string[] array) { return string.Join(',', array); } - // All other primitive values use invariant culture, matching the parser's invariant - // 'Convert.ChangeType' so the round-trip is deterministic. + // All other primitive values use invariant culture, matching the parser's + // invariant 'Convert.ChangeType' so the round-trip is deterministic. return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; } } diff --git a/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs b/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs index f635d005de..25a2bc7a6b 100644 --- a/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs +++ b/src/WinRT.Generator.Core/Parsing/ResponseFileParser.cs @@ -16,52 +16,25 @@ namespace WindowsRuntime.Generator.Parsing; /// -/// Parses a response file into a strongly-typed args record by reflecting over its -/// []-annotated public properties. +/// Helper to parse response files into argument objects. /// -/// -/// -/// The shape of the response file is one name]]>value per line, with optional -/// blank lines (the MSBuild ToolTask may emit them). The first token of each non-blank line -/// is the CLI flag name; everything after the first space (right-trimmed) is the value. -/// -/// -/// Per-property handling: -/// -/// -typed properties are set from the parser's token -/// parameter (they have no ). -/// Properties without are skipped. -/// Required properties (those with ) throw -/// -/// if the value is missing or fails to parse. -/// Optional properties default to the if -/// is present, otherwise to default(T) (with -/// [] defaulting to an empty array). -/// Value coercion is handled by -/// for primitives, with ; arrays use -/// with comma separator. -/// -/// -/// internal static class ResponseFileParser { - /// The required dynamic-access annotation for the TArgs type parameter. - private const DynamicallyAccessedMemberTypes ArgsAccessKinds = - DynamicallyAccessedMemberTypes.PublicProperties; - /// /// Parses an instance of from a response file at the given path. /// /// - /// The path may be prefixed with @ (matching MSBuild's default escaping for ToolTask - /// response files), which is stripped before reading the file. + /// The path may be prefixed with @ (matching MSBuild's default escaping + /// for ToolTask response files), which is stripped before reading the file. /// /// The strongly-typed args record. Must have a public parameterless constructor surface (only public properties are inspected via reflection). /// The per-tool well-known exception factory used to route parsing errors. /// The path to the response file (optionally prefixed with @). /// The cancellation token for the operation. /// The populated instance. - public static TArgs Parse<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(string path, CancellationToken token) + public static TArgs Parse<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] TArgs, TErr>( + string path, + CancellationToken token) where TArgs : class where TErr : IGeneratorErrorFactory { @@ -84,7 +57,9 @@ internal static class ResponseFileParser throw TErr.ResponseFileReadError(e); } - return ParseLines(lines, token); + Dictionary argsMap = BuildArgsMap(lines); + + return Populate(argsMap, token); } /// @@ -95,25 +70,14 @@ internal static class ResponseFileParser /// The stream containing the response file content. /// The cancellation token for the operation. /// The populated instance. - public static TArgs Parse<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(Stream stream, CancellationToken token) + public static TArgs Parse<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] TArgs, TErr>( + Stream stream, + CancellationToken token) where TArgs : class where TErr : IGeneratorErrorFactory { - return ParseLines(File.ReadAllLines(stream), token); - } + string[] lines = File.ReadAllLines(stream); - /// - /// Parses an instance of from the pre-split lines of a response file. - /// - /// The strongly-typed args record. - /// The per-tool well-known exception factory used to route parsing errors. - /// The lines read from the response file. - /// The cancellation token for the operation. - /// The populated instance. - public static TArgs ParseLines<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(string[] lines, CancellationToken token) - where TArgs : class - where TErr : IGeneratorErrorFactory - { Dictionary argsMap = BuildArgsMap(lines); return Populate(argsMap, token); @@ -164,20 +128,16 @@ private static Dictionary BuildArgsMap(string[] lines) } /// - /// Constructs via - /// (to bypass enforcement at construction time) and then - /// populates each public property by reflecting on its CLI attribute, nullability, default value, and type. + /// Populates an arguments object with the provided parsed values. /// /// The strongly-typed args record. /// The per-tool well-known exception factory used to route parsing errors. /// The pre-built name-to-value map. /// The cancellation token, assigned to any -typed property on . /// The populated instance. - [UnconditionalSuppressMessage( - "Trimming", - "IL2087:'type' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method.", - Justification = "GetUninitializedObject does not invoke any constructor; it allocates an instance of TArgs without running any user code, so the PublicConstructors/NonPublicConstructors annotation is not required.")] - private static TArgs Populate<[DynamicallyAccessedMembers(ArgsAccessKinds)] TArgs, TErr>(Dictionary argsMap, CancellationToken token) + private static TArgs Populate<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.AllConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] TArgs, TErr>( + Dictionary argsMap, + CancellationToken token) where TArgs : class where TErr : IGeneratorErrorFactory { @@ -186,8 +146,6 @@ private static Dictionary BuildArgsMap(string[] lines) // response file values, with explicit defaults applied for non-required properties. TArgs instance = (TArgs)RuntimeHelpers.GetUninitializedObject(typeof(TArgs)); - NullabilityInfoContext nullabilityContext = new(); - foreach (PropertyInfo property in typeof(TArgs).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { Type propertyType = property.PropertyType; @@ -214,7 +172,7 @@ private static Dictionary BuildArgsMap(string[] lines) if (!hasValue) { - ApplyDefault(instance, property, propertyType, isRequired, nullabilityContext); + ApplyDefault(instance, property, propertyType, isRequired); continue; } @@ -233,7 +191,7 @@ private static Dictionary BuildArgsMap(string[] lines) } else { - ApplyDefault(instance, property, propertyType, isRequired: false, nullabilityContext); + ApplyDefault(instance, property, propertyType, isRequired: false); } } @@ -248,13 +206,11 @@ private static Dictionary BuildArgsMap(string[] lines) /// The property being set. /// The property's type. /// Whether the property has . - /// The shared for nullable-reference inspection. private static void ApplyDefault( object instance, PropertyInfo property, Type propertyType, - bool isRequired, - NullabilityInfoContext nullabilityContext) + bool isRequired) where TErr : IGeneratorErrorFactory { if (isRequired) @@ -263,7 +219,7 @@ private static void ApplyDefault( } // '[DefaultValue("…")]' takes precedence: it lets per-tool args express initializer-style - // defaults that 'GetUninitializedObject' would otherwise skip. + // defaults that 'GetUninitializedObject' would otherwise skip (initializers aren't needed). DefaultValueAttribute? defaultValueAttribute = property.GetCustomAttribute(); if (defaultValueAttribute is not null) @@ -280,18 +236,6 @@ private static void ApplyDefault( return; } - - // Nullable reference types default to null (matching 'GetNullableStringArgument'). For value - // types and non-nullable reference types we leave 'default(T)' from 'GetUninitializedObject'. - if (!propertyType.IsValueType) - { - NullabilityInfo nullability = nullabilityContext.Create(property); - - if (nullability.ReadState == NullabilityState.Nullable) - { - property.SetValue(instance, null); - } - } } /// diff --git a/src/WinRT.Generator.Core/Properties/AssemblyInfo.cs b/src/WinRT.Generator.Core/Properties/AssemblyInfo.cs deleted file mode 100644 index 41f427dd1d..0000000000 --- a/src/WinRT.Generator.Core/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("cswinrtimplgen")] -[assembly: InternalsVisibleTo("cswinrtinteropgen")] -[assembly: InternalsVisibleTo("cswinrtprojectiongen")] -[assembly: InternalsVisibleTo("cswinrtprojectionrefgen")] -[assembly: InternalsVisibleTo("cswinrtwinmdgen")] diff --git a/src/WinRT.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 index 55ac42ef9a..5e0d083a0e 100644 --- a/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj +++ b/src/WinRT.Generator.Core/WinRT.Generator.Core.csproj @@ -29,4 +29,16 @@ 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 4d1f987fba..af5926b763 100644 --- a/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs +++ b/src/WinRT.Impl.Generator/Errors/UnhandledImplException.cs @@ -17,5 +17,5 @@ internal sealed class UnhandledImplException(string phase, Exception exception) protected override string ErrorPrefix => WellKnownImplExceptions.ErrorPrefix; /// - protected override string GeneratorDescription => "impl generator"; + protected override string GeneratorName => "impl"; } diff --git a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs index 960ee08993..ab965be045 100644 --- a/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs +++ b/src/WinRT.Impl.Generator/Errors/WellKnownImplExceptions.cs @@ -23,28 +23,22 @@ private WellKnownImplExceptions() { } - /// - /// Some exception was thrown when trying to read the response file. - /// + /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, "Failed to read the response file to run 'cswinrtimplgen'.", exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, exception); } - /// - /// Failed to parse an argument from the response file. - /// + /// public static Exception ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) { - return Exception(2, $"Failed to parse argument '{argumentName}' from response file.", exception); + return Exception(2, WellKnownGeneratorMessages.ResponseFileArgumentParsingError(argumentName), exception); } - /// - /// The input response file is malformed. - /// + /// public static Exception MalformedResponseFile() { - return Exception(3, "The response file is malformed and contains invalid content."); + return Exception(3, WellKnownGeneratorMessages.MalformedResponseFile); } /// @@ -103,28 +97,22 @@ public static Exception SignDllError(Exception exception) return Exception(10, "Failed to sign the impl .dll on disk.", exception); } - /// - /// The debug repro directory does not exist. - /// + /// public static Exception DebugReproDirectoryDoesNotExist(string path) { - return Exception(11, $"The debug repro directory '{path}' does not exist."); + return Exception(11, WellKnownGeneratorMessages.DebugReproDirectoryDoesNotExist(path)); } - /// - /// The debug repro contains a file entry that has no mapping. - /// + /// public static Exception DebugReproMissingFileEntryMapping(string path) { - return Exception(12, $"The debug repro file entry with path '{path}' is missing its assembly path mapping."); + return Exception(12, WellKnownGeneratorMessages.DebugReproMissingFileEntryMapping(path)); } - /// - /// The debug repro contains a file entry that was not recognized. - /// + /// public static Exception DebugReproUnrecognizedFileEntry(string path) { - return Exception(13, $"The debug repro file entry with path '{path}' was not recognized."); + return Exception(13, WellKnownGeneratorMessages.DebugReproUnrecognizedFileEntry(path)); } /// diff --git a/src/WinRT.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 7027c9802d..dea139fcf1 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.DebugRepro.cs @@ -33,11 +33,7 @@ internal static partial class ImplGenerator /// The path to the resulting response file to use. private static string UnpackDebugRepro(string path, CancellationToken token) { - // Create a temporary directory to extract the files from the debug repro - string tempFolderName = $"cswinrtimplgen-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); - - _ = Directory.CreateDirectory(tempDirectory); + string tempDirectory = DebugReproPacker.CreateUnpackTempDirectory("cswinrtimplgen"); token.ThrowIfCancellationRequested(); @@ -148,21 +144,13 @@ private static void SaveDebugRepro(ImplGeneratorArgs args) return; } - // The target folder must exist - if (!Directory.Exists(args.DebugReproDirectory)) - { - throw WellKnownImplExceptions.DebugReproDirectoryDoesNotExist(args.DebugReproDirectory); - } - - // Path for the ZIP archive - string zipPath = Path.Combine(args.DebugReproDirectory, "impl-debug-repro.zip"); + (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave( + args.DebugReproDirectory, + toolName: "cswinrtimplgen", + archiveFileName: "impl-debug-repro.zip"); - // Create a temporary directory to stage files for the ZIP - string tempFolderName = $"cswinrtimplgen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); string referenceDirectory = Path.Combine(tempDirectory, "reference"); - _ = Directory.CreateDirectory(tempDirectory); _ = Directory.CreateDirectory(referenceDirectory); // Map with all the original paths @@ -203,16 +191,6 @@ private static void SaveDebugRepro(ImplGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); - // Delete the previous file, if it exists - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - // Create the actual .zip file in the target directory - ZipFile.CreateFromDirectory(tempDirectory, zipPath); - - // Clean up the temporary directory - Directory.Delete(tempDirectory, recursive: true); + DebugReproPacker.FinalizeSave(tempDirectory, zipPath); } } diff --git a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs index a6e2e389a7..331c19a05d 100644 --- a/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs +++ b/src/WinRT.Impl.Generator/Generation/ImplGenerator.cs @@ -19,9 +19,11 @@ 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; namespace WindowsRuntime.ImplGenerator.Generation; @@ -68,7 +70,7 @@ internal static partial class ImplGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - ImplGeneratorArgs args = GeneratorHost.Prepare( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtimplgen", unpackDebugRepro: UnpackDebugRepro, @@ -78,82 +80,43 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) log: ConsoleApp.Log, token: token); - RuntimeContext runtimeContext; - ModuleDefinition outputModule; - // Initialize the assembly resolver and load the output module - try - { - LoadOutputModule(args, out runtimeContext, out outputModule); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledImplException("loading", e); - } - - 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; @@ -178,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) { @@ -276,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 }; @@ -284,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 }; @@ -292,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/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.Interop.Generator/Errors/UnhandledInteropException.cs b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs index c4b0ee30bf..972d473b14 100644 --- a/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs +++ b/src/WinRT.Interop.Generator/Errors/UnhandledInteropException.cs @@ -17,5 +17,5 @@ internal sealed class UnhandledInteropException(string phase, Exception exceptio protected override string ErrorPrefix => WellKnownInteropExceptions.ErrorPrefix; /// - protected override string GeneratorDescription => "interop generator"; + protected override string GeneratorName => "interop"; } diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs index 3f9e6c721f..6df46e720a 100644 --- a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs +++ b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs @@ -284,36 +284,28 @@ public static WellKnownInteropException IDictionary2TypeCodeGenerationError(Type return Exception(27, $"Failed to generate marshalling code for 'IDictionary' type '{dictionaryType}'.", exception); } - /// - /// Some exception was thrown when trying to read the response file. - /// + /// public static WellKnownInteropException ResponseFileReadError(Exception exception) { - return Exception(28, "Failed to read the response file to run 'cswinrtinteropgen'.", exception); + return Exception(28, WellKnownGeneratorMessages.ResponseFileReadError, exception); } - /// - /// Failed to parse an argument from the response file. - /// + /// public static WellKnownInteropException ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) { - return Exception(29, $"Failed to parse argument '{argumentName}' from response file.", exception); + return Exception(29, WellKnownGeneratorMessages.ResponseFileArgumentParsingError(argumentName), exception); } - /// - /// The input response file is malformed. - /// + /// public static WellKnownInteropException MalformedResponseFile() { - return Exception(30, "The response file is malformed and contains invalid content."); + return Exception(30, WellKnownGeneratorMessages.MalformedResponseFile); } - /// - /// The debug repro directory does not exist. - /// + /// public static WellKnownInteropException DebugReproDirectoryDoesNotExist(string path) { - return Exception(31, $"The debug repro directory '{path}' does not exist."); + return Exception(31, WellKnownGeneratorMessages.DebugReproDirectoryDoesNotExist(path)); } /// @@ -788,20 +780,16 @@ public static WellKnownInteropException ReservedDllOriginalPathMismatchFromDebug return Exception(88, $"The reserved '{dllName}' assembly has a mismatching path with the item supplied via '$(ReferencePath)': the debug repro cannot be generated."); } - /// - /// The debug repro contains a file entry that has no mapping. - /// + /// public static WellKnownInteropException DebugReproMissingFileEntryMapping(string path) { - return Exception(89, $"The debug repro file entry with path '{path}' is missing its assembly path mapping."); + return Exception(89, WellKnownGeneratorMessages.DebugReproMissingFileEntryMapping(path)); } - /// - /// The debug repro contains a file entry that was not recognized. - /// + /// public static WellKnownInteropException DebugReproUnrecognizedFileEntry(string path) { - return Exception(90, $"The debug repro file entry with path '{path}' was not recognized."); + return Exception(90, WellKnownGeneratorMessages.DebugReproUnrecognizedFileEntry(path)); } /// diff --git a/src/WinRT.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 e5309cf44c..f39a75b95b 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs @@ -38,11 +38,7 @@ internal partial class InteropGenerator /// The path to the resulting response file to use. private static string UnpackDebugRepro(string path, CancellationToken token) { - // Create a temporary directory to extract the files from the debug repro - string tempFolderName = $"cswinrtinteropgen-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); - - _ = Directory.CreateDirectory(tempDirectory); + string tempDirectory = DebugReproPacker.CreateUnpackTempDirectory("cswinrtinteropgen"); token.ThrowIfCancellationRequested(); @@ -205,22 +201,14 @@ private static void SaveDebugRepro(InteropGeneratorArgs args) return; } - // The target folder must exist - if (!Directory.Exists(args.DebugReproDirectory)) - { - throw WellKnownInteropExceptions.DebugReproDirectoryDoesNotExist(args.DebugReproDirectory); - } - - // Path for the ZIP archive - string zipPath = Path.Combine(args.DebugReproDirectory, "interop-debug-repro.zip"); + (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave( + args.DebugReproDirectory, + toolName: "cswinrtinteropgen", + archiveFileName: "interop-debug-repro.zip"); - // Create a temporary directory to stage files for the ZIP - string tempFolderName = $"cswinrtinteropgen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); string referenceDirectory = Path.Combine(tempDirectory, "reference"); string implementationDirectory = Path.Combine(tempDirectory, "implementation"); - _ = Directory.CreateDirectory(tempDirectory); _ = Directory.CreateDirectory(referenceDirectory); _ = Directory.CreateDirectory(implementationDirectory); @@ -282,17 +270,7 @@ private static void SaveDebugRepro(InteropGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); - // Delete the previous file, if it exists - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - // Create the actual .zip file in the target directory - ZipFile.CreateFromDirectory(tempDirectory, zipPath); - - // Clean up the temporary directory - Directory.Delete(tempDirectory, recursive: true); + DebugReproPacker.FinalizeSave(tempDirectory, zipPath); } /// diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index 1608a1ed40..42760ba3c2 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -11,6 +11,8 @@ 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; @@ -556,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 24da3772da..012efd323f 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Emit.cs @@ -10,10 +10,10 @@ using AsmResolver.DotNet.Signatures; using WindowsRuntime.Generator; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.Helpers; using WindowsRuntime.InteropGenerator.Builders; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Fixups; -using WindowsRuntime.InteropGenerator.Helpers; using WindowsRuntime.InteropGenerator.Models; using WindowsRuntime.InteropGenerator.References; using WindowsRuntime.InteropGenerator.Rewriters; diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs index 44693e753d..7ec2f9c60c 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.cs @@ -1,12 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.IO; using System.Threading; using ConsoleAppFramework; using WindowsRuntime.Generator; -using WindowsRuntime.Generator.Errors; using WindowsRuntime.Generator.Parsing; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.References; @@ -25,7 +23,7 @@ internal static partial class InteropGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - InteropGeneratorArgs args = GeneratorHost.Prepare( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtinteropgen", unpackDebugRepro: UnpackDebugRepro, @@ -35,38 +33,19 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) log: ConsoleApp.Log, token: token); - InteropGeneratorDiscoveryState discoveryState; + // 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); - // 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); - } + // 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/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.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs b/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs index 75bff109be..da7ea08ff4 100644 --- a/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Generator/Errors/UnhandledProjectionGeneratorException.cs @@ -17,6 +17,6 @@ internal sealed class UnhandledProjectionGeneratorException(string phase, Except protected override string ErrorPrefix => WellKnownProjectionGeneratorExceptions.ErrorPrefix; /// - protected override string GeneratorDescription => "projection generator"; + protected override string GeneratorName => "projection"; } diff --git a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs index 5a6c98cc04..4ac475f319 100644 --- a/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs +++ b/src/WinRT.Projection.Generator/Errors/WellKnownProjectionGeneratorExceptions.cs @@ -26,28 +26,22 @@ private WellKnownProjectionGeneratorExceptions() { } - /// - /// Some exception was thrown when trying to read the response file. - /// + /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, "Failed to read the response file to run 'cswinrtprojectiongen'.", exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, exception); } - /// - /// Failed to parse an argument from the response file. - /// + /// public static Exception ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) { - return Exception(2, $"Failed to parse argument '{argumentName}' from response file.", exception); + return Exception(2, WellKnownGeneratorMessages.ResponseFileArgumentParsingError(argumentName), exception); } - /// - /// The input response file is malformed. - /// + /// public static Exception MalformedResponseFile() { - return Exception(3, "The response file is malformed and contains invalid content."); + return Exception(3, WellKnownGeneratorMessages.MalformedResponseFile); } /// @@ -100,28 +94,22 @@ public static Exception CsWinRTProcessError(int exitCode, Exception exception) return Exception(8, $"The projection writer failed during source generation (exit code {exitCode}).", exception); } - /// - /// The debug repro directory does not exist. - /// + /// public static Exception DebugReproDirectoryDoesNotExist(string path) { - return Exception(9, $"The debug repro directory '{path}' does not exist."); + return Exception(9, WellKnownGeneratorMessages.DebugReproDirectoryDoesNotExist(path)); } - /// - /// The debug repro contains a file entry that has no mapping. - /// + /// public static Exception DebugReproMissingFileEntryMapping(string path) { - return Exception(10, $"The debug repro file entry with path '{path}' is missing its assembly path mapping."); + return Exception(10, WellKnownGeneratorMessages.DebugReproMissingFileEntryMapping(path)); } - /// - /// The debug repro contains a file entry that was not recognized. - /// + /// public static Exception DebugReproUnrecognizedFileEntry(string path) { - return Exception(11, $"The debug repro file entry with path '{path}' was not recognized."); + return Exception(11, WellKnownGeneratorMessages.DebugReproUnrecognizedFileEntry(path)); } /// diff --git a/src/WinRT.Projection.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 22d3816e1a..1e784160d3 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.DebugRepro.cs @@ -60,11 +60,7 @@ internal static partial class ProjectionGenerator /// The path to the resulting response file to use. private static string UnpackDebugRepro(string path, CancellationToken token) { - // Create a temporary directory to extract the files from the debug repro - string tempFolderName = $"cswinrtprojectiongen-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); - - _ = Directory.CreateDirectory(tempDirectory); + string tempDirectory = DebugReproPacker.CreateUnpackTempDirectory("cswinrtprojectiongen"); token.ThrowIfCancellationRequested(); @@ -209,23 +205,15 @@ private static void SaveDebugRepro(ProjectionGeneratorArgs args) return; } - // The target folder must exist - if (!Directory.Exists(args.DebugReproDirectory)) - { - throw WellKnownProjectionGeneratorExceptions.DebugReproDirectoryDoesNotExist(args.DebugReproDirectory); - } - - // Path for the ZIP archive - string zipPath = Path.Combine(args.DebugReproDirectory, "projection-debug-repro.zip"); + (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave( + args.DebugReproDirectory, + toolName: "cswinrtprojectiongen", + archiveFileName: "projection-debug-repro.zip"); - // Create a temporary directory to stage files for the ZIP - string tempFolderName = $"cswinrtprojectiongen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); string referenceDirectory = Path.Combine(tempDirectory, ReferenceSubfolder); string winmdDirectory = Path.Combine(tempDirectory, WinMDSubfolder); string windowsMetadataDirectory = Path.Combine(tempDirectory, WindowsMetadataSubfolder); - _ = Directory.CreateDirectory(tempDirectory); _ = Directory.CreateDirectory(referenceDirectory); _ = Directory.CreateDirectory(winmdDirectory); _ = Directory.CreateDirectory(windowsMetadataDirectory); @@ -301,16 +289,6 @@ private static void SaveDebugRepro(ProjectionGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); - // Delete the previous file, if it exists - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - // Create the actual .zip file in the target directory - ZipFile.CreateFromDirectory(tempDirectory, zipPath); - - // Clean up the temporary directory - Directory.Delete(tempDirectory, recursive: true); + DebugReproPacker.FinalizeSave(tempDirectory, zipPath); } } diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs index 71ad974fbe..506a93f8f8 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Emit.cs @@ -3,13 +3,14 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; +using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; using WindowsRuntime.Generator.Errors; +using WindowsRuntime.Generator.References; using WindowsRuntime.ProjectionGenerator.Errors; namespace WindowsRuntime.ProjectionGenerator.Generation; @@ -17,11 +18,6 @@ namespace WindowsRuntime.ProjectionGenerator.Generation; /// internal partial class ProjectionGenerator { - /// - /// The public key for CsWinRT assemblies, used for delay signing projection DLLs. - /// - private static readonly ImmutableArray CsWinRTPublicKey = [0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xB5, 0xFC, 0x90, 0xE7, 0x02, 0x7F, 0x67, 0x87, 0x1E, 0x77, 0x3A, 0x8F, 0xDE, 0x89, 0x38, 0xC8, 0x1D, 0xD4, 0x02, 0xBA, 0x65, 0xB9, 0x20, 0x1D, 0x60, 0x59, 0x3E, 0x96, 0xC4, 0x92, 0x65, 0x1E, 0x88, 0x9C, 0xC1, 0x3F, 0x14, 0x15, 0xEB, 0xB5, 0x3F, 0xAC, 0x11, 0x31, 0xAE, 0x0B, 0xD3, 0x33, 0xC5, 0xEE, 0x60, 0x21, 0x67, 0x2D, 0x97, 0x18, 0xEA, 0x31, 0xA8, 0xAE, 0xBD, 0x0D, 0xA0, 0x07, 0x2F, 0x25, 0xD8, 0x7D, 0xBA, 0x6F, 0xC9, 0x0F, 0xFD, 0x59, 0x8E, 0xD4, 0xDA, 0x35, 0xE4, 0x4C, 0x39, 0x8C, 0x45, 0x43, 0x07, 0xE8, 0xE3, 0x3B, 0x84, 0x26, 0x14, 0x3D, 0xAE, 0xC9, 0xF5, 0x96, 0x83, 0x6F, 0x97, 0xC8, 0xF7, 0x47, 0x50, 0xE5, 0x97, 0x5C, 0x64, 0xE2, 0x18, 0x9F, 0x45, 0xDE, 0xF4, 0x6B, 0x2A, 0x2B, 0x12, 0x47, 0xAD, 0xC3, 0x65, 0x2B, 0xF5, 0xC3, 0x08, 0x05, 0x5D, 0xA9]; - /// /// Runs the emit logic for the generator. /// @@ -68,7 +64,7 @@ private static void Emit(ProjectionGeneratorArgs args, ProjectionGeneratorProces allowUnsafe: true, optimizationLevel: OptimizationLevel.Release, deterministic: true, - cryptoPublicKey: CsWinRTPublicKey, + cryptoPublicKey: ImmutableCollectionsMarshal.AsImmutableArray(WellKnownPublicKeys.WindowsSdkProjection), delaySign: true, generalDiagnosticOption: ReportDiagnostic.Info)); } diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs index 3b2e71e2f5..58e6fba9ee 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.Generate.cs @@ -67,7 +67,7 @@ private static void GenerateSources(ProjectionGeneratorProcessingState processin // Invoke the projection writer in-process via its public C# API. try { - global::WindowsRuntime.ProjectionWriter.ProjectionWriter.Run(processingState.WriterOptions); + ProjectionWriter.ProjectionWriter.Run(processingState.WriterOptions); } catch (Exception e) when (!e.IsWellKnown) { diff --git a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs index 4844f07463..7bb3a9ffb1 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGenerator.cs @@ -1,12 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.IO; using System.Threading; using ConsoleAppFramework; using WindowsRuntime.Generator; -using WindowsRuntime.Generator.Errors; using WindowsRuntime.Generator.Parsing; using WindowsRuntime.ProjectionGenerator.Errors; @@ -24,7 +22,7 @@ internal static partial class ProjectionGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - ProjectionGeneratorArgs args = GeneratorHost.Prepare( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtprojectiongen", unpackDebugRepro: UnpackDebugRepro, @@ -34,29 +32,18 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) log: ConsoleApp.Log, token: token); - ProjectionGeneratorProcessingState processingState; - - // Process all .winmd references - try - { - // Show the appropriate message to inform users of what this generator is doing, - // based on the input arguments. If we don't have precompiled projections, this - // tool might run up to 3 times during builds, so this helps make things clearer. - ConsoleApp.Log(args switch + // Process all .winmd references. Show the appropriate message to inform users of what this + // generator is doing, based on the input arguments. If we don't have precompiled projections, + // this tool might run up to 3 times during builds, so this helps make things clearer. + ProjectionGeneratorProcessingState processingState = runner.RunPhase( + phaseName: "processing", + logMessage: 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). @@ -66,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/ProjectionGeneratorProcessingState.cs b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs index fd1ede400c..b4c09a58bb 100644 --- a/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs +++ b/src/WinRT.Projection.Generator/Generation/ProjectionGeneratorProcessingState.cs @@ -10,7 +10,7 @@ namespace WindowsRuntime.ProjectionGenerator.Generation; /// /// The path to the folder where sources will be generated. /// The reference assembly paths excluding projection assemblies. -/// The options to pass to . +/// The options to pass to . /// Whether any types were found to project. internal sealed class ProjectionGeneratorProcessingState( string sourcesFolder, @@ -29,7 +29,7 @@ internal sealed class ProjectionGeneratorProcessingState( public string[] ReferencesWithoutProjections { get; } = referencesWithoutProjections; /// - /// Gets the options used to invoke . + /// Gets the options used to invoke . /// public ProjectionWriterOptions WriterOptions { get; } = writerOptions; diff --git a/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs b/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs index 7c46c60ce8..1bf7f1f132 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/UnhandledReferenceProjectionGeneratorException.cs @@ -17,6 +17,6 @@ internal sealed class UnhandledReferenceProjectionGeneratorException(string phas protected override string ErrorPrefix => WellKnownReferenceProjectionGeneratorExceptions.ErrorPrefix; /// - protected override string GeneratorDescription => "reference projection generator"; + protected override string GeneratorName => "reference projection"; } diff --git a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs index b5ae41ee1b..aa3d8014d6 100644 --- a/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs +++ b/src/WinRT.Projection.Ref.Generator/Errors/WellKnownReferenceProjectionGeneratorExceptions.cs @@ -23,28 +23,22 @@ private WellKnownReferenceProjectionGeneratorExceptions() { } - /// - /// Some exception was thrown when trying to read the response file. - /// + /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, "Failed to read the response file to run 'cswinrtprojectionrefgen'.", exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, exception); } - /// - /// Failed to parse an argument from the response file. - /// + /// public static Exception ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) { - return Exception(2, $"Failed to parse argument '{argumentName}' from response file.", exception); + return Exception(2, WellKnownGeneratorMessages.ResponseFileArgumentParsingError(argumentName), exception); } - /// - /// The input response file is malformed. - /// + /// public static Exception MalformedResponseFile() { - return Exception(3, "The response file is malformed and contains invalid content."); + return Exception(3, WellKnownGeneratorMessages.MalformedResponseFile); } /// @@ -63,28 +57,22 @@ public static Exception CsWinRTProcessError(Exception exception) return Exception(5, "The projection writer failed during source generation.", exception); } - /// - /// The debug repro directory does not exist. - /// + /// public static Exception DebugReproDirectoryDoesNotExist(string path) { - return Exception(6, $"The debug repro directory '{path}' does not exist."); + return Exception(6, WellKnownGeneratorMessages.DebugReproDirectoryDoesNotExist(path)); } - /// - /// The debug repro contains a file entry that has no mapping. - /// + /// public static Exception DebugReproMissingFileEntryMapping(string path) { - return Exception(7, $"The debug repro file entry with path '{path}' is missing its assembly path mapping."); + return Exception(7, WellKnownGeneratorMessages.DebugReproMissingFileEntryMapping(path)); } - /// - /// The debug repro contains a file entry that was not recognized. - /// + /// public static Exception DebugReproUnrecognizedFileEntry(string path) { - return Exception(8, $"The debug repro file entry with path '{path}' was not recognized."); + return Exception(8, WellKnownGeneratorMessages.DebugReproUnrecognizedFileEntry(path)); } /// diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs index 1358d044e9..1c08ac14e1 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.DebugRepro.cs @@ -33,11 +33,7 @@ internal static partial class ReferenceProjectionGenerator /// The path to the resulting response file to use. private static string UnpackDebugRepro(string path, CancellationToken token) { - // Create a temporary directory to extract the files from the debug repro - string tempFolderName = $"cswinrtprojectionrefgen-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); - - _ = Directory.CreateDirectory(tempDirectory); + string tempDirectory = DebugReproPacker.CreateUnpackTempDirectory("cswinrtprojectionrefgen"); token.ThrowIfCancellationRequested(); @@ -146,21 +142,13 @@ private static void SaveDebugRepro(ReferenceProjectionGeneratorArgs args) return; } - // The target folder must exist - if (!Directory.Exists(args.DebugReproDirectory)) - { - throw WellKnownReferenceProjectionGeneratorExceptions.DebugReproDirectoryDoesNotExist(args.DebugReproDirectory); - } - - // Path for the ZIP archive - string zipPath = Path.Combine(args.DebugReproDirectory, "ref-projection-debug-repro.zip"); + (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave( + args.DebugReproDirectory, + toolName: "cswinrtprojectionrefgen", + archiveFileName: "ref-projection-debug-repro.zip"); - // Create a temporary directory to stage files for the ZIP - string tempFolderName = $"cswinrtprojectionrefgen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); string inputDirectory = Path.Combine(tempDirectory, "input"); - _ = Directory.CreateDirectory(tempDirectory); _ = Directory.CreateDirectory(inputDirectory); // Expand all input paths (which may be file paths, directories to recursively scan, or @@ -215,16 +203,6 @@ private static void SaveDebugRepro(ReferenceProjectionGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); - // Delete the previous file, if it exists - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - // Create the actual .zip file in the target directory - ZipFile.CreateFromDirectory(tempDirectory, zipPath); - - // Clean up the temporary directory - Directory.Delete(tempDirectory, recursive: true); + DebugReproPacker.FinalizeSave(tempDirectory, zipPath); } } diff --git a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs index 990f39a1ef..a242cbcd8e 100644 --- a/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs +++ b/src/WinRT.Projection.Ref.Generator/Generation/ReferenceProjectionGenerator.cs @@ -29,7 +29,7 @@ internal static partial class ReferenceProjectionGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - ReferenceProjectionGeneratorArgs args = GeneratorHost.Prepare( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtprojectionrefgen", unpackDebugRepro: UnpackDebugRepro, @@ -39,32 +39,25 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) log: ConsoleApp.Log, token: token); - // Validate the target framework. CsWinRT 3.0 requires .NET 10 or later. - if (!string.IsNullOrEmpty(args.TargetFramework) && !args.TargetFramework.StartsWith("net10.0", StringComparison.Ordinal)) + // Validate the target framework. CsWinRT 3.0 requires .NET 10 or later + if (!string.IsNullOrEmpty(runner.Args.TargetFramework) && !runner.Args.TargetFramework.StartsWith("net10.0", StringComparison.Ordinal)) { - throw WellKnownReferenceProjectionGeneratorExceptions.UnsupportedTargetFramework(args.TargetFramework); + throw WellKnownReferenceProjectionGeneratorExceptions.UnsupportedTargetFramework(runner.Args.TargetFramework); } // Build the writer options from the parsed arguments - ProjectionWriterOptions options; + ProjectionWriterOptions options = runner.RunPhase( + phaseName: "processing", + body: BuildWriterOptions); - try - { - options = BuildWriterOptions(args); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledReferenceProjectionGeneratorException("processing", e); - } - - args.Token.ThrowIfCancellationRequested(); - - // Invoke the projection writer (in-process) to generate the projection sources + // Invoke the projection writer (in-process) to generate the projection sources. We can't + // route this through the shared 'runner.RunPhase' helper because we wrap the exception + // into a well-known 'CsWinRTProcessError' rather than the per-tool 'Unhandled' factory. try { ConsoleApp.Log($"Generating reference projection sources -> {options.OutputFolder}"); - global::WindowsRuntime.ProjectionWriter.ProjectionWriter.Run(options); + ProjectionWriter.ProjectionWriter.Run(options); } catch (Exception e) when (!e.IsWellKnown) { diff --git a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs index 06a163f49d..b86a3c1360 100644 --- a/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs +++ b/src/WinRT.Runtime2/Properties/WindowsRuntimeExceptionExtensions.cs @@ -1106,7 +1106,7 @@ public static ArgumentException GetInvalidSeekOriginException(string? paramName) extension(UnauthorizedAccessException) { /// - /// Throws an indicating that the internal buffer of a cannot be accessed. + /// Throws an indicating that the internal buffer of a cannot be accessed. /// /// Always thrown. [DoesNotReturn] diff --git a/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs b/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs index 7faae43b4c..9002ee8c21 100644 --- a/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs +++ b/src/WinRT.WinMD.Generator/Errors/UnhandledWinMDException.cs @@ -17,5 +17,5 @@ internal sealed class UnhandledWinMDException(string phase, Exception exception) protected override string ErrorPrefix => WellKnownWinMDExceptions.ErrorPrefix; /// - protected override string GeneratorDescription => "WinMD generator"; + protected override string GeneratorName => "WinMD"; } diff --git a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs index 58a0678f00..d958c0c7a9 100644 --- a/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs +++ b/src/WinRT.WinMD.Generator/Errors/WellKnownWinMDExceptions.cs @@ -23,28 +23,22 @@ private WellKnownWinMDExceptions() { } - /// - /// Some exception was thrown when trying to read the response file. - /// + /// public static Exception ResponseFileReadError(Exception exception) { - return Exception(1, "Failed to read the response file to run 'cswinrtwinmdgen'.", exception); + return Exception(1, WellKnownGeneratorMessages.ResponseFileReadError, exception); } - /// - /// The input response file is malformed. - /// + /// public static Exception MalformedResponseFile() { - return Exception(2, "The response file is malformed and contains invalid content."); + return Exception(2, WellKnownGeneratorMessages.MalformedResponseFile); } - /// - /// Failed to parse an argument from the response file. - /// + /// public static Exception ResponseFileArgumentParsingError(string argumentName, Exception? exception = null) { - return Exception(3, $"Failed to parse argument '{argumentName}' from response file.", exception); + return Exception(3, WellKnownGeneratorMessages.ResponseFileArgumentParsingError(argumentName), exception); } /// @@ -79,28 +73,22 @@ public static Exception InputAssemblyRuntimeVersionNotFound(string path) return Exception(7, $"Failed to probe the .NET runtime version from the input assembly '{path}'."); } - /// - /// The debug repro directory does not exist. - /// + /// public static Exception DebugReproDirectoryDoesNotExist(string path) { - return Exception(8, $"The debug repro directory '{path}' does not exist."); + return Exception(8, WellKnownGeneratorMessages.DebugReproDirectoryDoesNotExist(path)); } - /// - /// The debug repro contains a file entry that has no mapping. - /// + /// public static Exception DebugReproMissingFileEntryMapping(string path) { - return Exception(9, $"The debug repro file entry with path '{path}' is missing its assembly path mapping."); + return Exception(9, WellKnownGeneratorMessages.DebugReproMissingFileEntryMapping(path)); } - /// - /// The debug repro contains a file entry that was not recognized. - /// + /// public static Exception DebugReproUnrecognizedFileEntry(string path) { - return Exception(10, $"The debug repro file entry with path '{path}' was not recognized."); + return Exception(10, WellKnownGeneratorMessages.DebugReproUnrecognizedFileEntry(path)); } /// diff --git a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs index 49834c4124..4f2ea5783e 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.DebugRepro.cs @@ -32,11 +32,7 @@ internal static partial class WinMDGenerator /// The path to the resulting response file to use. private static string UnpackDebugRepro(string path, CancellationToken token) { - // Create a temporary directory to extract the files from the debug repro - string tempFolderName = $"cswinrtwinmdgen-debug-repro-unpack-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); - - _ = Directory.CreateDirectory(tempDirectory); + string tempDirectory = DebugReproPacker.CreateUnpackTempDirectory("cswinrtwinmdgen"); token.ThrowIfCancellationRequested(); @@ -159,21 +155,13 @@ private static void SaveDebugRepro(WinMDGeneratorArgs args) return; } - // The target folder must exist - if (!Directory.Exists(args.DebugReproDirectory)) - { - throw WellKnownWinMDExceptions.DebugReproDirectoryDoesNotExist(args.DebugReproDirectory); - } - - // Path for the ZIP archive - string zipPath = Path.Combine(args.DebugReproDirectory, "winmd-debug-repro.zip"); + (string tempDirectory, string zipPath) = DebugReproPacker.BeginSave( + args.DebugReproDirectory, + toolName: "cswinrtwinmdgen", + archiveFileName: "winmd-debug-repro.zip"); - // Create a temporary directory to stage files for the ZIP - string tempFolderName = $"cswinrtwinmdgen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}"; - string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName); string referenceDirectory = Path.Combine(tempDirectory, "reference"); - _ = Directory.CreateDirectory(tempDirectory); _ = Directory.CreateDirectory(referenceDirectory); // Map with all the original paths @@ -214,16 +202,6 @@ private static void SaveDebugRepro(WinMDGeneratorArgs args) args.Token.ThrowIfCancellationRequested(); - // Delete the previous file, if it exists - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - - // Create the actual .zip file in the target directory - ZipFile.CreateFromDirectory(tempDirectory, zipPath); - - // Clean up the temporary directory - Directory.Delete(tempDirectory, recursive: true); + DebugReproPacker.FinalizeSave(tempDirectory, zipPath); } } 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 8b850ce782..1f71951433 100644 --- a/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs +++ b/src/WinRT.WinMD.Generator/Generation/WinMDGenerator.cs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Threading; using ConsoleAppFramework; using WindowsRuntime.Generator; -using WindowsRuntime.Generator.Errors; using WindowsRuntime.Generator.Parsing; using WindowsRuntime.WinMDGenerator.Errors; @@ -39,7 +37,7 @@ internal static partial class WinMDGenerator /// The token for the operation. public static void Run([Argument] string inputFilePath, CancellationToken token) { - WinMDGeneratorArgs args = GeneratorHost.Prepare( + GeneratorPhaseRunner runner = GeneratorHost.CreateRunner( inputFilePath: inputFilePath, toolName: "cswinrtwinmdgen", unpackDebugRepro: UnpackDebugRepro, @@ -50,33 +48,17 @@ public static void Run([Argument] string inputFilePath, CancellationToken token) token: token); // Discover the types to process - WinMDGeneratorDiscoveryState discoveryState; - - try - { - ConsoleApp.Log($"Processing assembly: '{System.IO.Path.GetFileName(args.InputAssemblyPath)}'"); - - discoveryState = Discover(args); - } - catch (Exception e) when (!e.IsWellKnown) - { - throw new UnhandledWinMDException("discovery", e); - } - - 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/References/WellKnownPublicKeyTokens.cs b/src/WinRT.WinMD.Generator/References/WellKnownPublicKeyTokens.cs deleted file mode 100644 index 77897462dc..0000000000 --- a/src/WinRT.WinMD.Generator/References/WellKnownPublicKeyTokens.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WindowsRuntime.WinMDGenerator.References; - -/// -/// Well known public key tokens. -/// -internal static class WellKnownPublicKeyTokens -{ - /// - /// The public key data for mscorlib. - /// - public static readonly byte[] MSCorLib = [0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89]; -} \ No newline at end of file diff --git a/src/WinRT.WinMD.Generator/Writers/WinMDWriter.TypeMapping.cs b/src/WinRT.WinMD.Generator/Writers/WinMDWriter.TypeMapping.cs index 4412429c39..07a492aa10 100644 --- a/src/WinRT.WinMD.Generator/Writers/WinMDWriter.TypeMapping.cs +++ b/src/WinRT.WinMD.Generator/Writers/WinMDWriter.TypeMapping.cs @@ -6,9 +6,9 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using AsmResolver.PE.DotNet.Metadata.Tables; +using WindowsRuntime.Generator.References; using WindowsRuntime.InteropGenerator.References; using WindowsRuntime.WinMDGenerator.Models; -using WindowsRuntime.WinMDGenerator.References; using AssemblyAttributes = AsmResolver.PE.DotNet.Metadata.Tables.AssemblyAttributes; #pragma warning disable IDE0072 diff --git a/src/WinRT.WinMD.Generator/Writers/WinMDWriter.cs b/src/WinRT.WinMD.Generator/Writers/WinMDWriter.cs index bd6ee8f52e..e7af3b363f 100644 --- a/src/WinRT.WinMD.Generator/Writers/WinMDWriter.cs +++ b/src/WinRT.WinMD.Generator/Writers/WinMDWriter.cs @@ -4,11 +4,11 @@ using System; using System.Collections.Generic; using AsmResolver.DotNet; +using WindowsRuntime.Generator.References; using WindowsRuntime.InteropGenerator.References; using WindowsRuntime.WinMDGenerator.Errors; using WindowsRuntime.WinMDGenerator.Helpers; using WindowsRuntime.WinMDGenerator.Models; -using WindowsRuntime.WinMDGenerator.References; using AssemblyAttributes = AsmResolver.PE.DotNet.Metadata.Tables.AssemblyAttributes; #pragma warning disable IDE0046