From d529525a1e331f3ef9ec2707791c99bd78fdd82f Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 30 May 2020 14:53:05 -0700 Subject: Basic patching support --- .../Bind/AttachPatchTransformsCommand.cs | 66 +-- .../Bind/BindDatabaseCommand.cs | 130 +---- .../Bind/BindTransformCommand.cs | 32 +- .../Bind/CreateDeltaPatchesCommand.cs | 2 +- .../Bind/CreateOutputFromIRCommand.cs | 3 +- .../Bind/CreatePatchTransformsCommand.cs | 2 +- .../Bind/FileSystemManager.cs | 71 +++ .../Bind/GenerateDatabaseCommand.cs | 9 +- .../Bind/GenerateTransformCommand.cs | 13 +- .../Bind/GetFileFacadesCommand.cs | 16 +- .../Bind/GetFileFacadesFromTransforms.cs | 576 +++------------------ .../Bind/MergeModulesCommand.cs | 59 ++- .../Bind/PatchTransform.cs | 227 -------- .../Bind/UpdateFileFacadesCommand.cs | 16 +- .../Bind/UpdateMediaSequencesCommand.cs | 61 +-- .../Bind/UpdateTransformsWithFileFacades.cs | 453 ++++++++++++++++ src/WixToolset.Core/Bind/FileFacade.cs | 24 +- src/WixToolset.Core/CommandLine/BuildCommand.cs | 1 + .../WixToolsetTest.CoreIntegration/PatchFixture.cs | 4 +- 19 files changed, 779 insertions(+), 986 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs (limited to 'src') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs index 214bf617..a16bafd7 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs @@ -147,7 +147,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { foreach (MediaRow transformMediaRow in transformMediaTable.Rows) { - if (mediaTuple.LastSequence < transformMediaRow.LastSequence) + if (!mediaTuple.LastSequence.HasValue || mediaTuple.LastSequence < transformMediaRow.LastSequence) { // The Binder will pre-increment the sequence. mediaTuple.LastSequence = transformMediaRow.LastSequence; @@ -156,14 +156,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind } // Use the Media/@DiskId if greater than the last sequence for backward compatibility. - if (mediaTuple.LastSequence < mediaTuple.DiskId) + if (!mediaTuple.LastSequence.HasValue || mediaTuple.LastSequence < mediaTuple.DiskId) { mediaTuple.LastSequence = mediaTuple.DiskId; } // Ignore media table in the transform. mainTransform.Transform.Tables.Remove("Media"); - mainTransform.Transform.Tables.Remove("WixMedia"); mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchIdTuple, mainTransform.Transform, mediaTuple, baselineTuple, out var productCode); @@ -767,15 +766,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (transform.TryGetTable("File", out var fileTable)) { var componentWithChangedKeyPath = new Dictionary(); - foreach (var row in fileTable.Rows) + foreach (FileRow row in fileTable.Rows) { if (RowOperation.None == row.Operation) { continue; } - var fileId = row.FieldAsString(0); - var componentId = row.FieldAsString(1); + var fileId = row.File; + var componentId = row.Component; // If this file is the keypath of a component if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal)) @@ -972,7 +971,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (row.Operation == RowOperation.None) { - // ignore the rows without section id. + // Ignore the rows without section id. if (isSectionIdEmpty) { currentTable.Rows.RemoveAt(i); @@ -1157,47 +1156,33 @@ namespace WixToolset.Core.WindowsInstaller.Bind return null; } - // copy File table + // Copy File table if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count) { -#if TODO_PATCHING - // We require file source information. - var mainWixFileTable = mainTransform.Tables["WixFile"]; - if (null == mainWixFileTable) - { - this.Messaging.Write(ErrorMessages.AdminImageRequired(productCode)); - return null; - } - - var mainFileRows = new RowDictionary(mainFileTable); - var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); - { - var mainFileRow = mainFileRows[mainWixFileRow.File]; - // set File.Sequence to non null to satisfy transform bind + foreach (FileRow mainFileRow in mainFileTable.Rows) + { + // Set File.Sequence to non null to satisfy transform bind. mainFileRow.Sequence = 1; - // delete's don't need rows in the paired transform + // Delete's don't need rows in the paired transform. if (mainFileRow.Operation == RowOperation.Delete) { continue; } - var pairedFileRow = (FileRow)pairedFileTable.CreateRow(null); + var pairedFileRow = (FileRow)pairedFileTable.CreateRow(mainFileRow.SourceLineNumbers); pairedFileRow.Operation = RowOperation.Modify; - for (var i = 0; i < mainFileRow.Fields.Length; i++) - { - pairedFileRow[i] = mainFileRow[i]; - } + mainFileRow.CopyTo(pairedFileRow); - // override authored media for patch bind - mainWixFileRow.DiskId = mediaTuple.DiskId; + // Override authored media for patch bind. + mainFileRow.DiskId = mediaTuple.DiskId; - // suppress any change to File.Sequence to avoid bloat + // Suppress any change to File.Sequence to avoid bloat. mainFileRow.Fields[7].Modified = false; - // force File row to appear in the transform + // Force File row to appear in the transform. switch (mainFileRow.Operation) { case RowOperation.Modify: @@ -1211,19 +1196,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind break; } } -#endif } // Add Media row to pairedTransform var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); - var pairedMediaRow = pairedMediaTable.CreateRow(mediaTuple.SourceLineNumbers); + var pairedMediaRow = (MediaRow)pairedMediaTable.CreateRow(mediaTuple.SourceLineNumbers); pairedMediaRow.Operation = RowOperation.Add; - pairedMediaRow[0] = mediaTuple.DiskId; - pairedMediaRow[1] = mediaTuple.LastSequence ?? 0; - pairedMediaRow[2] = mediaTuple.DiskPrompt; - pairedMediaRow[3] = mediaTuple.Cabinet; - pairedMediaRow[4] = mediaTuple.VolumeLabel; - pairedMediaRow[5] = mediaTuple.Source; + pairedMediaRow.DiskId = mediaTuple.DiskId; + pairedMediaRow.LastSequence = mediaTuple.LastSequence ?? 0; + pairedMediaRow.DiskPrompt = mediaTuple.DiskPrompt; + pairedMediaRow.Cabinet = mediaTuple.Cabinet; + pairedMediaRow.VolumeLabel = mediaTuple.VolumeLabel; + pairedMediaRow.Source = mediaTuple.Source; // Add PatchPackage for this Media var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); @@ -1283,7 +1267,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { var patchTargetTuples = tuples.OfType().ToList(); - if (patchTargetTuples.Count > 0) + if (patchTargetTuples.Any()) { var targets = new SortedSet(); var replace = true; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 8e901d30..d9d246f5 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -43,7 +43,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.DefaultCompressionLevel = context.DefaultCompressionLevel; this.DelayedFields = context.DelayedFields; this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; - this.FileSystemExtensions = context.FileSystemExtensions; + this.FileSystemManager = new FileSystemManager(context.FileSystemExtensions); this.Intermediate = context.IntermediateRepresentation; this.IntermediateFolder = context.IntermediateFolder; this.OutputPath = context.OutputPath; @@ -79,7 +79,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind public IEnumerable ExpectedEmbeddedFiles { get; } - public IEnumerable FileSystemExtensions { get; } + public FileSystemManager FileSystemManager { get; } public bool DeltaBinaryPatch { get; set; } @@ -116,7 +116,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind var trackedFiles = new List(); var containsMergeModules = false; - var suppressedTableNames = new HashSet(); // If there are any fields to resolve later, create the cache to populate during bind. var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; @@ -226,7 +225,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind return null; } - WindowsInstallerData output; this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound); this.Messaging.Write(VerboseMessages.UpdatingFileInformation()); @@ -268,14 +266,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else if (SectionType.Patch == section.Type) { - // Merge transform data into the output object. - //IEnumerable filesFromTransform = this.CopyFromTransformData(this.Output); - - //var command = new CopyTransformDataCommand(this.Messaging, /*output*/this.SubStorages, tableDefinitions, copyOutFileRows: true); - //command.Output = output; - //command.TableDefinitions = this.TableDefinitions; - //command.CopyOutFileRows = true; - var command = new GetFileFacadesFromTransforms(this.Messaging, this.SubStorages, tableDefinitions); + var command = new GetFileFacadesFromTransforms(this.Messaging, this.FileSystemManager, this.SubStorages); command.Execute(); var filesFromTransforms = command.FileFacades; @@ -338,6 +329,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind command.Execute(); } + // Update file sequence. + { + var command = new UpdateMediaSequencesCommand(section, fileFacades); + command.Execute(); + } + // stop processing if an error previously occurred if (this.Messaging.EncounteredError) { @@ -345,6 +342,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind } // Time to create the output object. Try to put as much above here as possible, updating the IR is better. + WindowsInstallerData output; { var command = new CreateOutputFromIRCommand(this.Messaging, section, tableDefinitions, this.BackendExtensions, this.WindowsInstallerBackendHelper); command.Execute(); @@ -352,14 +350,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind output = command.Output; } - // Update file sequence. - { - var command = new UpdateMediaSequencesCommand(output, fileFacades); - command.Execute(); - } - // Modularize identifiers. - if (OutputType.Module == output.Type) + if (output.Type == OutputType.Module) { var command = new ModularizeCommand(output, modularizationSuffix, section.Tuples.OfType()); command.Execute(); @@ -457,18 +449,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind trackedFiles.AddRange(command.TrackedFiles); } -#if DELETE - if (OutputType.Patch == output.Type) - { - // Copy output data back into the transforms. -#if TODO_PATCHING - var command = new CopyTransformDataCommand(this.Messaging, output, tableDefinitions, copyOutFileRows: false); - command.Execute(); - - this.CopyToTransformData(this.Output); -#endif - } -#endif + if (output.Type == OutputType.Patch) + { + // Copy output data back into the transforms. + var command = new UpdateTransformsWithFileFacades(this.Messaging, output, this.SubStorages, tableDefinitions, fileFacades); + command.Execute(); + } // stop processing if an error previously occurred if (this.Messaging.EncounteredError) @@ -483,8 +469,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind var trackMsi = this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final); trackedFiles.Add(trackMsi); - var temporaryFiles = this.GenerateDatabase(output, tableDefinitions, trackMsi.Path, false, false); - trackedFiles.AddRange(temporaryFiles); + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, trackMsi.Path, tableDefinitions, this.IntermediateFolder, this.Codepage, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false); + command.Execute(); + + trackedFiles.AddRange(command.GeneratedTemporaryFiles); } // Stop processing if an error previously occurred. @@ -498,31 +486,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind { this.Messaging.Write(VerboseMessages.MergingModules()); - // Add back possibly suppressed sequence tables since all sequence tables must be present - // for the merge process to work. We'll drop the suppressed sequence tables again as - // necessary. - foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) - { - var sequenceTableName = (sequence == SequenceTable.AdvertiseExecuteSequence) ? "AdvtExecuteSequence" : sequence.ToString(); - var sequenceTable = output.Tables[sequenceTableName]; - - if (null == sequenceTable) - { - sequenceTable = output.EnsureTable(tableDefinitions[sequenceTableName]); - } - - if (0 == sequenceTable.Rows.Count) - { - suppressedTableNames.Add(sequenceTableName); - } - } - var command = new MergeModulesCommand(this.Messaging); command.FileFacades = fileFacades; command.IntermediateFolder = this.IntermediateFolder; command.Output = output; command.OutputPath = this.OutputPath; - command.SuppressedTableNames = suppressedTableNames; + command.TableDefinitions = tableDefinitions; command.Execute(); } @@ -607,38 +576,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind return wixout; } -#if TODO_PATCHING - /// - /// Copy file data between transform substorages and the patch output object - /// - /// The output to bind. - /// True if copying from transform to patch, false the other way. - private IEnumerable CopyFromTransformData(Output output) - { - var command = new CopyTransformDataCommand(); - command.CopyOutFileRows = true; - command.Output = output; - command.TableDefinitions = this.TableDefinitions; - command.Execute(); - - return command.FileFacades; - } - - /// - /// Copy file data between transform substorages and the patch output object - /// - /// The output to bind. - /// True if copying from transform to patch, false the other way. - private void CopyToTransformData(Output output) - { - var command = new CopyTransformDataCommand(); - command.CopyOutFileRows = false; - command.Output = output; - command.TableDefinitions = this.TableDefinitions; - command.Execute(); - } -#endif - /// /// Validate that there are no duplicate GUIDs in the output. /// @@ -650,7 +587,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { if (output.TryGetTable("Component", out var componentTable)) { - Dictionary componentGuidConditions = new Dictionary(componentTable.Rows.Count); + var componentGuidConditions = new Dictionary(componentTable.Rows.Count); foreach (Data.WindowsInstaller.Rows.ComponentRow row in componentTable.Rows) { @@ -658,12 +595,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind // there's already an error that prevented it from being replaced with a real GUID. if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid) { - bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition); - bool allComponentsHaveConditions = thisComponentHasCondition; + var thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition); + var allComponentsHaveConditions = thisComponentHasCondition; if (componentGuidConditions.ContainsKey(row.Guid)) { - allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition; + allComponentsHaveConditions = thisComponentHasCondition && componentGuidConditions[row.Guid]; if (allComponentsHaveConditions) { @@ -728,20 +665,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind return layout; } - - /// - /// Creates the MSI/MSM/PCP database. - /// - /// Output to create database for. - /// The database file to create. - /// Whether to keep columns added in a transform. - /// Whether to use a subdirectory based on the file name for intermediate files. - private IEnumerable GenerateDatabase(WindowsInstallerData output, TableDefinitionCollection tableDefinitions, string databaseFile, bool keepAddedColumns, bool useSubdirectory) - { - var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemExtensions, output, databaseFile, tableDefinitions, this.IntermediateFolder, this.Codepage, keepAddedColumns, this.SuppressAddingValidationRows, useSubdirectory); - command.Execute(); - - return command.GeneratedTemporaryFiles; - } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs index ffe26249..ac98c82d 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs @@ -3,23 +3,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind { using System; - using System.Collections.Generic; using System.Globalization; using System.IO; using WixToolset.Core.WindowsInstaller.Msi; using WixToolset.Data; using WixToolset.Data.Tuples; using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; using WixToolset.Extensibility.Services; internal class BindTransformCommand { - public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable extensions, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions) + public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions) { this.Messaging = messaging; this.BackendHelper = backendHelper; - this.Extensions = extensions; + this.FileSystemManager = fileSystemManager; this.IntermediateFolder = intermediateFolder; this.Transform = transform; this.OutputPath = outputPath; @@ -30,7 +28,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IBackendHelper BackendHelper { get; } - private IEnumerable Extensions { get; } + private FileSystemManager FileSystemManager { get; } private TableDefinitionCollection TableDefinitions { get; } @@ -353,7 +351,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind targetRow[i] = emptyFile; modifiedRow = true; } - else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data)) + else if (!this.FileSystemManager.CompareFiles(objectField.PreviousData, (string)objectField.Data)) { targetRow[i] = objectField.PreviousData; modifiedRow = true; @@ -438,29 +436,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - private bool CompareFiles(string targetFile, string updatedFile) - { - bool? compared = null; - foreach (var extension in this.Extensions) - { - compared = extension.CompareFiles(targetFile, updatedFile); - if (compared.HasValue) - { - break; - } - } - - if (!compared.HasValue) - { - throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. - } - - return compared.Value; - } - private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns) { - var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.Extensions, output, outputPath, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true ); + var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, codepage: -1, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true); command.Execute(); } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs index f5ac00e6..c54e9c53 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs @@ -33,7 +33,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false; var apiPatchingSymbolFlags = (PatchSymbolFlagsType)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0); -#if TODO_PATCHING +#if TODO_PATCHING_DELTA foreach (FileFacade facade in this.FileFacades) { if (RowOperation.Modify == facade.File.Operation && diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs index 0cbb81d8..ffc4e84d 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs @@ -499,7 +499,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind row.FileSize = tuple.FileSize; row.Version = tuple.Version; row.Language = tuple.Language; - row.DiskId = tuple.DiskId ?? 1; // TODO: is 0 the correct thing to default here + row.DiskId = tuple.DiskId ?? 1; // TODO: is 1 the correct thing to default here + row.Sequence = tuple.Sequence; row.Source = tuple.Source.Path; var attributes = (tuple.Attributes & FileTupleAttributes.Checksum) == FileTupleAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs index 854d973e..f65f885b 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs @@ -45,7 +45,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var baselineData = this.GetData(tuple.BaselineFile.Path); var updateData = this.GetData(tuple.UpdateFile.Path); - var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, false); + var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false); transform = command.Execute(); } else diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs b/src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs new file mode 100644 index 00000000..75477271 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs @@ -0,0 +1,71 @@ +// 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. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System.Collections.Generic; + using System.IO; + using WixToolset.Extensibility; + + internal class FileSystemManager + { + public FileSystemManager(IEnumerable fileSystemExtensions) + { + this.Extensions = fileSystemExtensions; + } + + private IEnumerable Extensions { get; } + + public bool CompareFiles(string firstPath, string secondPath) + { + foreach (var extension in this.Extensions) + { + var compared = extension.CompareFiles(firstPath, secondPath); + if (compared.HasValue) + { + return compared.Value; + } + } + + return BuiltinCompareFiles(firstPath, secondPath); + } + + private static bool BuiltinCompareFiles(string firstPath, string secondPath) + { + using (var firstStream = File.OpenRead(firstPath)) + using (var secondStream = File.OpenRead(secondPath)) + { + if (firstStream.Length != secondStream.Length) + { + return false; + } + + // Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance. + // The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined. + var firstBuffer = new byte[16 * 1024]; + var secondBuffer = new byte[16 * 1024]; + + var firstReadLength = 0; + do + { + firstReadLength = firstStream.Read(firstBuffer, 0, firstBuffer.Length); + var secondReadLength = secondStream.Read(secondBuffer, 0, secondBuffer.Length); + + if (firstReadLength != secondReadLength) + { + return false; + } + + for (var i = 0; i < firstReadLength; ++i) + { + if (firstBuffer[i] != secondBuffer[i]) + { + return false; + } + } + } while (0 < firstReadLength); + } + + return true; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs index ed3b6f01..eff94e80 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs @@ -10,17 +10,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind using WixToolset.Core.WindowsInstaller.Msi; using WixToolset.Data; using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; internal class GenerateDatabaseCommand { - public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, IEnumerable fileSystemExtensions, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, int codepage, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory) + public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, int codepage, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory) { this.Messaging = messaging; this.BackendHelper = backendHelper; - this.Extensions = fileSystemExtensions; + this.FileSystemManager = fileSystemManager; this.Data = data; this.OutputPath = outputPath; this.TableDefinitions = tableDefinitions; @@ -35,7 +34,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind private IBackendHelper BackendHelper { get; } - private IEnumerable Extensions { get; } + private FileSystemManager FileSystemManager { get; } /// /// Whether to keep columns added in a transform. @@ -371,7 +370,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst")); // Bind the transform. - var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.Extensions, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions); + var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions); command.Execute(); if (this.Messaging.EncounteredError) diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs index 8a7dd702..201a890c 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs @@ -10,7 +10,6 @@ namespace WixToolset.Core.WindowsInstaller using WixToolset.Data.Tuples; using WixToolset.Data.WindowsInstaller; using WixToolset.Data.WindowsInstaller.Rows; - using WixToolset.Extensibility; using WixToolset.Extensibility.Services; /// @@ -25,11 +24,12 @@ namespace WixToolset.Core.WindowsInstaller /// /// Instantiates a new Differ class. /// - public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool showPedanticMessages) + public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool preserveUnchangedRows, bool showPedanticMessages) { this.messaging = messaging; this.TargetOutput = targetOutput; this.UpdatedOutput = updatedOutput; + this.PreserveUnchangedRows = preserveUnchangedRows; this.ShowPedanticMessages = showPedanticMessages; } @@ -111,10 +111,10 @@ namespace WixToolset.Core.WindowsInstaller } else if (TableOperation.None == operation) { - var modified = transform.EnsureTable(updatedTable.Definition); + var modifiedTable = transform.EnsureTable(updatedTable.Definition); foreach (var row in rows) { - modified.Rows.Add(row); + modifiedTable.Rows.Add(row); } } } @@ -242,10 +242,7 @@ namespace WixToolset.Core.WindowsInstaller { var columnDefinition = updatedRow.Fields[i].Column; - if (columnDefinition.Unreal) - { - } - else if (!columnDefinition.PrimaryKey) + if (!columnDefinition.PrimaryKey) { var modified = false; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs index 2844f797..55171da4 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs @@ -26,28 +26,26 @@ namespace WixToolset.Core.WindowsInstaller.Bind var facades = new List(); var assemblyFile = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); - //var wixFiles = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); //var deltaPatchFiles = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); foreach (var file in this.Section.Tuples.OfType()) { - //var wixFile = wixFiles[file.Id.Id]; + assemblyFile.TryGetValue(file.Id.Id, out var assembly); //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile); - //facades.Add(new FileFacade(file, wixFile, deltaPatchFile)); - - assemblyFile.TryGetValue(file.Id.Id, out var assembly); - facades.Add(new FileFacade(file, assembly)); + //facades.Add(new FileFacade(file, wixFile, deltaPatchFile)); } - //this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); +#if TODO_PATCHING_DELTA + this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades); +#endif this.FileFacades = facades; } -#if FIX_THIS +#if TODO_PATCHING_DELTA /// /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. /// @@ -132,7 +130,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind file.SymbolPaths = String.Concat(file.SymbolPaths, ";", row.SymbolPaths); } -#if TODO_PATCHING Field field = row.Fields[2]; if (null != field.PreviousData) { @@ -145,7 +142,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData); } } -#endif } #endif } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs index 9818f01a..99bf7101 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs @@ -4,12 +4,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind { using System; using System.Collections.Generic; - using System.Diagnostics; - using System.IO; using System.Linq; using WixToolset.Core.Bind; using WixToolset.Data; - using WixToolset.Data.Tuples; using WixToolset.Data.WindowsInstaller; using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility; @@ -17,90 +14,39 @@ namespace WixToolset.Core.WindowsInstaller.Bind internal class GetFileFacadesFromTransforms { - public GetFileFacadesFromTransforms(IMessaging messaging, IEnumerable subStorages, TableDefinitionCollection tableDefinitions) + public GetFileFacadesFromTransforms(IMessaging messaging, FileSystemManager fileSystemManager, IEnumerable subStorages) { this.Messaging = messaging; + this.FileSystemManager = fileSystemManager; this.SubStorages = subStorages; - this.TableDefinitions = tableDefinitions; } - public IEnumerable Extensions { get; } - private IMessaging Messaging { get; } - public IEnumerable SubStorages { get; } + private FileSystemManager FileSystemManager { get; } - private TableDefinitionCollection TableDefinitions { get; } + private IEnumerable SubStorages { get; } public IEnumerable FileFacades { get; private set; } public void Execute() { var allFileRows = new List(); - var copyToPatch = (allFileRows != null); -#if TODO_PATCHING - var patchMediaRows = new RowDictionary(); - var patchMediaFileRows = new Dictionary>(); + var patchMediaRows = new RowDictionary(); - var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); - var patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]); + var patchMediaFileRows = new Dictionary>(); - if (copyFromPatch) - { - // index patch files by diskId+fileId - foreach (WixFileRow patchFileRow in patchFileTable.Rows) - { - int diskId = patchFileRow.DiskId; - if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) - { - mediaFileRows = new RowDictionary(); - patchMediaFileRows.Add(diskId, mediaFileRows); - } - - mediaFileRows.Add(patchFileRow); - } - - var patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]); - patchMediaRows = new RowDictionary(patchMediaTable); - } + //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]); // Index paired transforms by name without their "#" prefix. - var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name.Substring(1), s => s.Data); + var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); - foreach (var substorage in this.SubStorages) + // Enumerate through main transforms. + foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) { - if (substorage.Name.StartsWith("#")) - { - // Skip paired transforms. - continue; - } - var mainTransform = substorage.Data; - var mainWixFiles = new RowDictionary(mainTransform.Tables["WixFile"]); - var mainMsiFileHashIndex = new RowDictionary(mainTransform.Tables["MsiFileHash"]); - var mainFileTable = mainTransform.Tables["File"]; - var pairedTransform = pairedTransforms[substorage.Name]; - - // copy Media.LastSequence and index the MsiFileHash table if it exists. - if (copyFromPatch) - { - var pairedMediaTable = pairedTransform.Tables["Media"]; - foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) - { - var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); - pairedMediaRow.Fields[1] = patchMediaRow.Fields[1]; - } - - if (null != mainMsiFileHashTable) - { - mainMsiFileHashIndex = new RowDictionary(mainMsiFileHashTable); - } - - // Validate file row changes for keypath-related issues - this.ValidateFileRowChanges(mainTransform); - } if (null == mainFileTable) { @@ -108,74 +54,21 @@ namespace WixToolset.Core.WindowsInstaller.Bind } // Index File table of pairedTransform + var pairedTransform = pairedTransforms["#" + substorage.Name]; var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); - foreach (FileRow mainFileRow in mainFileTable.Rows) + foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete)) { - if (RowOperation.Delete == mainFileRow.Operation) - { - continue; - } - else if (RowOperation.None == mainFileRow.Operation) - { - continue; - } + var mainFileId = mainFileRow.File; - var mainWixFileRow = mainWixFiles.Get(mainFileRow.File); + // We need compare the underlying files and include all file changes. + var objectField = (ObjectField)mainFileRow.Fields[9]; + var pairedFileRow = pairedFileRows.Get(mainFileId); - if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes. + // If the file is new, we always need to add it to the patch. + if (mainFileRow.Operation == RowOperation.Add) { - var objectField = (ObjectField)mainWixFileRow.Fields[6]; - var pairedFileRow = pairedFileRows.Get(mainFileRow.File); - - // If the file is new, we always need to add it to the patch. - if (mainFileRow.Operation != RowOperation.Add) - { - // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. - if (null == objectField.PreviousData) - { - if (mainFileRow.Operation == RowOperation.None) - { - continue; - } - } - else - { - // TODO: should this entire condition be placed in the binder file manager? - if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) && - !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString())) - { - // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. - mainFileRow.Operation = RowOperation.Modify; - if (null != pairedFileRow) - { - // Always patch-added, but never non-compressed. - pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; - pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; - pairedFileRow.Fields[6].Modified = true; - pairedFileRow.Operation = RowOperation.Modify; - } - } - else - { - // The File is same. We need mark all the attributes as unchanged. - mainFileRow.Operation = RowOperation.None; - foreach (var field in mainFileRow.Fields) - { - field.Modified = false; - } - - if (null != pairedFileRow) - { - pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded; - pairedFileRow.Fields[6].Modified = false; - pairedFileRow.Operation = RowOperation.None; - } - continue; - } - } - } - else if (null != pairedFileRow) // RowOperation.Add + if (null != pairedFileRow) // RowOperation.Add { // Always patch-added, but never non-compressed. pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; @@ -184,402 +77,93 @@ namespace WixToolset.Core.WindowsInstaller.Bind pairedFileRow.Operation = RowOperation.Add; } } - - // index patch files by diskId+fileId - int diskId = mainWixFileRow.DiskId; - - if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) - { - mediaFileRows = new RowDictionary(); - patchMediaFileRows.Add(diskId, mediaFileRows); - } - - var fileId = mainFileRow.File; - var patchFileRow = mediaFileRows.Get(fileId); - if (copyToPatch) + else { - if (null == patchFileRow) - { - var patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); - patchActualFileRow.CopyFrom(mainFileRow); - - patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); - patchFileRow.CopyFrom(mainWixFileRow); - - mediaFileRows.Add(patchFileRow); - - allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right? - } - else + // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. + if (null == objectField.PreviousData) { - // TODO: confirm the rest of data is identical? - - // make sure Source is same. Otherwise we are silently ignoring a file. - if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase)) + if (mainFileRow.Operation == RowOperation.None) { - this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source)); + continue; } - - // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. - patchFileRow.AppendPreviousDataFrom(mainWixFileRow); - } - } - //else - //{ - // // copy data from the patch back to the transform - // if (null != patchFileRow) - // { - // var pairedFileRow = pairedFileRows.Get(fileId); - // for (var i = 0; i < patchFileRow.Fields.Length; i++) - // { - // var patchValue = patchFileRow[i] == null ? String.Empty : patchFileRow.FieldAsString(i); - // var mainValue = mainFileRow[i] == null ? String.Empty : mainFileRow.FieldAsString(i); - - // if (1 == i) - // { - // // File.Component_ changes should not come from the shared file rows - // // that contain the file information as each individual transform might - // // have different changes (or no changes at all). - // } - // // File.Attributes should not changed for binary deltas - // else if (6 == i) - // { - // if (null != patchFileRow.Patch) - // { - // // File.Attribute should not change for binary deltas - // pairedFileRow.Attributes = mainFileRow.Attributes; - // mainFileRow.Fields[i].Modified = false; - // } - // } - // // File.Sequence is updated in pairedTransform, not mainTransform - // else if (7 == i) - // { - // // file sequence is updated in Patch table instead of File table for delta patches - // if (null != patchFileRow.Patch) - // { - // pairedFileRow.Fields[i].Modified = false; - // } - // else - // { - // pairedFileRow[i] = patchFileRow[i]; - // pairedFileRow.Fields[i].Modified = true; - // } - // mainFileRow.Fields[i].Modified = false; - // } - // else if (patchValue != mainValue) - // { - // mainFileRow[i] = patchFileRow[i]; - // mainFileRow.Fields[i].Modified = true; - // if (mainFileRow.Operation == RowOperation.None) - // { - // mainFileRow.Operation = RowOperation.Modify; - // } - // } - // } - - // // copy MsiFileHash row for this File - // if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) - // { - // patchHashRow = patchFileRow.Hash; - // } - - // if (null != patchHashRow) - // { - // var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); - // var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); - // for (var i = 0; i < patchHashRow.Fields.Length; i++) - // { - // mainHashRow[i] = patchHashRow[i]; - // if (i > 1) - // { - // // assume all hash fields have been modified - // mainHashRow.Fields[i].Modified = true; - // } - // } - - // // assume the MsiFileHash operation follows the File one - // mainHashRow.Operation = mainFileRow.Operation; - // } - - // // copy MsiAssemblyName rows for this File - // List patchAssemblyNameRows = patchFileRow.AssemblyNames; - // if (null != patchAssemblyNameRows) - // { - // var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); - // foreach (var patchAssemblyNameRow in patchAssemblyNameRows) - // { - // // Copy if there isn't an identical modified/added row already in the transform. - // var foundMatchingModifiedRow = false; - // foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows) - // { - // if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) - // { - // foundMatchingModifiedRow = true; - // break; - // } - // } - - // if (!foundMatchingModifiedRow) - // { - // var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); - // for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++) - // { - // mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; - // } - - // // assume value field has been modified - // mainAssemblyNameRow.Fields[2].Modified = true; - // mainAssemblyNameRow.Operation = mainFileRow.Operation; - // } - // } - // } - - // // Add patch header for this file - // if (null != patchFileRow.Patch) - // { - // // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. - // this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); - // this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); - - // // Add to Patch table - // var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); - // if (0 == patchTable.Rows.Count) - // { - // patchTable.Operation = TableOperation.Add; - // } - - // var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); - // patchRow[0] = patchFileRow.File; - // patchRow[1] = patchFileRow.Sequence; - - // var patchFile = new FileInfo(patchFileRow.Source); - // patchRow[2] = (int)patchFile.Length; - // patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; - - // var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; - // if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) - // { - // streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); - - // var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); - // if (0 == patchHeadersTable.Rows.Count) - // { - // patchHeadersTable.Operation = TableOperation.Add; - // } - - // var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); - // patchHeadersRow[0] = streamName; - // patchHeadersRow[1] = patchFileRow.Patch; - // patchRow[5] = streamName; - // patchHeadersRow.Operation = RowOperation.Add; - // } - // else - // { - // patchRow[4] = patchFileRow.Patch; - // } - // patchRow.Operation = RowOperation.Add; - // } - // } - // else - // { - // // TODO: throw because all transform rows should have made it into the patch - // } - //} - } - } -#endif - this.FileFacades = allFileRows; - } - - /// - /// Adds the PatchFiles action to the sequence table if it does not already exist. - /// - /// The sequence table to check or modify. - /// The primary authoring transform. - /// The secondary patch transform. - /// The file row that contains information about the patched file. - private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow) - { - var tableName = table.ToString(); - - // Find/add PatchFiles action (also determine sequence for it). - // Search mainTransform first, then pairedTransform (pairedTransform overrides). - var hasPatchFilesAction = false; - var installFilesSequence = 0; - var duplicateFilesSequence = 0; - - TestSequenceTableForPatchFilesAction( - mainTransform.Tables[tableName], - ref hasPatchFilesAction, - ref installFilesSequence, - ref duplicateFilesSequence); - TestSequenceTableForPatchFilesAction( - pairedTransform.Tables[tableName], - ref hasPatchFilesAction, - ref installFilesSequence, - ref duplicateFilesSequence); - if (!hasPatchFilesAction) - { - WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionTuple); - - var sequence = patchFilesActionTuple.Sequence; - - // Test for default sequence value's appropriateness - if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence)) - { - if (0 != duplicateFilesSequence) - { - if (duplicateFilesSequence < installFilesSequence) - { - throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action)); } else { - sequence = (duplicateFilesSequence + installFilesSequence) / 2; - if (installFilesSequence == sequence || duplicateFilesSequence == sequence) + // TODO: should this entire condition be placed in the binder file manager? + if (/*(0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&*/ + !this.FileSystemManager.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString())) { - throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action)); + // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. + mainFileRow.Operation = RowOperation.Modify; + if (null != pairedFileRow) + { + // Always patch-added, but never non-compressed. + pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed; + pairedFileRow.Fields[6].Modified = true; + pairedFileRow.Operation = RowOperation.Modify; + } + } + else + { + // The File is same. We need mark all the attributes as unchanged. + mainFileRow.Operation = RowOperation.None; + foreach (var field in mainFileRow.Fields) + { + field.Modified = false; + } + + if (null != pairedFileRow) + { + pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded; + pairedFileRow.Fields[6].Modified = false; + pairedFileRow.Operation = RowOperation.None; + } + continue; } } } - else - { - sequence = installFilesSequence + 1; - } - } - var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); - if (0 == sequenceTable.Rows.Count) - { - sequenceTable.Operation = TableOperation.Add; - } - - var patchAction = sequenceTable.CreateRow(null); - patchAction[0] = patchFilesActionTuple.Action; - patchAction[1] = patchFilesActionTuple.Condition; - patchAction[2] = sequence; - patchAction.Operation = RowOperation.Add; - } - } + // index patch files by diskId+fileId + var diskId = mainFileRow.DiskId; - /// - /// Tests sequence table for PatchFiles and associated actions - /// - /// The table to test. - /// Set to true if PatchFiles action is found. Left unchanged otherwise. - /// Set to sequence value of InstallFiles action if found. Left unchanged otherwise. - /// Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise. - private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence) - { - if (null != sequenceTable) - { - foreach (var row in sequenceTable.Rows) - { - var actionName = row.FieldAsString(0); - switch (actionName) + if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows)) { - case "PatchFiles": - hasPatchFilesAction = true; - break; - - case "InstallFiles": - installFilesSequence = row.FieldAsInteger(2); - break; - - case "DuplicateFiles": - duplicateFilesSequence = row.FieldAsInteger(2); - break; + mediaFileRows = new RowDictionary(); + patchMediaFileRows.Add(diskId, mediaFileRows); } - } - } - } - - /// - /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. - /// - /// The output to validate. - private void ValidateFileRowChanges(WindowsInstallerData transform) - { - var componentTable = transform.Tables["Component"]; - var fileTable = transform.Tables["File"]; - - // There's no sense validating keypaths if the transform has no component or file table - if (componentTable == null || fileTable == null) - { - return; - } - var componentKeyPath = new Dictionary(componentTable.Rows.Count); - - // Index the Component table for non-directory & non-registry key paths. - foreach (var row in componentTable.Rows) - { - var keyPath = row.FieldAsString(5); - if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) - { - componentKeyPath.Add(row.FieldAsString(0), keyPath); - } - } + var patchFileRow = mediaFileRows.Get(mainFileId); - var componentWithChangedKeyPath = new Dictionary(); - var componentWithNonKeyPathChanged = new Dictionary(); - // Verify changes in the file table, now that file diffing has occurred - foreach (FileRow row in fileTable.Rows) - { - if (RowOperation.Modify != row.Operation) - { - continue; - } + if (null == patchFileRow) + { + //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers); + patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers); + mainFileRow.CopyTo(patchFileRow); - var fileId = row.FieldAsString(0); - var componentId = row.FieldAsString(1); + mediaFileRows.Add(patchFileRow); - // If this file is the keypath of a component - if (componentKeyPath.ContainsValue(fileId)) - { - if (!componentWithChangedKeyPath.ContainsKey(componentId)) - { - componentWithChangedKeyPath.Add(componentId, fileId); + allFileRows.Add(new FileFacade(patchFileRow)); // TODO: should we be passing along delta information? Probably, right? } - } - else - { - if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) + else { - componentWithNonKeyPathChanged.Add(componentId, fileId); - } - } - } - - foreach (var componentFile in componentWithNonKeyPathChanged) - { - // Make sure all changes to non keypath files also had a change in the keypath. - if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath)) - { - this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath)); - } - } - } + // TODO: confirm the rest of data is identical? - private bool CompareFiles(string targetFile, string updatedFile) - { - bool? compared = null; - foreach (var extension in this.Extensions) - { - compared = extension.CompareFiles(targetFile, updatedFile); + // make sure Source is same. Otherwise we are silently ignoring a file. + if (0 != String.Compare(patchFileRow.Source, mainFileRow.Source, StringComparison.OrdinalIgnoreCase)) + { + this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, mainFileId, patchFileRow.Source, mainFileRow.Source)); + } - if (compared.HasValue) - { - break; +#if TODO_PATCHING_DELTA + // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. + patchFileRow.AppendPreviousDataFrom(mainFileRow); +#endif + } } } - if (!compared.HasValue) - { - throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result. - } - - return compared.Value; + this.FileFacades = allFileRows; } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs index cd6170d0..bddcccb7 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs @@ -12,6 +12,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind using WixToolset.Core.Native; using WixToolset.Core.WindowsInstaller.Msi; using WixToolset.Data; + using WixToolset.Data.Tuples; using WixToolset.Data.WindowsInstaller; using WixToolset.Data.WindowsInstaller.Rows; using WixToolset.Extensibility.Services; @@ -34,7 +35,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind public string OutputPath { private get; set; } - public IEnumerable SuppressedTableNames { private get; set; } + public TableDefinitionCollection TableDefinitions { private get; set; } public string IntermediateFolder { private get; set; } @@ -49,6 +50,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind return; } + var suppressedTableNames = this.AddBackSuppresedSequenceTables(); + IMsmMerge2 merge = null; bool commit = true; bool logOpen = false; @@ -212,9 +215,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind return; } - using (Database db = new Database(this.OutputPath, OpenDatabase.Direct)) + using (var db = new Database(this.OutputPath, OpenDatabase.Direct)) { - Table suppressActionTable = this.Output.Tables["WixSuppressAction"]; + var suppressActionTable = this.Output.Tables["WixSuppressAction"]; // suppress individual actions if (null != suppressActionTable) @@ -239,40 +242,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind } // query for merge module actions in suppressed sequences and drop them - foreach (string tableName in this.SuppressedTableNames) + foreach (var tableName in suppressedTableNames) { if (!db.TableExists(tableName)) { continue; } - using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) + using (var view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) { - foreach (Record resultRecord in view.Records) + foreach (var resultRecord in view.Records) { this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName)); } } // drop suppressed sequences - using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) + using (var view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) { } // delete the validation rows - using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) + using (var view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) + using (var record = new Record(1)) { - using (Record record = new Record(1)) - { - record.SetString(1, tableName); - view.Execute(record); - } + record.SetString(1, tableName); + view.Execute(record); } } // now update the Attributes column for the files from the Merge Modules this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles()); - using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) + using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) { foreach (var file in this.FileFacades) { @@ -281,13 +282,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind continue; } - using (Record record = new Record(1)) + using (var record = new Record(1)) { record.SetString(1, file.Id); view.Execute(record); } - using (Record recordUpdate = view.Fetch()) + using (var recordUpdate = view.Fetch()) { if (null == recordUpdate) { @@ -332,5 +333,31 @@ namespace WixToolset.Core.WindowsInstaller.Bind db.Commit(); } } + + private IEnumerable AddBackSuppresedSequenceTables() + { + // Add back possibly suppressed sequence tables since all sequence tables must be present + // for the merge process to work. We'll drop the suppressed sequence tables again as + // necessary. + var suppressedTableNames = new HashSet(); + + foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) + { + var sequenceTableName = (sequence == SequenceTable.AdvertiseExecuteSequence) ? "AdvtExecuteSequence" : sequence.ToString(); + var sequenceTable = this.Output.Tables[sequenceTableName]; + + if (null == sequenceTable) + { + sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); + } + + if (0 == sequenceTable.Rows.Count) + { + suppressedTableNames.Add(sequenceTableName); + } + } + + return suppressedTableNames; + } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs b/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs index 5ada29de..4d849753 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs @@ -2,14 +2,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind { - using System; - using System.Collections; - using System.Globalization; - using System.Text; - using System.Text.RegularExpressions; - using WixToolset.Data; using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility; internal class PatchTransform { @@ -22,225 +15,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind public string Baseline { get; } public WindowsInstallerData Transform { get; } - - /// - /// Validates that the differences in the transform are valid for patch transforms. - /// - public void Validate() - { -#if TODO_PATCHING - // Changing the ProdocutCode in a patch transform is not recommended. - Table propertyTable = this.Transform.Tables["Property"]; - if (null != propertyTable) - { - foreach (Row row in propertyTable.Rows) - { - // Only interested in modified rows; fast check. - if (RowOperation.Modify == row.Operation) - { - if (0 == String.CompareOrdinal("ProductCode", (string)row[0])) - { - this.OnMessage(WixWarnings.MajorUpgradePatchNotRecommended()); - } - } - } - } - - // If there is nothing in the component table we can return early because the remaining checks are component based. - Table componentTable = this.Transform.Tables["Component"]; - if (null == componentTable) - { - return; - } - - // Index Feature table row operations - Table featureTable = this.Transform.Tables["Feature"]; - Table featureComponentsTable = this.Transform.Tables["FeatureComponents"]; - Hashtable featureOps = null; - if (null != featureTable) - { - int capacity = featureTable.Rows.Count; - featureOps = new Hashtable(capacity); - - foreach (Row row in featureTable.Rows) - { - featureOps[(string)row[0]] = row.Operation; - } - } - else - { - featureOps = new Hashtable(); - } - - // Index Component table and check for keypath modifications - Hashtable deletedComponent = new Hashtable(); - Hashtable componentKeyPath = new Hashtable(); - foreach (Row row in componentTable.Rows) - { - string id = row.Fields[0].Data.ToString(); - string keypath = (null == row.Fields[5].Data) ? String.Empty : row.Fields[5].Data.ToString(); - - componentKeyPath.Add(id, keypath); - if (RowOperation.Delete == row.Operation) - { - deletedComponent.Add(id, row); - } - else if (RowOperation.Modify == row.Operation) - { - if (row.Fields[1].Modified) - { - // Changing the guid of a component is equal to deleting the old one and adding a new one. - deletedComponent.Add(id, row); - } - - // If the keypath is modified its an error - if (row.Fields[5].Modified) - { - this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, id, this.transformPath)); - } - } - } - - // Verify changes in the file table - Table fileTable = this.Transform.Tables["File"]; - if (null != fileTable) - { - Hashtable componentWithChangedKeyPath = new Hashtable(); - foreach (Row row in fileTable.Rows) - { - if (RowOperation.None != row.Operation) - { - string fileId = row.Fields[0].Data.ToString(); - string componentId = row.Fields[1].Data.ToString(); - - // If this file is the keypath of a component - if (String.Equals((string)componentKeyPath[componentId], fileId, StringComparison.Ordinal)) - { - if (row.Fields[2].Modified) - { - // You cant change the filename of a file that is the keypath of a component. - this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, componentId, this.transformPath)); - } - - if (!componentWithChangedKeyPath.ContainsKey(componentId)) - { - componentWithChangedKeyPath.Add(componentId, fileId); - } - } - - if (RowOperation.Delete == row.Operation) - { - // If the file is removed from a component that is not deleted. - if (!deletedComponent.ContainsKey(componentId)) - { - bool foundRemoveFileEntry = false; - string filename = Common.GetName((string)row[2], false, true); - - Table removeFileTable = this.Transform.Tables["RemoveFile"]; - if (null != removeFileTable) - { - foreach (Row removeFileRow in removeFileTable.Rows) - { - if (RowOperation.Delete == removeFileRow.Operation) - { - continue; - } - - if (componentId == (string)removeFileRow[1]) - { - // Check if there is a RemoveFile entry for this file - if (null != removeFileRow[2]) - { - string removeFileName = Common.GetName((string)removeFileRow[2], false, true); - - // Convert the MSI format for a wildcard string to Regex format. - removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); - - Regex regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - if (regex.IsMatch(filename)) - { - foundRemoveFileEntry = true; - break; - } - } - } - } - } - - if (!foundRemoveFileEntry) - { - this.OnMessage(WixWarnings.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId)); - } - } - } - } - } - } - - if (0 < deletedComponent.Count) - { - // Index FeatureComponents table. - Hashtable featureComponents = new Hashtable(); - - if (null != featureComponentsTable) - { - foreach (Row row in featureComponentsTable.Rows) - { - ArrayList features; - string componentId = row.Fields[1].Data.ToString(); - - if (featureComponents.Contains(componentId)) - { - features = (ArrayList)featureComponents[componentId]; - } - else - { - features = new ArrayList(); - featureComponents.Add(componentId, features); - } - features.Add(row.Fields[0].Data.ToString()); - } - } - - // Check to make sure if a component was deleted, the feature was too. - foreach (DictionaryEntry entry in deletedComponent) - { - if (featureComponents.Contains(entry.Key.ToString())) - { - ArrayList features = (ArrayList)featureComponents[entry.Key.ToString()]; - foreach (string featureId in features) - { - if (!featureOps.ContainsKey(featureId) || RowOperation.Delete != (RowOperation)featureOps[featureId]) - { - // The feature was not deleted. - this.OnMessage(WixErrors.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, this.transformPath)); - } - } - } - } - } - - // Warn if new components are added to existing features - if (null != featureComponentsTable) - { - foreach (Row row in featureComponentsTable.Rows) - { - if (RowOperation.Add == row.Operation) - { - // Check if the feature is in the Feature table - string feature_ = (string)row[0]; - string component_ = (string)row[1]; - - // Features may not be present if not referenced - if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_]) - { - this.OnMessage(WixWarnings.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, this.transformPath)); - } - } - } - } -#endif - throw new NotImplementedException(); - } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs index 4ca5ec48..63a8b3d9 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs @@ -39,20 +39,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind public void Execute() { + var assemblyNameTuples = this.Section.Tuples.OfType().ToDictionary(t => t.Id.Id); + foreach (var file in this.UpdateFileFacades) { - this.UpdateFileFacade(file); + this.UpdateFileFacade(file, assemblyNameTuples); } } - private void UpdateFileFacade(FileFacade facade) + private void UpdateFileFacade(FileFacade facade, Dictionary assemblyNameTuples) { - var assemblyNameTuples = new Dictionary(); - foreach (var assemblyTuple in this.Section.Tuples.OfType()) - { - assemblyNameTuples.Add(assemblyTuple.ComponentRef + "/" + assemblyTuple.Name, assemblyTuple); - } - FileInfo fileInfo = null; try { @@ -335,7 +331,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind var lookup = String.Concat(facade.ComponentRef, "/", name); if (!assemblyNameTuples.TryGetValue(lookup, out var assemblyNameTuple)) { - assemblyNameTuple = this.Section.AddTuple(new MsiAssemblyNameTuple(facade.SourceLineNumber) + assemblyNameTuple = this.Section.AddTuple(new MsiAssemblyNameTuple(facade.SourceLineNumber, new Identifier(AccessModifier.Private, facade.ComponentRef, name)) { ComponentRef = facade.ComponentRef, Name = name, @@ -348,6 +344,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind } facade.AssemblyNames.Add(assemblyNameTuple); + + assemblyNameTuples.Add(assemblyNameTuple.Id.Id, assemblyNameTuple); } assemblyNameTuple.Value = value; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs index 5d18a230..9aab7b98 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs @@ -6,62 +6,59 @@ namespace WixToolset.Core.WindowsInstaller.Bind using System.Linq; using WixToolset.Core.Bind; using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Data.Tuples; internal class UpdateMediaSequencesCommand { - public UpdateMediaSequencesCommand(WindowsInstallerData output, List fileFacades) + public UpdateMediaSequencesCommand(IntermediateSection section, List fileFacades) { - this.Output = output; + this.Section = section; this.FileFacades = fileFacades; } - private WindowsInstallerData Output { get; } + private IntermediateSection Section { get; } private List FileFacades { get; } public void Execute() { - var fileRows = new RowDictionary(this.Output.Tables["File"]); - var mediaRows = new RowDictionary(this.Output.Tables["Media"]); + var mediaRows = this.Section.Tuples.OfType().ToDictionary(t => t.DiskId); // Calculate sequence numbers and media disk id layout for all file media information objects. - if (OutputType.Module == this.Output.Type) + if (SectionType.Module == this.Section.Type) { var lastSequence = 0; // Order by Component to group the files by directory. var optimized = this.OptimizedFileFacades(); - foreach (var fileId in optimized.Select(f => f.Id)) + foreach (var facade in optimized) { - var fileRow = fileRows.Get(fileId); - fileRow.Sequence = ++lastSequence; + facade.Sequence = ++lastSequence; } } else { var lastSequence = 0; - MediaRow mediaRow = null; + MediaTuple mediaTuple = null; var patchGroups = new Dictionary>(); // sequence the non-patch-added files var optimized = this.OptimizedFileFacades(); foreach (var facade in optimized) { - if (null == mediaRow) + if (null == mediaTuple) { - mediaRow = mediaRows.Get(facade.DiskId); - if (OutputType.Patch == this.Output.Type) + mediaTuple = mediaRows[facade.DiskId]; + if (SectionType.Patch == this.Section.Type) { // patch Media cannot start at zero - lastSequence = mediaRow.LastSequence; + lastSequence = mediaTuple.LastSequence ?? 1; } } - else if (mediaRow.DiskId != facade.DiskId) + else if (mediaTuple.DiskId != facade.DiskId) { - mediaRow.LastSequence = lastSequence; - mediaRow = mediaRows.Get(facade.DiskId); + mediaTuple.LastSequence = lastSequence; + mediaTuple = mediaRows[facade.DiskId]; } if (facade.PatchGroup.HasValue) @@ -76,15 +73,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind } else if (!facade.FromModule) { - var fileRow = fileRows.Get(facade.Id); - fileRow.Sequence = ++lastSequence; + facade.Sequence = ++lastSequence; } } - if (null != mediaRow) + if (null != mediaTuple) { - mediaRow.LastSequence = lastSequence; - mediaRow = null; + mediaTuple.LastSequence = lastSequence; + mediaTuple = null; } // sequence the patch-added files @@ -92,24 +88,23 @@ namespace WixToolset.Core.WindowsInstaller.Bind { foreach (var facade in patchGroup) { - if (null == mediaRow) + if (null == mediaTuple) { - mediaRow = mediaRows.Get(facade.DiskId); + mediaTuple = mediaRows[facade.DiskId]; } - else if (mediaRow.DiskId != facade.DiskId) + else if (mediaTuple.DiskId != facade.DiskId) { - mediaRow.LastSequence = lastSequence; - mediaRow = mediaRows.Get(facade.DiskId); + mediaTuple.LastSequence = lastSequence; + mediaTuple = mediaRows[facade.DiskId]; } - var fileRow = fileRows.Get(facade.Id); - fileRow.Sequence = ++lastSequence; + facade.Sequence = ++lastSequence; } } - if (null != mediaRow) + if (null != mediaTuple) { - mediaRow.LastSequence = lastSequence; + mediaTuple.LastSequence = lastSequence; } } } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs new file mode 100644 index 00000000..af2e8f85 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs @@ -0,0 +1,453 @@ +// 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. + +namespace WixToolset.Core.WindowsInstaller.Bind +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using WixToolset.Core.Bind; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Data.WindowsInstaller.Rows; + using WixToolset.Extensibility.Services; + + internal class UpdateTransformsWithFileFacades + { + public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable subStorages, TableDefinitionCollection tableDefinitions, IEnumerable fileFacades) + { + this.Messaging = messaging; + this.Output = output; + this.SubStorages = subStorages; + this.TableDefinitions = tableDefinitions; + this.FileFacades = fileFacades; + } + + private IMessaging Messaging { get; } + + private WindowsInstallerData Output { get; } + + private IEnumerable SubStorages { get; } + + private TableDefinitionCollection TableDefinitions { get; } + + private IEnumerable FileFacades { get; } + + public void Execute() + { + var fileFacadesByDiskId = new Dictionary>(); + + // Index patch file facades by diskId+fileId. + foreach (var facade in this.FileFacades) + { + if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades)) + { + mediaFacades = new Dictionary(); + fileFacadesByDiskId.Add(facade.DiskId, mediaFacades); + } + + mediaFacades.Add(facade.Id, facade); + } + + var patchMediaRows = new RowDictionary(this.Output.Tables["Media"]); + + // Index paired transforms by name without the "#" prefix. + var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data); + + // Copy File bind data into substorages + foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#"))) + { + var mainTransform = substorage.Data; + + var mainMsiFileHashIndex = new RowDictionary(mainTransform.Tables["MsiFileHash"]); + + var pairedTransform = pairedTransforms["#" + substorage.Name]; + + // Copy Media.LastSequence. + var pairedMediaTable = pairedTransform.Tables["Media"]; + foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) + { + var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId); + pairedMediaRow.LastSequence = patchMediaRow.LastSequence; + } + + // Validate file row changes for keypath-related issues + this.ValidateFileRowChanges(mainTransform); + + // Index File table of pairedTransform + var pairedFileRows = new RowDictionary(pairedTransform.Tables["File"]); + + var mainFileTable = mainTransform.Tables["File"]; + if (null != mainFileTable) + { + // Remove the MsiFileHash table because it will be updated later with the final file hash for each file + mainTransform.Tables.Remove("MsiFileHash"); + + foreach (FileRow mainFileRow in mainFileTable.Rows) + { + if (RowOperation.Delete == mainFileRow.Operation) + { + continue; + } + else if (RowOperation.None == mainFileRow.Operation) + { + continue; + } + + // Index patch files by diskId+fileId + if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades)) + { + mediaFacades = new Dictionary(); + fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades); + } + + // copy data from the patch back to the transform + if (mediaFacades.TryGetValue(mainFileRow.File, out var facade)) + { + var patchFileRow = facade.GetFileRow(); + var pairedFileRow = pairedFileRows.Get(mainFileRow.File); + + for (var i = 0; i < patchFileRow.Fields.Length; i++) + { + var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty; + var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty; + + if (1 == i) + { + // File.Component_ changes should not come from the shared file rows + // that contain the file information as each individual transform might + // have different changes (or no changes at all). + } + else if (6 == i) // File.Attributes should not changed for binary deltas + { +#if TODO_PATCHING_DELTA + if (null != patchFileRow.Patch) + { + // File.Attribute should not change for binary deltas + pairedFileRow.Attributes = mainFileRow.Attributes; + mainFileRow.Fields[i].Modified = false; + } +#endif + } + else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform + { + // file sequence is updated in Patch table instead of File table for delta patches +#if TODO_PATCHING_DELTA + if (null != patchFileRow.Patch) + { + pairedFileRow.Fields[i].Modified = false; + } + else +#endif + { + pairedFileRow[i] = patchFileRow[i]; + pairedFileRow.Fields[i].Modified = true; + } + mainFileRow.Fields[i].Modified = false; + } + else if (patchValue != mainValue) + { + mainFileRow[i] = patchFileRow[i]; + mainFileRow.Fields[i].Modified = true; + if (mainFileRow.Operation == RowOperation.None) + { + mainFileRow.Operation = RowOperation.Modify; + } + } + } + + // Copy MsiFileHash row for this File. + if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow)) + { + //patchHashRow = patchFileRow.Hash; + throw new NotImplementedException(); + } + + if (null != patchHashRow) + { + var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]); + var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); + for (var i = 0; i < patchHashRow.Fields.Length; i++) + { + mainHashRow[i] = patchHashRow[i]; + if (i > 1) + { + // assume all hash fields have been modified + mainHashRow.Fields[i].Modified = true; + } + } + + // assume the MsiFileHash operation follows the File one + mainHashRow.Operation = mainFileRow.Operation; + } + + // copy MsiAssemblyName rows for this File +#if TODO_PATCHING + List patchAssemblyNameRows = patchFileRow.AssemblyNames; + if (null != patchAssemblyNameRows) + { + var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); + foreach (var patchAssemblyNameRow in patchAssemblyNameRows) + { + // Copy if there isn't an identical modified/added row already in the transform. + var foundMatchingModifiedRow = false; + foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows) + { + if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) + { + foundMatchingModifiedRow = true; + break; + } + } + + if (!foundMatchingModifiedRow) + { + var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); + for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++) + { + mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; + } + + // assume value field has been modified + mainAssemblyNameRow.Fields[2].Modified = true; + mainAssemblyNameRow.Operation = mainFileRow.Operation; + } + } + } +#endif + + // Add patch header for this file +#if TODO_PATCHING_DELTA + if (null != patchFileRow.Patch) + { + // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. + this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); + this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); + + // Add to Patch table + var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]); + if (0 == patchTable.Rows.Count) + { + patchTable.Operation = TableOperation.Add; + } + + var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); + patchRow[0] = patchFileRow.File; + patchRow[1] = patchFileRow.Sequence; + + var patchFile = new FileInfo(patchFileRow.Source); + patchRow[2] = (int)patchFile.Length; + patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; + + var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; + if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length) + { + streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_'); + + var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]); + if (0 == patchHeadersTable.Rows.Count) + { + patchHeadersTable.Operation = TableOperation.Add; + } + + var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); + patchHeadersRow[0] = streamName; + patchHeadersRow[1] = patchFileRow.Patch; + patchRow[5] = streamName; + patchHeadersRow.Operation = RowOperation.Add; + } + else + { + patchRow[4] = patchFileRow.Patch; + } + patchRow.Operation = RowOperation.Add; + } +#endif + } + else + { + // TODO: throw because all transform rows should have made it into the patch + } + } + } + + this.Output.Tables.Remove("Media"); + this.Output.Tables.Remove("File"); + this.Output.Tables.Remove("MsiFileHash"); + this.Output.Tables.Remove("MsiAssemblyName"); + } + } + + /// + /// Adds the PatchFiles action to the sequence table if it does not already exist. + /// + /// The sequence table to check or modify. + /// The primary authoring transform. + /// The secondary patch transform. + /// The file row that contains information about the patched file. + private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow) + { + var tableName = table.ToString(); + + // Find/add PatchFiles action (also determine sequence for it). + // Search mainTransform first, then pairedTransform (pairedTransform overrides). + var hasPatchFilesAction = false; + var installFilesSequence = 0; + var duplicateFilesSequence = 0; + + TestSequenceTableForPatchFilesAction( + mainTransform.Tables[tableName], + ref hasPatchFilesAction, + ref installFilesSequence, + ref duplicateFilesSequence); + TestSequenceTableForPatchFilesAction( + pairedTransform.Tables[tableName], + ref hasPatchFilesAction, + ref installFilesSequence, + ref duplicateFilesSequence); + if (!hasPatchFilesAction) + { + WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionTuple); + + var sequence = patchFilesActionTuple.Sequence; + + // Test for default sequence value's appropriateness + if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence)) + { + if (0 != duplicateFilesSequence) + { + if (duplicateFilesSequence < installFilesSequence) + { + throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action)); + } + else + { + sequence = (duplicateFilesSequence + installFilesSequence) / 2; + if (installFilesSequence == sequence || duplicateFilesSequence == sequence) + { + throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionTuple.Action)); + } + } + } + else + { + sequence = installFilesSequence + 1; + } + } + + var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]); + if (0 == sequenceTable.Rows.Count) + { + sequenceTable.Operation = TableOperation.Add; + } + + var patchAction = sequenceTable.CreateRow(null); + patchAction[0] = patchFilesActionTuple.Action; + patchAction[1] = patchFilesActionTuple.Condition; + patchAction[2] = sequence; + patchAction.Operation = RowOperation.Add; + } + } + + /// + /// Tests sequence table for PatchFiles and associated actions + /// + /// The table to test. + /// Set to true if PatchFiles action is found. Left unchanged otherwise. + /// Set to sequence value of InstallFiles action if found. Left unchanged otherwise. + /// Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise. + private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence) + { + if (null != sequenceTable) + { + foreach (var row in sequenceTable.Rows) + { + var actionName = row.FieldAsString(0); + switch (actionName) + { + case "PatchFiles": + hasPatchFilesAction = true; + break; + + case "InstallFiles": + installFilesSequence = row.FieldAsInteger(2); + break; + + case "DuplicateFiles": + duplicateFilesSequence = row.FieldAsInteger(2); + break; + } + } + } + } + + /// + /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component. + /// + /// The output to validate. + private void ValidateFileRowChanges(WindowsInstallerData transform) + { + var componentTable = transform.Tables["Component"]; + var fileTable = transform.Tables["File"]; + + // There's no sense validating keypaths if the transform has no component or file table + if (componentTable == null || fileTable == null) + { + return; + } + + var componentKeyPath = new Dictionary(componentTable.Rows.Count); + + // Index the Component table for non-directory & non-registry key paths. + foreach (var row in componentTable.Rows) + { + var keyPath = row.FieldAsString(5); + if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath)) + { + componentKeyPath.Add(row.FieldAsString(0), keyPath); + } + } + + var componentWithChangedKeyPath = new Dictionary(); + var componentWithNonKeyPathChanged = new Dictionary(); + // Verify changes in the file table, now that file diffing has occurred + foreach (FileRow row in fileTable.Rows) + { + if (RowOperation.Modify != row.Operation) + { + continue; + } + + var fileId = row.FieldAsString(0); + var componentId = row.FieldAsString(1); + + // If this file is the keypath of a component + if (componentKeyPath.ContainsValue(fileId)) + { + if (!componentWithChangedKeyPath.ContainsKey(componentId)) + { + componentWithChangedKeyPath.Add(componentId, fileId); + } + } + else + { + if (!componentWithNonKeyPathChanged.ContainsKey(componentId)) + { + componentWithNonKeyPathChanged.Add(componentId, fileId); + } + } + } + + foreach (var componentFile in componentWithNonKeyPathChanged) + { + // Make sure all changes to non keypath files also had a change in the keypath. + if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath)) + { + this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath)); + } + } + } + } +} diff --git a/src/WixToolset.Core/Bind/FileFacade.cs b/src/WixToolset.Core/Bind/FileFacade.cs index 7bfdb9bb..f0ce14ca 100644 --- a/src/WixToolset.Core/Bind/FileFacade.cs +++ b/src/WixToolset.Core/Bind/FileFacade.cs @@ -15,18 +15,27 @@ namespace WixToolset.Core.Bind { this.FileTuple = file; this.AssemblyTuple = assembly; + + this.Identifier = file.Id; + this.ComponentRef = file.ComponentRef; } public FileFacade(bool fromModule, FileTuple file) { this.FromModule = fromModule; this.FileTuple = file; + + this.Identifier = file.Id; + this.ComponentRef = file.ComponentRef; } - internal FileFacade(FileRow row) + public FileFacade(FileRow row) { this.FromTransform = true; this.FileRow = row; + + this.Identifier = new Identifier(AccessModifier.Private, row.File); + this.ComponentRef = row.Component; } public bool FromModule { get; } @@ -39,11 +48,11 @@ namespace WixToolset.Core.Bind private AssemblyTuple AssemblyTuple { get; } - public string Id => this.FileRow == null ? this.FileTuple.Id.Id : this.FileRow.File; + public string Id => this.Identifier.Id; - public Identifier Identifier => this.FileRow == null ? this.FileTuple.Id : throw new NotImplementedException(); + public Identifier Identifier { get; } - public string ComponentRef => this.FileRow == null ? this.FileTuple.ComponentRef : this.FileRow.Component; + public string ComponentRef { get; } public int DiskId { @@ -137,7 +146,7 @@ namespace WixToolset.Core.Bind } } - public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblyTuple?.Type : throw new NotImplementedException(); + public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblyTuple?.Type : null; public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblyTuple?.ApplicationFileRef : throw new NotImplementedException(); @@ -153,5 +162,10 @@ namespace WixToolset.Core.Bind /// Gets or sets the MsiFileHash row for this file. /// public MsiFileHashTuple Hash { get; set; } + + /// + /// Allows direct access to the underlying FileRow as requried for patching. + /// + public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException(); } } diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index 80003392..8392131f 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs @@ -334,6 +334,7 @@ namespace WixToolset.Core.CommandLine context.DelayedFields = resolveResult.DelayedFields; context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; context.Extensions = this.ExtensionManager.GetServices(); + context.FileSystemExtensions = this.ExtensionManager.GetServices(); context.Ices = Array.Empty(); // TODO: set this correctly context.IntermediateFolder = intermediateFolder; context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; diff --git a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs index 584f86fe..3616bcab 100644 --- a/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/PatchFixture.cs @@ -16,7 +16,7 @@ namespace WixToolsetTest.CoreIntegration { private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd"; - [Fact(Skip = "Skip until patches have files in them")] + [Fact] public void CanBuildSimplePatch() { var folder = TestData.Get(@"TestData\PatchSingle"); @@ -45,7 +45,7 @@ namespace WixToolsetTest.CoreIntegration Assert.True(File.Exists(cab)); var files = Query.GetCabinetFiles(cab); - Assert.Equal(new[] { "a", "b" }, files.Select(f => f.ArchiveName).ToArray()); // This test may not be quite correct, yet. + Assert.Equal(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray()); } } -- cgit v1.2.3-55-g6feb