Skip to content

Commit db15e77

Browse files
Sergio0694Copilot
andauthored
Update debug repro in 'cswinrtinteropgen' to work in end-to-end scenarios (#2306)
* Support separate reference/implementation path maps Split the single original-paths.json into two maps (original-reference-paths.json and original-implementation-paths.json) and update debug repro packing/unpacking to handle reference vs implementation DLLs separately. Added filename constants, created reference/implementation directories, adjusted hashed-file copy calls to populate separate path maps, and updated extraction to use the correct map per DLL. Added helpers CopyPathMapToDirectory and ExtractPathMapToDirectory and a new DebugReproUnrecognizedFileEntry exception to validate unexpected ZIP entries. Renamed variables for clarity and ensured projection/component assembly paths are tracked correctly. * Fix typo * Rename ExtractPathMapToDirectory to ExtractPathMap Rename the helper method ExtractPathMapToDirectory to ExtractPathMap and update its call sites in InteropGenerator.DebugRepro.cs. The two variables that load originalReferenceDllPaths and originalImplementationDllPaths now call ExtractPathMap, clarifying the method name and keeping behavior unchanged. * Iterate dllEntries directly in debug repro Replace the tuple-based enumeration foreach ((int index, ZipArchiveEntry dllEntry) in dllEntries.Index()) with a direct foreach (ZipArchiveEntry dllEntry in dllEntries) in InteropGenerator.DebugRepro.cs. The index was unused, so this simplifies the loop and removes an unnecessary tuple/deconstruction. * Add missing-mapping error for debug repro Introduce a new WellKnownInteropException for debug repro entries that lack an assembly path mapping and throw it when constructing the temporary debug repro. This adds DebugReproMissingFileEntryMapping and updates the InteropGenerator to validate originalDllPaths via TryGetValue before using the mapping. Also increment the error code for DebugReproUnrecognizedFileEntry to avoid collision (now 89) and remove the redundant lookup of originalPath. * Copy WinRT assemblies to implementationDirectory Change CopyHashedFileToDirectory target from tempDirectory to implementationDirectory for the WinRT projection and component assemblies. This ensures the well-known WinRT assemblies are placed in the implementation directory (matching other implementation files) when producing the debug repro, avoiding potential file resolution or placement issues. * Fix typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent bc8706e commit db15e77

2 files changed

Lines changed: 123 additions & 39 deletions

File tree

src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,23 @@ public static WellKnownInteropException ReservedDllOriginalPathMismatch(string d
743743
/// </summary>
744744
public static WellKnownInteropException ReservedDllOriginalPathMismatchFromDebugRepro(string dllName)
745745
{
746-
return Exception(87, $"The reserved '{dllName}' assembly has a mismatching path with the item supplied via '$(ReferencePath)': the debug repro canot be generated.");
746+
return Exception(87, $"The reserved '{dllName}' assembly has a mismatching path with the item supplied via '$(ReferencePath)': the debug repro cannot be generated.");
747+
}
748+
749+
/// <summary>
750+
/// The debug repro contains a file entry that has no mapping.
751+
/// </summary>
752+
public static WellKnownInteropException DebugReproMissingFileEntryMapping(string path)
753+
{
754+
return Exception(88, $"The debug repro file entry with path '{path}' is missing its assembly path mapping.");
755+
}
756+
757+
/// <summary>
758+
/// The debug repro contains a file entry that was not recognized.
759+
/// </summary>
760+
public static WellKnownInteropException DebugReproUnrecognizedFileEntry(string path)
761+
{
762+
return Exception(89, $"The debug repro file entry with path '{path}' was not recognized.");
747763
}
748764

749765
/// <summary>

src/WinRT.Interop.Generator/Generation/InteropGenerator.DebugRepro.cs

Lines changed: 106 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ namespace WindowsRuntime.InteropGenerator.Generation;
2121
/// <inheritdoc cref="InteropGenerator"/>
2222
internal partial class InteropGenerator
2323
{
24+
/// <summary>
25+
/// The file name for the original names of the reference .dll-s.
26+
/// </summary>
27+
private const string ReferencePathMapFileName = "original-reference-paths.json";
28+
29+
/// <summary>
30+
/// The file name for the original names of the implementation .dll-s.
31+
/// </summary>
32+
private const string ImplementationPathMapFileName = "original-implementation-paths.json";
33+
2434
/// <summary>
2535
/// Runs the debug repro unpack logic for the generator.
2636
/// </summary>
@@ -41,7 +51,8 @@ private static string UnpackDebugRepro(string path, CancellationToken token)
4151

4252
// Get all entries of interest
4353
ZipArchiveEntry responseFileEntry = archive.Entries.Single(entry => entry.Name == "cswinrtinteropgen.rsp");
44-
ZipArchiveEntry originalPathsEntry = archive.Entries.Single(entry => entry.Name == "original-paths.json");
54+
ZipArchiveEntry originalReferenceDllPathsEntry = archive.Entries.Single(entry => entry.Name == ReferencePathMapFileName);
55+
ZipArchiveEntry originalImplementationDllPathsEntry = archive.Entries.Single(entry => entry.Name == ImplementationPathMapFileName);
4556
ZipArchiveEntry[] dllEntries = [.. archive.Entries.Where(entry => Path.GetExtension(Path.Normalize(entry.Name)) == ".dll")];
4657

4758
token.ThrowIfCancellationRequested();
@@ -56,36 +67,52 @@ private static string UnpackDebugRepro(string path, CancellationToken token)
5667

5768
token.ThrowIfCancellationRequested();
5869

59-
Dictionary<string, string> originalPaths;
60-
61-
// Load the mapping with all the original file paths for the included .dll-s
62-
using (Stream stream = originalPathsEntry.Open())
63-
{
64-
originalPaths = JsonSerializer.Deserialize(stream, InteropGeneratorJsonSerializerContext.Default.DictionaryStringString)!;
65-
}
70+
// Load the mappings with all the original file paths for both reference and implementation .dll-s
71+
Dictionary<string, string> originalReferenceDllPaths = ExtractPathMap(originalReferenceDllPathsEntry);
72+
Dictionary<string, string> originalImplementationDllPaths = ExtractPathMap(originalImplementationDllPathsEntry);
6673

6774
token.ThrowIfCancellationRequested();
6875

6976
List<string> referencePaths = [];
7077
List<string> implementationPaths = [];
7178
string? outputAssemblyPath = null;
72-
string? winRTProjectionAssemblyHashedName = null;
73-
string? winRTComponentAssemblyHashedName = null;
79+
string? winRTProjectionAssemblyPath = null;
80+
string? winRTComponentAssemblyPath = null;
7481

75-
// Create another subdirectory for all the input assembly paths. We don't put these in the top level
82+
// Define two subdirectories for all the input assembly paths. We don't put these in the top level
7683
// temporary folder so that the number of files there remains very small. The reason is just to
7784
// make inspecting the resulting .dll easier, without having to scroll past hundreds of folders.
78-
string assembliesDirectory = Path.Combine(tempDirectory, "in");
85+
// Also, this makes it possible to directly inspect the two sets of input assembly paths.
86+
string referenceDllDirectory = Path.Combine(tempDirectory, "reference");
87+
string implementationDllDirectory = Path.Combine(tempDirectory, "implementation");
88+
89+
// Create the directories too in advance, so that we can directly extract the .dll-s there
90+
_ = Directory.CreateDirectory(referenceDllDirectory);
91+
_ = Directory.CreateDirectory(implementationDllDirectory);
7992

8093
// Extract all .dll-s, one per directory, so we can ensure there's no name conflicts
81-
foreach ((int index, ZipArchiveEntry dllEntry) in dllEntries.Index())
94+
foreach (ZipArchiveEntry dllEntry in dllEntries)
8295
{
83-
string destinationFolder = Path.Combine(assembliesDirectory, index.ToString("000"));
96+
bool isReferenceDll = Path.IsWithinDirectoryName(dllEntry.FullName, "reference");
97+
bool isImplementationDll = Path.IsWithinDirectoryName(dllEntry.FullName, "implementation");
98+
99+
// Select the right mapping (we have two, as we might have .dll-s with the same name in both sets)
100+
Dictionary<string, string> originalDllPaths = isReferenceDll ? originalReferenceDllPaths : originalImplementationDllPaths;
101+
102+
// Also select the right destination folder (the output .dll will just go directly in the temporary directory)
103+
string destinationFolder = isReferenceDll
104+
? referenceDllDirectory
105+
: (isImplementationDll
106+
? implementationDllDirectory
107+
: tempDirectory);
84108

85-
_ = Directory.CreateDirectory(destinationFolder);
109+
// Make sure the debug repro is well-formed and contains the mapping for this entry
110+
if (!originalDllPaths.TryGetValue(dllEntry.Name, out string? originalPath))
111+
{
112+
throw WellKnownInteropExceptions.DebugReproMissingFileEntryMapping(dllEntry.FullName);
113+
}
86114

87115
// Construct the path in the temporary subfolder with the original .dll name
88-
string originalPath = originalPaths[dllEntry.Name];
89116
string originalName = Path.GetFileName(Path.Normalize(originalPath));
90117
string destinationPath = Path.Combine(destinationFolder, originalName);
91118

@@ -99,23 +126,29 @@ private static string UnpackDebugRepro(string path, CancellationToken token)
99126
{
100127
outputAssemblyPath = destinationPath;
101128
}
102-
else if (Path.IsWithinDirectoryName(dllEntry.FullName, "references"))
129+
else if (isReferenceDll)
103130
{
104131
referencePaths.Add(destinationPath);
105132
}
106-
else
133+
else if (isImplementationDll)
107134
{
108135
implementationPaths.Add(destinationPath);
109136
}
137+
else
138+
{
139+
// We should never hit this case, so throw to validate that the debug repro is valid. Entries
140+
// should always be either reference .dll-s, implementation .dll-s, or the output assembly.
141+
throw WellKnownInteropExceptions.DebugReproUnrecognizedFileEntry(dllEntry.FullName);
142+
}
110143

111144
// Also track the private implementation detail .dll-s (these are also in the set of references)
112145
if (dllEntry.Name == args.WinRTProjectionAssemblyPath)
113146
{
114-
winRTProjectionAssemblyHashedName = destinationPath;
147+
winRTProjectionAssemblyPath = destinationPath;
115148
}
116149
else if (args.WinRTComponentAssemblyPath is not null && dllEntry.Name == args.WinRTComponentAssemblyPath)
117150
{
118-
winRTComponentAssemblyHashedName = destinationPath;
151+
winRTComponentAssemblyPath = destinationPath;
119152
}
120153
}
121154

@@ -127,8 +160,8 @@ private static string UnpackDebugRepro(string path, CancellationToken token)
127160
ReferenceAssemblyPaths = [.. referencePaths],
128161
ImplementationAssemblyPaths = [.. implementationPaths],
129162
OutputAssemblyPath = outputAssemblyPath!,
130-
WinRTProjectionAssemblyPath = winRTProjectionAssemblyHashedName!,
131-
WinRTComponentAssemblyPath = winRTComponentAssemblyHashedName,
163+
WinRTProjectionAssemblyPath = winRTProjectionAssemblyPath!,
164+
WinRTComponentAssemblyPath = winRTComponentAssemblyPath,
132165
GeneratedAssemblyDirectory = tempDirectory,
133166
UseWindowsUIXamlProjections = args.UseWindowsUIXamlProjections,
134167
ValidateWinRTRuntimeAssemblyVersion = args.ValidateWinRTRuntimeAssemblyVersion,
@@ -173,27 +206,28 @@ private static void SaveDebugRepro(InteropGeneratorArgs args)
173206
// Create a temporary directory to stage files for the ZIP
174207
string tempFolderName = $"cswinrtinteropgen-debug-repro-{Guid.NewGuid().ToString().ToUpperInvariant()}";
175208
string tempDirectory = Path.Combine(Path.GetTempPath(), tempFolderName);
176-
string referencesDirectory = Path.Combine(tempDirectory, "references");
209+
string referenceDirectory = Path.Combine(tempDirectory, "reference");
177210
string implementationDirectory = Path.Combine(tempDirectory, "implementation");
178211

179212
_ = Directory.CreateDirectory(tempDirectory);
180-
_ = Directory.CreateDirectory(referencesDirectory);
213+
_ = Directory.CreateDirectory(referenceDirectory);
181214
_ = Directory.CreateDirectory(implementationDirectory);
182215

183-
// Map with all the original paths
184-
Dictionary<string, string> originalPaths = new(args.ReferenceAssemblyPaths.Length + 1);
216+
// Maps with all the original paths
217+
Dictionary<string, string> originalReferenceDllPaths = new(args.ReferenceAssemblyPaths.Length + 1);
218+
Dictionary<string, string> originalImplementationDllPaths = new(args.ImplementationAssemblyPaths.Length + 1);
185219

186220
// Add all reference and implementation paths with hashed names to the respective subdirectories under the
187221
// temporary directory, and store them with the updated names in a list to use to build the .rsp file.
188-
List<string> updatedReferenceDllNames = CopyHashedFilesToDirectory(args.ReferenceAssemblyPaths, referencesDirectory, originalPaths, args.Token);
189-
List<string> updatedImplementationDllNames = CopyHashedFilesToDirectory(args.ImplementationAssemblyPaths, implementationDirectory, originalPaths, args.Token);
222+
List<string> updatedReferenceDllNames = CopyHashedFilesToDirectory(args.ReferenceAssemblyPaths, referenceDirectory, originalReferenceDllPaths, args.Token);
223+
List<string> updatedImplementationDllNames = CopyHashedFilesToDirectory(args.ImplementationAssemblyPaths, implementationDirectory, originalImplementationDllPaths, args.Token);
190224

191225
args.Token.ThrowIfCancellationRequested();
192226

193227
// Hash and copy the well known assemblies we use as input
194-
string outputAssemblyHashedName = CopyHashedFileToDirectory(args.OutputAssemblyPath, tempDirectory, originalPaths, args.Token);
195-
string winRTProjectionAssemblyHashedName = CopyHashedFileToDirectory(args.WinRTProjectionAssemblyPath, tempDirectory, originalPaths, args.Token);
196-
string? winRTComponentAssemblyHashedName = CopyHashedFileToDirectory(args.WinRTComponentAssemblyPath, tempDirectory, originalPaths, args.Token);
228+
string outputAssemblyHashedName = CopyHashedFileToDirectory(args.OutputAssemblyPath, tempDirectory, originalImplementationDllPaths, args.Token);
229+
string winRTProjectionAssemblyHashedName = CopyHashedFileToDirectory(args.WinRTProjectionAssemblyPath, implementationDirectory, originalImplementationDllPaths, args.Token);
230+
string? winRTComponentAssemblyHashedName = CopyHashedFileToDirectory(args.WinRTComponentAssemblyPath, implementationDirectory, originalImplementationDllPaths, args.Token);
197231

198232
args.Token.ThrowIfCancellationRequested();
199233

@@ -223,14 +257,13 @@ private static void SaveDebugRepro(InteropGeneratorArgs args)
223257

224258
args.Token.ThrowIfCancellationRequested();
225259

226-
// Create the .json file with the original paths
227-
string jsonFilePath = Path.Combine(tempDirectory, "original-paths.json");
260+
// Create the .json file with the reference path map
261+
CopyPathMapToDirectory(originalReferenceDllPaths, tempDirectory, ReferencePathMapFileName);
228262

229-
// Serialize the original paths
230-
using (Stream jsonStream = File.Create(jsonFilePath))
231-
{
232-
JsonSerializer.Serialize(jsonStream, originalPaths, InteropGeneratorJsonSerializerContext.Default.DictionaryStringString);
233-
}
263+
args.Token.ThrowIfCancellationRequested();
264+
265+
// Do the same for the implementation path map
266+
CopyPathMapToDirectory(originalImplementationDllPaths, tempDirectory, ImplementationPathMapFileName);
234267

235268
args.Token.ThrowIfCancellationRequested();
236269

@@ -346,4 +379,39 @@ private static List<string> CopyHashedFilesToDirectory(
346379

347380
return hashedName;
348381
}
382+
383+
/// <summary>
384+
/// Copies an input path map to a target directory, as a serialized JSON file.
385+
/// </summary>
386+
/// <param name="pathMap">The input path map.</param>
387+
/// <param name="destinationDirectory">The target directory to copy the assemblies to.</param>
388+
/// <param name="fileName">The name to use for the file with the serialized path map.</param>
389+
private static void CopyPathMapToDirectory(
390+
Dictionary<string, string> pathMap,
391+
string destinationDirectory,
392+
string fileName)
393+
{
394+
// Create the .json file with the input path map
395+
string jsonFilePath = Path.Combine(destinationDirectory, fileName);
396+
397+
using Stream jsonStream = File.Create(jsonFilePath);
398+
399+
// Serialize the path map to the target file
400+
JsonSerializer.Serialize(jsonStream, pathMap, InteropGeneratorJsonSerializerContext.Default.DictionaryStringString);
401+
}
402+
403+
/// <summary>
404+
/// Extracts an input path from a .zip archive entry.
405+
/// </summary>
406+
/// <param name="pathMapEntry">The input path map entry.</param>
407+
/// <remarks>
408+
/// The <paramref name="pathMapEntry"/> value is expected to have the content produced by calls to <see cref="CopyPathMapToDirectory"/>.
409+
/// </remarks>
410+
private static Dictionary<string, string> ExtractPathMap(ZipArchiveEntry pathMapEntry)
411+
{
412+
using Stream stream = pathMapEntry.Open();
413+
414+
// Load the mapping with all the original file paths for the included .dll-s
415+
return JsonSerializer.Deserialize(stream, InteropGeneratorJsonSerializerContext.Default.DictionaryStringString)!;
416+
}
349417
}

0 commit comments

Comments
 (0)