diff options
author | Martin Broholm Andersen <090578@gmail.com> | 2024-05-21 22:35:14 +0200 |
---|---|---|
committer | Bob Arnson <github@bobs.org> | 2024-09-02 23:59:47 -0400 |
commit | 4357d18fdc6908eab97886868596af3bd3121f2e (patch) | |
tree | 546a90f5c19532bee663a7fea9bd32444feaf96a | |
parent | 638532e58ab4c06c35f17421d36ae02ef02ffaf2 (diff) | |
download | wix-4357d18fdc6908eab97886868596af3bd3121f2e.tar.gz wix-4357d18fdc6908eab97886868596af3bd3121f2e.tar.bz2 wix-4357d18fdc6908eab97886868596af3bd3121f2e.zip |
Replace UpdateFileFacade loop with Parallel.ForEach
-rw-r--r-- | src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs | 9 | ||||
-rw-r--r-- | src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs | 541 |
2 files changed, 331 insertions, 219 deletions
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 4db1bfbe..f1c5f851 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs | |||
@@ -6,6 +6,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
7 | using System.IO; | 7 | using System.IO; |
8 | using System.Linq; | 8 | using System.Linq; |
9 | using System.Threading; | ||
9 | using WixToolset.Data; | 10 | using WixToolset.Data; |
10 | using WixToolset.Data.Symbols; | 11 | using WixToolset.Data.Symbols; |
11 | using WixToolset.Data.WindowsInstaller; | 12 | using WixToolset.Data.WindowsInstaller; |
@@ -50,6 +51,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
50 | this.PatchSubStorages = patchSubStorages; | 51 | this.PatchSubStorages = patchSubStorages; |
51 | 52 | ||
52 | this.BackendExtensions = backendExtension; | 53 | this.BackendExtensions = backendExtension; |
54 | |||
55 | this.CancellationToken = context.CancellationToken; | ||
53 | } | 56 | } |
54 | 57 | ||
55 | private IServiceProvider ServiceProvider { get; } | 58 | private IServiceProvider ServiceProvider { get; } |
@@ -100,6 +103,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
100 | 103 | ||
101 | private string IntermediateFolder { get; } | 104 | private string IntermediateFolder { get; } |
102 | 105 | ||
106 | private CancellationToken CancellationToken { get; } | ||
107 | |||
103 | public IBindResult Execute() | 108 | public IBindResult Execute() |
104 | { | 109 | { |
105 | if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) | 110 | if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) |
@@ -275,7 +280,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
275 | 280 | ||
276 | // Gather information about files that do not come from merge modules. | 281 | // Gather information about files that do not come from merge modules. |
277 | { | 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); |
279 | command.Execute(); | 284 | command.Execute(); |
280 | } | 285 | } |
281 | 286 | ||
@@ -319,7 +324,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
319 | { | 324 | { |
320 | var updatedFacades = reresolvedFiles.Select(f => allFileFacades.First(ff => ff.Id == f.Id?.Id)); | 325 | var updatedFacades = reresolvedFiles.Select(f => allFileFacades.First(ff => ff.Id == f.Id?.Id)); |
321 | 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); |
323 | command.Execute(); | 328 | command.Execute(); |
324 | } | 329 | } |
325 | } | 330 | } |
diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs index b51e3a0f..5792fdcb 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs | |||
@@ -3,11 +3,14 @@ | |||
3 | namespace WixToolset.Core.WindowsInstaller.Bind | 3 | namespace WixToolset.Core.WindowsInstaller.Bind |
4 | { | 4 | { |
5 | using System; | 5 | using System; |
6 | using System.Collections.Concurrent; | ||
6 | using System.Collections.Generic; | 7 | using System.Collections.Generic; |
7 | using System.ComponentModel; | 8 | using System.ComponentModel; |
8 | using System.Globalization; | 9 | using System.Globalization; |
9 | using System.IO; | 10 | using System.IO; |
10 | using System.Linq; | 11 | using System.Linq; |
12 | using System.Threading; | ||
13 | using System.Threading.Tasks; | ||
11 | using WixToolset.Core.Native.Msi; | 14 | using WixToolset.Core.Native.Msi; |
12 | using WixToolset.Data; | 15 | using WixToolset.Data; |
13 | using WixToolset.Data.Symbols; | 16 | using WixToolset.Data.Symbols; |
@@ -19,7 +22,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
19 | /// </summary> | 22 | /// </summary> |
20 | internal class UpdateFileFacadesCommand | 23 | internal class UpdateFileFacadesCommand |
21 | { | 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) |
23 | { | 26 | { |
24 | this.Messaging = messaging; | 27 | this.Messaging = messaging; |
25 | this.FileSystem = fileSystem; | 28 | this.FileSystem = fileSystem; |
@@ -28,6 +31,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
28 | this.UpdateFileFacades = updateFileFacades; | 31 | this.UpdateFileFacades = updateFileFacades; |
29 | this.VariableCache = variableCache; | 32 | this.VariableCache = variableCache; |
30 | this.OverwriteHash = overwriteHash; | 33 | this.OverwriteHash = overwriteHash; |
34 | this.CancellationToken = cancellationToken; | ||
31 | } | 35 | } |
32 | 36 | ||
33 | private IMessaging Messaging { get; } | 37 | private IMessaging Messaging { get; } |
@@ -44,306 +48,409 @@ namespace WixToolset.Core.WindowsInstaller.Bind | |||
44 | 48 | ||
45 | private IDictionary<string, string> VariableCache { get; } | 49 | private IDictionary<string, string> VariableCache { get; } |
46 | 50 | ||
47 | public void Execute() | 51 | private CancellationToken CancellationToken { get; } |
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 | 52 | ||
52 | foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null)) | 53 | public void Execute() |
53 | { | ||
54 | this.UpdateFileFacade(file, assemblySymbols, assemblyNameSymbols); | ||
55 | } | ||
56 | } | ||
57 | |||
58 | private void UpdateFileFacade(IFileFacade facade, Dictionary<string, AssemblySymbol> assemblySymbols, Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols) | ||
59 | { | 54 | { |
60 | FileInfo fileInfo = null; | ||
61 | try | 55 | try |
62 | { | 56 | { |
63 | fileInfo = new FileInfo(facade.SourcePath); | 57 | this.UpdateFileFacadesInParallel(this.UpdateFileFacades.Where(f => f.SourcePath != null)); |
64 | } | 58 | } |
65 | catch (ArgumentException) | 59 | catch (AggregateException ae) |
66 | { | 60 | { |
67 | this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); | 61 | foreach (var ex in ae.Flatten().InnerExceptions) |
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) | ||
90 | { | 62 | { |
91 | throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath)); | 63 | throw ex; |
92 | } | 64 | } |
93 | |||
94 | facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); | ||
95 | } | 65 | } |
66 | } | ||
96 | 67 | ||
97 | string version = null; | 68 | private void UpdateFileFacadesInParallel(IEnumerable<IFileFacade> facades) |
98 | string language = null; | 69 | { |
99 | try | 70 | var mut = new Mutex(); |
100 | { | 71 | var exceptions = new ConcurrentQueue<Exception>(); |
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 | } | ||
114 | 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. | 73 | var assemblySymbols = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id); |
116 | if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table | 74 | |
117 | { | 75 | Parallel.ForEach(facades, |
118 | if (!this.OverwriteHash) | 76 | new ParallelOptions{ |
77 | CancellationToken = this.CancellationToken | ||
78 | }, | ||
79 | () => | ||
119 | { | 80 | { |
120 | // not overwriting hash, so don't do the rest of these options. | 81 | return new LocalData( |
121 | } | 82 | this.Messaging, |
122 | else if (null != facade.Version) | 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) => | ||
123 | { | 91 | { |
124 | // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks | 92 | try |
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))) | ||
132 | { | 93 | { |
133 | this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); | 94 | local.UpdateFileFacade(file, assemblySymbols); |
134 | } | 95 | } |
135 | } | 96 | catch (Exception ex) |
136 | else | ||
137 | { | ||
138 | if (null != facade.Language) | ||
139 | { | 97 | { |
140 | this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id)); | 98 | exceptions.Enqueue(ex); |
99 | loopstate.Stop(); | ||
141 | } | 100 | } |
142 | 101 | ||
143 | int[] hash; | 102 | return local; |
144 | try | 103 | }, |
145 | { | 104 | (local) => |
146 | Installer.GetFileHash(fileInfo.FullName, 0, out hash); | 105 | { |
147 | } | 106 | // Merge local variable cache back into common variable cache |
148 | catch (Win32Exception e) | 107 | if (null != this.VariableCache) |
149 | { | 108 | { |
150 | if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND | 109 | mut.WaitOne(); |
110 | try | ||
151 | { | 111 | { |
152 | throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName)); | 112 | local.MergeVariableCacheWith(this.VariableCache); |
153 | } | 113 | } |
154 | else | 114 | finally |
155 | { | 115 | { |
156 | throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); | 116 | mut.ReleaseMutex(); |
157 | } | 117 | } |
158 | } | 118 | } |
119 | } | ||
120 | ); | ||
121 | |||
122 | if (!exceptions.IsEmpty) | ||
123 | { | ||
124 | throw new AggregateException(exceptions); | ||
125 | } | ||
126 | } | ||
159 | 127 | ||
160 | // Remember the hash symbol for use later. | 128 | |
161 | facade.MsiFileHashSymbol = new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier) | 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) | ||
162 | { | 158 | { |
163 | Options = 0, | 159 | variableCache[v.Key] = v.Value; |
164 | HashPart1 = hash[0], | 160 | } |
165 | HashPart2 = hash[1], | ||
166 | HashPart3 = hash[2], | ||
167 | HashPart4 = hash[3], | ||
168 | }; | ||
169 | } | 161 | } |
170 | } | 162 | } |
171 | else // update the file row with the version and language information. | 163 | |
164 | public void UpdateFileFacade(IFileFacade facade, Dictionary<string, AssemblySymbol> assemblySymbols) | ||
172 | { | 165 | { |
173 | // If no version was provided by the user, use the version from the file itself. | 166 | FileInfo fileInfo = null; |
174 | // This is the most common case. | 167 | try |
175 | if (String.IsNullOrEmpty(facade.Version)) | ||
176 | { | 168 | { |
177 | facade.Version = version; | 169 | fileInfo = new FileInfo(facade.SourcePath); |
178 | } | 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) |
180 | { | 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 | 173 | this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); |
182 | // the version value). We didn't find it so, we will override the default version they provided with the actual | 174 | return; |
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; | ||
191 | } | 175 | } |
192 | 176 | catch (PathTooLongException) | |
193 | if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language)) | ||
194 | { | 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; | ||
196 | } | 180 | } |
197 | else // override the default provided by the user (usually nothing) with the actual language from the file itself. | 181 | catch (NotSupportedException) |
198 | { | 182 | { |
199 | facade.Language = language; | 183 | this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath)); |
184 | return; | ||
200 | } | 185 | } |
201 | } | ||
202 | 186 | ||
203 | // Populate the binder variables for this file information if requested. | 187 | if (!fileInfo.Exists) |
204 | if (null != this.VariableCache) | 188 | { |
205 | { | 189 | this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath)); |
206 | this.VariableCache[$"fileversion.{facade.Id}"] = facade.Version ?? String.Empty; | 190 | return; |
207 | this.VariableCache[$"filelanguage.{facade.Id}"] = facade.Language ?? String.Empty; | 191 | } |
208 | } | ||
209 | 192 | ||
210 | // If there is an assembly for this file. | 193 | using (var fileStream = this.FileSystem.OpenFile(facade.SourceLineNumber, fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) |
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) | ||
215 | { | 194 | { |
216 | try | 195 | if (Int32.MaxValue < fileStream.Length) |
217 | { | 196 | { |
218 | var assemblyName = AssemblyNameReader.ReadAssembly(this.FileSystem, facade.SourceLineNumber, fileInfo.FullName, version); | 197 | throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath)); |
198 | } | ||
219 | 199 | ||
220 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "name", assemblyName.Name); | 200 | facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); |
221 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "culture", assemblyName.Culture); | 201 | } |
222 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version); | ||
223 | 202 | ||
224 | if (!String.IsNullOrEmpty(assemblyName.Architecture)) | 203 | string version = null; |
225 | { | 204 | string language = null; |
226 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "processorArchitecture", assemblyName.Architecture); | 205 | try |
227 | } | 206 | { |
228 | // TODO: WiX v3 seemed to do this but not clear it should actually be done. | 207 | Installer.GetFileVersion(fileInfo.FullName, out version, out language); |
229 | //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) | 208 | } |
230 | //{ | 209 | catch (Win32Exception e) |
231 | // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); | 210 | { |
232 | //} | 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 | } | ||
233 | 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))) | ||
235 | { | 238 | { |
236 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken); | 239 | this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id)); |
237 | } | 240 | } |
238 | else if (assemblySymbol.ApplicationFileRef == null) | 241 | } |
242 | else | ||
243 | { | ||
244 | if (null != facade.Language) | ||
239 | { | 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)); |
241 | } | 247 | } |
242 | 248 | ||
243 | if (!String.IsNullOrEmpty(assemblyName.FileVersion)) | 249 | int[] hash; |
250 | try | ||
244 | { | 251 | { |
245 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "fileVersion", assemblyName.FileVersion); | 252 | Installer.GetFileHash(fileInfo.FullName, 0, out hash); |
246 | } | 253 | } |
247 | 254 | catch (Win32Exception e) | |
248 | // add the assembly name to the information cache | ||
249 | if (null != this.VariableCache) | ||
250 | { | 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 | } | ||
252 | } | 264 | } |
253 | } | 265 | |
254 | catch (WixException e) | 266 | // Remember the hash symbol for use later. |
255 | { | 267 | facade.MsiFileHashSymbol = new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier) |
256 | this.Messaging.Write(e.Error); | 268 | { |
269 | Options = 0, | ||
270 | HashPart1 = hash[0], | ||
271 | HashPart2 = hash[1], | ||
272 | HashPart3 = hash[2], | ||
273 | HashPart4 = hash[3], | ||
274 | }; | ||
257 | } | 275 | } |
258 | } | 276 | } |
259 | else if (AssemblyType.Win32Assembly == assemblySymbol.Type) | 277 | else // update the file row with the version and language information. |
260 | { | 278 | { |
261 | // TODO: Consider passing in the this.AllFileFacades as an indexed collection instead of searching through | 279 | // If no version was provided by the user, use the version from the file itself. |
262 | // all files like this. Even though this is a rare case it looks like we might be able to index the | 280 | // This is the most common case. |
263 | // file earlier. | 281 | if (String.IsNullOrEmpty(facade.Version)) |
264 | var fileManifest = this.AllFileFacades.FirstOrDefault(r => r.Id.Equals(assemblySymbol.ManifestFileRef, StringComparison.Ordinal)); | 282 | { |
265 | if (null == fileManifest) | 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. | ||
266 | { | 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; | ||
268 | } | 297 | } |
269 | 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. | ||
271 | { | 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 | } | ||
273 | 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 | ||
275 | { | 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 | } | ||
277 | } | 359 | } |
278 | 360 | catch (WixException e) | |
279 | if (!String.IsNullOrEmpty(assemblyName.Version)) | ||
280 | { | 361 | { |
281 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "version", assemblyName.Version); | 362 | this.Messaging.Write(e.Error); |
282 | } | 363 | } |
283 | 364 | } | |
284 | if (!String.IsNullOrEmpty(assemblyName.Type)) | 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) | ||
285 | { | 372 | { |
286 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "type", assemblyName.Type); | 373 | this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, assemblySymbol.ManifestFileRef)); |
287 | } | 374 | } |
288 | 375 | ||
289 | if (!String.IsNullOrEmpty(assemblyName.Architecture)) | 376 | try |
290 | { | 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 | } | ||
292 | } | 404 | } |
293 | 405 | catch (WixException e) | |
294 | if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken)) | ||
295 | { | 406 | { |
296 | this.SetMsiAssemblyName(assemblyNameSymbols, facade, assemblySymbol, "publicKeyToken", assemblyName.PublicKeyToken); | 407 | this.Messaging.Write(e.Error); |
297 | } | 408 | } |
298 | } | 409 | } |
299 | catch (WixException e) | ||
300 | { | ||
301 | this.Messaging.Write(e.Error); | ||
302 | } | ||
303 | } | 410 | } |
304 | } | 411 | } |
305 | } | ||
306 | 412 | ||
307 | private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols, IFileFacade facade, AssemblySymbol assemblySymbol, string name, string value) | 413 | private void SetMsiAssemblyName(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)) | ||
311 | { | 414 | { |
312 | this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name)); | 415 | // check for null value (this can occur when grabbing the file version from an assembly without one) |
313 | } | 416 | if (String.IsNullOrEmpty(value)) |
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)) | ||
327 | { | 417 | { |
328 | assemblyNameSymbol.Value = value; | 418 | this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name)); |
329 | } | 419 | } |
330 | else | 420 | else |
331 | { | 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)) | ||
333 | { | 426 | { |
334 | ComponentRef = facade.ComponentRef, | 427 | this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value)); |
335 | Name = name, | 428 | } |
336 | Value = value, | ||
337 | }; | ||
338 | 429 | ||
339 | facade.AssemblyNameSymbols.Add(assemblyNameSymbol); | 430 | // Override directly authored value, otherwise remember the gathered information on the facade for use later. |
340 | assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol); | 431 | var lookup = String.Concat(facade.ComponentRef, "/", name); |
341 | } | 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 | }; | ||
342 | 444 | ||
343 | if (this.VariableCache != null) | 445 | facade.AssemblyNameSymbols.Add(assemblyNameSymbol); |
344 | { | 446 | this.AssemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol); |
345 | var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant(); | 447 | } |
346 | this.VariableCache[key] = value; | 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 | } | ||
347 | } | 454 | } |
348 | } | 455 | } |
349 | } | 456 | } |