@joebigelow / wix-1 / commits / 4357d18f

Replace UpdateFileFacade loop with Parallel.ForEach

Martin Broholm Andersen committed May 21, 2024 at 22:35 UTC 4357d18fdc6908eab97886868596af3bd3121f2e
2 files changed +331 -219
src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+7 -2
@@ -6,6 +6,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 + using System.Threading;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12 using WixToolset.Data.WindowsInstaller;
@@ -50,6 +51,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
51 this.PatchSubStorages = patchSubStorages;
52
53 this.BackendExtensions = backendExtension;
54 +
55 + this.CancellationToken = context.CancellationToken;
56 }
57
58 private IServiceProvider ServiceProvider { get; }
@@ -100,6 +103,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
103
104 private string IntermediateFolder { get; }
105
106 + private CancellationToken CancellationToken { get; }
107 +
108 public IBindResult Execute()
109 {
110 if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved))
@@ -275,7 +280,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
280
281 // Gather information about files that do not come from merge modules.
282 {
278 - var command = new UpdateFileFacadesCommand(this.Messaging, this.FileSystem, section, allFileFacades, fileFacadesFromIntermediate, variableCache, overwriteHash: true);
283 + var command = new UpdateFileFacadesCommand(this.Messaging, this.FileSystem, section, allFileFacades, fileFacadesFromIntermediate, variableCache, overwriteHash: true, this.CancellationToken);
284 command.Execute();
285 }
286
@@ -319,7 +324,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
324 {
325 var updatedFacades = reresolvedFiles.Select(f => allFileFacades.First(ff => ff.Id == f.Id?.Id));
326
322 - var command = new UpdateFileFacadesCommand(this.Messaging, this.FileSystem, section, allFileFacades, updatedFacades, variableCache, overwriteHash: false);
327 + var command = new UpdateFileFacadesCommand(this.Messaging, this.FileSystem, section, allFileFacades, updatedFacades, variableCache, overwriteHash: false, this.CancellationToken);
328 command.Execute();
329 }
330 }
src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+324 -217
@@ -3,11 +3,14 @@
3 namespace WixToolset.Core.WindowsInstaller.Bind
4 {
5 using System;
6 + using System.Collections.Concurrent;
7 using System.Collections.Generic;
8 using System.ComponentModel;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 + using System.Threading;
13 + using System.Threading.Tasks;
14 using WixToolset.Core.Native.Msi;
15 using WixToolset.Data;
16 using WixToolset.Data.Symbols;
@@ -19,7 +22,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
22 /// </summary>
23 internal class UpdateFileFacadesCommand
24 {
22 - public UpdateFileFacadesCommand(IMessaging messaging, IFileSystem fileSystem, IntermediateSection section, IEnumerable<IFileFacade> allFileFacades, IEnumerable<IFileFacade> updateFileFacades, IDictionary<string, string> variableCache, bool overwriteHash)
25 + public UpdateFileFacadesCommand(IMessaging messaging, IFileSystem fileSystem, IntermediateSection section, IEnumerable<IFileFacade> allFileFacades, IEnumerable<IFileFacade> updateFileFacades, IDictionary<string, string> variableCache, bool overwriteHash, CancellationToken cancellationToken)
26 {
27 this.Messaging = messaging;
28 this.FileSystem = fileSystem;
@@ -28,6 +31,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
31 this.UpdateFileFacades = updateFileFacades;
32 this.VariableCache = variableCache;
33 this.OverwriteHash = overwriteHash;
34 + this.CancellationToken = cancellationToken;
35 }
36
37 private IMessaging Messaging { get; }
@@ -44,306 +48,409 @@ namespace WixToolset.Core.WindowsInstaller.Bind
48
49 private IDictionary<string, string> VariableCache { get; }
50
47 - public void Execute()
48 - {
49 - var assemblySymbols = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id);
50 - var assemblyNameSymbols = this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id);
51 + private CancellationToken CancellationToken { get; }
52
52 - foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null))
53 - {
54 - this.UpdateFileFacade(file, assemblySymbols, assemblyNameSymbols);
55 - }
56 - }
57 -
58 - private void UpdateFileFacade(IFileFacade facade, Dictionary<string, AssemblySymbol> assemblySymbols, Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols)
53 + public void Execute()
54 {
60 - FileInfo fileInfo = null;
55 try
56 {
63 - fileInfo = new FileInfo(facade.SourcePath);
57 + this.UpdateFileFacadesInParallel(this.UpdateFileFacades.Where(f => f.SourcePath != null));
58 }
65 - catch (ArgumentException)
59 + catch (AggregateException ae)
60 {
67 - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
68 - return;
69 - }
70 - catch (PathTooLongException)
71 - {
72 - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
73 - return;
74 - }
75 - catch (NotSupportedException)
76 - {
77 - this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
78 - return;
79 - }
80 -
81 - if (!fileInfo.Exists)
82 - {
83 - this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath));
84 - return;
85 - }
86 -
87 - using (var fileStream = this.FileSystem.OpenFile(facade.SourceLineNumber, fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
88 - {
89 - if (Int32.MaxValue < fileStream.Length)
61 + foreach (var ex in ae.Flatten().InnerExceptions)
62 {
91 - throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath));
63 + throw ex;
64 }
93 -
94 - facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
65 }
66 + }
67
97 - string version = null;
98 - string language = null;
99 - try
100 - {
101 - Installer.GetFileVersion(fileInfo.FullName, out version, out language);
102 - }
103 - catch (Win32Exception e)
104 - {
105 - if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
106 - {
107 - throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
108 - }
109 - else
110 - {
111 - throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message));
112 - }
113 - }
68 + private void UpdateFileFacadesInParallel(IEnumerable<IFileFacade> facades)
69 + {
70 + var mut = new Mutex();
71 + var exceptions = new ConcurrentQueue<Exception>();
72
115 - // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install.
116 - if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table
117 - {
118 - if (!this.OverwriteHash)
73 + var assemblySymbols = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id);
74 +
75 + Parallel.ForEach(facades,
76 + new ParallelOptions{
77 + CancellationToken = this.CancellationToken
78 + },
79 + () =>
80 {
120 - // not overwriting hash, so don't do the rest of these options.
121 - }
122 - else if (null != facade.Version)
81 + return new LocalData(
82 + this.Messaging,
83 + this.FileSystem,
84 + this.AllFileFacades,
85 + this.OverwriteHash,
86 + this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id),
87 + useVariableCache: null != this.VariableCache
88 + );
89 + },
90 + (file, loopstate, local) =>
91 {
124 - // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
125 - // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up.
126 - // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing
127 - // all the file rows) for a relatively uncommon situation. Let's not do that.
128 - //
129 - // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
130 - // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
131 - if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal)))
92 + try
93 {
133 - this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id));
94 + local.UpdateFileFacade(file, assemblySymbols);
95 }
135 - }
136 - else
137 - {
138 - if (null != facade.Language)
96 + catch (Exception ex)
97 {
140 - this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
98 + exceptions.Enqueue(ex);
99 + loopstate.Stop();
100 }
101
143 - int[] hash;
144 - try
145 - {
146 - Installer.GetFileHash(fileInfo.FullName, 0, out hash);
147 - }
148 - catch (Win32Exception e)
102 + return local;
103 + },
104 + (local) =>
105 + {
106 + // Merge local variable cache back into common variable cache
107 + if (null != this.VariableCache)
108 {
150 - if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
109 + mut.WaitOne();
110 + try
111 {
152 - throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
112 + local.MergeVariableCacheWith(this.VariableCache);
113 }
154 - else
114 + finally
115 {
156 - throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message));
116 + mut.ReleaseMutex();
117 }
118 }
119 + }
120 + );
121 +
122 + if (!exceptions.IsEmpty)
123 + {
124 + throw new AggregateException(exceptions);
125 + }
126 + }
127
160 - // Remember the hash symbol for use later.
161 - facade.MsiFileHashSymbol = new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)
128 +
129 + internal class LocalData
130 + {
131 + public LocalData(IMessaging messaging, IFileSystem fileSystem, IEnumerable<IFileFacade> allFileFacades, bool overwriteHash, Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols, bool useVariableCache)
132 + {
133 + this.Messaging = messaging;
134 + this.FileSystem = fileSystem;
135 + this.AllFileFacades = allFileFacades;
136 + this.OverwriteHash = overwriteHash;
137 + this.AssemblyNameSymbols = assemblyNameSymbols;
138 + this.VariableCache = useVariableCache ? new Dictionary<string, string>() : null;
139 + }
140 +
141 + private IMessaging Messaging { get; }
142 +
143 + private IFileSystem FileSystem { get; }
144 +
145 + private IEnumerable<IFileFacade> AllFileFacades { get; }
146 +
147 + private bool OverwriteHash { get; }
148 +
149 + private Dictionary<string, MsiAssemblyNameSymbol> AssemblyNameSymbols { get; }
150 +
151 + private Dictionary<string, string> VariableCache { get; }
152 +
153 + public void MergeVariableCacheWith(IDictionary<string, string> variableCache)
154 + {
155 + if (null != variableCache && null != this.VariableCache)
156 + {
157 + foreach (var v in this.VariableCache)
158 {
163 - Options = 0,
164 - HashPart1 = hash[0],
165 - HashPart2 = hash[1],
166 - HashPart3 = hash[2],
167 - HashPart4 = hash[3],
168 - };
159 + variableCache[v.Key] = v.Value;
160 + }
161 }
162 }
171 - else // update the file row with the version and language information.
163 +
164 + public void UpdateFileFacade(IFileFacade facade, Dictionary<string, AssemblySymbol> assemblySymbols)
165 {
173 - // If no version was provided by the user, use the version from the file itself.
174 - // This is the most common case.
175 - if (String.IsNullOrEmpty(facade.Version))
166 + FileInfo fileInfo = null;
167 + try
168 {
177 - facade.Version = version;
169 + fileInfo = new FileInfo(facade.SourcePath);
170 }
179 - else if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
171 + catch (ArgumentException)
172 {
181 - // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
182 - // the version value). We didn't find it so, we will override the default version they provided with the actual
183 - // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match
184 - // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case
185 - // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more
186 - // CPU intensive search to save on the memory intensive index that wouldn't be used much.
187 - //
188 - // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
189 - // That's typically even more rare than companion files so again, no index, just search.
190 - facade.Version = version;
173 + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
174 + return;
175 }
192 -
193 - if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language))
176 + catch (PathTooLongException)
177 {
195 - this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
178 + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
179 + return;
180 }
197 - else // override the default provided by the user (usually nothing) with the actual language from the file itself.
181 + catch (NotSupportedException)
182 {
199 - facade.Language = language;
183 + this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
184 + return;
185 }
201 - }
186
203 - // Populate the binder variables for this file information if requested.
204 - if (null != this.VariableCache)
205 - {
206 - this.VariableCache[$"fileversion.{facade.Id}"] = facade.Version ?? String.Empty;
207 - this.VariableCache[$"filelanguage.{facade.Id}"] = facade.Language ?? String.Empty;
208 - }
187 + if (!fileInfo.Exists)
188 + {
189 + this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath));
190 + return;
191 + }
192
210 - // If there is an assembly for this file.
211 - if (assemblySymbols.TryGetValue(facade.Id, out var assemblySymbol))
212 - {
213 - // If this is a CLR assembly, load the assembly and get the assembly name information
214 - if (AssemblyType.DotNetAssembly == assemblySymbol.Type)
193 + using (var fileStream = this.FileSystem.OpenFile(facade.SourceLineNumber, fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
194 {
216 - try
195 + if (Int32.MaxValue < fileStream.Length)
196 {
218 - var assemblyName = AssemblyNameReader.ReadAssembly(this.FileSystem, facade.SourceLineNumber, fileInfo.FullName, version);
197 + throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath));
198 + }
199
220 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "name", assemblyName.Name);
221 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "culture", assemblyName.Culture);
222 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version);
200 + facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
201 + }
202
224 - if (!String.IsNullOrEmpty(assemblyName.Architecture))
225 - {
226 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture);
227 - }
228 - // TODO: WiX v3 seemed to do this but not clear it should actually be done.
229 - //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
230 - //{
231 - // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
232 - //}
203 + string version = null;
204 + string language = null;
205 + try
206 + {
207 + Installer.GetFileVersion(fileInfo.FullName, out version, out language);
208 + }
209 + catch (Win32Exception e)
210 + {
211 + if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
212 + {
213 + throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
214 + }
215 + else
216 + {
217 + throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message));
218 + }
219 + }
220
234 - if (assemblyName.StrongNamedSigned)
221 + // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install.
222 + if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table
223 + {
224 + if (!this.OverwriteHash)
225 + {
226 + // not overwriting hash, so don't do the rest of these options.
227 + }
228 + else if (null != facade.Version)
229 + {
230 + // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
231 + // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up.
232 + // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing
233 + // all the file rows) for a relatively uncommon situation. Let's not do that.
234 + //
235 + // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
236 + // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
237 + if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal)))
238 {
236 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken);
239 + this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id));
240 }
238 - else if (assemblySymbol.ApplicationFileRef == null)
241 + }
242 + else
243 + {
244 + if (null != facade.Language)
245 {
240 - throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef));
246 + this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
247 }
248
243 - if (!String.IsNullOrEmpty(assemblyName.FileVersion))
249 + int[] hash;
250 + try
251 {
245 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "fileVersion", assemblyName.FileVersion);
252 + Installer.GetFileHash(fileInfo.FullName, 0, out hash);
253 }
247 -
248 - // add the assembly name to the information cache
249 - if (null != this.VariableCache)
254 + catch (Win32Exception e)
255 {
251 - this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName();
256 + if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
257 + {
258 + throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
259 + }
260 + else
261 + {
262 + throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message));
263 + }
264 }
253 - }
254 - catch (WixException e)
255 - {
256 - this.Messaging.Write(e.Error);
265 +
266 + // Remember the hash symbol for use later.
267 + facade.MsiFileHashSymbol = new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier)
268 + {
269 + Options = 0,
270 + HashPart1 = hash[0],
271 + HashPart2 = hash[1],
272 + HashPart3 = hash[2],
273 + HashPart4 = hash[3],
274 + };
275 }
276 }
259 - else if (AssemblyType.Win32Assembly == assemblySymbol.Type)
277 + else // update the file row with the version and language information.
278 {
261 - // TODO: Consider passing in the this.AllFileFacades as an indexed collection instead of searching through
262 - // all files like this. Even though this is a rare case it looks like we might be able to index the
263 - // file earlier.
264 - var fileManifest = this.AllFileFacades.FirstOrDefault(r => r.Id.Equals(assemblySymbol.ManifestFileRef, StringComparison.Ordinal));
265 - if (null == fileManifest)
279 + // If no version was provided by the user, use the version from the file itself.
280 + // This is the most common case.
281 + if (String.IsNullOrEmpty(facade.Version))
282 + {
283 + facade.Version = version;
284 + }
285 + else if (!this.AllFileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
286 {
267 - this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, assemblySymbol.ManifestFileRef));
287 + // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
288 + // the version value). We didn't find it so, we will override the default version they provided with the actual
289 + // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match
290 + // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case
291 + // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more
292 + // CPU intensive search to save on the memory intensive index that wouldn't be used much.
293 + //
294 + // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
295 + // That's typically even more rare than companion files so again, no index, just search.
296 + facade.Version = version;
297 }
298
270 - try
299 + if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language))
300 + {
301 + this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
302 + }
303 + else // override the default provided by the user (usually nothing) with the actual language from the file itself.
304 {
272 - var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath);
305 + facade.Language = language;
306 + }
307 + }
308 +
309 + // Populate the binder variables for this file information if requested.
310 + if (null != this.VariableCache)
311 + {
312 + this.VariableCache[$"fileversion.{facade.Id}"] = facade.Version ?? String.Empty;
313 + this.VariableCache[$"filelanguage.{facade.Id}"] = facade.Language ?? String.Empty;
314 + }
315
274 - if (!String.IsNullOrEmpty(assemblyName.Name))
316 + // If there is an assembly for this file.
317 + if (assemblySymbols.TryGetValue(facade.Id, out var assemblySymbol))
318 + {
319 + // If this is a CLR assembly, load the assembly and get the assembly name information
320 + if (AssemblyType.DotNetAssembly == assemblySymbol.Type)
321 + {
322 + try
323 {
276 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "name", assemblyName.Name);
324 + var assemblyName = AssemblyNameReader.ReadAssembly(this.FileSystem, facade.SourceLineNumber, fileInfo.FullName, version);
325 +
326 + this.SetMsiAssemblyName(facade, assemblySymbol, "name", assemblyName.Name);
327 + this.SetMsiAssemblyName(facade, assemblySymbol, "culture", assemblyName.Culture);
328 + this.SetMsiAssemblyName(facade, assemblySymbol, "version", assemblyName.Version);
329 +
330 + if (!String.IsNullOrEmpty(assemblyName.Architecture))
331 + {
332 + this.SetMsiAssemblyName(facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture);
333 + }
334 + // TODO: WiX v3 seemed to do this but not clear it should actually be done.
335 + //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
336 + //{
337 + // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
338 + //}
339 +
340 + if (assemblyName.StrongNamedSigned)
341 + {
342 + this.SetMsiAssemblyName(facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken);
343 + }
344 + else if (assemblySymbol.ApplicationFileRef == null)
345 + {
346 + throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef));
347 + }
348 +
349 + if (!String.IsNullOrEmpty(assemblyName.FileVersion))
350 + {
351 + this.SetMsiAssemblyName(facade, assemblySymbol, "fileVersion", assemblyName.FileVersion);
352 + }
353 +
354 + // add the assembly name to the information cache
355 + if (null != this.VariableCache)
356 + {
357 + this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName();
358 + }
359 }
278 -
279 - if (!String.IsNullOrEmpty(assemblyName.Version))
360 + catch (WixException e)
361 {
281 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version);
362 + this.Messaging.Write(e.Error);
363 }
283 -
284 - if (!String.IsNullOrEmpty(assemblyName.Type))
364 + }
365 + else if (AssemblyType.Win32Assembly == assemblySymbol.Type)
366 + {
367 + // TODO: Consider passing in the this.AllFileFacades as an indexed collection instead of searching through
368 + // all files like this. Even though this is a rare case it looks like we might be able to index the
369 + // file earlier.
370 + var fileManifest = this.AllFileFacades.FirstOrDefault(r => r.Id.Equals(assemblySymbol.ManifestFileRef, StringComparison.Ordinal));
371 + if (null == fileManifest)
372 {
286 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "type", assemblyName.Type);
373 + this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, assemblySymbol.ManifestFileRef));
374 }
375
289 - if (!String.IsNullOrEmpty(assemblyName.Architecture))
376 + try
377 {
291 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture);
378 + var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath);
379 +
380 + if (!String.IsNullOrEmpty(assemblyName.Name))
381 + {
382 + this.SetMsiAssemblyName(facade, assemblySymbol, "name", assemblyName.Name);
383 + }
384 +
385 + if (!String.IsNullOrEmpty(assemblyName.Version))
386 + {
387 + this.SetMsiAssemblyName(facade, assemblySymbol, "version", assemblyName.Version);
388 + }
389 +
390 + if (!String.IsNullOrEmpty(assemblyName.Type))
391 + {
392 + this.SetMsiAssemblyName(facade, assemblySymbol, "type", assemblyName.Type);
393 + }
394 +
395 + if (!String.IsNullOrEmpty(assemblyName.Architecture))
396 + {
397 + this.SetMsiAssemblyName(facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture);
398 + }
399 +
400 + if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken))
401 + {
402 + this.SetMsiAssemblyName(facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken);
403 + }
404 }
293 -
294 - if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken))
405 + catch (WixException e)
406 {
296 - this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken);
407 + this.Messaging.Write(e.Error);
408 }
409 }
299 - catch (WixException e)
300 - {
301 - this.Messaging.Write(e.Error);
302 - }
410 }
411 }
305 - }
412
307 - private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols, IFileFacade facade, AssemblySymbol assemblySymbol, string name, string value)
308 - {
309 - // check for null value (this can occur when grabbing the file version from an assembly without one)
310 - if (String.IsNullOrEmpty(value))
413 + private void SetMsiAssemblyName(IFileFacade facade, AssemblySymbol assemblySymbol, string name, string value)
414 {
312 - this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name));
313 - }
314 - else
315 - {
316 - // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail.
317 - if ("name" == name && AssemblyType.DotNetAssembly == assemblySymbol.Type &&
318 - String.IsNullOrEmpty(assemblySymbol.ApplicationFileRef) &&
319 - !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase))
320 - {
321 - this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value));
322 - }
323 -
324 - // Override directly authored value, otherwise remember the gathered information on the facade for use later.
325 - var lookup = String.Concat(facade.ComponentRef, "/", name);
326 - if (assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol))
415 + // check for null value (this can occur when grabbing the file version from an assembly without one)
416 + if (String.IsNullOrEmpty(value))
417 {
328 - assemblyNameSymbol.Value = value;
418 + this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name));
419 }
420 else
421 {
332 - assemblyNameSymbol = new MsiAssemblyNameSymbol(assemblySymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, facade.ComponentRef, name))
422 + // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail.
423 + if ("name" == name && AssemblyType.DotNetAssembly == assemblySymbol.Type &&
424 + String.IsNullOrEmpty(assemblySymbol.ApplicationFileRef) &&
425 + !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase))
426 {
334 - ComponentRef = facade.ComponentRef,
335 - Name = name,
336 - Value = value,
337 - };
427 + this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value));
428 + }
429
339 - facade.AssemblyNameSymbols.Add(assemblyNameSymbol);
340 - assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol);
341 - }
430 + // Override directly authored value, otherwise remember the gathered information on the facade for use later.
431 + var lookup = String.Concat(facade.ComponentRef, "/", name);
432 + if (this.AssemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol))
433 + {
434 + assemblyNameSymbol.Value = value;
435 + }
436 + else
437 + {
438 + assemblyNameSymbol = new MsiAssemblyNameSymbol(assemblySymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, facade.ComponentRef, name))
439 + {
440 + ComponentRef = facade.ComponentRef,
441 + Name = name,
442 + Value = value,
443 + };
444
343 - if (this.VariableCache != null)
344 - {
345 - var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant();
346 - this.VariableCache[key] = value;
445 + facade.AssemblyNameSymbols.Add(assemblyNameSymbol);
446 + this.AssemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol);
447 + }
448 +
449 + if (this.VariableCache != null)
450 + {
451 + var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant();
452 + this.VariableCache[key] = value;
453 + }
454 }
455 }
456 }