Basic patching support
Rob Mensching committed
May 30, 2020 at 14:53 UTC
d529525a1e331f3ef9ec2707791c99bd78fdd82f
19 files changed
+779
-986
src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs
+25
-41
@@ -147,7 +147,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
147
{
148
foreach (MediaRow transformMediaRow in transformMediaTable.Rows)
149
{
150
- if (mediaTuple.LastSequence < transformMediaRow.LastSequence)
150
+ if (!mediaTuple.LastSequence.HasValue || mediaTuple.LastSequence < transformMediaRow.LastSequence)
151
{
152
// The Binder will pre-increment the sequence.
153
mediaTuple.LastSequence = transformMediaRow.LastSequence;
@@ -156,14 +156,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
156
}
157
158
// Use the Media/@DiskId if greater than the last sequence for backward compatibility.
159
- if (mediaTuple.LastSequence < mediaTuple.DiskId)
159
+ if (!mediaTuple.LastSequence.HasValue || mediaTuple.LastSequence < mediaTuple.DiskId)
160
{
161
mediaTuple.LastSequence = mediaTuple.DiskId;
162
}
163
164
// Ignore media table in the transform.
165
mainTransform.Transform.Tables.Remove("Media");
166
- mainTransform.Transform.Tables.Remove("WixMedia");
166
mainTransform.Transform.Tables.Remove("MsiDigitalSignature");
167
168
var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchIdTuple, mainTransform.Transform, mediaTuple, baselineTuple, out var productCode);
@@ -767,15 +766,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind
766
if (transform.TryGetTable("File", out var fileTable))
767
{
768
var componentWithChangedKeyPath = new Dictionary<string, string>();
770
- foreach (var row in fileTable.Rows)
769
+ foreach (FileRow row in fileTable.Rows)
770
{
771
if (RowOperation.None == row.Operation)
772
{
773
continue;
774
}
775
777
- var fileId = row.FieldAsString(0);
778
- var componentId = row.FieldAsString(1);
776
+ var fileId = row.File;
777
+ var componentId = row.Component;
778
779
// If this file is the keypath of a component
780
if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal))
@@ -972,7 +971,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
971
972
if (row.Operation == RowOperation.None)
973
{
975
- // ignore the rows without section id.
974
+ // Ignore the rows without section id.
975
if (isSectionIdEmpty)
976
{
977
currentTable.Rows.RemoveAt(i);
@@ -1157,47 +1156,33 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1156
return null;
1157
}
1158
1160
- // copy File table
1159
+ // Copy File table
1160
if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count)
1161
{
1163
-#if TODO_PATCHING
1164
- // We require file source information.
1165
- var mainWixFileTable = mainTransform.Tables["WixFile"];
1166
- if (null == mainWixFileTable)
1167
- {
1168
- this.Messaging.Write(ErrorMessages.AdminImageRequired(productCode));
1169
- return null;
1170
- }
1171
-
1172
- var mainFileRows = new RowDictionary<FileRow>(mainFileTable);
1173
-
1162
var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition);
1175
- {
1176
- var mainFileRow = mainFileRows[mainWixFileRow.File];
1163
1178
- // set File.Sequence to non null to satisfy transform bind
1164
+ foreach (FileRow mainFileRow in mainFileTable.Rows)
1165
+ {
1166
+ // Set File.Sequence to non null to satisfy transform bind.
1167
mainFileRow.Sequence = 1;
1168
1181
- // delete's don't need rows in the paired transform
1169
+ // Delete's don't need rows in the paired transform.
1170
if (mainFileRow.Operation == RowOperation.Delete)
1171
{
1172
continue;
1173
}
1174
1187
- var pairedFileRow = (FileRow)pairedFileTable.CreateRow(null);
1175
+ var pairedFileRow = (FileRow)pairedFileTable.CreateRow(mainFileRow.SourceLineNumbers);
1176
pairedFileRow.Operation = RowOperation.Modify;
1189
- for (var i = 0; i < mainFileRow.Fields.Length; i++)
1190
- {
1191
- pairedFileRow[i] = mainFileRow[i];
1192
- }
1177
+ mainFileRow.CopyTo(pairedFileRow);
1178
1194
- // override authored media for patch bind
1195
- mainWixFileRow.DiskId = mediaTuple.DiskId;
1179
+ // Override authored media for patch bind.
1180
+ mainFileRow.DiskId = mediaTuple.DiskId;
1181
1197
- // suppress any change to File.Sequence to avoid bloat
1182
+ // Suppress any change to File.Sequence to avoid bloat.
1183
mainFileRow.Fields[7].Modified = false;
1184
1200
- // force File row to appear in the transform
1185
+ // Force File row to appear in the transform.
1186
switch (mainFileRow.Operation)
1187
{
1188
case RowOperation.Modify:
@@ -1211,19 +1196,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1196
break;
1197
}
1198
}
1214
-#endif
1199
}
1200
1201
// Add Media row to pairedTransform
1202
var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]);
1219
- var pairedMediaRow = pairedMediaTable.CreateRow(mediaTuple.SourceLineNumbers);
1203
+ var pairedMediaRow = (MediaRow)pairedMediaTable.CreateRow(mediaTuple.SourceLineNumbers);
1204
pairedMediaRow.Operation = RowOperation.Add;
1221
- pairedMediaRow[0] = mediaTuple.DiskId;
1222
- pairedMediaRow[1] = mediaTuple.LastSequence ?? 0;
1223
- pairedMediaRow[2] = mediaTuple.DiskPrompt;
1224
- pairedMediaRow[3] = mediaTuple.Cabinet;
1225
- pairedMediaRow[4] = mediaTuple.VolumeLabel;
1226
- pairedMediaRow[5] = mediaTuple.Source;
1205
+ pairedMediaRow.DiskId = mediaTuple.DiskId;
1206
+ pairedMediaRow.LastSequence = mediaTuple.LastSequence ?? 0;
1207
+ pairedMediaRow.DiskPrompt = mediaTuple.DiskPrompt;
1208
+ pairedMediaRow.Cabinet = mediaTuple.Cabinet;
1209
+ pairedMediaRow.VolumeLabel = mediaTuple.VolumeLabel;
1210
+ pairedMediaRow.Source = mediaTuple.Source;
1211
1212
// Add PatchPackage for this Media
1213
var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]);
@@ -1283,7 +1267,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1267
{
1268
var patchTargetTuples = tuples.OfType<WixPatchTargetTuple>().ToList();
1269
1286
- if (patchTargetTuples.Count > 0)
1270
+ if (patchTargetTuples.Any())
1271
{
1272
var targets = new SortedSet<string>();
1273
var replace = true;
src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+26
-104
@@ -43,7 +43,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
43
this.DefaultCompressionLevel = context.DefaultCompressionLevel;
44
this.DelayedFields = context.DelayedFields;
45
this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles;
46
- this.FileSystemExtensions = context.FileSystemExtensions;
46
+ this.FileSystemManager = new FileSystemManager(context.FileSystemExtensions);
47
this.Intermediate = context.IntermediateRepresentation;
48
this.IntermediateFolder = context.IntermediateFolder;
49
this.OutputPath = context.OutputPath;
@@ -79,7 +79,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
79
80
public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; }
81
82
- public IEnumerable<IFileSystemExtension> FileSystemExtensions { get; }
82
+ public FileSystemManager FileSystemManager { get; }
83
84
public bool DeltaBinaryPatch { get; set; }
85
@@ -116,7 +116,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
116
var trackedFiles = new List<ITrackedFile>();
117
118
var containsMergeModules = false;
119
- var suppressedTableNames = new HashSet<string>();
119
120
// If there are any fields to resolve later, create the cache to populate during bind.
121
var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null;
@@ -226,7 +225,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
225
return null;
226
}
227
229
- WindowsInstallerData output;
228
this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound);
229
this.Messaging.Write(VerboseMessages.UpdatingFileInformation());
230
@@ -268,14 +266,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
266
}
267
else if (SectionType.Patch == section.Type)
268
{
271
- // Merge transform data into the output object.
272
- //IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output);
273
-
274
- //var command = new CopyTransformDataCommand(this.Messaging, /*output*/this.SubStorages, tableDefinitions, copyOutFileRows: true);
275
- //command.Output = output;
276
- //command.TableDefinitions = this.TableDefinitions;
277
- //command.CopyOutFileRows = true;
278
- var command = new GetFileFacadesFromTransforms(this.Messaging, this.SubStorages, tableDefinitions);
269
+ var command = new GetFileFacadesFromTransforms(this.Messaging, this.FileSystemManager, this.SubStorages);
270
command.Execute();
271
var filesFromTransforms = command.FileFacades;
272
@@ -338,6 +329,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
329
command.Execute();
330
}
331
332
+ // Update file sequence.
333
+ {
334
+ var command = new UpdateMediaSequencesCommand(section, fileFacades);
335
+ command.Execute();
336
+ }
337
+
338
// stop processing if an error previously occurred
339
if (this.Messaging.EncounteredError)
340
{
@@ -345,6 +342,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
342
}
343
344
// Time to create the output object. Try to put as much above here as possible, updating the IR is better.
345
+ WindowsInstallerData output;
346
{
347
var command = new CreateOutputFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions, this.WindowsInstallerBackendHelper);
348
command.Execute();
@@ -352,14 +350,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
350
output = command.Output;
351
}
352
355
- // Update file sequence.
356
- {
357
- var command = new UpdateMediaSequencesCommand(output, fileFacades);
358
- command.Execute();
359
- }
360
-
353
// Modularize identifiers.
362
- if (OutputType.Module == output.Type)
354
+ if (output.Type == OutputType.Module)
355
{
356
var command = new ModularizeCommand(output, modularizationSuffix, section.Tuples.OfType<WixSuppressModularizationTuple>());
357
command.Execute();
@@ -457,18 +449,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
449
trackedFiles.AddRange(command.TrackedFiles);
450
}
451
460
-#if DELETE
461
- if (OutputType.Patch == output.Type)
462
- {
463
- // Copy output data back into the transforms.
464
-#if TODO_PATCHING
465
- var command = new CopyTransformDataCommand(this.Messaging, output, tableDefinitions, copyOutFileRows: false);
466
- command.Execute();
467
-
468
- this.CopyToTransformData(this.Output);
469
-#endif
470
- }
471
-#endif
452
+ if (output.Type == OutputType.Patch)
453
+ {
454
+ // Copy output data back into the transforms.
455
+ var command = new UpdateTransformsWithFileFacades(this.Messaging, output, this.SubStorages, tableDefinitions, fileFacades);
456
+ command.Execute();
457
+ }
458
459
// stop processing if an error previously occurred
460
if (this.Messaging.EncounteredError)
@@ -483,8 +469,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
469
var trackMsi = this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final);
470
trackedFiles.Add(trackMsi);
471
486
- var temporaryFiles = this.GenerateDatabase(output, tableDefinitions, trackMsi.Path, false, false);
487
- trackedFiles.AddRange(temporaryFiles);
472
+ var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, trackMsi.Path, tableDefinitions, this.IntermediateFolder, this.Codepage, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false);
473
+ command.Execute();
474
+
475
+ trackedFiles.AddRange(command.GeneratedTemporaryFiles);
476
}
477
478
// Stop processing if an error previously occurred.
@@ -498,31 +486,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
486
{
487
this.Messaging.Write(VerboseMessages.MergingModules());
488
501
- // Add back possibly suppressed sequence tables since all sequence tables must be present
502
- // for the merge process to work. We'll drop the suppressed sequence tables again as
503
- // necessary.
504
- foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
505
- {
506
- var sequenceTableName = (sequence == SequenceTable.AdvertiseExecuteSequence) ? "AdvtExecuteSequence" : sequence.ToString();
507
- var sequenceTable = output.Tables[sequenceTableName];
508
-
509
- if (null == sequenceTable)
510
- {
511
- sequenceTable = output.EnsureTable(tableDefinitions[sequenceTableName]);
512
- }
513
-
514
- if (0 == sequenceTable.Rows.Count)
515
- {
516
- suppressedTableNames.Add(sequenceTableName);
517
- }
518
- }
519
-
489
var command = new MergeModulesCommand(this.Messaging);
490
command.FileFacades = fileFacades;
491
command.IntermediateFolder = this.IntermediateFolder;
492
command.Output = output;
493
command.OutputPath = this.OutputPath;
525
- command.SuppressedTableNames = suppressedTableNames;
494
+ command.TableDefinitions = tableDefinitions;
495
command.Execute();
496
}
497
@@ -607,38 +576,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
576
return wixout;
577
}
578
610
-#if TODO_PATCHING
611
- /// <summary>
612
- /// Copy file data between transform substorages and the patch output object
613
- /// </summary>
614
- /// <param name="output">The output to bind.</param>
615
- /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
616
- private IEnumerable<FileFacade> CopyFromTransformData(Output output)
617
- {
618
- var command = new CopyTransformDataCommand();
619
- command.CopyOutFileRows = true;
620
- command.Output = output;
621
- command.TableDefinitions = this.TableDefinitions;
622
- command.Execute();
623
-
624
- return command.FileFacades;
625
- }
626
-
627
- /// <summary>
628
- /// Copy file data between transform substorages and the patch output object
629
- /// </summary>
630
- /// <param name="output">The output to bind.</param>
631
- /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
632
- private void CopyToTransformData(Output output)
633
- {
634
- var command = new CopyTransformDataCommand();
635
- command.CopyOutFileRows = false;
636
- command.Output = output;
637
- command.TableDefinitions = this.TableDefinitions;
638
- command.Execute();
639
- }
640
-#endif
641
-
579
/// <summary>
580
/// Validate that there are no duplicate GUIDs in the output.
581
/// </summary>
@@ -650,7 +587,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
587
{
588
if (output.TryGetTable("Component", out var componentTable))
589
{
653
- Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count);
590
+ var componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count);
591
592
foreach (Data.WindowsInstaller.Rows.ComponentRow row in componentTable.Rows)
593
{
@@ -658,12 +595,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
595
// there's already an error that prevented it from being replaced with a real GUID.
596
if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid)
597
{
661
- bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition);
662
- bool allComponentsHaveConditions = thisComponentHasCondition;
598
+ var thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition);
599
+ var allComponentsHaveConditions = thisComponentHasCondition;
600
601
if (componentGuidConditions.ContainsKey(row.Guid))
602
{
666
- allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition;
603
+ allComponentsHaveConditions = thisComponentHasCondition && componentGuidConditions[row.Guid];
604
605
if (allComponentsHaveConditions)
606
{
@@ -728,20 +665,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind
665
666
return layout;
667
}
731
-
732
- /// <summary>
733
- /// Creates the MSI/MSM/PCP database.
734
- /// </summary>
735
- /// <param name="output">Output to create database for.</param>
736
- /// <param name="databaseFile">The database file to create.</param>
737
- /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
738
- /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param>
739
- private IEnumerable<ITrackedFile> GenerateDatabase(WindowsInstallerData output, TableDefinitionCollection tableDefinitions, string databaseFile, bool keepAddedColumns, bool useSubdirectory)
740
- {
741
- var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemExtensions, output, databaseFile, tableDefinitions, this.IntermediateFolder, this.Codepage, keepAddedColumns, this.SuppressAddingValidationRows, useSubdirectory);
742
- command.Execute();
743
-
744
- return command.GeneratedTemporaryFiles;
745
- }
668
}
669
}
src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
+5
-27
@@ -3,23 +3,21 @@
3
namespace WixToolset.Core.WindowsInstaller.Bind
4
{
5
using System;
6
- using System.Collections.Generic;
6
using System.Globalization;
7
using System.IO;
8
using WixToolset.Core.WindowsInstaller.Msi;
9
using WixToolset.Data;
10
using WixToolset.Data.Tuples;
11
using WixToolset.Data.WindowsInstaller;
13
- using WixToolset.Extensibility;
12
using WixToolset.Extensibility.Services;
13
14
internal class BindTransformCommand
15
{
18
- public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IFileSystemExtension> extensions, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions)
16
+ public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions)
17
{
18
this.Messaging = messaging;
19
this.BackendHelper = backendHelper;
22
- this.Extensions = extensions;
20
+ this.FileSystemManager = fileSystemManager;
21
this.IntermediateFolder = intermediateFolder;
22
this.Transform = transform;
23
this.OutputPath = outputPath;
@@ -30,7 +28,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
28
29
private IBackendHelper BackendHelper { get; }
30
33
- private IEnumerable<IFileSystemExtension> Extensions { get; }
31
+ private FileSystemManager FileSystemManager { get; }
32
33
private TableDefinitionCollection TableDefinitions { get; }
34
@@ -353,7 +351,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
351
targetRow[i] = emptyFile;
352
modifiedRow = true;
353
}
356
- else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data))
354
+ else if (!this.FileSystemManager.CompareFiles(objectField.PreviousData, (string)objectField.Data))
355
{
356
targetRow[i] = objectField.PreviousData;
357
modifiedRow = true;
@@ -438,29 +436,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
436
}
437
}
438
441
- private bool CompareFiles(string targetFile, string updatedFile)
442
- {
443
- bool? compared = null;
444
- foreach (var extension in this.Extensions)
445
- {
446
- compared = extension.CompareFiles(targetFile, updatedFile);
447
- if (compared.HasValue)
448
- {
449
- break;
450
- }
451
- }
452
-
453
- if (!compared.HasValue)
454
- {
455
- throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
456
- }
457
-
458
- return compared.Value;
459
- }
460
-
439
private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns)
440
{
463
- var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.Extensions, output, outputPath, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true );
441
+ var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true);
442
command.Execute();
443
}
444
}
src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs
+1
-1
@@ -33,7 +33,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
33
var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false;
34
var apiPatchingSymbolFlags = (PatchSymbolFlagsType)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0);
35
36
-#if TODO_PATCHING
36
+#if TODO_PATCHING_DELTA
37
foreach (FileFacade facade in this.FileFacades)
38
{
39
if (RowOperation.Modify == facade.File.Operation &&
src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
+2
-1
@@ -499,7 +499,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
499
row.FileSize = tuple.FileSize;
500
row.Version = tuple.Version;
501
row.Language = tuple.Language;
502
- row.DiskId = tuple.DiskId ?? 1; // TODO: is 0 the correct thing to default here
502
+ row.DiskId = tuple.DiskId ?? 1; // TODO: is 1 the correct thing to default here
503
+ row.Sequence = tuple.Sequence;
504
row.Source = tuple.Source.Path;
505
506
var attributes = (tuple.Attributes & FileTupleAttributes.Checksum) == FileTupleAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0;
src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs
+1
-1
@@ -45,7 +45,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
45
var baselineData = this.GetData(tuple.BaselineFile.Path);
46
var updateData = this.GetData(tuple.UpdateFile.Path);
47
48
- var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, false);
48
+ var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false);
49
transform = command.Execute();
50
}
51
else
src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs
new
+71
@@ -0,0 +1,71 @@
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
+
3
+namespace WixToolset.Core.WindowsInstaller.Bind
4
+{
5
+ using System.Collections.Generic;
6
+ using System.IO;
7
+ using WixToolset.Extensibility;
8
+
9
+ internal class FileSystemManager
10
+ {
11
+ public FileSystemManager(IEnumerable<IFileSystemExtension> fileSystemExtensions)
12
+ {
13
+ this.Extensions = fileSystemExtensions;
14
+ }
15
+
16
+ private IEnumerable<IFileSystemExtension> Extensions { get; }
17
+
18
+ public bool CompareFiles(string firstPath, string secondPath)
19
+ {
20
+ foreach (var extension in this.Extensions)
21
+ {
22
+ var compared = extension.CompareFiles(firstPath, secondPath);
23
+ if (compared.HasValue)
24
+ {
25
+ return compared.Value;
26
+ }
27
+ }
28
+
29
+ return BuiltinCompareFiles(firstPath, secondPath);
30
+ }
31
+
32
+ private static bool BuiltinCompareFiles(string firstPath, string secondPath)
33
+ {
34
+ using (var firstStream = File.OpenRead(firstPath))
35
+ using (var secondStream = File.OpenRead(secondPath))
36
+ {
37
+ if (firstStream.Length != secondStream.Length)
38
+ {
39
+ return false;
40
+ }
41
+
42
+ // Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance.
43
+ // The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined.
44
+ var firstBuffer = new byte[16 * 1024];
45
+ var secondBuffer = new byte[16 * 1024];
46
+
47
+ var firstReadLength = 0;
48
+ do
49
+ {
50
+ firstReadLength = firstStream.Read(firstBuffer, 0, firstBuffer.Length);
51
+ var secondReadLength = secondStream.Read(secondBuffer, 0, secondBuffer.Length);
52
+
53
+ if (firstReadLength != secondReadLength)
54
+ {
55
+ return false;
56
+ }
57
+
58
+ for (var i = 0; i < firstReadLength; ++i)
59
+ {
60
+ if (firstBuffer[i] != secondBuffer[i])
61
+ {
62
+ return false;
63
+ }
64
+ }
65
+ } while (0 < firstReadLength);
66
+ }
67
+
68
+ return true;
69
+ }
70
+ }
71
+}
src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
+4
-5
@@ -10,17 +10,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
10
using WixToolset.Core.WindowsInstaller.Msi;
11
using WixToolset.Data;
12
using WixToolset.Data.WindowsInstaller;
13
- using WixToolset.Extensibility;
13
using WixToolset.Extensibility.Data;
14
using WixToolset.Extensibility.Services;
15
16
internal class GenerateDatabaseCommand
17
{
19
- public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IFileSystemExtension> fileSystemExtensions, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, int codepage, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory)
18
+ public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, int codepage, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory)
19
{
20
this.Messaging = messaging;
21
this.BackendHelper = backendHelper;
23
- this.Extensions = fileSystemExtensions;
22
+ this.FileSystemManager = fileSystemManager;
23
this.Data = data;
24
this.OutputPath = outputPath;
25
this.TableDefinitions = tableDefinitions;
@@ -35,7 +34,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
34
35
private IBackendHelper BackendHelper { get; }
36
38
- private IEnumerable<IFileSystemExtension> Extensions { get; }
37
+ private FileSystemManager FileSystemManager { get; }
38
39
/// <summary>
40
/// Whether to keep columns added in a transform.
@@ -371,7 +370,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
370
var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst"));
371
372
// Bind the transform.
374
- var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.Extensions, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions);
373
+ var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions);
374
command.Execute();
375
376
if (this.Messaging.EncounteredError)
src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs
+5
-8
@@ -10,7 +10,6 @@ namespace WixToolset.Core.WindowsInstaller
10
using WixToolset.Data.Tuples;
11
using WixToolset.Data.WindowsInstaller;
12
using WixToolset.Data.WindowsInstaller.Rows;
13
- using WixToolset.Extensibility;
13
using WixToolset.Extensibility.Services;
14
15
/// <summary>
@@ -25,11 +24,12 @@ namespace WixToolset.Core.WindowsInstaller
24
/// <summary>
25
/// Instantiates a new Differ class.
26
/// </summary>
28
- public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool showPedanticMessages)
27
+ public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool preserveUnchangedRows, bool showPedanticMessages)
28
{
29
this.messaging = messaging;
30
this.TargetOutput = targetOutput;
31
this.UpdatedOutput = updatedOutput;
32
+ this.PreserveUnchangedRows = preserveUnchangedRows;
33
this.ShowPedanticMessages = showPedanticMessages;
34
}
35
@@ -111,10 +111,10 @@ namespace WixToolset.Core.WindowsInstaller
111
}
112
else if (TableOperation.None == operation)
113
{
114
- var modified = transform.EnsureTable(updatedTable.Definition);
114
+ var modifiedTable = transform.EnsureTable(updatedTable.Definition);
115
foreach (var row in rows)
116
{
117
- modified.Rows.Add(row);
117
+ modifiedTable.Rows.Add(row);
118
}
119
}
120
}
@@ -242,10 +242,7 @@ namespace WixToolset.Core.WindowsInstaller
242
{
243
var columnDefinition = updatedRow.Fields[i].Column;
244
245
- if (columnDefinition.Unreal)
246
- {
247
- }
248
- else if (!columnDefinition.PrimaryKey)
245
+ if (!columnDefinition.PrimaryKey)
246
{
247
var modified = false;
248
src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs
+6
-10
@@ -26,28 +26,26 @@ namespace WixToolset.Core.WindowsInstaller.Bind
26
var facades = new List<FileFacade>();
27
28
var assemblyFile = this.Section.Tuples.OfType<AssemblyTuple>().ToDictionary(t => t.Id.Id);
29
- //var wixFiles = this.Section.Tuples.OfType<WixFileTuple>().ToDictionary(t => t.Id.Id);
29
//var deltaPatchFiles = this.Section.Tuples.OfType<WixDeltaPatchFileTuple>().ToDictionary(t => t.Id.Id);
30
31
foreach (var file in this.Section.Tuples.OfType<FileTuple>())
32
{
34
- //var wixFile = wixFiles[file.Id.Id];
33
+ assemblyFile.TryGetValue(file.Id.Id, out var assembly);
34
35
//deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile);
36
38
- //facades.Add(new FileFacade(file, wixFile, deltaPatchFile));
39
-
40
- assemblyFile.TryGetValue(file.Id.Id, out var assembly);
41
-
37
facades.Add(new FileFacade(file, assembly));
38
+ //facades.Add(new FileFacade(file, wixFile, deltaPatchFile));
39
}
40
45
- //this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
41
+#if TODO_PATCHING_DELTA
42
+ this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
43
+#endif
44
45
this.FileFacades = facades;
46
}
47
50
-#if FIX_THIS
48
+#if TODO_PATCHING_DELTA
49
/// <summary>
50
/// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows.
51
/// </summary>
@@ -132,7 +130,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
130
file.SymbolPaths = String.Concat(file.SymbolPaths, ";", row.SymbolPaths);
131
}
132
135
-#if TODO_PATCHING
133
Field field = row.Fields[2];
134
if (null != field.PreviousData)
135
{
@@ -145,7 +142,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
142
file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData);
143
}
144
}
148
-#endif
145
}
146
#endif
147
}
src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs
+80
-496
@@ -4,12 +4,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
4
{
5
using System;
6
using System.Collections.Generic;
7
- using System.Diagnostics;
8
- using System.IO;
7
using System.Linq;
8
using WixToolset.Core.Bind;
9
using WixToolset.Data;
12
- using WixToolset.Data.Tuples;
10
using WixToolset.Data.WindowsInstaller;
11
using WixToolset.Data.WindowsInstaller.Rows;
12
using WixToolset.Extensibility;
@@ -17,90 +14,39 @@ namespace WixToolset.Core.WindowsInstaller.Bind
14
15
internal class GetFileFacadesFromTransforms
16
{
20
- public GetFileFacadesFromTransforms(IMessaging messaging, IEnumerable<SubStorage> subStorages, TableDefinitionCollection tableDefinitions)
17
+ public GetFileFacadesFromTransforms(IMessaging messaging, FileSystemManager fileSystemManager, IEnumerable<SubStorage> subStorages)
18
{
19
this.Messaging = messaging;
20
+ this.FileSystemManager = fileSystemManager;
21
this.SubStorages = subStorages;
24
- this.TableDefinitions = tableDefinitions;
22
}
23
27
- public IEnumerable<IFileSystemExtension> Extensions { get; }
28
-
24
private IMessaging Messaging { get; }
25
31
- public IEnumerable<SubStorage> SubStorages { get; }
26
+ private FileSystemManager FileSystemManager { get; }
27
33
- private TableDefinitionCollection TableDefinitions { get; }
28
+ private IEnumerable<SubStorage> SubStorages { get; }
29
30
public IEnumerable<FileFacade> FileFacades { get; private set; }
31
32
public void Execute()
33
{
34
var allFileRows = new List<FileFacade>();
40
- var copyToPatch = (allFileRows != null);
41
-#if TODO_PATCHING
42
- var patchMediaRows = new RowDictionary<MediaRow>();
35
44
- var patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>();
36
+ var patchMediaRows = new RowDictionary<MediaRow>();
37
46
- var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
47
- var patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]);
38
+ var patchMediaFileRows = new Dictionary<int, RowDictionary<FileRow>>();
39
49
- if (copyFromPatch)
50
- {
51
- // index patch files by diskId+fileId
52
- foreach (WixFileRow patchFileRow in patchFileTable.Rows)
53
- {
54
- int diskId = patchFileRow.DiskId;
55
- if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows))
56
- {
57
- mediaFileRows = new RowDictionary<WixFileRow>();
58
- patchMediaFileRows.Add(diskId, mediaFileRows);
59
- }
60
-
61
- mediaFileRows.Add(patchFileRow);
62
- }
63
-
64
- var patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]);
65
- patchMediaRows = new RowDictionary<MediaRow>(patchMediaTable);
66
- }
40
+ //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
41
42
// Index paired transforms by name without their "#" prefix.
69
- var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name.Substring(1), s => s.Data);
43
+ var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data);
44
71
- foreach (var substorage in this.SubStorages)
45
+ // Enumerate through main transforms.
46
+ foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#")))
47
{
73
- if (substorage.Name.StartsWith("#"))
74
- {
75
- // Skip paired transforms.
76
- continue;
77
- }
78
-
48
var mainTransform = substorage.Data;
80
- var mainWixFiles = new RowDictionary<WixFileRow>(mainTransform.Tables["WixFile"]);
81
- var mainMsiFileHashIndex = new RowDictionary<Row>(mainTransform.Tables["MsiFileHash"]);
82
-
49
var mainFileTable = mainTransform.Tables["File"];
84
- var pairedTransform = pairedTransforms[substorage.Name];
85
-
86
- // copy Media.LastSequence and index the MsiFileHash table if it exists.
87
- if (copyFromPatch)
88
- {
89
- var pairedMediaTable = pairedTransform.Tables["Media"];
90
- foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
91
- {
92
- var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
93
- pairedMediaRow.Fields[1] = patchMediaRow.Fields[1];
94
- }
95
-
96
- if (null != mainMsiFileHashTable)
97
- {
98
- mainMsiFileHashIndex = new RowDictionary<Row>(mainMsiFileHashTable);
99
- }
100
-
101
- // Validate file row changes for keypath-related issues
102
- this.ValidateFileRowChanges(mainTransform);
103
- }
50
51
if (null == mainFileTable)
52
{
@@ -108,74 +54,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind
54
}
55
56
// Index File table of pairedTransform
57
+ var pairedTransform = pairedTransforms["#" + substorage.Name];
58
var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]);
59
113
- foreach (FileRow mainFileRow in mainFileTable.Rows)
60
+ foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete))
61
{
115
- if (RowOperation.Delete == mainFileRow.Operation)
116
- {
117
- continue;
118
- }
119
- else if (RowOperation.None == mainFileRow.Operation)
120
- {
121
- continue;
122
- }
62
+ var mainFileId = mainFileRow.File;
63
124
- var mainWixFileRow = mainWixFiles.Get(mainFileRow.File);
64
+ // We need compare the underlying files and include all file changes.
65
+ var objectField = (ObjectField)mainFileRow.Fields[9];
66
+ var pairedFileRow = pairedFileRows.Get(mainFileId);
67
126
- if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes.
68
+ // If the file is new, we always need to add it to the patch.
69
+ if (mainFileRow.Operation == RowOperation.Add)
70
{
128
- var objectField = (ObjectField)mainWixFileRow.Fields[6];
129
- var pairedFileRow = pairedFileRows.Get(mainFileRow.File);
130
-
131
- // If the file is new, we always need to add it to the patch.
132
- if (mainFileRow.Operation != RowOperation.Add)
133
- {
134
- // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare.
135
- if (null == objectField.PreviousData)
136
- {
137
- if (mainFileRow.Operation == RowOperation.None)
138
- {
139
- continue;
140
- }
141
- }
142
- else
143
- {
144
- // TODO: should this entire condition be placed in the binder file manager?
145
- if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&
146
- !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString()))
147
- {
148
- // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified.
149
- mainFileRow.Operation = RowOperation.Modify;
150
- if (null != pairedFileRow)
151
- {
152
- // Always patch-added, but never non-compressed.
153
- pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
154
- pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
155
- pairedFileRow.Fields[6].Modified = true;
156
- pairedFileRow.Operation = RowOperation.Modify;
157
- }
158
- }
159
- else
160
- {
161
- // The File is same. We need mark all the attributes as unchanged.
162
- mainFileRow.Operation = RowOperation.None;
163
- foreach (var field in mainFileRow.Fields)
164
- {
165
- field.Modified = false;
166
- }
167
-
168
- if (null != pairedFileRow)
169
- {
170
- pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
171
- pairedFileRow.Fields[6].Modified = false;
172
- pairedFileRow.Operation = RowOperation.None;
173
- }
174
- continue;
175
- }
176
- }
177
- }
178
- else if (null != pairedFileRow) // RowOperation.Add
71
+ if (null != pairedFileRow) // RowOperation.Add
72
{
73
// Always patch-added, but never non-compressed.
74
pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
@@ -184,402 +77,93 @@ namespace WixToolset.Core.WindowsInstaller.Bind
77
pairedFileRow.Operation = RowOperation.Add;
78
}
79
}
187
-
188
- // index patch files by diskId+fileId
189
- int diskId = mainWixFileRow.DiskId;
190
-
191
- if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows))
192
- {
193
- mediaFileRows = new RowDictionary<WixFileRow>();
194
- patchMediaFileRows.Add(diskId, mediaFileRows);
195
- }
196
-
197
- var fileId = mainFileRow.File;
198
- var patchFileRow = mediaFileRows.Get(fileId);
199
- if (copyToPatch)
80
+ else
81
{
201
- if (null == patchFileRow)
202
- {
203
- var patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
204
- patchActualFileRow.CopyFrom(mainFileRow);
205
-
206
- patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
207
- patchFileRow.CopyFrom(mainWixFileRow);
208
-
209
- mediaFileRows.Add(patchFileRow);
210
-
211
- allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right?
212
- }
213
- else
82
+ // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare.
83
+ if (null == objectField.PreviousData)
84
{
215
- // TODO: confirm the rest of data is identical?
216
-
217
- // make sure Source is same. Otherwise we are silently ignoring a file.
218
- if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase))
85
+ if (mainFileRow.Operation == RowOperation.None)
86
{
220
- this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source));
87
+ continue;
88
}
222
-
223
- // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow.
224
- patchFileRow.AppendPreviousDataFrom(mainWixFileRow);
225
- }
226
- }
227
- //else
228
- //{
229
- // // copy data from the patch back to the transform
230
- // if (null != patchFileRow)
231
- // {
232
- // var pairedFileRow = pairedFileRows.Get(fileId);
233
- // for (var i = 0; i < patchFileRow.Fields.Length; i++)
234
- // {
235
- // var patchValue = patchFileRow[i] == null ? String.Empty : patchFileRow.FieldAsString(i);
236
- // var mainValue = mainFileRow[i] == null ? String.Empty : mainFileRow.FieldAsString(i);
237
-
238
- // if (1 == i)
239
- // {
240
- // // File.Component_ changes should not come from the shared file rows
241
- // // that contain the file information as each individual transform might
242
- // // have different changes (or no changes at all).
243
- // }
244
- // // File.Attributes should not changed for binary deltas
245
- // else if (6 == i)
246
- // {
247
- // if (null != patchFileRow.Patch)
248
- // {
249
- // // File.Attribute should not change for binary deltas
250
- // pairedFileRow.Attributes = mainFileRow.Attributes;
251
- // mainFileRow.Fields[i].Modified = false;
252
- // }
253
- // }
254
- // // File.Sequence is updated in pairedTransform, not mainTransform
255
- // else if (7 == i)
256
- // {
257
- // // file sequence is updated in Patch table instead of File table for delta patches
258
- // if (null != patchFileRow.Patch)
259
- // {
260
- // pairedFileRow.Fields[i].Modified = false;
261
- // }
262
- // else
263
- // {
264
- // pairedFileRow[i] = patchFileRow[i];
265
- // pairedFileRow.Fields[i].Modified = true;
266
- // }
267
- // mainFileRow.Fields[i].Modified = false;
268
- // }
269
- // else if (patchValue != mainValue)
270
- // {
271
- // mainFileRow[i] = patchFileRow[i];
272
- // mainFileRow.Fields[i].Modified = true;
273
- // if (mainFileRow.Operation == RowOperation.None)
274
- // {
275
- // mainFileRow.Operation = RowOperation.Modify;
276
- // }
277
- // }
278
- // }
279
-
280
- // // copy MsiFileHash row for this File
281
- // if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow))
282
- // {
283
- // patchHashRow = patchFileRow.Hash;
284
- // }
285
-
286
- // if (null != patchHashRow)
287
- // {
288
- // var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
289
- // var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
290
- // for (var i = 0; i < patchHashRow.Fields.Length; i++)
291
- // {
292
- // mainHashRow[i] = patchHashRow[i];
293
- // if (i > 1)
294
- // {
295
- // // assume all hash fields have been modified
296
- // mainHashRow.Fields[i].Modified = true;
297
- // }
298
- // }
299
-
300
- // // assume the MsiFileHash operation follows the File one
301
- // mainHashRow.Operation = mainFileRow.Operation;
302
- // }
303
-
304
- // // copy MsiAssemblyName rows for this File
305
- // List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
306
- // if (null != patchAssemblyNameRows)
307
- // {
308
- // var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
309
- // foreach (var patchAssemblyNameRow in patchAssemblyNameRows)
310
- // {
311
- // // Copy if there isn't an identical modified/added row already in the transform.
312
- // var foundMatchingModifiedRow = false;
313
- // foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows)
314
- // {
315
- // if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
316
- // {
317
- // foundMatchingModifiedRow = true;
318
- // break;
319
- // }
320
- // }
321
-
322
- // if (!foundMatchingModifiedRow)
323
- // {
324
- // var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
325
- // for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
326
- // {
327
- // mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
328
- // }
329
-
330
- // // assume value field has been modified
331
- // mainAssemblyNameRow.Fields[2].Modified = true;
332
- // mainAssemblyNameRow.Operation = mainFileRow.Operation;
333
- // }
334
- // }
335
- // }
336
-
337
- // // Add patch header for this file
338
- // if (null != patchFileRow.Patch)
339
- // {
340
- // // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
341
- // this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
342
- // this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
343
-
344
- // // Add to Patch table
345
- // var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
346
- // if (0 == patchTable.Rows.Count)
347
- // {
348
- // patchTable.Operation = TableOperation.Add;
349
- // }
350
-
351
- // var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
352
- // patchRow[0] = patchFileRow.File;
353
- // patchRow[1] = patchFileRow.Sequence;
354
-
355
- // var patchFile = new FileInfo(patchFileRow.Source);
356
- // patchRow[2] = (int)patchFile.Length;
357
- // patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1;
358
-
359
- // var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
360
- // if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length)
361
- // {
362
- // streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_');
363
-
364
- // var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
365
- // if (0 == patchHeadersTable.Rows.Count)
366
- // {
367
- // patchHeadersTable.Operation = TableOperation.Add;
368
- // }
369
-
370
- // var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
371
- // patchHeadersRow[0] = streamName;
372
- // patchHeadersRow[1] = patchFileRow.Patch;
373
- // patchRow[5] = streamName;
374
- // patchHeadersRow.Operation = RowOperation.Add;
375
- // }
376
- // else
377
- // {
378
- // patchRow[4] = patchFileRow.Patch;
379
- // }
380
- // patchRow.Operation = RowOperation.Add;
381
- // }
382
- // }
383
- // else
384
- // {
385
- // // TODO: throw because all transform rows should have made it into the patch
386
- // }
387
- //}
388
- }
389
- }
390
-#endif
391
- this.FileFacades = allFileRows;
392
- }
393
-
394
- /// <summary>
395
- /// Adds the PatchFiles action to the sequence table if it does not already exist.
396
- /// </summary>
397
- /// <param name="table">The sequence table to check or modify.</param>
398
- /// <param name="mainTransform">The primary authoring transform.</param>
399
- /// <param name="pairedTransform">The secondary patch transform.</param>
400
- /// <param name="mainFileRow">The file row that contains information about the patched file.</param>
401
- private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow)
402
- {
403
- var tableName = table.ToString();
404
-
405
- // Find/add PatchFiles action (also determine sequence for it).
406
- // Search mainTransform first, then pairedTransform (pairedTransform overrides).
407
- var hasPatchFilesAction = false;
408
- var installFilesSequence = 0;
409
- var duplicateFilesSequence = 0;
410
-
411
- TestSequenceTableForPatchFilesAction(
412
- mainTransform.Tables[tableName],
413
- ref hasPatchFilesAction,
414
- ref installFilesSequence,
415
- ref duplicateFilesSequence);
416
- TestSequenceTableForPatchFilesAction(
417
- pairedTransform.Tables[tableName],
418
- ref hasPatchFilesAction,
419
- ref installFilesSequence,
420
- ref duplicateFilesSequence);
421
- if (!hasPatchFilesAction)
422
- {
423
- WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionTuple);
424
-
425
- var sequence = patchFilesActionTuple.Sequence;
426
-
427
- // Test for default sequence value's appropriateness
428
- if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence))
429
- {
430
- if (0 != duplicateFilesSequence)
431
- {
432
- if (duplicateFilesSequence < installFilesSequence)
433
- {
434
- throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action));
89
}
90
else
91
{
438
- sequence = (duplicateFilesSequence + installFilesSequence) / 2;
439
- if (installFilesSequence == sequence || duplicateFilesSequence == sequence)
92
+ // TODO: should this entire condition be placed in the binder file manager?
93
+ if (/*(0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&*/
94
+ !this.FileSystemManager.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString()))
95
{
441
- throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action));
96
+ // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified.
97
+ mainFileRow.Operation = RowOperation.Modify;
98
+ if (null != pairedFileRow)
99
+ {
100
+ // Always patch-added, but never non-compressed.
101
+ pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
102
+ pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
103
+ pairedFileRow.Fields[6].Modified = true;
104
+ pairedFileRow.Operation = RowOperation.Modify;
105
+ }
106
+ }
107
+ else
108
+ {
109
+ // The File is same. We need mark all the attributes as unchanged.
110
+ mainFileRow.Operation = RowOperation.None;
111
+ foreach (var field in mainFileRow.Fields)
112
+ {
113
+ field.Modified = false;
114
+ }
115
+
116
+ if (null != pairedFileRow)
117
+ {
118
+ pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
119
+ pairedFileRow.Fields[6].Modified = false;
120
+ pairedFileRow.Operation = RowOperation.None;
121
+ }
122
+ continue;
123
}
124
}
125
}
445
- else
446
- {
447
- sequence = installFilesSequence + 1;
448
- }
449
- }
126
451
- var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]);
452
- if (0 == sequenceTable.Rows.Count)
453
- {
454
- sequenceTable.Operation = TableOperation.Add;
455
- }
456
-
457
- var patchAction = sequenceTable.CreateRow(null);
458
- patchAction[0] = patchFilesActionTuple.Action;
459
- patchAction[1] = patchFilesActionTuple.Condition;
460
- patchAction[2] = sequence;
461
- patchAction.Operation = RowOperation.Add;
462
- }
463
- }
127
+ // index patch files by diskId+fileId
128
+ var diskId = mainFileRow.DiskId;
129
465
- /// <summary>
466
- /// Tests sequence table for PatchFiles and associated actions
467
- /// </summary>
468
- /// <param name="sequenceTable">The table to test.</param>
469
- /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param>
470
- /// <param name="installFilesSequence">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param>
471
- /// <param name="duplicateFilesSequence">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param>
472
- private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence)
473
- {
474
- if (null != sequenceTable)
475
- {
476
- foreach (var row in sequenceTable.Rows)
477
- {
478
- var actionName = row.FieldAsString(0);
479
- switch (actionName)
130
+ if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows))
131
{
481
- case "PatchFiles":
482
- hasPatchFilesAction = true;
483
- break;
484
-
485
- case "InstallFiles":
486
- installFilesSequence = row.FieldAsInteger(2);
487
- break;
488
-
489
- case "DuplicateFiles":
490
- duplicateFilesSequence = row.FieldAsInteger(2);
491
- break;
132
+ mediaFileRows = new RowDictionary<FileRow>();
133
+ patchMediaFileRows.Add(diskId, mediaFileRows);
134
}
493
- }
494
- }
495
- }
496
-
497
- /// <summary>
498
- /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component.
499
- /// </summary>
500
- /// <param name="output">The output to validate.</param>
501
- private void ValidateFileRowChanges(WindowsInstallerData transform)
502
- {
503
- var componentTable = transform.Tables["Component"];
504
- var fileTable = transform.Tables["File"];
505
-
506
- // There's no sense validating keypaths if the transform has no component or file table
507
- if (componentTable == null || fileTable == null)
508
- {
509
- return;
510
- }
135
512
- var componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
513
-
514
- // Index the Component table for non-directory & non-registry key paths.
515
- foreach (var row in componentTable.Rows)
516
- {
517
- var keyPath = row.FieldAsString(5);
518
- if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath))
519
- {
520
- componentKeyPath.Add(row.FieldAsString(0), keyPath);
521
- }
522
- }
136
+ var patchFileRow = mediaFileRows.Get(mainFileId);
137
524
- var componentWithChangedKeyPath = new Dictionary<string, string>();
525
- var componentWithNonKeyPathChanged = new Dictionary<string, string>();
526
- // Verify changes in the file table, now that file diffing has occurred
527
- foreach (FileRow row in fileTable.Rows)
528
- {
529
- if (RowOperation.Modify != row.Operation)
530
- {
531
- continue;
532
- }
138
+ if (null == patchFileRow)
139
+ {
140
+ //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
141
+ patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers);
142
+ mainFileRow.CopyTo(patchFileRow);
143
534
- var fileId = row.FieldAsString(0);
535
- var componentId = row.FieldAsString(1);
144
+ mediaFileRows.Add(patchFileRow);
145
537
- // If this file is the keypath of a component
538
- if (componentKeyPath.ContainsValue(fileId))
539
- {
540
- if (!componentWithChangedKeyPath.ContainsKey(componentId))
541
- {
542
- componentWithChangedKeyPath.Add(componentId, fileId);
146
+ allFileRows.Add(new FileFacade(patchFileRow)); // TODO: should we be passing along delta information? Probably, right?
147
}
544
- }
545
- else
546
- {
547
- if (!componentWithNonKeyPathChanged.ContainsKey(componentId))
148
+ else
149
{
549
- componentWithNonKeyPathChanged.Add(componentId, fileId);
550
- }
551
- }
552
- }
553
-
554
- foreach (var componentFile in componentWithNonKeyPathChanged)
555
- {
556
- // Make sure all changes to non keypath files also had a change in the keypath.
557
- if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath))
558
- {
559
- this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath));
560
- }
561
- }
562
- }
150
+ // TODO: confirm the rest of data is identical?
151
564
- private bool CompareFiles(string targetFile, string updatedFile)
565
- {
566
- bool? compared = null;
567
- foreach (var extension in this.Extensions)
568
- {
569
- compared = extension.CompareFiles(targetFile, updatedFile);
152
+ // make sure Source is same. Otherwise we are silently ignoring a file.
153
+ if (0 != String.Compare(patchFileRow.Source, mainFileRow.Source, StringComparison.OrdinalIgnoreCase))
154
+ {
155
+ this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, mainFileId, patchFileRow.Source, mainFileRow.Source));
156
+ }
157
571
- if (compared.HasValue)
572
- {
573
- break;
158
+#if TODO_PATCHING_DELTA
159
+ // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow.
160
+ patchFileRow.AppendPreviousDataFrom(mainFileRow);
161
+#endif
162
+ }
163
}
164
}
165
577
- if (!compared.HasValue)
578
- {
579
- throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
580
- }
581
-
582
- return compared.Value;
166
+ this.FileFacades = allFileRows;
167
}
168
}
169
}
src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+43
-16
@@ -12,6 +12,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
12
using WixToolset.Core.Native;
13
using WixToolset.Core.WindowsInstaller.Msi;
14
using WixToolset.Data;
15
+ using WixToolset.Data.Tuples;
16
using WixToolset.Data.WindowsInstaller;
17
using WixToolset.Data.WindowsInstaller.Rows;
18
using WixToolset.Extensibility.Services;
@@ -34,7 +35,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
35
36
public string OutputPath { private get; set; }
37
37
- public IEnumerable<string> SuppressedTableNames { private get; set; }
38
+ public TableDefinitionCollection TableDefinitions { private get; set; }
39
40
public string IntermediateFolder { private get; set; }
41
@@ -49,6 +50,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
50
return;
51
}
52
53
+ var suppressedTableNames = this.AddBackSuppresedSequenceTables();
54
+
55
IMsmMerge2 merge = null;
56
bool commit = true;
57
bool logOpen = false;
@@ -212,9 +215,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
215
return;
216
}
217
215
- using (Database db = new Database(this.OutputPath, OpenDatabase.Direct))
218
+ using (var db = new Database(this.OutputPath, OpenDatabase.Direct))
219
{
217
- Table suppressActionTable = this.Output.Tables["WixSuppressAction"];
220
+ var suppressActionTable = this.Output.Tables["WixSuppressAction"];
221
222
// suppress individual actions
223
if (null != suppressActionTable)
@@ -239,40 +242,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind
242
}
243
244
// query for merge module actions in suppressed sequences and drop them
242
- foreach (string tableName in this.SuppressedTableNames)
245
+ foreach (var tableName in suppressedTableNames)
246
{
247
if (!db.TableExists(tableName))
248
{
249
continue;
250
}
251
249
- using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
252
+ using (var view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
253
{
251
- foreach (Record resultRecord in view.Records)
254
+ foreach (var resultRecord in view.Records)
255
{
256
this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName));
257
}
258
}
259
260
// drop suppressed sequences
258
- using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName)))
261
+ using (var view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName)))
262
{
263
}
264
265
// delete the validation rows
263
- using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?")))
266
+ using (var view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?")))
267
+ using (var record = new Record(1))
268
{
265
- using (Record record = new Record(1))
266
- {
267
- record.SetString(1, tableName);
268
- view.Execute(record);
269
- }
269
+ record.SetString(1, tableName);
270
+ view.Execute(record);
271
}
272
}
273
274
// now update the Attributes column for the files from the Merge Modules
275
this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles());
275
- using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
276
+ using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
277
{
278
foreach (var file in this.FileFacades)
279
{
@@ -281,13 +282,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
282
continue;
283
}
284
284
- using (Record record = new Record(1))
285
+ using (var record = new Record(1))
286
{
287
record.SetString(1, file.Id);
288
view.Execute(record);
289
}
290
290
- using (Record recordUpdate = view.Fetch())
291
+ using (var recordUpdate = view.Fetch())
292
{
293
if (null == recordUpdate)
294
{
@@ -332,5 +333,31 @@ namespace WixToolset.Core.WindowsInstaller.Bind
333
db.Commit();
334
}
335
}
336
+
337
+ private IEnumerable<string> AddBackSuppresedSequenceTables()
338
+ {
339
+ // Add back possibly suppressed sequence tables since all sequence tables must be present
340
+ // for the merge process to work. We'll drop the suppressed sequence tables again as
341
+ // necessary.
342
+ var suppressedTableNames = new HashSet<string>();
343
+
344
+ foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
345
+ {
346
+ var sequenceTableName = (sequence == SequenceTable.AdvertiseExecuteSequence) ? "AdvtExecuteSequence" : sequence.ToString();
347
+ var sequenceTable = this.Output.Tables[sequenceTableName];
348
+
349
+ if (null == sequenceTable)
350
+ {
351
+ sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]);
352
+ }
353
+
354
+ if (0 == sequenceTable.Rows.Count)
355
+ {
356
+ suppressedTableNames.Add(sequenceTableName);
357
+ }
358
+ }
359
+
360
+ return suppressedTableNames;
361
+ }
362
}
363
}
src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs
-227
@@ -2,14 +2,7 @@
2
3
namespace WixToolset.Core.WindowsInstaller.Bind
4
{
5
- using System;
6
- using System.Collections;
7
- using System.Globalization;
8
- using System.Text;
9
- using System.Text.RegularExpressions;
10
- using WixToolset.Data;
5
using WixToolset.Data.WindowsInstaller;
12
- using WixToolset.Extensibility;
6
7
internal class PatchTransform
8
{
@@ -22,225 +15,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind
15
public string Baseline { get; }
16
17
public WindowsInstallerData Transform { get; }
25
-
26
- /// <summary>
27
- /// Validates that the differences in the transform are valid for patch transforms.
28
- /// </summary>
29
- public void Validate()
30
- {
31
-#if TODO_PATCHING
32
- // Changing the ProdocutCode in a patch transform is not recommended.
33
- Table propertyTable = this.Transform.Tables["Property"];
34
- if (null != propertyTable)
35
- {
36
- foreach (Row row in propertyTable.Rows)
37
- {
38
- // Only interested in modified rows; fast check.
39
- if (RowOperation.Modify == row.Operation)
40
- {
41
- if (0 == String.CompareOrdinal("ProductCode", (string)row[0]))
42
- {
43
- this.OnMessage(WixWarnings.MajorUpgradePatchNotRecommended());
44
- }
45
- }
46
- }
47
- }
48
-
49
- // If there is nothing in the component table we can return early because the remaining checks are component based.
50
- Table componentTable = this.Transform.Tables["Component"];
51
- if (null == componentTable)
52
- {
53
- return;
54
- }
55
-
56
- // Index Feature table row operations
57
- Table featureTable = this.Transform.Tables["Feature"];
58
- Table featureComponentsTable = this.Transform.Tables["FeatureComponents"];
59
- Hashtable featureOps = null;
60
- if (null != featureTable)
61
- {
62
- int capacity = featureTable.Rows.Count;
63
- featureOps = new Hashtable(capacity);
64
-
65
- foreach (Row row in featureTable.Rows)
66
- {
67
- featureOps[(string)row[0]] = row.Operation;
68
- }
69
- }
70
- else
71
- {
72
- featureOps = new Hashtable();
73
- }
74
-
75
- // Index Component table and check for keypath modifications
76
- Hashtable deletedComponent = new Hashtable();
77
- Hashtable componentKeyPath = new Hashtable();
78
- foreach (Row row in componentTable.Rows)
79
- {
80
- string id = row.Fields[0].Data.ToString();
81
- string keypath = (null == row.Fields[5].Data) ? String.Empty : row.Fields[5].Data.ToString();
82
-
83
- componentKeyPath.Add(id, keypath);
84
- if (RowOperation.Delete == row.Operation)
85
- {
86
- deletedComponent.Add(id, row);
87
- }
88
- else if (RowOperation.Modify == row.Operation)
89
- {
90
- if (row.Fields[1].Modified)
91
- {
92
- // Changing the guid of a component is equal to deleting the old one and adding a new one.
93
- deletedComponent.Add(id, row);
94
- }
95
-
96
- // If the keypath is modified its an error
97
- if (row.Fields[5].Modified)
98
- {
99
- this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, id, this.transformPath));
100
- }
101
- }
102
- }
103
-
104
- // Verify changes in the file table
105
- Table fileTable = this.Transform.Tables["File"];
106
- if (null != fileTable)
107
- {
108
- Hashtable componentWithChangedKeyPath = new Hashtable();
109
- foreach (Row row in fileTable.Rows)
110
- {
111
- if (RowOperation.None != row.Operation)
112
- {
113
- string fileId = row.Fields[0].Data.ToString();
114
- string componentId = row.Fields[1].Data.ToString();
115
-
116
- // If this file is the keypath of a component
117
- if (String.Equals((string)componentKeyPath[componentId], fileId, StringComparison.Ordinal))
118
- {
119
- if (row.Fields[2].Modified)
120
- {
121
- // You cant change the filename of a file that is the keypath of a component.
122
- this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, componentId, this.transformPath));
123
- }
124
-
125
- if (!componentWithChangedKeyPath.ContainsKey(componentId))
126
- {
127
- componentWithChangedKeyPath.Add(componentId, fileId);
128
- }
129
- }
130
-
131
- if (RowOperation.Delete == row.Operation)
132
- {
133
- // If the file is removed from a component that is not deleted.
134
- if (!deletedComponent.ContainsKey(componentId))
135
- {
136
- bool foundRemoveFileEntry = false;
137
- string filename = Common.GetName((string)row[2], false, true);
138
-
139
- Table removeFileTable = this.Transform.Tables["RemoveFile"];
140
- if (null != removeFileTable)
141
- {
142
- foreach (Row removeFileRow in removeFileTable.Rows)
143
- {
144
- if (RowOperation.Delete == removeFileRow.Operation)
145
- {
146
- continue;
147
- }
148
-
149
- if (componentId == (string)removeFileRow[1])
150
- {
151
- // Check if there is a RemoveFile entry for this file
152
- if (null != removeFileRow[2])
153
- {
154
- string removeFileName = Common.GetName((string)removeFileRow[2], false, true);
155
-
156
- // Convert the MSI format for a wildcard string to Regex format.
157
- removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\.");
158
-
159
- Regex regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
160
- if (regex.IsMatch(filename))
161
- {
162
- foundRemoveFileEntry = true;
163
- break;
164
- }
165
- }
166
- }
167
- }
168
- }
169
-
170
- if (!foundRemoveFileEntry)
171
- {
172
- this.OnMessage(WixWarnings.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId));
173
- }
174
- }
175
- }
176
- }
177
- }
178
- }
179
-
180
- if (0 < deletedComponent.Count)
181
- {
182
- // Index FeatureComponents table.
183
- Hashtable featureComponents = new Hashtable();
184
-
185
- if (null != featureComponentsTable)
186
- {
187
- foreach (Row row in featureComponentsTable.Rows)
188
- {
189
- ArrayList features;
190
- string componentId = row.Fields[1].Data.ToString();
191
-
192
- if (featureComponents.Contains(componentId))
193
- {
194
- features = (ArrayList)featureComponents[componentId];
195
- }
196
- else
197
- {
198
- features = new ArrayList();
199
- featureComponents.Add(componentId, features);
200
- }
201
- features.Add(row.Fields[0].Data.ToString());
202
- }
203
- }
204
-
205
- // Check to make sure if a component was deleted, the feature was too.
206
- foreach (DictionaryEntry entry in deletedComponent)
207
- {
208
- if (featureComponents.Contains(entry.Key.ToString()))
209
- {
210
- ArrayList features = (ArrayList)featureComponents[entry.Key.ToString()];
211
- foreach (string featureId in features)
212
- {
213
- if (!featureOps.ContainsKey(featureId) || RowOperation.Delete != (RowOperation)featureOps[featureId])
214
- {
215
- // The feature was not deleted.
216
- this.OnMessage(WixErrors.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, this.transformPath));
217
- }
218
- }
219
- }
220
- }
221
- }
222
-
223
- // Warn if new components are added to existing features
224
- if (null != featureComponentsTable)
225
- {
226
- foreach (Row row in featureComponentsTable.Rows)
227
- {
228
- if (RowOperation.Add == row.Operation)
229
- {
230
- // Check if the feature is in the Feature table
231
- string feature_ = (string)row[0];
232
- string component_ = (string)row[1];
233
-
234
- // Features may not be present if not referenced
235
- if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_])
236
- {
237
- this.OnMessage(WixWarnings.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, this.transformPath));
238
- }
239
- }
240
- }
241
- }
242
-#endif
243
- throw new NotImplementedException();
244
- }
18
}
19
}
src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+7
-9
@@ -39,20 +39,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
39
40
public void Execute()
41
{
42
+ var assemblyNameTuples = this.Section.Tuples.OfType<MsiAssemblyNameTuple>().ToDictionary(t => t.Id.Id);
43
+
44
foreach (var file in this.UpdateFileFacades)
45
{
44
- this.UpdateFileFacade(file);
46
+ this.UpdateFileFacade(file, assemblyNameTuples);
47
}
48
}
49
48
- private void UpdateFileFacade(FileFacade facade)
50
+ private void UpdateFileFacade(FileFacade facade, Dictionary<string, MsiAssemblyNameTuple> assemblyNameTuples)
51
{
50
- var assemblyNameTuples = new Dictionary<string, MsiAssemblyNameTuple>();
51
- foreach (var assemblyTuple in this.Section.Tuples.OfType<MsiAssemblyNameTuple>())
52
- {
53
- assemblyNameTuples.Add(assemblyTuple.ComponentRef + "/" + assemblyTuple.Name, assemblyTuple);
54
- }
55
-
52
FileInfo fileInfo = null;
53
try
54
{
@@ -335,7 +331,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
331
var lookup = String.Concat(facade.ComponentRef, "/", name);
332
if (!assemblyNameTuples.TryGetValue(lookup, out var assemblyNameTuple))
333
{
338
- assemblyNameTuple = this.Section.AddTuple(new MsiAssemblyNameTuple(facade.SourceLineNumber)
334
+ assemblyNameTuple = this.Section.AddTuple(new MsiAssemblyNameTuple(facade.SourceLineNumber, new Identifier(AccessModifier.Private, facade.ComponentRef, name))
335
{
336
ComponentRef = facade.ComponentRef,
337
Name = name,
@@ -348,6 +344,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
344
}
345
346
facade.AssemblyNames.Add(assemblyNameTuple);
347
+
348
+ assemblyNameTuples.Add(assemblyNameTuple.Id.Id, assemblyNameTuple);
349
}
350
351
assemblyNameTuple.Value = value;
src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
+28
-33
@@ -6,62 +6,59 @@ namespace WixToolset.Core.WindowsInstaller.Bind
6
using System.Linq;
7
using WixToolset.Core.Bind;
8
using WixToolset.Data;
9
- using WixToolset.Data.WindowsInstaller;
10
- using WixToolset.Data.WindowsInstaller.Rows;
9
+ using WixToolset.Data.Tuples;
10
11
internal class UpdateMediaSequencesCommand
12
{
14
- public UpdateMediaSequencesCommand(WindowsInstallerData output, List<FileFacade> fileFacades)
13
+ public UpdateMediaSequencesCommand(IntermediateSection section, List<FileFacade> fileFacades)
14
{
16
- this.Output = output;
15
+ this.Section = section;
16
this.FileFacades = fileFacades;
17
}
18
20
- private WindowsInstallerData Output { get; }
19
+ private IntermediateSection Section { get; }
20
21
private List<FileFacade> FileFacades { get; }
22
23
public void Execute()
24
{
26
- var fileRows = new RowDictionary<FileRow>(this.Output.Tables["File"]);
27
- var mediaRows = new RowDictionary<MediaRow>(this.Output.Tables["Media"]);
25
+ var mediaRows = this.Section.Tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId);
26
27
// Calculate sequence numbers and media disk id layout for all file media information objects.
30
- if (OutputType.Module == this.Output.Type)
28
+ if (SectionType.Module == this.Section.Type)
29
{
30
var lastSequence = 0;
31
32
// Order by Component to group the files by directory.
33
var optimized = this.OptimizedFileFacades();
36
- foreach (var fileId in optimized.Select(f => f.Id))
34
+ foreach (var facade in optimized)
35
{
38
- var fileRow = fileRows.Get(fileId);
39
- fileRow.Sequence = ++lastSequence;
36
+ facade.Sequence = ++lastSequence;
37
}
38
}
39
else
40
{
41
var lastSequence = 0;
45
- MediaRow mediaRow = null;
42
+ MediaTuple mediaTuple = null;
43
var patchGroups = new Dictionary<int, List<FileFacade>>();
44
45
// sequence the non-patch-added files
46
var optimized = this.OptimizedFileFacades();
47
foreach (var facade in optimized)
48
{
52
- if (null == mediaRow)
49
+ if (null == mediaTuple)
50
{
54
- mediaRow = mediaRows.Get(facade.DiskId);
55
- if (OutputType.Patch == this.Output.Type)
51
+ mediaTuple = mediaRows[facade.DiskId];
52
+ if (SectionType.Patch == this.Section.Type)
53
{
54
// patch Media cannot start at zero
58
- lastSequence = mediaRow.LastSequence;
55
+ lastSequence = mediaTuple.LastSequence ?? 1;
56
}
57
}
61
- else if (mediaRow.DiskId != facade.DiskId)
58
+ else if (mediaTuple.DiskId != facade.DiskId)
59
{
63
- mediaRow.LastSequence = lastSequence;
64
- mediaRow = mediaRows.Get(facade.DiskId);
60
+ mediaTuple.LastSequence = lastSequence;
61
+ mediaTuple = mediaRows[facade.DiskId];
62
}
63
64
if (facade.PatchGroup.HasValue)
@@ -76,15 +73,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
73
}
74
else if (!facade.FromModule)
75
{
79
- var fileRow = fileRows.Get(facade.Id);
80
- fileRow.Sequence = ++lastSequence;
76
+ facade.Sequence = ++lastSequence;
77
}
78
}
79
84
- if (null != mediaRow)
80
+ if (null != mediaTuple)
81
{
86
- mediaRow.LastSequence = lastSequence;
87
- mediaRow = null;
82
+ mediaTuple.LastSequence = lastSequence;
83
+ mediaTuple = null;
84
}
85
86
// sequence the patch-added files
@@ -92,24 +88,23 @@ namespace WixToolset.Core.WindowsInstaller.Bind
88
{
89
foreach (var facade in patchGroup)
90
{
95
- if (null == mediaRow)
91
+ if (null == mediaTuple)
92
{
97
- mediaRow = mediaRows.Get(facade.DiskId);
93
+ mediaTuple = mediaRows[facade.DiskId];
94
}
99
- else if (mediaRow.DiskId != facade.DiskId)
95
+ else if (mediaTuple.DiskId != facade.DiskId)
96
{
101
- mediaRow.LastSequence = lastSequence;
102
- mediaRow = mediaRows.Get(facade.DiskId);
97
+ mediaTuple.LastSequence = lastSequence;
98
+ mediaTuple = mediaRows[facade.DiskId];
99
}
100
105
- var fileRow = fileRows.Get(facade.Id);
106
- fileRow.Sequence = ++lastSequence;
101
+ facade.Sequence = ++lastSequence;
102
}
103
}
104
110
- if (null != mediaRow)
105
+ if (null != mediaTuple)
106
{
112
- mediaRow.LastSequence = lastSequence;
107
+ mediaTuple.LastSequence = lastSequence;
108
}
109
}
110
}
src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs
new
+453
@@ -0,0 +1,453 @@
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
+
3
+namespace WixToolset.Core.WindowsInstaller.Bind
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.Diagnostics;
8
+ using System.IO;
9
+ using System.Linq;
10
+ using WixToolset.Core.Bind;
11
+ using WixToolset.Data;
12
+ using WixToolset.Data.Tuples;
13
+ using WixToolset.Data.WindowsInstaller;
14
+ using WixToolset.Data.WindowsInstaller.Rows;
15
+ using WixToolset.Extensibility.Services;
16
+
17
+ internal class UpdateTransformsWithFileFacades
18
+ {
19
+ public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable<SubStorage> subStorages, TableDefinitionCollection tableDefinitions, IEnumerable<FileFacade> fileFacades)
20
+ {
21
+ this.Messaging = messaging;
22
+ this.Output = output;
23
+ this.SubStorages = subStorages;
24
+ this.TableDefinitions = tableDefinitions;
25
+ this.FileFacades = fileFacades;
26
+ }
27
+
28
+ private IMessaging Messaging { get; }
29
+
30
+ private WindowsInstallerData Output { get; }
31
+
32
+ private IEnumerable<SubStorage> SubStorages { get; }
33
+
34
+ private TableDefinitionCollection TableDefinitions { get; }
35
+
36
+ private IEnumerable<FileFacade> FileFacades { get; }
37
+
38
+ public void Execute()
39
+ {
40
+ var fileFacadesByDiskId = new Dictionary<int, Dictionary<string, FileFacade>>();
41
+
42
+ // Index patch file facades by diskId+fileId.
43
+ foreach (var facade in this.FileFacades)
44
+ {
45
+ if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades))
46
+ {
47
+ mediaFacades = new Dictionary<string, FileFacade>();
48
+ fileFacadesByDiskId.Add(facade.DiskId, mediaFacades);
49
+ }
50
+
51
+ mediaFacades.Add(facade.Id, facade);
52
+ }
53
+
54
+ var patchMediaRows = new RowDictionary<MediaRow>(this.Output.Tables["Media"]);
55
+
56
+ // Index paired transforms by name without the "#" prefix.
57
+ var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data);
58
+
59
+ // Copy File bind data into substorages
60
+ foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#")))
61
+ {
62
+ var mainTransform = substorage.Data;
63
+
64
+ var mainMsiFileHashIndex = new RowDictionary<Row>(mainTransform.Tables["MsiFileHash"]);
65
+
66
+ var pairedTransform = pairedTransforms["#" + substorage.Name];
67
+
68
+ // Copy Media.LastSequence.
69
+ var pairedMediaTable = pairedTransform.Tables["Media"];
70
+ foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
71
+ {
72
+ var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
73
+ pairedMediaRow.LastSequence = patchMediaRow.LastSequence;
74
+ }
75
+
76
+ // Validate file row changes for keypath-related issues
77
+ this.ValidateFileRowChanges(mainTransform);
78
+
79
+ // Index File table of pairedTransform
80
+ var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]);
81
+
82
+ var mainFileTable = mainTransform.Tables["File"];
83
+ if (null != mainFileTable)
84
+ {
85
+ // Remove the MsiFileHash table because it will be updated later with the final file hash for each file
86
+ mainTransform.Tables.Remove("MsiFileHash");
87
+
88
+ foreach (FileRow mainFileRow in mainFileTable.Rows)
89
+ {
90
+ if (RowOperation.Delete == mainFileRow.Operation)
91
+ {
92
+ continue;
93
+ }
94
+ else if (RowOperation.None == mainFileRow.Operation)
95
+ {
96
+ continue;
97
+ }
98
+
99
+ // Index patch files by diskId+fileId
100
+ if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades))
101
+ {
102
+ mediaFacades = new Dictionary<string, FileFacade>();
103
+ fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades);
104
+ }
105
+
106
+ // copy data from the patch back to the transform
107
+ if (mediaFacades.TryGetValue(mainFileRow.File, out var facade))
108
+ {
109
+ var patchFileRow = facade.GetFileRow();
110
+ var pairedFileRow = pairedFileRows.Get(mainFileRow.File);
111
+
112
+ for (var i = 0; i < patchFileRow.Fields.Length; i++)
113
+ {
114
+ var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty;
115
+ var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty;
116
+
117
+ if (1 == i)
118
+ {
119
+ // File.Component_ changes should not come from the shared file rows
120
+ // that contain the file information as each individual transform might
121
+ // have different changes (or no changes at all).
122
+ }
123
+ else if (6 == i) // File.Attributes should not changed for binary deltas
124
+ {
125
+#if TODO_PATCHING_DELTA
126
+ if (null != patchFileRow.Patch)
127
+ {
128
+ // File.Attribute should not change for binary deltas
129
+ pairedFileRow.Attributes = mainFileRow.Attributes;
130
+ mainFileRow.Fields[i].Modified = false;
131
+ }
132
+#endif
133
+ }
134
+ else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform
135
+ {
136
+ // file sequence is updated in Patch table instead of File table for delta patches
137
+#if TODO_PATCHING_DELTA
138
+ if (null != patchFileRow.Patch)
139
+ {
140
+ pairedFileRow.Fields[i].Modified = false;
141
+ }
142
+ else
143
+#endif
144
+ {
145
+ pairedFileRow[i] = patchFileRow[i];
146
+ pairedFileRow.Fields[i].Modified = true;
147
+ }
148
+ mainFileRow.Fields[i].Modified = false;
149
+ }
150
+ else if (patchValue != mainValue)
151
+ {
152
+ mainFileRow[i] = patchFileRow[i];
153
+ mainFileRow.Fields[i].Modified = true;
154
+ if (mainFileRow.Operation == RowOperation.None)
155
+ {
156
+ mainFileRow.Operation = RowOperation.Modify;
157
+ }
158
+ }
159
+ }
160
+
161
+ // Copy MsiFileHash row for this File.
162
+ if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow))
163
+ {
164
+ //patchHashRow = patchFileRow.Hash;
165
+ throw new NotImplementedException();
166
+ }
167
+
168
+ if (null != patchHashRow)
169
+ {
170
+ var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
171
+ var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
172
+ for (var i = 0; i < patchHashRow.Fields.Length; i++)
173
+ {
174
+ mainHashRow[i] = patchHashRow[i];
175
+ if (i > 1)
176
+ {
177
+ // assume all hash fields have been modified
178
+ mainHashRow.Fields[i].Modified = true;
179
+ }
180
+ }
181
+
182
+ // assume the MsiFileHash operation follows the File one
183
+ mainHashRow.Operation = mainFileRow.Operation;
184
+ }
185
+
186
+ // copy MsiAssemblyName rows for this File
187
+#if TODO_PATCHING
188
+ List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
189
+ if (null != patchAssemblyNameRows)
190
+ {
191
+ var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
192
+ foreach (var patchAssemblyNameRow in patchAssemblyNameRows)
193
+ {
194
+ // Copy if there isn't an identical modified/added row already in the transform.
195
+ var foundMatchingModifiedRow = false;
196
+ foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows)
197
+ {
198
+ if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
199
+ {
200
+ foundMatchingModifiedRow = true;
201
+ break;
202
+ }
203
+ }
204
+
205
+ if (!foundMatchingModifiedRow)
206
+ {
207
+ var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
208
+ for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
209
+ {
210
+ mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
211
+ }
212
+
213
+ // assume value field has been modified
214
+ mainAssemblyNameRow.Fields[2].Modified = true;
215
+ mainAssemblyNameRow.Operation = mainFileRow.Operation;
216
+ }
217
+ }
218
+ }
219
+#endif
220
+
221
+ // Add patch header for this file
222
+#if TODO_PATCHING_DELTA
223
+ if (null != patchFileRow.Patch)
224
+ {
225
+ // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
226
+ this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
227
+ this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
228
+
229
+ // Add to Patch table
230
+ var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
231
+ if (0 == patchTable.Rows.Count)
232
+ {
233
+ patchTable.Operation = TableOperation.Add;
234
+ }
235
+
236
+ var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
237
+ patchRow[0] = patchFileRow.File;
238
+ patchRow[1] = patchFileRow.Sequence;
239
+
240
+ var patchFile = new FileInfo(patchFileRow.Source);
241
+ patchRow[2] = (int)patchFile.Length;
242
+ patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1;
243
+
244
+ var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
245
+ if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length)
246
+ {
247
+ streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_');
248
+
249
+ var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
250
+ if (0 == patchHeadersTable.Rows.Count)
251
+ {
252
+ patchHeadersTable.Operation = TableOperation.Add;
253
+ }
254
+
255
+ var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
256
+ patchHeadersRow[0] = streamName;
257
+ patchHeadersRow[1] = patchFileRow.Patch;
258
+ patchRow[5] = streamName;
259
+ patchHeadersRow.Operation = RowOperation.Add;
260
+ }
261
+ else
262
+ {
263
+ patchRow[4] = patchFileRow.Patch;
264
+ }
265
+ patchRow.Operation = RowOperation.Add;
266
+ }
267
+#endif
268
+ }
269
+ else
270
+ {
271
+ // TODO: throw because all transform rows should have made it into the patch
272
+ }
273
+ }
274
+ }
275
+
276
+ this.Output.Tables.Remove("Media");
277
+ this.Output.Tables.Remove("File");
278
+ this.Output.Tables.Remove("MsiFileHash");
279
+ this.Output.Tables.Remove("MsiAssemblyName");
280
+ }
281
+ }
282
+
283
+ /// <summary>
284
+ /// Adds the PatchFiles action to the sequence table if it does not already exist.
285
+ /// </summary>
286
+ /// <param name="table">The sequence table to check or modify.</param>
287
+ /// <param name="mainTransform">The primary authoring transform.</param>
288
+ /// <param name="pairedTransform">The secondary patch transform.</param>
289
+ /// <param name="mainFileRow">The file row that contains information about the patched file.</param>
290
+ private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow)
291
+ {
292
+ var tableName = table.ToString();
293
+
294
+ // Find/add PatchFiles action (also determine sequence for it).
295
+ // Search mainTransform first, then pairedTransform (pairedTransform overrides).
296
+ var hasPatchFilesAction = false;
297
+ var installFilesSequence = 0;
298
+ var duplicateFilesSequence = 0;
299
+
300
+ TestSequenceTableForPatchFilesAction(
301
+ mainTransform.Tables[tableName],
302
+ ref hasPatchFilesAction,
303
+ ref installFilesSequence,
304
+ ref duplicateFilesSequence);
305
+ TestSequenceTableForPatchFilesAction(
306
+ pairedTransform.Tables[tableName],
307
+ ref hasPatchFilesAction,
308
+ ref installFilesSequence,
309
+ ref duplicateFilesSequence);
310
+ if (!hasPatchFilesAction)
311
+ {
312
+ WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionTuple);
313
+
314
+ var sequence = patchFilesActionTuple.Sequence;
315
+
316
+ // Test for default sequence value's appropriateness
317
+ if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence))
318
+ {
319
+ if (0 != duplicateFilesSequence)
320
+ {
321
+ if (duplicateFilesSequence < installFilesSequence)
322
+ {
323
+ throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action));
324
+ }
325
+ else
326
+ {
327
+ sequence = (duplicateFilesSequence + installFilesSequence) / 2;
328
+ if (installFilesSequence == sequence || duplicateFilesSequence == sequence)
329
+ {
330
+ throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action));
331
+ }
332
+ }
333
+ }
334
+ else
335
+ {
336
+ sequence = installFilesSequence + 1;
337
+ }
338
+ }
339
+
340
+ var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]);
341
+ if (0 == sequenceTable.Rows.Count)
342
+ {
343
+ sequenceTable.Operation = TableOperation.Add;
344
+ }
345
+
346
+ var patchAction = sequenceTable.CreateRow(null);
347
+ patchAction[0] = patchFilesActionTuple.Action;
348
+ patchAction[1] = patchFilesActionTuple.Condition;
349
+ patchAction[2] = sequence;
350
+ patchAction.Operation = RowOperation.Add;
351
+ }
352
+ }
353
+
354
+ /// <summary>
355
+ /// Tests sequence table for PatchFiles and associated actions
356
+ /// </summary>
357
+ /// <param name="sequenceTable">The table to test.</param>
358
+ /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param>
359
+ /// <param name="installFilesSequence">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param>
360
+ /// <param name="duplicateFilesSequence">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param>
361
+ private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence)
362
+ {
363
+ if (null != sequenceTable)
364
+ {
365
+ foreach (var row in sequenceTable.Rows)
366
+ {
367
+ var actionName = row.FieldAsString(0);
368
+ switch (actionName)
369
+ {
370
+ case "PatchFiles":
371
+ hasPatchFilesAction = true;
372
+ break;
373
+
374
+ case "InstallFiles":
375
+ installFilesSequence = row.FieldAsInteger(2);
376
+ break;
377
+
378
+ case "DuplicateFiles":
379
+ duplicateFilesSequence = row.FieldAsInteger(2);
380
+ break;
381
+ }
382
+ }
383
+ }
384
+ }
385
+
386
+ /// <summary>
387
+ /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component.
388
+ /// </summary>
389
+ /// <param name="output">The output to validate.</param>
390
+ private void ValidateFileRowChanges(WindowsInstallerData transform)
391
+ {
392
+ var componentTable = transform.Tables["Component"];
393
+ var fileTable = transform.Tables["File"];
394
+
395
+ // There's no sense validating keypaths if the transform has no component or file table
396
+ if (componentTable == null || fileTable == null)
397
+ {
398
+ return;
399
+ }
400
+
401
+ var componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
402
+
403
+ // Index the Component table for non-directory & non-registry key paths.
404
+ foreach (var row in componentTable.Rows)
405
+ {
406
+ var keyPath = row.FieldAsString(5);
407
+ if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath))
408
+ {
409
+ componentKeyPath.Add(row.FieldAsString(0), keyPath);
410
+ }
411
+ }
412
+
413
+ var componentWithChangedKeyPath = new Dictionary<string, string>();
414
+ var componentWithNonKeyPathChanged = new Dictionary<string, string>();
415
+ // Verify changes in the file table, now that file diffing has occurred
416
+ foreach (FileRow row in fileTable.Rows)
417
+ {
418
+ if (RowOperation.Modify != row.Operation)
419
+ {
420
+ continue;
421
+ }
422
+
423
+ var fileId = row.FieldAsString(0);
424
+ var componentId = row.FieldAsString(1);
425
+
426
+ // If this file is the keypath of a component
427
+ if (componentKeyPath.ContainsValue(fileId))
428
+ {
429
+ if (!componentWithChangedKeyPath.ContainsKey(componentId))
430
+ {
431
+ componentWithChangedKeyPath.Add(componentId, fileId);
432
+ }
433
+ }
434
+ else
435
+ {
436
+ if (!componentWithNonKeyPathChanged.ContainsKey(componentId))
437
+ {
438
+ componentWithNonKeyPathChanged.Add(componentId, fileId);
439
+ }
440
+ }
441
+ }
442
+
443
+ foreach (var componentFile in componentWithNonKeyPathChanged)
444
+ {
445
+ // Make sure all changes to non keypath files also had a change in the keypath.
446
+ if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath))
447
+ {
448
+ this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath));
449
+ }
450
+ }
451
+ }
452
+ }
453
+}
src/WixToolset.Core/Bind/FileFacade.cs
+19
-5
@@ -15,18 +15,27 @@ namespace WixToolset.Core.Bind
15
{
16
this.FileTuple = file;
17
this.AssemblyTuple = assembly;
18
+
19
+ this.Identifier = file.Id;
20
+ this.ComponentRef = file.ComponentRef;
21
}
22
23
public FileFacade(bool fromModule, FileTuple file)
24
{
25
this.FromModule = fromModule;
26
this.FileTuple = file;
27
+
28
+ this.Identifier = file.Id;
29
+ this.ComponentRef = file.ComponentRef;
30
}
31
26
- internal FileFacade(FileRow row)
32
+ public FileFacade(FileRow row)
33
{
34
this.FromTransform = true;
35
this.FileRow = row;
36
+
37
+ this.Identifier = new Identifier(AccessModifier.Private, row.File);
38
+ this.ComponentRef = row.Component;
39
}
40
41
public bool FromModule { get; }
@@ -39,11 +48,11 @@ namespace WixToolset.Core.Bind
48
49
private AssemblyTuple AssemblyTuple { get; }
50
42
- public string Id => this.FileRow == null ? this.FileTuple.Id.Id : this.FileRow.File;
51
+ public string Id => this.Identifier.Id;
52
44
- public Identifier Identifier => this.FileRow == null ? this.FileTuple.Id : throw new NotImplementedException();
53
+ public Identifier Identifier { get; }
54
46
- public string ComponentRef => this.FileRow == null ? this.FileTuple.ComponentRef : this.FileRow.Component;
55
+ public string ComponentRef { get; }
56
57
public int DiskId
58
{
@@ -137,7 +146,7 @@ namespace WixToolset.Core.Bind
146
}
147
}
148
140
- public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblyTuple?.Type : throw new NotImplementedException();
149
+ public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblyTuple?.Type : null;
150
151
public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblyTuple?.ApplicationFileRef : throw new NotImplementedException();
152
@@ -153,5 +162,10 @@ namespace WixToolset.Core.Bind
162
/// Gets or sets the MsiFileHash row for this file.
163
/// </summary>
164
public MsiFileHashTuple Hash { get; set; }
165
+
166
+ /// <summary>
167
+ /// Allows direct access to the underlying FileRow as requried for patching.
168
+ /// </summary>
169
+ public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException();
170
}
171
}
src/WixToolset.Core/CommandLine/BuildCommand.cs
+1
@@ -334,6 +334,7 @@ namespace WixToolset.Core.CommandLine
334
context.DelayedFields = resolveResult.DelayedFields;
335
context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles;
336
context.Extensions = this.ExtensionManager.GetServices<IBinderExtension>();
337
+ context.FileSystemExtensions = this.ExtensionManager.GetServices<IFileSystemExtension>();
338
context.Ices = Array.Empty<string>(); // TODO: set this correctly
339
context.IntermediateFolder = intermediateFolder;
340
context.IntermediateRepresentation = resolveResult.IntermediateRepresentation;
src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs
+2
-2
@@ -16,7 +16,7 @@ namespace WixToolsetTest.CoreIntegration
16
{
17
private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd";
18
19
- [Fact(Skip = "Skip until patches have files in them")]
19
+ [Fact]
20
public void CanBuildSimplePatch()
21
{
22
var folder = TestData.Get(@"TestData\PatchSingle");
@@ -45,7 +45,7 @@ namespace WixToolsetTest.CoreIntegration
45
Assert.True(File.Exists(cab));
46
47
var files = Query.GetCabinetFiles(cab);
48
- Assert.Equal(new[] { "a", "b" }, files.Select(f => f.ArchiveName).ToArray()); // This test may not be quite correct, yet.
48
+ Assert.Equal(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray());
49
}
50
}
51