aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Differ.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
committerRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
commitdbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch)
tree0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core.WindowsInstaller/Differ.cs
parentfbf986eb97f68396797a89fc7d40dec07b775440 (diff)
downloadwix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Differ.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Differ.cs611
1 files changed, 611 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Differ.cs b/src/WixToolset.Core.WindowsInstaller/Differ.cs
new file mode 100644
index 00000000..bdd06d32
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Differ.cs
@@ -0,0 +1,611 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using WixToolset.Core;
10 using WixToolset.Data;
11 using WixToolset.Data.Rows;
12 using WixToolset.Extensibility;
13 using WixToolset.Msi;
14
15 /// <summary>
16 /// Creates a transform by diffing two outputs.
17 /// </summary>
18 public sealed class Differ : IMessageHandler
19 {
20 private List<IInspectorExtension> inspectorExtensions;
21 private bool showPedanticMessages;
22 private bool suppressKeepingSpecialRows;
23 private bool preserveUnchangedRows;
24 private const char sectionDelimiter = '/';
25 private SummaryInformationStreams transformSummaryInfo;
26
27 /// <summary>
28 /// Instantiates a new Differ class.
29 /// </summary>
30 public Differ()
31 {
32 this.inspectorExtensions = new List<IInspectorExtension>();
33 }
34
35 /// <summary>
36 /// Gets or sets the option to show pedantic messages.
37 /// </summary>
38 /// <value>The option to show pedantic messages.</value>
39 public bool ShowPedanticMessages
40 {
41 get { return this.showPedanticMessages; }
42 set { this.showPedanticMessages = value; }
43 }
44
45 /// <summary>
46 /// Gets or sets the option to suppress keeping special rows.
47 /// </summary>
48 /// <value>The option to suppress keeping special rows.</value>
49 public bool SuppressKeepingSpecialRows
50 {
51 get { return this.suppressKeepingSpecialRows; }
52 set { this.suppressKeepingSpecialRows = value; }
53 }
54
55 /// <summary>
56 /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output.
57 /// </summary>
58 /// <value>The option to keep all rows including unchanged rows.</value>
59 public bool PreserveUnchangedRows
60 {
61 get { return this.preserveUnchangedRows; }
62 set { this.preserveUnchangedRows = value; }
63 }
64
65 /// <summary>
66 /// Adds an extension.
67 /// </summary>
68 /// <param name="extension">The extension to add.</param>
69 public void AddExtension(IInspectorExtension extension)
70 {
71 this.inspectorExtensions.Add(extension);
72 }
73
74 /// <summary>
75 /// Creates a transform by diffing two outputs.
76 /// </summary>
77 /// <param name="targetOutput">The target output.</param>
78 /// <param name="updatedOutput">The updated output.</param>
79 /// <returns>The transform.</returns>
80 public Output Diff(Output targetOutput, Output updatedOutput)
81 {
82 return Diff(targetOutput, updatedOutput, 0);
83 }
84
85 /// <summary>
86 /// Creates a transform by diffing two outputs.
87 /// </summary>
88 /// <param name="targetOutput">The target output.</param>
89 /// <param name="updatedOutput">The updated output.</param>
90 /// <param name="validationFlags"></param>
91 /// <returns>The transform.</returns>
92 public Output Diff(Output targetOutput, Output updatedOutput, TransformFlags validationFlags)
93 {
94 Output transform = new Output(null);
95 transform.Type = OutputType.Transform;
96 transform.Codepage = updatedOutput.Codepage;
97 this.transformSummaryInfo = new SummaryInformationStreams();
98
99 // compare the codepages
100 if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags))
101 {
102 this.OnMessage(WixErrors.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage));
103 if (null != updatedOutput.SourceLineNumbers)
104 {
105 this.OnMessage(WixErrors.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers));
106 }
107 }
108
109 // compare the output types
110 if (targetOutput.Type != updatedOutput.Type)
111 {
112 throw new WixException(WixErrors.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString()));
113 }
114
115 // compare the contents of the tables
116 foreach (Table targetTable in targetOutput.Tables)
117 {
118 Table updatedTable = updatedOutput.Tables[targetTable.Name];
119 TableOperation operation = TableOperation.None;
120
121 List<Row> rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation);
122
123 if (TableOperation.Drop == operation)
124 {
125 Table droppedTable = transform.EnsureTable(targetTable.Definition);
126 droppedTable.Operation = TableOperation.Drop;
127 }
128 else if (TableOperation.None == operation)
129 {
130 Table modified = transform.EnsureTable(updatedTable.Definition);
131 rows.ForEach(r => modified.Rows.Add(r));
132 }
133 }
134
135 // added tables
136 foreach (Table updatedTable in updatedOutput.Tables)
137 {
138 if (null == targetOutput.Tables[updatedTable.Name])
139 {
140 Table addedTable = transform.EnsureTable(updatedTable.Definition);
141 addedTable.Operation = TableOperation.Add;
142
143 foreach (Row updatedRow in updatedTable.Rows)
144 {
145 updatedRow.Operation = RowOperation.Add;
146 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
147 addedTable.Rows.Add(updatedRow);
148 }
149 }
150 }
151
152 // set summary information properties
153 if (!this.suppressKeepingSpecialRows)
154 {
155 Table summaryInfoTable = transform.Tables["_SummaryInformation"];
156 this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags);
157 }
158
159 return transform;
160 }
161
162 /// <summary>
163 /// Sends a message to the message delegate if there is one.
164 /// </summary>
165 /// <param name="mea">Message event arguments.</param>
166 public void OnMessage(MessageEventArgs e)
167 {
168 Messaging.Instance.OnMessage(e);
169 }
170
171 /// <summary>
172 /// Add a row to the <paramref name="index"/> using the primary key.
173 /// </summary>
174 /// <param name="index">The indexed rows.</param>
175 /// <param name="row">The row to index.</param>
176 private void AddIndexedRow(IDictionary index, Row row)
177 {
178 string primaryKey = row.GetPrimaryKey('/');
179 if (null != primaryKey)
180 {
181 // Overriding WixActionRows have a primary key defined and take precedence in the index.
182 if (row is WixActionRow)
183 {
184 WixActionRow currentRow = (WixActionRow)row;
185 if (index.Contains(primaryKey))
186 {
187 // If the current row is not overridable, see if the indexed row is.
188 if (!currentRow.Overridable)
189 {
190 WixActionRow indexedRow = index[primaryKey] as WixActionRow;
191 if (null != indexedRow && indexedRow.Overridable)
192 {
193 // The indexed key is overridable and should be replaced
194 // (not removed and re-added which results in two Array.Copy
195 // operations for SortedList, or may be re-hashing in other
196 // implementations of IDictionary).
197 index[primaryKey] = currentRow;
198 }
199 }
200
201 // If we got this far, the row does not need to be indexed.
202 return;
203 }
204 }
205
206 // Nothing else should be added more than once.
207 if (!index.Contains(primaryKey))
208 {
209 index.Add(primaryKey, row);
210 }
211 else if (this.showPedanticMessages)
212 {
213 this.OnMessage(WixWarnings.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name));
214 }
215 }
216 else // use the string representation of the row as its primary key (it may not be unique)
217 {
218 // this is provided for compatibility with unreal tables with no primary key
219 // all real tables must specify at least one column as the primary key
220 primaryKey = row.ToString();
221 index[primaryKey] = row;
222 }
223 }
224
225 private Row CompareRows(Table targetTable, Row targetRow, Row updatedRow, out RowOperation operation, out bool keepRow)
226 {
227 Row comparedRow = null;
228 keepRow = false;
229 operation = RowOperation.None;
230
231 if (null == targetRow ^ null == updatedRow)
232 {
233 if (null == targetRow)
234 {
235 operation = updatedRow.Operation = RowOperation.Add;
236 comparedRow = updatedRow;
237 }
238 else if (null == updatedRow)
239 {
240 operation = targetRow.Operation = RowOperation.Delete;
241 targetRow.SectionId = targetRow.SectionId + sectionDelimiter;
242 comparedRow = targetRow;
243 keepRow = true;
244 }
245 }
246 else // possibly modified
247 {
248 updatedRow.Operation = RowOperation.None;
249 if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name)
250 {
251 // ignore rows that shouldn't be in a transform
252 if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0]))
253 {
254 updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
255 comparedRow = updatedRow;
256 keepRow = true;
257 operation = RowOperation.Modify;
258 }
259 }
260 else
261 {
262 if (this.preserveUnchangedRows)
263 {
264 keepRow = true;
265 }
266
267 for (int i = 0; i < updatedRow.Fields.Length; i++)
268 {
269 ColumnDefinition columnDefinition = updatedRow.Fields[i].Column;
270
271 if (!columnDefinition.PrimaryKey)
272 {
273 bool modified = false;
274
275 if (i >= targetRow.Fields.Length)
276 {
277 columnDefinition.Added = true;
278 modified = true;
279 }
280 else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
281 {
282 if (null == targetRow[i] ^ null == updatedRow[i])
283 {
284 modified = true;
285 }
286 else if (null != targetRow[i] && null != updatedRow[i])
287 {
288 modified = ((int)targetRow[i] != (int)updatedRow[i]);
289 }
290 }
291 else if (ColumnType.Preserved == columnDefinition.Type)
292 {
293 updatedRow.Fields[i].PreviousData = (string)targetRow.Fields[i].Data;
294
295 // keep rows containing preserved fields so the historical data is available to the binder
296 keepRow = !this.suppressKeepingSpecialRows;
297 }
298 else if (ColumnType.Object == columnDefinition.Type)
299 {
300 ObjectField targetObjectField = (ObjectField)targetRow.Fields[i];
301 ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i];
302
303 updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex;
304 updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri;
305
306 // always keep a copy of the previous data even if they are identical
307 // This makes diff.wixmst clean and easier to control patch logic
308 updatedObjectField.PreviousData = (string)targetObjectField.Data;
309
310 // always remember the unresolved data for target build
311 updatedObjectField.UnresolvedPreviousData = (string)targetObjectField.UnresolvedData;
312
313 // keep rows containing object fields so the files can be compared in the binder
314 keepRow = !this.suppressKeepingSpecialRows;
315 }
316 else
317 {
318 modified = ((string)targetRow[i] != (string)updatedRow[i]);
319 }
320
321 if (modified)
322 {
323 if (null != updatedRow.Fields[i].PreviousData)
324 {
325 updatedRow.Fields[i].PreviousData = targetRow.Fields[i].Data.ToString();
326 }
327
328 updatedRow.Fields[i].Modified = true;
329 operation = updatedRow.Operation = RowOperation.Modify;
330 keepRow = true;
331 }
332 }
333 }
334
335 if (keepRow)
336 {
337 comparedRow = updatedRow;
338 comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
339 }
340 }
341 }
342
343 return comparedRow;
344 }
345
346 private List<Row> CompareTables(Output targetOutput, Table targetTable, Table updatedTable, out TableOperation operation)
347 {
348 List<Row> rows = new List<Row>();
349 operation = TableOperation.None;
350
351 // dropped tables
352 if (null == updatedTable ^ null == targetTable)
353 {
354 if (null == targetTable)
355 {
356 operation = TableOperation.Add;
357 rows.AddRange(updatedTable.Rows);
358 }
359 else if (null == updatedTable)
360 {
361 operation = TableOperation.Drop;
362 }
363 }
364 else // possibly modified tables
365 {
366 SortedList updatedPrimaryKeys = new SortedList();
367 SortedList targetPrimaryKeys = new SortedList();
368
369 // compare the table definitions
370 if (0 != targetTable.Definition.CompareTo(updatedTable.Definition))
371 {
372 // continue to the next table; may be more mismatches
373 this.OnMessage(WixErrors.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name));
374 }
375 else
376 {
377 this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys);
378
379 // diff the target and updated rows
380 foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys)
381 {
382 string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key;
383 bool keepRow = false;
384 RowOperation rowOperation = RowOperation.None;
385
386 Row compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value as Row, updatedPrimaryKeys[targetPrimaryKey] as Row, out rowOperation, out keepRow);
387
388 if (keepRow)
389 {
390 rows.Add(compared);
391 }
392 }
393
394 // find the inserted rows
395 foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys)
396 {
397 string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key;
398
399 if (!targetPrimaryKeys.Contains(updatedPrimaryKey))
400 {
401 Row updatedRow = (Row)updatedPrimaryKeyEntry.Value;
402
403 updatedRow.Operation = RowOperation.Add;
404 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
405 rows.Add(updatedRow);
406 }
407 }
408 }
409 }
410
411 return rows;
412 }
413
414 private void IndexPrimaryKeys(Table targetTable, SortedList targetPrimaryKeys, Table updatedTable, SortedList updatedPrimaryKeys)
415 {
416 // index the target rows
417 foreach (Row row in targetTable.Rows)
418 {
419 this.AddIndexedRow(targetPrimaryKeys, row);
420
421 if ("Property" == targetTable.Name)
422 {
423 if ("ProductCode" == (string)row[0])
424 {
425 this.transformSummaryInfo.TargetProductCode = (string)row[1];
426 if ("*" == this.transformSummaryInfo.TargetProductCode)
427 {
428 this.OnMessage(WixErrors.ProductCodeInvalidForTransform(row.SourceLineNumbers));
429 }
430 }
431 else if ("ProductVersion" == (string)row[0])
432 {
433 this.transformSummaryInfo.TargetProductVersion = (string)row[1];
434 }
435 else if ("UpgradeCode" == (string)row[0])
436 {
437 this.transformSummaryInfo.TargetUpgradeCode = (string)row[1];
438 }
439 }
440 else if ("_SummaryInformation" == targetTable.Name)
441 {
442 if (1 == (int)row[0]) // PID_CODEPAGE
443 {
444 this.transformSummaryInfo.TargetSummaryInfoCodepage = (string)row[1];
445 }
446 else if (7 == (int)row[0]) // PID_TEMPLATE
447 {
448 this.transformSummaryInfo.TargetPlatformAndLanguage = (string)row[1];
449 }
450 else if (14 == (int)row[0]) // PID_PAGECOUNT
451 {
452 this.transformSummaryInfo.TargetMinimumVersion = (string)row[1];
453 }
454 }
455 }
456
457 // index the updated rows
458 foreach (Row row in updatedTable.Rows)
459 {
460 this.AddIndexedRow(updatedPrimaryKeys, row);
461
462 if ("Property" == updatedTable.Name)
463 {
464 if ("ProductCode" == (string)row[0])
465 {
466 this.transformSummaryInfo.UpdatedProductCode = (string)row[1];
467 if ("*" == this.transformSummaryInfo.UpdatedProductCode)
468 {
469 this.OnMessage(WixErrors.ProductCodeInvalidForTransform(row.SourceLineNumbers));
470 }
471 }
472 else if ("ProductVersion" == (string)row[0])
473 {
474 this.transformSummaryInfo.UpdatedProductVersion = (string)row[1];
475 }
476 }
477 else if ("_SummaryInformation" == updatedTable.Name)
478 {
479 if (1 == (int)row[0]) // PID_CODEPAGE
480 {
481 this.transformSummaryInfo.UpdatedSummaryInfoCodepage = (string)row[1];
482 }
483 else if (7 == (int)row[0]) // PID_TEMPLATE
484 {
485 this.transformSummaryInfo.UpdatedPlatformAndLanguage = (string)row[1];
486 }
487 else if (14 == (int)row[0]) // PID_PAGECOUNT
488 {
489 this.transformSummaryInfo.UpdatedMinimumVersion = (string)row[1];
490 }
491 }
492 }
493 }
494
495 private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags)
496 {
497 // calculate the minimum version of MSI required to process the transform
498 int targetMin;
499 int updatedMin;
500 int minimumVersion = 100;
501
502 if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out updatedMin))
503 {
504 minimumVersion = Math.Max(targetMin, updatedMin);
505 }
506
507 Hashtable summaryRows = new Hashtable(summaryInfoTable.Rows.Count);
508 foreach (Row row in summaryInfoTable.Rows)
509 {
510 summaryRows[row[0]] = row;
511
512 if ((int)SummaryInformation.Transform.CodePage == (int)row[0])
513 {
514 row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage;
515 row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage;
516 }
517 else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0])
518 {
519 row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage;
520 }
521 else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
522 {
523 row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage;
524 }
525 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
526 {
527 row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode);
528 }
529 else if ((int)SummaryInformation.Transform.InstallerRequirement == (int)row[0])
530 {
531 row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture);
532 }
533 else if ((int)SummaryInformation.Transform.Security == (int)row[0])
534 {
535 row[1] = "4";
536 }
537 }
538
539 if (!summaryRows.Contains((int)SummaryInformation.Transform.TargetPlatformAndLanguage))
540 {
541 Row summaryRow = summaryInfoTable.CreateRow(null);
542 summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage;
543 summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage;
544 }
545
546 if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage))
547 {
548 Row summaryRow = summaryInfoTable.CreateRow(null);
549 summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage;
550 summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage;
551 }
552
553 if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags))
554 {
555 Row summaryRow = summaryInfoTable.CreateRow(null);
556 summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
557 summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture);
558 }
559
560 if (!summaryRows.Contains((int)SummaryInformation.Transform.InstallerRequirement))
561 {
562 Row summaryRow = summaryInfoTable.CreateRow(null);
563 summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement;
564 summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture);
565 }
566
567 if (!summaryRows.Contains((int)SummaryInformation.Transform.Security))
568 {
569 Row summaryRow = summaryInfoTable.CreateRow(null);
570 summaryRow[0] = (int)SummaryInformation.Transform.Security;
571 summaryRow[1] = "4";
572 }
573 }
574
575 private class SummaryInformationStreams
576 {
577 public string TargetSummaryInfoCodepage
578 { get; set; }
579
580 public string TargetPlatformAndLanguage
581 { get; set; }
582
583 public string TargetProductCode
584 { get; set; }
585
586 public string TargetProductVersion
587 { get; set; }
588
589 public string TargetUpgradeCode
590 { get; set; }
591
592 public string TargetMinimumVersion
593 { get; set; }
594
595 public string UpdatedSummaryInfoCodepage
596 { get; set; }
597
598 public string UpdatedPlatformAndLanguage
599 { get; set; }
600
601 public string UpdatedProductCode
602 { get; set; }
603
604 public string UpdatedProductVersion
605 { get; set; }
606
607 public string UpdatedMinimumVersion
608 { get; set; }
609 }
610 }
611}