@@ -21,6 +21,16 @@ namespace WindowsRuntime.InteropGenerator.Generation;
2121/// <inheritdoc cref="InteropGenerator"/>
2222internal 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