From 0d4df529a7e34f033112a1c6f16f749880334e7d Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 16 Mar 2021 10:52:55 -0700 Subject: Use validation now implemented in Core.Native Fixes wixtoolset/issues#5946 --- .../Bind/BindDatabaseCommand.cs | 50 +++--- .../Bind/ValidateDatabaseCommand.cs | 177 +++++++++++++++++++++ 2 files changed, 201 insertions(+), 26 deletions(-) create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs (limited to 'src/WixToolset.Core.WindowsInstaller/Bind') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index df1c824a..6503ba65 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -21,11 +21,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); - public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, Validator validator) : this(context, backendExtension, null, validator) + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, string cubeFile) : this(context, backendExtension, null, cubeFile) { } - public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, Validator validator) + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, string cubeFile) { this.ServiceProvider = context.ServiceProvider; @@ -50,7 +50,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.SuppressLayout = context.SuppressLayout; this.SubStorages = subStorages; - this.Validator = validator; + + this.SuppressValidation = context.SuppressValidation; + this.Ices = context.Ices; + this.SuppressedIces = context.SuppressIces; + this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile }; this.BackendExtensions = backendExtension; } @@ -97,7 +101,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind private string IntermediateFolder { get; } - private Validator Validator { get; } + private bool SuppressValidation { get; } + + private IEnumerable Ices { get; } + + private IEnumerable SuppressedIces { get; } + + private IEnumerable CubeFiles { get; } public IBindResult Execute() { @@ -522,34 +532,22 @@ namespace WixToolset.Core.WindowsInstaller.Bind if (this.Messaging.EncounteredError) { return null; - } + } -#if TODO_FINISH_VALIDATION - // Validate the output if there is an MSI validator. - if (null != this.Validator) + // Validate the output if there are CUBe files and we're not explicitly suppressing validation. + if (this.CubeFiles != null && !this.SuppressValidation) { - Stopwatch stopwatch = Stopwatch.StartNew(); - - // set the output file for source line information - this.Validator.Output = this.Output; - - Messaging.Instance.Write(WixVerboses.ValidatingDatabase()); - - this.Validator.Validate(this.OutputPath); - - stopwatch.Stop(); - Messaging.Instance.Write(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); + var command = new ValidateDatabaseCommand(this.Messaging, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces); + command.Execute(); + } - // Stop processing if an error occurred. - if (Messaging.Instance.EncounteredError) - { - return; - } + if (this.Messaging.EncounteredError) + { + return null; } -#endif // Process uncompressed files. - if (!this.Messaging.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) + if (!this.SuppressLayout && uncompressedFiles.Any()) { var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver); command.Compressed = compressed; diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs new file mode 100644 index 00000000..35b6018c --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs @@ -0,0 +1,177 @@ +// 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.ComponentModel; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Threading; + using WixToolset.Core.Native; + using WixToolset.Core.Native.Msi; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class ValidateDatabaseCommand : IWindowsInstallerValidatorCallback + { + // Set of ICEs that have equivalent-or-better checks in WiX. + private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" }; + + public ValidateDatabaseCommand(IMessaging messaging, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable cubeFiles, IEnumerable ices, IEnumerable suppressedIces) + { + this.Messaging = messaging; + this.Data = data; + this.OutputPath = outputPath; + this.CubeFiles = cubeFiles; + this.Ices = ices; + this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces); + + this.IntermediateFolder = intermediateFolder; + this.OutputSourceLineNumber = new SourceLineNumber(outputPath); + } + + /// + /// Encountered error implementation for . + /// + public bool EncounteredError => this.Messaging.EncounteredError; + + private IMessaging Messaging { get; } + + private WindowsInstallerData Data { get; } + + private string OutputPath { get; } + + private IEnumerable CubeFiles { get; } + + private IEnumerable Ices { get; } + + private IEnumerable SuppressedIces { get; } + + private string IntermediateFolder { get; } + + /// + /// Fallback when an exact source line number cannot be calculated for a validation error. + /// + private SourceLineNumber OutputSourceLineNumber { get; set; } + + private Dictionary SourceLineNumbersByTablePrimaryKey { get; set; } + + public void Execute() + { + var stopwatch = Stopwatch.StartNew(); + + this.Messaging.Write(VerboseMessages.ValidatingDatabase()); + + // Ensure the temporary files can be created the working folder. + var workingFolder = Path.Combine(this.IntermediateFolder, "_validate"); + Directory.CreateDirectory(workingFolder); + + // Copy the database to a temporary location so it can be manipulated. + // Ensure it is not read-only. + var workingDatabasePath = Path.Combine(workingFolder, Path.GetFileName(this.OutputPath)); + FileSystem.CopyFile(this.OutputPath, workingDatabasePath, allowHardlink: false); + + var attributes = File.GetAttributes(workingDatabasePath); + File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly); + + var validator = new WindowsInstallerValidator(this, workingDatabasePath, this.CubeFiles, this.Ices, this.SuppressedIces); + validator.Execute(); + + stopwatch.Stop(); + this.Messaging.Write(VerboseMessages.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); + } + + private void LogValidationMessage(ValidationMessage message) + { + var messageSourceLineNumbers = this.OutputSourceLineNumber; + if (!String.IsNullOrEmpty(message.Table) && !String.IsNullOrEmpty(message.Column) && message.PrimaryKeys != null) + { + messageSourceLineNumbers = this.GetSourceLineNumbers(message.Table, message.PrimaryKeys); + } + + switch (message.Type) + { + case ValidationMessageType.InternalFailure: + case ValidationMessageType.Error: + this.Messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, message.IceName, message.Description)); + break; + case ValidationMessageType.Warning: + this.Messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, message.IceName, message.Description)); + break; + case ValidationMessageType.Info: + this.Messaging.Write(VerboseMessages.ValidationInfo(message.IceName, message.Description)); + break; + default: + throw new WixException(ErrorMessages.InvalidValidatorMessageType(message.Type.ToString())); + } + } + + /// + /// Validation blocked by other installation operation for . + /// + public void ValidationBlocked() + { + this.Messaging.Write(VerboseMessages.ValidationSerialized()); + } + + /// + /// Validation message implementation for . + /// + public bool ValidationMessage(ValidationMessage message) + { + this.LogValidationMessage(message); + return true; + } + + /// + /// Gets the source line information (if available) for a row by its table name and primary key. + /// + /// The table name of the row. + /// The primary keys of the row. + /// The source line number information if found; null otherwise. + private SourceLineNumber GetSourceLineNumbers(string tableName, IEnumerable primaryKeys) + { + // Source line information only exists if an output file was supplied + if (this.Data == null) + { + // Use the file name as the source line information. + return this.OutputSourceLineNumber; + } + + // Index the source line information if it hasn't been indexed already. + if (this.SourceLineNumbersByTablePrimaryKey == null) + { + this.SourceLineNumbersByTablePrimaryKey = new Dictionary(); + + // Index each real table + foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal)) + { + // Index each row that contain source line information + foreach (var row in table.Rows.Where(r => r.SourceLineNumbers != null)) + { + // Index the row using its table name and primary key + var primaryKey = row.GetPrimaryKey(';'); + + if (!String.IsNullOrEmpty(primaryKey)) + { + try + { + var key = String.Concat(table.Name, ":", primaryKey); + this.SourceLineNumbersByTablePrimaryKey.Add(key, row.SourceLineNumbers); + } + catch (ArgumentException) + { + this.Messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); + } + } + } + } + } + + return this.SourceLineNumbersByTablePrimaryKey.TryGetValue(String.Concat(tableName, ":", String.Join(";", primaryKeys)), out var sourceLineNumbers) ? sourceLineNumbers : null; + } + } +} -- cgit v1.2.3-55-g6feb