From 24e8f28dc9bea0a72a82abc6cafebe2364c94ef1 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Mon, 7 Feb 2022 21:28:07 -0800 Subject: Make validation independent of the build command In MSBuild this adds a new WindowsInstallerValidation target that automatically runs validation after a successful build. From the command-line the "wix msi" command gets a "validate" subcommand that will validate an MSI or MSM file. --- .../WixToolset.Extensibility/Data/IBindContext.cs | 15 -- src/test/burn/Directory.wixproj.props | 1 + .../Package.wxs | 2 +- .../WixprojPackageVcxprojWindowsApp/Package.wxs | 2 +- .../Package.wxs | 2 +- .../WindowsInstallerValidation.cs | 59 +++++++ src/wix/WixToolset.BuildTasks/WixBuild.cs | 9 +- src/wix/WixToolset.Core.Native/FileSystem.cs | 4 +- .../Bind/BindDatabaseCommand.cs | 31 +--- .../Bind/ValidateDatabaseCommand.cs | 187 --------------------- .../CommandLine/ValidateSubcommand.cs | 131 +++++++++++++++ .../CommandLine/WindowsInstallerCommand.cs | 5 + .../WixToolset.Core.WindowsInstaller/MsiBackend.cs | 2 +- .../WixToolset.Core.WindowsInstaller/MsmBackend.cs | 2 +- .../WixToolset.Core.WindowsInstaller/MspBackend.cs | 2 +- .../Validate/ValidateDatabaseCommand.cs | 184 ++++++++++++++++++++ src/wix/WixToolset.Core/BindContext.cs | 6 - .../WixToolset.Core/CommandLine/BuildCommand.cs | 27 --- src/wix/WixToolset.Sdk/tools/wix.targets | 39 ++++- .../ModuleFixture.cs | 93 +++++++++- .../MsiQueryFixture.cs | 65 ------- .../BindVariables/PackageWithBindVariables.wxs | 2 +- .../TestData/SimpleMerge/.data/test.msm | Bin 24576 -> 0 bytes .../TestData/SimpleMerge/Package.wxs | 3 - .../TestData/Validation/PackageWithIceIssues.wxs | 32 ++++ .../ValidationFixture.cs | 133 +++++++++++++++ src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs | 81 ++++++++- .../test/WixToolsetTest.Sdk/MsbuildUtilities.cs | 3 +- .../MsiPackage/MsiPackage.wixproj | 65 +++++++ .../MsiPackage/Package.en-us.wxl | 11 ++ .../MsiPackageWithIceError/MsiPackage/Package.wxs | 26 +++ .../MsiPackage/PackageComponents.wxs | 10 ++ .../MsiPackage/data/test.txt | 1 + .../SimpleMsiPackage/MsiPackage/Package.wxs | 2 +- 34 files changed, 873 insertions(+), 364 deletions(-) create mode 100644 src/wix/WixToolset.BuildTasks/WindowsInstallerValidation.cs delete mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/CommandLine/ValidateSubcommand.cs create mode 100644 src/wix/WixToolset.Core.WindowsInstaller/Validate/ValidateDatabaseCommand.cs delete mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Validation/PackageWithIceIssues.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/ValidationFixture.cs create mode 100644 src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/MsiPackage.wixproj create mode 100644 src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/Package.en-us.wxl create mode 100644 src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/Package.wxs create mode 100644 src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/PackageComponents.wxs create mode 100644 src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/data/test.txt diff --git a/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs b/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs index d0c65683..671da292 100644 --- a/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs +++ b/src/api/wix/WixToolset.Extensibility/Data/IBindContext.cs @@ -52,11 +52,6 @@ namespace WixToolset.Extensibility.Data /// IReadOnlyCollection FileSystemExtensions { get; set; } - /// - /// Set of ICEs to execute. - /// - IReadOnlyCollection Ices { get; set; } - /// /// Intermedaite folder. /// @@ -97,16 +92,6 @@ namespace WixToolset.Extensibility.Data /// int? ResolvedLcid { get; set; } - /// - /// Set of ICEs to skip. - /// - IReadOnlyCollection SuppressIces { get; set; } - - /// - /// Skip all ICEs. - /// - bool SuppressValidation { get; set; } - /// /// Skip creation of output. /// diff --git a/src/test/burn/Directory.wixproj.props b/src/test/burn/Directory.wixproj.props index af5ef196..21ab5107 100644 --- a/src/test/burn/Directory.wixproj.props +++ b/src/test/burn/Directory.wixproj.props @@ -7,6 +7,7 @@ None -wx 1154;$(SuppressSpecificWarnings) + true $(BaseOutputPath)$(Configuration)\netstandard2.0\ForTestingUseOnly.wixext.dll diff --git a/src/test/wix/TestData/WixprojPackageCsprojWebApplicationNetCore/Package.wxs b/src/test/wix/TestData/WixprojPackageCsprojWebApplicationNetCore/Package.wxs index c228bb33..cbc8cae1 100644 --- a/src/test/wix/TestData/WixprojPackageCsprojWebApplicationNetCore/Package.wxs +++ b/src/test/wix/TestData/WixprojPackageCsprojWebApplicationNetCore/Package.wxs @@ -1,5 +1,5 @@ - + diff --git a/src/test/wix/TestData/WixprojPackageVcxprojWindowsApp/Package.wxs b/src/test/wix/TestData/WixprojPackageVcxprojWindowsApp/Package.wxs index c0370683..fe849608 100644 --- a/src/test/wix/TestData/WixprojPackageVcxprojWindowsApp/Package.wxs +++ b/src/test/wix/TestData/WixprojPackageVcxprojWindowsApp/Package.wxs @@ -1,5 +1,5 @@ - + diff --git a/src/test/wix/TestData/WixprojPackageVcxprojWindowsAppMultiArch/Package.wxs b/src/test/wix/TestData/WixprojPackageVcxprojWindowsAppMultiArch/Package.wxs index 9d3df756..ad95dac3 100644 --- a/src/test/wix/TestData/WixprojPackageVcxprojWindowsAppMultiArch/Package.wxs +++ b/src/test/wix/TestData/WixprojPackageVcxprojWindowsAppMultiArch/Package.wxs @@ -1,5 +1,5 @@ - + diff --git a/src/wix/WixToolset.BuildTasks/WindowsInstallerValidation.cs b/src/wix/WixToolset.BuildTasks/WindowsInstallerValidation.cs new file mode 100644 index 00000000..ae708c0e --- /dev/null +++ b/src/wix/WixToolset.BuildTasks/WindowsInstallerValidation.cs @@ -0,0 +1,59 @@ +// 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.BuildTasks +{ + using Microsoft.Build.Framework; + + /// + /// An MSBuild task to run WiX to detach bundle engine to be signed. + /// + public sealed partial class WindowsInstallerValidation : WixExeBaseTask + { + /// + /// Gets or sets the path to the database to validate. + /// + [Required] + public ITaskItem DatabaseFile { get; set; } + + /// + /// Gets or sets the intermedidate folder to use. + /// + public ITaskItem IntermediateDirectory { get; set; } + + /// + /// Gets or sets the paths to ICE CUBes to execute. + /// + public string[] CubeFiles { get; set; } + + /// + /// Gets or sets the ICEs to execute. + /// + public string[] Ices { get; set; } + + /// + /// Gets or sets the ICEs to suppress. + /// + public string[] SuppressIces { get; set; } + + /// + /// Gets or sets the .wixpdb for the database. + /// + public ITaskItem WixpdbFile { get; set; } + + protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) + { + commandLineBuilder.AppendTextUnquoted("msi validate"); + + commandLineBuilder.AppendFileNameIfNotNull(this.DatabaseFile); + commandLineBuilder.AppendSwitchIfNotNull("-pdb ", this.WixpdbFile); + commandLineBuilder.AppendSwitchIfNotNull("-intermediatefolder ", this.IntermediateDirectory); + commandLineBuilder.AppendArrayIfNotNull("-cub ", this.CubeFiles); + commandLineBuilder.AppendArrayIfNotNull("-ice ", this.Ices); + commandLineBuilder.AppendArrayIfNotNull("-sice ", this.SuppressIces); + + base.BuildCommandLine(commandLineBuilder); + + commandLineBuilder.AppendTextIfNotWhitespace(this.AdditionalOptions); + } + } +} diff --git a/src/wix/WixToolset.BuildTasks/WixBuild.cs b/src/wix/WixToolset.BuildTasks/WixBuild.cs index 2d35c391..11de0242 100644 --- a/src/wix/WixToolset.BuildTasks/WixBuild.cs +++ b/src/wix/WixToolset.BuildTasks/WixBuild.cs @@ -64,13 +64,8 @@ namespace WixToolset.BuildTasks public ITaskItem UnreferencedSymbolsFile { get; set; } public ITaskItem WixProjectFile { get; set; } - public string[] WixVariables { get; set; } - - public bool SuppressValidation { get; set; } - public string[] SuppressIces { get; set; } - - public string AdditionalCub { get; set; } + public string[] WixVariables { get; set; } protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder) { @@ -85,8 +80,6 @@ namespace WixToolset.BuildTasks commandLineBuilder.AppendArrayIfNotNull("-d ", this.DefineConstants); commandLineBuilder.AppendArrayIfNotNull("-I ", this.IncludeSearchPaths); commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.ReferencePaths); - commandLineBuilder.AppendIfTrue("-sval", this.SuppressValidation); - commandLineBuilder.AppendArrayIfNotNull("-sice ", this.SuppressIces); commandLineBuilder.AppendSwitchIfNotNull("-usf ", this.UnreferencedSymbolsFile); commandLineBuilder.AppendSwitchIfNotNull("-cc ", this.CabinetCachePath); commandLineBuilder.AppendSwitchIfNotNull("-intermediatefolder ", this.IntermediateDirectory); diff --git a/src/wix/WixToolset.Core.Native/FileSystem.cs b/src/wix/WixToolset.Core.Native/FileSystem.cs index d843a9e8..3dc336d6 100644 --- a/src/wix/WixToolset.Core.Native/FileSystem.cs +++ b/src/wix/WixToolset.Core.Native/FileSystem.cs @@ -62,14 +62,14 @@ namespace WixToolset.Core.Native private static void EnsureDirectoryWithoutFile(string path) { - File.Delete(path); - var directory = Path.GetDirectoryName(path); if (!String.IsNullOrEmpty(directory)) { Directory.CreateDirectory(directory); } + + File.Delete(path); } [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index f2a8b9c6..ab471702 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -21,11 +21,7 @@ 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, string cubeFile) : this(context, backendExtension, null, cubeFile) - { - } - - public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages, string cubeFile) + public BindDatabaseCommand(IBindContext context, IEnumerable backendExtension, IEnumerable subStorages = null) { this.ServiceProvider = context.ServiceProvider; @@ -53,11 +49,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind this.SubStorages = subStorages; - this.SuppressValidation = context.SuppressValidation; - this.Ices = context.Ices; - this.SuppressedIces = context.SuppressIces; - this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile }; - this.BackendExtensions = backendExtension; } @@ -109,12 +100,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind private bool SuppressValidation { get; } - private IEnumerable Ices { get; } - - private IEnumerable SuppressedIces { get; } - - private IEnumerable CubeFiles { get; } - public IBindResult Execute() { if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved)) @@ -507,20 +492,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind return null; } - // Validate the output if there are CUBe files and we're not explicitly suppressing validation. - if (this.CubeFiles != null && !this.SuppressValidation) - { - var command = new ValidateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces); - command.Execute(); - - trackedFiles.AddRange(command.TrackedFiles); - } - - if (this.Messaging.EncounteredError) - { - return null; - } - // Process uncompressed files. if (!this.SuppressLayout && uncompressedFiles.Any()) { diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs deleted file mode 100644 index cf1e21c2..00000000 --- a/src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs +++ /dev/null @@ -1,187 +0,0 @@ -// 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.Native; - using WixToolset.Data; - using WixToolset.Data.WindowsInstaller; - using WixToolset.Extensibility.Data; - 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, IBackendHelper backendHelper, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable cubeFiles, IEnumerable ices, IEnumerable suppressedIces) - { - this.Messaging = messaging; - this.BackendHelper = backendHelper; - 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); - } - - public IEnumerable TrackedFiles { get; private set; } - - /// - /// Encountered error implementation for . - /// - public bool EncounteredError => this.Messaging.EncounteredError; - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { 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 trackedFiles = new List(); - 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 trackWorkingDatabase = this.BackendHelper.TrackFile(workingDatabasePath, TrackedFileType.Temporary); - trackedFiles.Add(trackWorkingDatabase); - - 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)); - - - this.TrackedFiles = trackedFiles; - } - - 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; - } - } -} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/ValidateSubcommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/ValidateSubcommand.cs new file mode 100644 index 00000000..7d77aa30 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/ValidateSubcommand.cs @@ -0,0 +1,131 @@ +// 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.CommandLine +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using WixToolset.Core.WindowsInstaller.Validate; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Services; + + internal class ValidateSubcommand : WindowsInstallerSubcommandBase + { + public ValidateSubcommand(IServiceProvider serviceProvider) + { + this.Messaging = serviceProvider.GetService(); + } + + private IMessaging Messaging { get; } + + private string DatabasePath { get; set; } + + private string WixpdbPath { get; set; } + + private string IntermediateFolder { get; set; } + + private List CubeFiles { get; } = new List(); + + private List Ices { get; } = new List(); + + private List SuppressIces { get; } = new List(); + + public override Task ExecuteAsync(CancellationToken cancellationToken) + { + WindowsInstallerData data = null; + + if (String.IsNullOrEmpty(this.DatabasePath)) + { + Console.Error.WriteLine("Input MSI or MSM database is required"); + return Task.FromResult(-1); + } + + if (this.CubeFiles.Count == 0) + { + var ext = Path.GetExtension(this.DatabasePath); + switch (ext.ToLowerInvariant()) + { + case ".msi": + this.CubeFiles.Add("darice.cub"); + break; + + case ".msm": + this.CubeFiles.Add("mergemod.cub"); + break; + + default: + Console.Error.WriteLine("Unknown extension: {0}. Use the -cub switch to specify the path to the ICE CUBe file", ext); + return Task.FromResult(-1); + } + } + + if (String.IsNullOrEmpty(this.WixpdbPath)) + { + this.WixpdbPath = Path.ChangeExtension(this.DatabasePath, ".wixpdb"); + } + + if (String.IsNullOrEmpty(this.IntermediateFolder)) + { + this.IntermediateFolder = Path.GetTempPath(); + } + + if (File.Exists(this.WixpdbPath)) + { + data = WindowsInstallerData.Load(this.WixpdbPath); + } + + var command = new ValidateDatabaseCommand(this.Messaging, this.IntermediateFolder, this.DatabasePath, data, this.CubeFiles, this.Ices, this.SuppressIces); + command.Execute(); + + return Task.FromResult(this.Messaging.EncounteredError ? 1 : 0); + } + + public override bool TryParseArgument(ICommandLineParser parser, string argument) + { + if (parser.IsSwitch(argument)) + { + var parameter = argument.Substring(1); + switch (parameter.ToLowerInvariant()) + { + case "cub": + { + var value = parser.GetNextArgumentOrError(argument); + this.CubeFiles.Add(value); + return true; + } + + case "ice": + { + var value = parser.GetNextArgumentOrError(argument); + this.Ices.Add(value); + return true; + } + + case "intermediatefolder": + this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(argument); + return true; + + case "pdb": + this.WixpdbPath = parser.GetNextArgumentAsFilePathOrError(argument); + return true; + + case "sice": + { + var value = parser.GetNextArgumentOrError(argument); + this.SuppressIces.Add(value); + return true; + } + } + } + else if (String.IsNullOrEmpty(this.DatabasePath)) + { + this.DatabasePath = argument; + return true; + } + + return false; + } + } +} diff --git a/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/WindowsInstallerCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/WindowsInstallerCommand.cs index db178ddf..a0da7fa4 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/WindowsInstallerCommand.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/CommandLine/WindowsInstallerCommand.cs @@ -48,6 +48,10 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine case "inscribe": this.Subcommand = new InscribeSubcommand(this.ServiceProvider); return true; + + case "validate": + this.Subcommand = new ValidateSubcommand(this.ServiceProvider); + return true; } return false; @@ -68,6 +72,7 @@ namespace WixToolset.Core.WindowsInstaller.CommandLine Console.WriteLine("Commands:"); Console.WriteLine(); Console.WriteLine(" inscribe Updates MSI database with cabinet signature information."); + Console.WriteLine(" validate Validates MSI database using standard or custom ICEs."); } } } diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs index 37de1904..33aa7a74 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs @@ -27,7 +27,7 @@ namespace WixToolset.Core.WindowsInstaller var dispose = true; try { - var command = new BindDatabaseCommand(context, backendExtensions, "darice.cub"); + var command = new BindDatabaseCommand(context, backendExtensions); result = command.Execute(); foreach (var extension in backendExtensions) diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs index 450bf888..02ea5f45 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs @@ -26,7 +26,7 @@ namespace WixToolset.Core.WindowsInstaller IBindResult result = null; try { - var command = new BindDatabaseCommand(context, backendExtensions, "mergemod.cub"); + var command = new BindDatabaseCommand(context, backendExtensions); result = command.Execute(); foreach (var extension in backendExtensions) diff --git a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs index 1ef9671b..d1f5eb99 100644 --- a/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs +++ b/src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs @@ -52,7 +52,7 @@ namespace WixToolset.Core.WindowsInstaller IBindResult result = null; try { - var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null); + var command = new BindDatabaseCommand(context, backendExtensions, subStorages); result = command.Execute(); foreach (var extension in backendExtensions) diff --git a/src/wix/WixToolset.Core.WindowsInstaller/Validate/ValidateDatabaseCommand.cs b/src/wix/WixToolset.Core.WindowsInstaller/Validate/ValidateDatabaseCommand.cs new file mode 100644 index 00000000..fe2fd2c2 --- /dev/null +++ b/src/wix/WixToolset.Core.WindowsInstaller/Validate/ValidateDatabaseCommand.cs @@ -0,0 +1,184 @@ +// 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.Validate +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using WixToolset.Core.Native; + using WixToolset.Data; + using WixToolset.Data.WindowsInstaller; + using WixToolset.Extensibility.Data; + 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, string databasePath, WindowsInstallerData data, IEnumerable cubeFiles, IEnumerable ices, IEnumerable suppressedIces) + { + this.Messaging = messaging; + this.Data = data; + this.DatabasePath = databasePath; + this.CubeFiles = cubeFiles; + this.Ices = ices; + this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces); + + this.IntermediateFolder = intermediateFolder; + this.OutputSourceLineNumber = new SourceLineNumber(databasePath); + } + + public IEnumerable TrackedFiles { get; private set; } + + /// + /// Encountered error implementation for . + /// + public bool EncounteredError => this.Messaging.EncounteredError; + + private IMessaging Messaging { get; } + + private WindowsInstallerData Data { get; } + + private string DatabasePath { 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()); + + // Copy the database to a temporary location so it can be manipulated. + // Ensure it is not read-only. + var workingDatabaseFilename = String.Concat(Path.GetFileNameWithoutExtension(this.DatabasePath), "_validate", Path.GetExtension(this.DatabasePath)); + var workingDatabasePath = Path.Combine(this.IntermediateFolder, workingDatabaseFilename); + try + { + FileSystem.CopyFile(this.DatabasePath, 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(); + } + finally + { + if (File.Exists(workingDatabasePath)) + { + File.Delete(workingDatabasePath); + } + } + + 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; + } + } +} diff --git a/src/wix/WixToolset.Core/BindContext.cs b/src/wix/WixToolset.Core/BindContext.cs index 052382f1..9dd6aa46 100644 --- a/src/wix/WixToolset.Core/BindContext.cs +++ b/src/wix/WixToolset.Core/BindContext.cs @@ -36,8 +36,6 @@ namespace WixToolset.Core public IReadOnlyCollection FileSystemExtensions { get; set; } - public IReadOnlyCollection Ices { get; set; } - public string IntermediateFolder { get; set; } public Intermediate IntermediateRepresentation { get; set; } @@ -54,10 +52,6 @@ namespace WixToolset.Core public int? ResolvedLcid { get; set; } - public IReadOnlyCollection SuppressIces { get; set; } - - public bool SuppressValidation { get; set; } - public bool SuppressLayout { get; set; } public CancellationToken CancellationToken { get; set; } diff --git a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs index 3ea47279..d289b557 100644 --- a/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/wix/WixToolset.Core/CommandLine/BuildCommand.cs @@ -349,14 +349,11 @@ namespace WixToolset.Core.CommandLine context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; context.Extensions = this.ExtensionManager.GetServices(); context.FileSystemExtensions = this.ExtensionManager.GetServices(); - context.Ices = this.commandLine.Ices; context.IntermediateFolder = intermediateFolder; context.IntermediateRepresentation = resolveResult.IntermediateRepresentation; context.OutputPath = this.OutputFile; context.PdbType = this.PdbType; context.PdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(this.OutputFile, ".wixpdb"); - context.SuppressIces = this.commandLine.SuppressIces; - context.SuppressValidation = this.commandLine.SuppressValidation; context.CancellationToken = cancellationToken; var binder = this.ServiceProvider.GetService(); @@ -540,12 +537,6 @@ namespace WixToolset.Core.CommandLine public string TrackingFile { get; private set; } - public List Ices { get; } = new List(); - - public List SuppressIces { get; } = new List(); - - public bool SuppressValidation { get; set; } - public bool ResetAcls { get; set; } public CommandLine(IServiceProvider serviceProvider, IMessaging messaging) @@ -627,13 +618,6 @@ namespace WixToolset.Core.CommandLine parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths); return true; - case "ice": - { - var value = parser.GetNextArgumentOrError(arg); - this.Ices.Add(value); - return true; - } - case "intermediatefolder": this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg); return true; @@ -670,17 +654,6 @@ namespace WixToolset.Core.CommandLine return false; } - case "sice": - { - var value = parser.GetNextArgumentOrError(arg); - this.SuppressIces.Add(value); - return true; - } - - case "sval": - this.SuppressValidation = true; - return true; - case "resetacls": this.ResetAcls = true; return true; diff --git a/src/wix/WixToolset.Sdk/tools/wix.targets b/src/wix/WixToolset.Sdk/tools/wix.targets index 487c5cb3..bbb73a52 100644 --- a/src/wix/WixToolset.Sdk/tools/wix.targets +++ b/src/wix/WixToolset.Sdk/tools/wix.targets @@ -168,6 +168,10 @@ + + + + + + $(CompileDependsOn); + WindowsInstallerValidation + AssignTargetPaths; CreateManifestResourceNames; @@ -604,9 +612,6 @@ Pedantic="$(Pedantic)" ReferencePaths="$(ReferencePaths)" - SuppressSpecificWarnings="$(SuppressSpecificWarnings)" - TreatSpecificWarningsAsErrors="$(TreatSpecificWarningsAsErrors)" - BindInputPaths="@(LinkerBindInputPaths)" BindFiles="$(BindFiles)" BindTrackingFile="$(IntermediateOutputPath)$(BindTrackingFilePrefix)%(CultureGroup.Identity)$(BindTrackingFileExtension)" @@ -619,12 +624,10 @@ WixProjectFile="$(ProjectPath)" WixVariables="$(WixVariables)" - SuppressValidation="$(SuppressValidation)" - SuppressIces="$(SuppressIces)" - AdditionalCub="$(AdditionalCub)" - SuppressAllWarnings="$(SuppressAllWarnings)" + SuppressSpecificWarnings="$(SuppressSpecificWarnings)" TreatWarningsAsErrors="$(TreatWarningsAsErrors)" + TreatSpecificWarningsAsErrors="$(TreatSpecificWarningsAsErrors)" VerboseOutput="$(VerboseOutput)" RunAsSeparateProcess="$(RunWixToolsOutOfProc)" @@ -636,6 +639,28 @@ + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Validation/PackageWithIceIssues.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Validation/PackageWithIceIssues.wxs new file mode 100644 index 00000000..4d3acdf7 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Validation/PackageWithIceIssues.wxs @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ValidationFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ValidationFixture.cs new file mode 100644 index 00000000..48339b89 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ValidationFixture.cs @@ -0,0 +1,133 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class ValidationFixture + { + [Fact] + public void CanValidateMsiWithIceIssues() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin", "test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Validation", "PackageWithIceIssues.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath, + }); + + result.AssertSuccess(); + + var validationResult = WixRunner.Execute(new[] + { + "msi", "validate", + "-intermediateFolder", intermediateFolder, + msiPath + }); + + Assert.Equal(1, validationResult.ExitCode); + + var messages = validationResult.Messages.Select(m => m.ToString()).ToArray(); + WixAssert.CompareLineByLine(new[] + { + @"ICE12: CustomAction: CausesICE12Error is of type: 35. Therefore it must come after CostFinalize @ 1000 in Seq Table: InstallExecuteSequence. CA Seq#: 49", + @"ICE46: Property 'Myproperty' referenced in column 'LaunchCondition'.'Condition' of row 'Myproperty' differs from a defined property by case only." + }, messages); + } + } + + [Fact] + public void CanValidateMsiSuppressIceError() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin", "test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Validation", "PackageWithIceIssues.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath, + }); + + result.AssertSuccess(); + + var validationResult = WixRunner.Execute(warningsAsErrors: false, new[] + { + "msi", "validate", + "-intermediateFolder", intermediateFolder, + "-sice", "ICE12", + msiPath + }); + + validationResult.AssertSuccess(); + + var messages = validationResult.Messages.Select(m => m.ToString()).ToArray(); + WixAssert.CompareLineByLine(new[] + { + @"ICE46: Property 'Myproperty' referenced in column 'LaunchCondition'.'Condition' of row 'Myproperty' differs from a defined property by case only." + }, messages); + } + } + + [Fact] + public void CanValidateMsiWithWarningsAsErrors() + { + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var msiPath = Path.Combine(baseFolder, @"bin", "test.msi"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "Validation", "PackageWithIceIssues.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath, + }); + + result.AssertSuccess(); + + var validationResult = WixRunner.Execute(warningsAsErrors: true, new[] + { + "msi", "validate", + "-intermediateFolder", intermediateFolder, + "-sice", "ICE12", + msiPath + }); + + Assert.Equal(1, validationResult.ExitCode); + + var messages = validationResult.Messages.Select(m => m.ToString()).ToArray(); + WixAssert.CompareLineByLine(new[] + { + @"ICE46: Property 'Myproperty' referenced in column 'LaunchCondition'.'Condition' of row 'Myproperty' differs from a defined property by case only." + }, messages); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs b/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs index 3a9ce776..1a82c035 100644 --- a/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs +++ b/src/wix/test/WixToolsetTest.Sdk/MsbuildFixture.cs @@ -271,6 +271,7 @@ namespace WixToolsetTest.Sdk var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[] { wixpdbType == null ? String.Empty : $"-p:WixPdbType={wixpdbType}", + "-p:SuppressValidation=true" }); result.AssertSuccess(); @@ -323,9 +324,44 @@ namespace WixToolsetTest.Sdk [InlineData(BuildSystem.DotNetCoreSdk)] [InlineData(BuildSystem.MSBuild)] [InlineData(BuildSystem.MSBuild64)] - public void CanBuildSimpleMsiPackageWithIceSuppressions(BuildSystem buildSystem) + public void CannotBuildMsiPackageWithIceIssues(BuildSystem buildSystem) { - var sourceFolder = TestData.Get(@"TestData\SimpleMsiPackage\MsiPackage"); + var sourceFolder = TestData.Get(@"TestData\MsiPackageWithIceError\MsiPackage"); + + using (var fs = new TestDataFolderFileSystem()) + { + fs.Initialize(sourceFolder); + var baseFolder = fs.BaseFolder; + var binFolder = Path.Combine(baseFolder, @"bin\"); + var projectPath = Path.Combine(baseFolder, "MsiPackage.wixproj"); + + var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, suppressValidation: false); + Assert.Equal(1, result.ExitCode); + + var iceIssues = result.Output.Where(line => line.Contains(": error") || line.Contains(": warning")) + .Select(line => line.Replace(baseFolder, "") + .Replace("1>", String.Empty) // remove any multi-proc markers + .Replace("WIX204", "WIX0204") // normalize error number reporting because MSBuild is not consistent on zero padding + .Trim()) + .OrderBy(s => s, StringComparer.OrdinalIgnoreCase) + .ToArray(); + WixAssert.CompareLineByLine(new[] + { + @"\Package.wxs(17): error WIX0204: ICE12: CustomAction: CausesICE12Error is of type: 35. Therefore it must come after CostFinalize @ 1000 in Seq Table: InstallExecuteSequence. CA Seq#: 49 [\MsiPackage.wixproj]", + @"\Package.wxs(17): error WIX0204: ICE12: CustomAction: CausesICE12Error is of type: 35. Therefore it must come after CostFinalize @ 1000 in Seq Table: InstallExecuteSequence. CA Seq#: 49 [\MsiPackage.wixproj]", + @"\Package.wxs(8): warning WIX1076: ICE46: Property 'Myproperty' referenced in column 'LaunchCondition'.'Condition' of row 'Myproperty' differs from a defined property by case only. [\MsiPackage.wixproj]", + @"\Package.wxs(8): warning WIX1076: ICE46: Property 'Myproperty' referenced in column 'LaunchCondition'.'Condition' of row 'Myproperty' differs from a defined property by case only. [\MsiPackage.wixproj]", + }, iceIssues); + } + } + + [Theory] + [InlineData(BuildSystem.DotNetCoreSdk)] + [InlineData(BuildSystem.MSBuild)] + [InlineData(BuildSystem.MSBuild64)] + public void CannotBuildMsiPackageWithIceWarningsAsErrors(BuildSystem buildSystem) + { + var sourceFolder = TestData.Get(@"TestData\MsiPackageWithIceError\MsiPackage"); using (var fs = new TestDataFolderFileSystem()) { @@ -336,8 +372,45 @@ namespace WixToolsetTest.Sdk var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[] { - MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "SuppressIces", "ICE45;ICE46"), - }); + $"-p:TreatWarningsAsErrors=true", + MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "SuppressIces", "ICE12"), + }, suppressValidation: false); + Assert.Equal(1, result.ExitCode); + + var iceIssues = result.Output.Where(line => (line.Contains(": error") || line.Contains(": warning"))) + .Select(line => line.Replace(baseFolder, "") + .Replace("1>", String.Empty) // remove any multi-proc markers + .Replace("WIX204", "WIX0204") // normalize error number reporting because MSBuild is not consistent on zero padding + .Trim()) + .OrderBy(s => s, StringComparer.OrdinalIgnoreCase) + .ToArray(); + WixAssert.CompareLineByLine(new[] + { + @"\Package.wxs(8): error WIX1076: ICE46: Property 'Myproperty' referenced in column 'LaunchCondition'.'Condition' of row 'Myproperty' differs from a defined property by case only. [\MsiPackage.wixproj]", + @"\Package.wxs(8): error WIX1076: ICE46: Property 'Myproperty' referenced in column 'LaunchCondition'.'Condition' of row 'Myproperty' differs from a defined property by case only. [\MsiPackage.wixproj]", + }, iceIssues); + } + } + + [Theory] + [InlineData(BuildSystem.DotNetCoreSdk)] + [InlineData(BuildSystem.MSBuild)] + [InlineData(BuildSystem.MSBuild64)] + public void CanBuildMsiPackageWithIceSuppressions(BuildSystem buildSystem) + { + var sourceFolder = TestData.Get(@"TestData\MsiPackageWithIceError\MsiPackage"); + + using (var fs = new TestDataFolderFileSystem()) + { + fs.Initialize(sourceFolder); + var baseFolder = fs.BaseFolder; + var binFolder = Path.Combine(baseFolder, @"bin\"); + var projectPath = Path.Combine(baseFolder, "MsiPackage.wixproj"); + + var result = MsbuildUtilities.BuildProject(buildSystem, projectPath, new[] + { + MsbuildUtilities.GetQuotedPropertySwitch(buildSystem, "SuppressIces", "ICE12"), + }, suppressValidation: false); result.AssertSuccess(); } } diff --git a/src/wix/test/WixToolsetTest.Sdk/MsbuildUtilities.cs b/src/wix/test/WixToolsetTest.Sdk/MsbuildUtilities.cs index 28e0c219..875e3279 100644 --- a/src/wix/test/WixToolsetTest.Sdk/MsbuildUtilities.cs +++ b/src/wix/test/WixToolsetTest.Sdk/MsbuildUtilities.cs @@ -20,12 +20,13 @@ namespace WixToolsetTest.Sdk public static readonly string WixMsbuildPath = Path.Combine(Path.GetDirectoryName(new Uri(typeof(MsbuildUtilities).Assembly.CodeBase).AbsolutePath), "..", "publish", "WixToolset.Sdk"); public static readonly string WixPropsPath = Path.Combine(WixMsbuildPath, "build", "WixToolset.Sdk.props"); - public static MsbuildRunnerResult BuildProject(BuildSystem buildSystem, string projectPath, string[] arguments = null, string configuration = "Release", bool? outOfProc = null, string verbosityLevel = "normal") + public static MsbuildRunnerResult BuildProject(BuildSystem buildSystem, string projectPath, string[] arguments = null, string configuration = "Release", bool? outOfProc = null, string verbosityLevel = "normal", bool suppressValidation = true) { var allArgs = new List { $"-verbosity:{verbosityLevel}", $"-p:Configuration={configuration}", + $"-p:SuppressValidation={suppressValidation}", GetQuotedPropertySwitch(buildSystem, "WixMSBuildProps", MsbuildUtilities.WixPropsPath), // Node reuse means that child msbuild processes can stay around after the build completes. // Under that scenario, the root msbuild does not reliably close its streams which causes us to hang. diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/MsiPackage.wixproj b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/MsiPackage.wixproj new file mode 100644 index 00000000..acbcd85d --- /dev/null +++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/MsiPackage.wixproj @@ -0,0 +1,65 @@ + + + + + Debug + x86 + + + + 7fb77005-c6e0-454f-8c2d-0a4a79c918ba + + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + Debug + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + Debug + + + $(Platform) + bin\$(Platform)\$(Configuration)\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/Package.en-us.wxl b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/Package.en-us.wxl new file mode 100644 index 00000000..38c12ac1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/Package.en-us.wxl @@ -0,0 +1,11 @@ + + + + + + A newer version of [ProductName] is already installed. + MsiPackage + + diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/Package.wxs b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/Package.wxs new file mode 100644 index 00000000..e187ed7b --- /dev/null +++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/Package.wxs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/PackageComponents.wxs b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/PackageComponents.wxs new file mode 100644 index 00000000..e26c4509 --- /dev/null +++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/PackageComponents.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/data/test.txt b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/data/test.txt new file mode 100644 index 00000000..cd0db0e1 --- /dev/null +++ b/src/wix/test/WixToolsetTest.Sdk/TestData/MsiPackageWithIceError/MsiPackage/data/test.txt @@ -0,0 +1 @@ +This is test.txt. \ No newline at end of file diff --git a/src/wix/test/WixToolsetTest.Sdk/TestData/SimpleMsiPackage/MsiPackage/Package.wxs b/src/wix/test/WixToolsetTest.Sdk/TestData/SimpleMsiPackage/MsiPackage/Package.wxs index 0a133c7d..30be1ace 100644 --- a/src/wix/test/WixToolsetTest.Sdk/TestData/SimpleMsiPackage/MsiPackage/Package.wxs +++ b/src/wix/test/WixToolsetTest.Sdk/TestData/SimpleMsiPackage/MsiPackage/Package.wxs @@ -12,7 +12,7 @@ - + -- cgit v1.2.3-55-g6feb