From 3441afb46c4dc056493ab84f9b27434c4185d713 Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Sat, 10 Apr 2021 15:04:04 -0700 Subject: Improve implicit Component/@Id generation and duplicate GUID errors --- .../Bind/BindDatabaseCommand.cs | 9 +- .../Bind/CalculateComponentGuids.cs | 181 -------------- .../Bind/FinalizeComponentGuids.cs | 262 +++++++++++++++++++++ .../Bind/ValidateComponentGuidsCommand.cs | 63 ----- src/WixToolset.Core/Compiler.cs | 23 +- .../ComponentFixture.cs | 45 ++++ .../TestData/Component/GuidCollision.wxs | 14 ++ 7 files changed, 336 insertions(+), 261 deletions(-) delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs create mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs delete mode 100644 src/WixToolset.Core.WindowsInstaller/Bind/ValidateComponentGuidsCommand.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs (limited to 'src') diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs index 06b51ba1..9f36cd78 100644 --- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs +++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs @@ -354,14 +354,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind } } - // Set generated component guids. + // Set generated component guids and validate all guids. { - var command = new CalculateComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); - command.Execute(); - } - - { - var command = new ValidateComponentGuidsCommand(this.Messaging, section); + var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform); command.Execute(); } diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs deleted file mode 100644 index 5cb297e5..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs +++ /dev/null @@ -1,181 +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.IO; - using System.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Data; - using WixToolset.Extensibility.Services; - - /// - /// Set the guids for components with generatable guids. - /// - internal class CalculateComponentGuids - { - internal CalculateComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform) - { - this.Messaging = messaging; - this.BackendHelper = helper; - this.PathResolver = pathResolver; - this.Section = section; - this.Platform = platform; - } - - private IMessaging Messaging { get; } - - private IBackendHelper BackendHelper { get; } - - private IPathResolver PathResolver { get; } - - private IntermediateSection Section { get; } - - private Platform Platform { get; } - - public void Execute() - { - Dictionary registryKeyRows = null; - Dictionary targetPathsByDirectoryId = null; - Dictionary componentIdGenSeeds = null; - Dictionary> filesByComponentId = null; - - // Find components with generatable guids. - foreach (var componentSymbol in this.Section.Symbols.OfType()) - { - // Skip components that do not specify generate guid. - if (componentSymbol.ComponentId != "*") - { - continue; - } - - if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType) - { - this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers)); - continue; - } - - if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType) - { - if (registryKeyRows is null) - { - registryKeyRows = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); - } - - if (registryKeyRows.TryGetValue(componentSymbol.KeyPath, out var foundRow)) - { - var bitness = componentSymbol.Win64 ? "64" : String.Empty; - var regkey = String.Concat(bitness, foundRow.Root, "\\", foundRow.Key, "\\", foundRow.Name); - componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); - } - } - else // must be a File KeyPath. - { - // If the directory table hasn't been loaded into an indexed hash - // of directory ids to target names do that now. - if (targetPathsByDirectoryId is null) - { - var directories = this.Section.Symbols.OfType().ToList(); - - targetPathsByDirectoryId = new Dictionary(directories.Count); - - // Get the target paths for all directories. - foreach (var directory in directories) - { - // If the directory Id already exists, we will skip it here since - // checking for duplicate primary keys is done later when importing tables - // into database - if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) - { - continue; - } - - var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); - targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); - } - } - - // If the component id generation seeds have not been indexed - // from the Directory symbols do that now. - if (componentIdGenSeeds is null) - { - // If there are any Directory symbols, build up the Component Guid - // generation seeds indexed by Directory/@Id. - componentIdGenSeeds = this.Section.Symbols.OfType() - .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) - .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed); - } - - // if the file rows have not been indexed by File.Component yet - // then do that now - if (filesByComponentId is null) - { - var files = this.Section.Symbols.OfType().ToList(); - - filesByComponentId = new Dictionary>(files.Count); - - foreach (var file in files) - { - if (!filesByComponentId.TryGetValue(file.ComponentRef, out var componentFiles)) - { - componentFiles = new List(); - filesByComponentId.Add(file.ComponentRef, componentFiles); - } - - componentFiles.Add(file); - } - } - - // validate component meets all the conditions to have a generated guid - var currentComponentFiles = filesByComponentId[componentSymbol.Id.Id]; - var numFilesInComponent = currentComponentFiles.Count; - string path = null; - - foreach (var fileRow in currentComponentFiles) - { - if (fileRow.Id.Id == componentSymbol.KeyPath) - { - // calculate the key file's canonical target path - var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform); - var fileName = this.BackendHelper.GetMsiFileName(fileRow.Name, false, true).ToLowerInvariant(); - path = Path.Combine(directoryPath, fileName); - - // find paths that are not canonicalized - if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || - path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || - path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || - path.StartsWith("TARGETDIR", StringComparison.Ordinal) || - path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || - path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) - { - this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileRow.ComponentRef, path)); - } - - // if component has more than one file, the key path must be versioned - if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version)) - { - this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.SourceLineNumbers)); - } - } - else - { - // not a key path, so it must be an unversioned file if component has more than one file - if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version)) - { - this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers)); - } - } - } - - // if the rules were followed, reward with a generated guid - if (!this.Messaging.EncounteredError) - { - componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path); - } - } - } - } - } -} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs new file mode 100644 index 00000000..3cdc0c28 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs @@ -0,0 +1,262 @@ +// 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.IO; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Set the guids for components with generatable guids and validate all are appropriately unique. + /// + internal class FinalizeComponentGuids + { + internal FinalizeComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform) + { + this.Messaging = messaging; + this.BackendHelper = helper; + this.PathResolver = pathResolver; + this.Section = section; + this.Platform = platform; + } + + private IMessaging Messaging { get; } + + private IBackendHelper BackendHelper { get; } + + private IPathResolver PathResolver { get; } + + private IntermediateSection Section { get; } + + private Platform Platform { get; } + + private Dictionary ComponentIdGenSeeds { get; set; } + + private ILookup FilesByComponentId { get; set; } + + private Dictionary RegistrySymbolsById { get; set; } + + private Dictionary TargetPathsByDirectoryId { get; set; } + + public void Execute() + { + var componentGuidConditions = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var guidCollisions = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var componentSymbol in this.Section.Symbols.OfType()) + { + if (componentSymbol.ComponentId == "*") + { + this.GenerateComponentGuid(componentSymbol); + } + + // Now check for GUID collisions, but we don't care about unmanaged components and + // if there's a * GUID remaining, there's already an error that explained why it + // was not replaced with a real GUID. + if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && componentSymbol.ComponentId != "*") + { + if (!componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var components)) + { + components = new List(); + componentGuidConditions.Add(componentSymbol.ComponentId, components); + } + + components.Add(componentSymbol); + if (components.Count > 1) + { + guidCollisions.Add(componentSymbol.ComponentId); + } + } + } + + if (guidCollisions.Count > 0) + { + this.ReportGuidCollisions(guidCollisions, componentGuidConditions); + } + } + + private void GenerateComponentGuid(ComponentSymbol componentSymbol) + { + if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType) + { + this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers)); + return; + } + + if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType) + { + if (this.RegistrySymbolsById is null) + { + this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + if (this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol)) + { + var bitness = componentSymbol.Win64 ? "64" : String.Empty; + var regkey = String.Concat(bitness, registrySymbol.Root, "\\", registrySymbol.Key, "\\", registrySymbol.Name); + componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()); + } + } + else // must be a File KeyPath. + { + // If the directory table hasn't been loaded into an indexed hash + // of directory ids to target names do that now. + if (this.TargetPathsByDirectoryId is null) + { + this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); + } + + // If the component id generation seeds have not been indexed + // from the Directory symbols do that now. + if (this.ComponentIdGenSeeds is null) + { + // If there are any Directory symbols, build up the Component Guid + // generation seeds indexed by Directory/@Id. + this.ComponentIdGenSeeds = this.Section.Symbols.OfType() + .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed)) + .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed); + } + + // If the file symbols have not been indexed by File's ComponentRef yet + // then do that now. + if (this.FilesByComponentId is null) + { + this.FilesByComponentId = this.Section.Symbols.OfType().ToLookup(f => f.ComponentRef); + } + + // validate component meets all the conditions to have a generated guid + var currentComponentFiles = this.FilesByComponentId[componentSymbol.Id.Id]; + var numFilesInComponent = currentComponentFiles.Count(); + string path = null; + + foreach (var fileSymbol in currentComponentFiles) + { + if (fileSymbol.Id.Id == componentSymbol.KeyPath) + { + // calculate the key file's canonical target path + var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, this.ComponentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform); + var fileName = this.BackendHelper.GetMsiFileName(fileSymbol.Name, false, true).ToLowerInvariant(); + path = Path.Combine(directoryPath, fileName); + + // find paths that are not canonicalized + if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) || + path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) || + path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) || + path.StartsWith("TARGETDIR", StringComparison.Ordinal) || + path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) || + path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal)) + { + this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileSymbol.ComponentRef, path)); + } + + // if component has more than one file, the key path must be versioned + if (1 < numFilesInComponent && String.IsNullOrEmpty(fileSymbol.Version)) + { + this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.SourceLineNumbers)); + } + } + else + { + // not a key path, so it must be an unversioned file if component has more than one file + if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileSymbol.Version)) + { + this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers)); + } + } + } + + // if the rules were followed, reward with a generated guid + if (!this.Messaging.EncounteredError) + { + componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path); + } + } + } + + private void ReportGuidCollisions(HashSet guidCollisions, Dictionary> componentGuidConditions) + { + Dictionary fileSymbolsById = null; + + foreach (var guid in guidCollisions) + { + var collidingComponents = componentGuidConditions[guid]; + var allComponentsHaveConditions = collidingComponents.All(c => !String.IsNullOrEmpty(c.Condition)); + + foreach (var componentSymbol in collidingComponents) + { + string path; + string type; + + if (componentSymbol.KeyPathType == ComponentKeyPathType.File) + { + if (fileSymbolsById is null) + { + fileSymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + path = fileSymbolsById.TryGetValue(componentSymbol.KeyPath, out var fileSymbol) ? fileSymbol.Source.Path : componentSymbol.KeyPath; + type = "source path"; + } + else if (componentSymbol.KeyPathType == ComponentKeyPathType.Registry) + { + if (this.RegistrySymbolsById is null) + { + this.RegistrySymbolsById = this.Section.Symbols.OfType().ToDictionary(t => t.Id.Id); + } + + path = this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol) ? String.Concat(registrySymbol.Key, "\\", registrySymbol.Name) : componentSymbol.KeyPath; + type = "registry path"; + } + else + { + if (this.TargetPathsByDirectoryId is null) + { + this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths(); + } + + path = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, componentIdGenSeeds: null, componentSymbol.DirectoryRef, this.Platform); + type = "directory"; + } + + if (allComponentsHaveConditions) + { + this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); + } + else + { + this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path)); + } + } + } + } + + private Dictionary ResolveDirectoryTargetPaths() + { + var directories = this.Section.Symbols.OfType().ToList(); + + var targetPathsByDirectoryId = new Dictionary(directories.Count); + + // Get the target paths for all directories. + foreach (var directory in directories) + { + // If the directory Id already exists, we will skip it here since + // checking for duplicate primary keys is done later when importing tables + // into database + if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id)) + { + continue; + } + + var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name); + targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory); + } + + return targetPathsByDirectoryId; + } + } +} diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ValidateComponentGuidsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ValidateComponentGuidsCommand.cs deleted file mode 100644 index 5cad9247..00000000 --- a/src/WixToolset.Core.WindowsInstaller/Bind/ValidateComponentGuidsCommand.cs +++ /dev/null @@ -1,63 +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.Linq; - using WixToolset.Data; - using WixToolset.Data.Symbols; - using WixToolset.Extensibility.Services; - - /// - /// Validate that there are no duplicate GUIDs in the output. - /// - /// - /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a - /// warning, as the conditions might be mutually exclusive. - /// - internal class ValidateComponentGuidsCommand - { - internal ValidateComponentGuidsCommand(IMessaging messaging, IntermediateSection section) - { - this.Messaging = messaging; - this.Section = section; - } - - private IMessaging Messaging { get; } - - private IntermediateSection Section { get; } - - public void Execute() - { - var componentGuidConditions = new Dictionary(); - - foreach (var componentSymbol in this.Section.Symbols.OfType()) - { - // We don't care about unmanaged components and if there's a * GUID remaining, - // there's already an error that prevented it from being replaced with a real GUID. - if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && "*" != componentSymbol.ComponentId) - { - var thisComponentHasCondition = !String.IsNullOrEmpty(componentSymbol.Condition); - var allComponentsHaveConditions = thisComponentHasCondition; - - if (componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var alreadyCheckedCondition)) - { - allComponentsHaveConditions = thisComponentHasCondition && alreadyCheckedCondition; - - if (allComponentsHaveConditions) - { - this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId)); - } - else - { - this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId)); - } - } - - componentGuidConditions[componentSymbol.ComponentId] = allComponentsHaveConditions; - } - } - } - } -} diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index c5f3a763..c39bec70 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -2454,24 +2454,27 @@ namespace WixToolset.Core } } - // check for conditions that exclude this component from using generated guids - var isGeneratableGuidOk = "*" == guid; - if (isGeneratableGuidOk) + // Check for conditions that exclude this component from using implicit ids and/or generated guids. + var allowImplicitIds = true; + if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType) { - if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType) + allowImplicitIds = false; + if (guid == "*") { this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers)); - isGeneratableGuidOk = false; } - else if (0 < files && ComponentKeyPathType.Registry == keyPathType) + } + else if (0 < files && ComponentKeyPathType.Registry == keyPathType) + { + allowImplicitIds = false; + if (guid == "*") { this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true)); - isGeneratableGuidOk = false; } } - // check for implicit KeyPath which can easily be accidentally changed - if (this.ShowPedanticMessages && !keyFound && !isGeneratableGuidOk) + // Check for implicit KeyPath which can easily be accidentally changed + if (this.ShowPedanticMessages && !keyFound && !allowImplicitIds) { this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id)); } @@ -2481,7 +2484,7 @@ namespace WixToolset.Core // generatable guid must be met. if (componentIdPlaceholder == id.Id) { - if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath)) + if (allowImplicitIds || keyFound && !String.IsNullOrEmpty(keyPath)) { this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath); diff --git a/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs b/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs new file mode 100644 index 00000000..d24ba08c --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs @@ -0,0 +1,45 @@ +// 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; + using System.IO; + using System.Linq; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using WixToolset.Data; + using Xunit; + + public class ComponentFixture + { + [Fact] + public void CanDetectDuplicateComponentGuids() + { + 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, "Component", "GuidCollision.wxs"), + Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + var errors = result.Messages.Where(m => m.Level == MessageLevel.Error); + Array.Equals(new[] + { + 369, + 369 + }, errors.Select(e => e.Id).ToArray()); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs new file mode 100644 index 00000000..a0e921cb --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + -- cgit v1.2.3-55-g6feb