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