diff --git a/src/api/wix/WixToolset.Data/ErrorMessages.cs b/src/api/wix/WixToolset.Data/ErrorMessages.cs index e7c886134..79b835cda 100644 --- a/src/api/wix/WixToolset.Data/ErrorMessages.cs +++ b/src/api/wix/WixToolset.Data/ErrorMessages.cs @@ -2256,6 +2256,11 @@ public static Message IllegalInnerText(SourceLineNumber sourceLineNumbers, strin return Message(sourceLineNumbers, Ids.IllegalInnerText, "The {0} element contains inner text which is obsolete. Use the {1} attribute instead.", elementName, attributeName); } + public static Message IllegalAttributeWhenNested(SourceLineNumber sourceLineNumbers, string attributeName) + { + return Message(sourceLineNumbers, Ids.IllegalAttributeWhenNested, "The File element contains an attribute '{0}' that cannot be used in a File element that is a child of a Component element.", attributeName); + } + private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) { return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); diff --git a/src/wix/WixToolset.Core/Compiler.cs b/src/wix/WixToolset.Core/Compiler.cs index 7088cfba0..cd90892d4 100644 --- a/src/wix/WixToolset.Core/Compiler.cs +++ b/src/wix/WixToolset.Core/Compiler.cs @@ -2252,9 +2252,11 @@ private void ParseComponentElement(XElement node, ComplexReferenceParentType par if (String.IsNullOrEmpty(directoryId)) { - this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory")); + directoryId = "INSTALLFOLDER"; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); } - else if (!String.IsNullOrEmpty(subdirectory)) + + if (!String.IsNullOrEmpty(subdirectory)) { directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory); } @@ -2427,6 +2429,7 @@ private void ParseComponentElement(XElement node, ComplexReferenceParentType par keyBit = ComponentKeyPathType.File; keyPossible = possibleKeyPath.Id; break; + case PossibleKeyPathType.Directory: keyBit = ComponentKeyPathType.Directory; keyPossible = String.Empty; @@ -2581,8 +2584,8 @@ private void ParseComponentElement(XElement node, ComplexReferenceParentType par /// Parses a component group element. /// /// Element to parse. - /// - /// + /// Type of complex reference parent. Will be Unknown if there is no parent. + /// Optional identifier for primary parent. private void ParseComponentGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); @@ -2649,6 +2652,9 @@ private void ParseComponentGroupElement(XElement node, ComplexReferenceParentTyp case "Component": this.ParseComponentElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null, CompilerConstants.IntegerNotSet, directoryId, source); break; + case "File": + this.ParseNakedFileElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, directoryId, source); + break; default: this.Core.UnexpectedElement(node, child); break; @@ -3872,6 +3878,9 @@ private void ParseDirectoryElement(XElement node, string parentId, int diskId, s case "Directory": this.ParseDirectoryElement(child, id.Id, diskId, fileSource); break; + case "File": + this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id.Id, fileSource); + break; case "Merge": this.ParseMergeElement(child, id.Id, diskId); break; @@ -3984,6 +3993,9 @@ private void ParseDirectoryRefElement(XElement node) case "Directory": this.ParseDirectoryElement(child, id, diskId, fileSource); break; + case "File": + this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id, fileSource); + break; case "Merge": this.ParseMergeElement(child, id, diskId); break; @@ -4421,6 +4433,9 @@ private void ParseFeatureElement(XElement node, ComplexReferenceParentType paren case "FeatureRef": this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id.Id); break; + case "File": + this.ParseNakedFileElement(child, ComplexReferenceParentType.Feature, id.Id, null, null); + break; case "Level": this.ParseLevelElement(child, id.Id); break; @@ -4561,6 +4576,9 @@ private void ParseFeatureRefElement(XElement node, ComplexReferenceParentType pa case "FeatureRef": this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id); break; + case "File": + this.ParseNakedFileElement(child, ComplexReferenceParentType.Feature, id, null, null); + break; case "MergeRef": this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id); break; @@ -4646,6 +4664,9 @@ private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType case "FeatureRef": this.ParseFeatureRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); break; + case "File": + this.ParseNakedFileElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, null); + break; case "MergeRef": this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id); break; @@ -5029,9 +5050,8 @@ private void ParseExtensionElement(XElement node, string componentId, YesNoType } } - /// - /// Parses a file element. + /// Parses a File element's attributes. /// /// File element to parse. /// Parent's component id. @@ -5039,10 +5059,12 @@ private void ParseExtensionElement(XElement node, string componentId, YesNoType /// Disk id inherited from parent component. /// Default source path of parent directory. /// This will be set with the possible keyPath for the parent component. - /// true if the component is 64-bit. - /// + /// Component GUID (including `*`). + /// Whether the File element being parsed is outside a Component element. + /// Outgoing file symbol containing parsed attributes. + /// Outgoing assembly symbol containing parsed attributes. /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. - private YesNoType ParseFileElement(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, bool win64Component, string componentGuid) + private YesNoType ParseFileElementAttributes(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, string componentGuid, bool isNakedFile, out FileSymbol fileSymbol, out AssemblySymbol assemblySymbol) { var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); Identifier id = null; @@ -5083,12 +5105,25 @@ private YesNoType ParseFileElement(XElement node, string componentId, string dir var source = sourcePath; // assume we'll use the parents as the source for this file var sourceSet = false; + fileSymbol = null; + assemblySymbol = null; + foreach (var attrib in node.Attributes()) { if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) { switch (attrib.Name.LocalName) { + case "Bitness": + case "Condition": + case "Directory": + case "Subdirectory": + // Naked files handle their attributes in ParseNakedFileElement. + if (!isNakedFile) + { + this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, attrib.Name.LocalName)); + } + break; case "Id": id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib); break; @@ -5329,70 +5364,6 @@ private YesNoType ParseFileElement(XElement node, string componentId, string dir } } - foreach (var child in node.Elements()) - { - if (CompilerCore.WixNamespace == child.Name.Namespace) - { - switch (child.Name.LocalName) - { - case "AppId": - this.ParseAppIdElement(child, componentId, YesNoType.NotSet, id.Id, null, null); - break; - case "AssemblyName": - this.ParseAssemblyName(child, componentId); - break; - case "Class": - this.ParseClassElement(child, componentId, YesNoType.NotSet, id.Id, null, null, null); - break; - case "CopyFile": - this.ParseCopyFileElement(child, componentId, id.Id); - break; - case "IgnoreRange": - this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); - break; - case "ODBCDriver": - this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCDriver); - break; - case "ODBCTranslator": - this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCTranslator); - break; - case "Permission": - this.ParsePermissionElement(child, id.Id, "File"); - break; - case "PermissionEx": - this.ParsePermissionExElement(child, id.Id, "File"); - break; - case "ProtectRange": - this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); - break; - case "Shortcut": - this.ParseShortcutElement(child, componentId, node.Name.LocalName, id.Id, keyPath); - break; - case "SymbolPath": - if (null != symbols) - { - symbols += ";" + this.ParseSymbolPathElement(child); - } - else - { - symbols = this.ParseSymbolPathElement(child); - } - break; - case "TypeLib": - this.ParseTypeLibElement(child, componentId, id.Id, win64Component); - break; - default: - this.Core.UnexpectedElement(node, child); - break; - } - } - else - { - var context = new Dictionary() { { "FileId", id?.Id }, { "ComponentId", componentId }, { "DirectoryId", directoryId }, { "Win64", win64Component.ToString() } }; - this.Core.ParseExtensionElement(node, child, context); - } - } - if (!this.Core.EncounteredError) { var patchAttributes = PatchAttributeType.None; @@ -5427,7 +5398,7 @@ private YesNoType ParseFileElement(XElement node, string componentId, string dir attributes |= compressed.HasValue && compressed == true ? FileSymbolAttributes.Compressed : 0; attributes |= compressed.HasValue && compressed == false ? FileSymbolAttributes.Uncompressed : 0; - this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id) + fileSymbol = new FileSymbol(sourceLineNumbers, id) { ComponentRef = componentId, Name = name, @@ -5454,11 +5425,11 @@ private YesNoType ParseFileElement(XElement node, string componentId, string dir IgnoreLengths = ignoreLengths, RetainOffsets = protectOffsets, SymbolPaths = symbols, - }); + }; if (AssemblyType.NotAnAssembly != assemblyType) { - this.Core.AddSymbol(new AssemblySymbol(sourceLineNumbers, id) + assemblySymbol = new AssemblySymbol(sourceLineNumbers, id) { ComponentRef = componentId, FeatureRef = Guid.Empty.ToString("B"), @@ -5466,7 +5437,7 @@ private YesNoType ParseFileElement(XElement node, string componentId, string dir ApplicationFileRef = assemblyApplication, Type = assemblyType, ProcessorArchitecture = procArch, - }); + }; } } @@ -5485,6 +5456,227 @@ private YesNoType ParseFileElement(XElement node, string componentId, string dir return keyPath; } + /// File element to parse. + /// The partially-parsed file symbol. + /// Whether the file is the keypath of its component. + /// true if the component is 64-bit. + private void ParseFileElementChildren(XElement node, FileSymbol fileSymbol, YesNoType keyPath, bool win64Component) + { + var directoryId = fileSymbol.DirectoryRef; + var componentId = fileSymbol.ComponentRef; + var id = fileSymbol.Id; + var ignoreOffsets = fileSymbol.IgnoreOffsets; + var ignoreLengths = fileSymbol.IgnoreLengths; + var protectOffsets = fileSymbol.RetainOffsets; + var protectLengths = fileSymbol.RetainLengths; + var symbols = fileSymbol.SymbolPaths; + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "AppId": + this.ParseAppIdElement(child, componentId, YesNoType.NotSet, id.Id, null, null); + break; + case "AssemblyName": + this.ParseAssemblyName(child, componentId); + break; + case "Class": + this.ParseClassElement(child, componentId, YesNoType.NotSet, id.Id, null, null, null); + break; + case "CopyFile": + this.ParseCopyFileElement(child, componentId, id.Id); + break; + case "IgnoreRange": + this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths); + break; + case "ODBCDriver": + this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCDriver); + break; + case "ODBCTranslator": + this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCTranslator); + break; + case "Permission": + this.ParsePermissionElement(child, id.Id, "File"); + break; + case "PermissionEx": + this.ParsePermissionExElement(child, id.Id, "File"); + break; + case "ProtectRange": + this.ParseRangeElement(child, ref protectOffsets, ref protectLengths); + break; + case "Shortcut": + this.ParseShortcutElement(child, componentId, node.Name.LocalName, id.Id, keyPath); + break; + case "SymbolPath": + if (null != symbols) + { + symbols += ";" + this.ParseSymbolPathElement(child); + } + else + { + symbols = this.ParseSymbolPathElement(child); + } + break; + case "TypeLib": + this.ParseTypeLibElement(child, componentId, id.Id, win64Component); + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + var context = new Dictionary() { { "FileId", id?.Id }, { "ComponentId", componentId }, { "DirectoryId", directoryId }, { "Win64", win64Component.ToString() } }; + this.Core.ParseExtensionElement(node, child, context); + } + } + + fileSymbol.IgnoreOffsets = ignoreOffsets; + fileSymbol.IgnoreLengths = ignoreLengths; + fileSymbol.RetainOffsets = protectOffsets; + fileSymbol.RetainLengths = protectLengths; + fileSymbol.SymbolPaths = symbols; + } + + + /// + /// Parses a File element. + /// + /// File element to parse. + /// Parent's component id. + /// Ancestor's directory id. + /// Disk id inherited from parent component. + /// Default source path of parent directory. + /// This will be set with the possible keyPath for the parent component. + /// true if the component is 64-bit. + /// Component GUID (including `*`). + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private YesNoType ParseFileElement(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, bool win64Component, string componentGuid) + { + var keyPath = this.ParseFileElementAttributes(node, componentId, directoryId, diskId, sourcePath, out possibleKeyPath, componentGuid, isNakedFile: false, out var fileSymbol, out var assemblySymbol); + + if (!this.Core.EncounteredError) + { + this.Core.AddSymbol(fileSymbol); + + if (assemblySymbol != null) + { + this.Core.AddSymbol(assemblySymbol); + } + + this.ParseFileElementChildren(node, fileSymbol, keyPath, win64Component); + } + + return keyPath; + } + + /// + /// Parses a file element outside a component. + /// + /// File element to parse. + /// Type of complex reference parent. Will be Unknown if there is no parent. + /// Optional identifier for primary parent. + /// Ancestor's directory id. + /// Default source path of parent directory. + /// Yes if this element was marked as the parent component's key path, No if explicitly marked as not being a key path, or NotSet otherwise. + private void ParseNakedFileElement(XElement node, ComplexReferenceParentType parentType, string parentId, string directoryId, string sourcePath) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + var win64 = this.Context.IsCurrentPlatform64Bit; + string condition = null; + string subdirectory = null; + + var keyPath = this.ParseFileElementAttributes(node, "TemporaryComponentId", directoryId, diskId: CompilerConstants.IntegerNotSet, sourcePath, out var _, componentGuid: "*", isNakedFile: true, out var fileSymbol, out var assemblySymbol); + + if (!this.Core.EncounteredError) + { + // Naked files have additional attributes to handle common component attributes. + foreach (var attrib in node.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Bitness": + var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + switch (bitnessValue) + { + case "always32": + win64 = false; + break; + case "always64": + win64 = true; + break; + case "default": + case "": + break; + default: + this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64")); + break; + } + break; + case "Condition": + condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Directory": + directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + break; + case "Subdirectory": + subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true); + break; + } + } + } + + if (String.IsNullOrEmpty(directoryId)) + { + directoryId = "INSTALLFOLDER"; + this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId); + } + + directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory"); + + this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, fileSymbol.Id) + { + ComponentId = "*", + DirectoryRef = directoryId, + Location = ComponentLocation.LocalOnly, + Condition = condition, + KeyPath = fileSymbol.Id.Id, + KeyPathType = ComponentKeyPathType.File, + DisableRegistryReflection = false, + NeverOverwrite = false, + Permanent = false, + SharedDllRefCount = false, + Shared = false, + Transitive = false, + UninstallWhenSuperseded = false, + Win64 = win64, + }); + + fileSymbol.ComponentRef = fileSymbol.Id.Id; + this.Core.AddSymbol(fileSymbol); + + if (assemblySymbol != null) + { + this.Core.AddSymbol(assemblySymbol); + } + + this.ParseFileElementChildren(node, fileSymbol, keyPath, win64); + + if (ComplexReferenceParentType.Unknown != parentType && null != parentId) // if parent was provided, add a complex reference to that. + { + // If the naked file's component is defined directly under a feature, then mark the complex reference primary. + this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Component, fileSymbol.Id.Id, ComplexReferenceParentType.Feature == parentType); + } + } + } + /// /// Parses a file search element. /// @@ -5802,6 +5994,9 @@ private void ParseFragmentElement(XElement node) case "FeatureRef": this.ParseFeatureRefElement(child, ComplexReferenceParentType.Unknown, null); break; + case "File": + this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, null, null); + break; case "Icon": this.ParseIconElement(child); break; @@ -7149,6 +7344,9 @@ private void ParseStandardDirectoryElement(XElement node) case "Directory": this.ParseDirectoryElement(child, id, diskId: CompilerConstants.IntegerNotSet, fileSource: String.Empty); break; + case "File": + this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id, null); + break; case "Merge": this.ParseMergeElement(child, id, diskId: CompilerConstants.IntegerNotSet); break; @@ -7300,7 +7498,7 @@ private void ParseLevelElement(XElement node, string featureId) /// Parses a merge reference element. /// /// Element to parse. - /// Parents complex reference type. + /// Parent's complex reference type. /// Identifier for parent feature or feature group. private void ParseMergeRefElement(XElement node, ComplexReferenceParentType parentType, string parentId) { diff --git a/src/wix/WixToolset.Core/Compiler_Module.cs b/src/wix/WixToolset.Core/Compiler_Module.cs index 092f34732..19f57773f 100644 --- a/src/wix/WixToolset.Core/Compiler_Module.cs +++ b/src/wix/WixToolset.Core/Compiler_Module.cs @@ -175,6 +175,9 @@ private void ParseModuleElement(XElement node) case "Exclusion": this.ParseExclusionElement(child); break; + case "File": + this.ParseNakedFileElement(child, ComplexReferenceParentType.Module, this.activeName, null, null); + break; case "Icon": this.ParseIconElement(child); break; diff --git a/src/wix/WixToolset.Core/Compiler_Package.cs b/src/wix/WixToolset.Core/Compiler_Package.cs index 31b8e81cc..220a2a761 100644 --- a/src/wix/WixToolset.Core/Compiler_Package.cs +++ b/src/wix/WixToolset.Core/Compiler_Package.cs @@ -303,6 +303,9 @@ private void ParsePackageElement(XElement node) case "FeatureGroupRef": this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode); break; + case "File": + this.ParseNakedFileElement(child, ComplexReferenceParentType.Product, productCode, null, null); + break; case "Icon": this.ParseIconElement(child); break; diff --git a/src/wix/WixToolset.Core/Link/AddDefaultSymbolsCommand.cs b/src/wix/WixToolset.Core/Link/AddDefaultSymbolsCommand.cs index 8d9f7cab7..28ad93513 100644 --- a/src/wix/WixToolset.Core/Link/AddDefaultSymbolsCommand.cs +++ b/src/wix/WixToolset.Core/Link/AddDefaultSymbolsCommand.cs @@ -32,6 +32,7 @@ public void Execute() return; } + // If a directory with id INSTALLFOLDER hasn't been authored, provide a default one. if (!this.Find.SymbolsByName.ContainsKey(WixStandardInstallFolderReference)) { var sourceLineNumber = new SourceLineNumber("DefaultInstallFolder"); @@ -51,6 +52,9 @@ public void Execute() ); } + // If an upgrade hasn't been authored and the upgrade strategy is MajorUpgrade, + // conjure a default major upgrade with the stdlib localization string for the + // downgrade error message. var symbols = this.Sections.SelectMany(section => section.Symbols); var upgradeSymbols = symbols.OfType(); if (!upgradeSymbols.Any()) diff --git a/src/wix/WixToolset.Core/Link/SymbolWithSection.cs b/src/wix/WixToolset.Core/Link/SymbolWithSection.cs index 5bdf83603..c00cce407 100644 --- a/src/wix/WixToolset.Core/Link/SymbolWithSection.cs +++ b/src/wix/WixToolset.Core/Link/SymbolWithSection.cs @@ -4,6 +4,7 @@ namespace WixToolset.Core.Link { using System; using System.Collections.Generic; + using System.Diagnostics; using System.Linq; using WixToolset.Data; using WixToolset.Data.Symbols; @@ -11,6 +12,7 @@ namespace WixToolset.Core.Link /// /// Symbol with section representing a single unique symbol. /// + [DebuggerDisplay("{Symbol.DebuggerDisplay}")] internal class SymbolWithSection { private List directReferences; diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs index 9348afa55..71b807830 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs @@ -41,34 +41,5 @@ public void CanDetectDuplicateComponentGuids() }, errors.Select(e => e.Id).ToArray()); } } - - [Fact] - public void CannotBuildMissingDirectoryAttributeWithSubdirectory() - { - 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", "MissingDirectoryWithSubdirectory.wxs"), - Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"), - "-bindpath", Path.Combine(folder, "SingleFile", "data"), - "-intermediateFolder", intermediateFolder, - "-o", msiPath - }); - - var errors = result.Messages.Select(m => m.ToString()).ToArray(); - WixAssert.CompareLineByLine(new[] - { - "The Component/@Directory attribute was not found; it is required." - }, errors); - } - } } } diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/NakedFileFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/NakedFileFixture.cs new file mode 100644 index 000000000..6d874ff05 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/NakedFileFixture.cs @@ -0,0 +1,247 @@ +// 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.Data; + using System.IO; + using System.Linq; + using WixInternal.Core.TestPackage; + using WixInternal.TestSupport; + using WixToolset.Data.WindowsInstaller; + using Xunit; + + public class NakedFileFixture + { + [Fact] + public void CanBuildNakedFilesInComponentGroup() + { + var rows = BuildAndQueryComponentAndFileTables("ComponentGroup.wxs"); + + AssertFileComponentIds(2, rows); + } + + [Fact] + public void CanBuildNakedFilesInFeature() + { + var rows = BuildAndQueryComponentAndFileTables("Feature.wxs"); + + AssertFileComponentIds(2, rows); + } + + [Fact] + public void CanBuildNakedFilesInDirectory() + { + var rows = BuildAndQueryComponentAndFileTables("Directory.wxs"); + + AssertFileComponentIds(2, rows); + } + + [Fact] + public void CanBuildNakedFilesInDirectoryRef() + { + var rows = BuildAndQueryComponentAndFileTables("DirectoryRef.wxs"); + + AssertFileComponentIds(2, rows); + } + + [Fact] + public void CanBuildNakedFilesInFeatureRef() + { + var rows = BuildAndQueryComponentAndFileTables("FeatureRef.wxs"); + + AssertFileComponentIds(2, rows); + } + + [Fact] + public void CanBuildNakedFilesInFeatureGroup() + { + var rows = BuildAndQueryComponentAndFileTables("FeatureGroup.wxs"); + + AssertFileComponentIds(2, rows); + } + + [Fact] + public void CanBuildNakedFilesInFragments() + { + var rows = BuildAndQueryComponentAndFileTables("Fragment.wxs"); + + AssertFileComponentIds(2, rows); + } + + [Fact] + public void CanBuildNakedFilesInStandardDirectory() + { + var rows = BuildAndQueryComponentAndFileTables("StandardDirectory.wxs"); + + AssertFileComponentIds(2, rows); + } + + [Fact] + public void CanBuildNakedFilesInModule() + { + var rows = BuildAndQueryComponentAndFileTables("Module.wxs", isPackage: false); + + AssertFileComponentIds(2, rows); + } + + [Fact] + public void CanBuildNakedFilesWithConditions() + { + var rows = BuildAndQueryComponentAndFileTables("Condition.wxs"); + var componentRows = rows.Where(row => row.StartsWith("Component:")).ToArray(); + + // Coincidentally, the files' ids are the same as the component conditions. + foreach (var componentRow in componentRows) + { + var columns = componentRow.Split(':', '\t'); + Assert.Equal(columns[1], columns[5]); + } + } + + [Fact] + public void CanBuildNakedFilesUnderPackage() + { + var rows = BuildAndQueryComponentAndFileTables("Package.wxs"); + AssertFileComponentIds(4, rows); + } + + [Fact] + public void CanBuildNakedFilesUnderPackageWithDefaultInstallFolder() + { + var rows = BuildAndQueryComponentAndFileTables("PackageWithDefaultInstallFolder.wxs"); + AssertFileComponentIds(4, rows); + } + + [Fact] + public void NakedFilesUnderPackageWithAuthoredFeatureAreOrphaned() + { + var messages = BuildAndQueryComponentAndFileTables("PackageWithoutDefaultFeature.wxs", isPackage: true, 267); + Assert.Equal(new[] + { + "267", + "267", + }, messages); + } + + [Fact] + public void IllegalAttributesWhenNonNakedFailTheBuild() + { + var messages = BuildAndQueryComponentAndFileTables("BadAttributes.wxs", isPackage: true, 62); + Assert.Equal(new[] + { + "62", + "62", + "62", + "62", + }, messages); + } + + [Fact] + public void CanBuildNakedFileFromWixlibComponentGroup() + { + var rows = BuildPackageWithWixlib("WixlibComponentGroup.wxs", "WixlibComponentGroupPackage.wxs"); + + AssertFileComponentIds(2, rows); + } + + private static string[] BuildPackageWithWixlib(string wixlibSourcePath, string msiSourcePath) + { + var folder = TestData.Get("TestData", "NakedFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var wixlibPath = Path.Combine(binFolder, Path.ChangeExtension(wixlibSourcePath, ".wixlib")); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, wixlibSourcePath), + "-intermediateFolder", intermediateFolder, + "-bindpath", folder, + "-o", wixlibPath, + }); + + result.AssertSuccess(); + + var msiPath = Path.Combine(binFolder, "test.msi"); + + result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, msiSourcePath), + wixlibPath, + "-intermediateFolder", intermediateFolder, + "-bindpath", folder, + "-o", msiPath, + }); + result.AssertSuccess(); + + return Query.QueryDatabase(msiPath, new[] { "Component", "File" }) + .OrderBy(s => s) + .ToArray(); + } + } + + private static string[] BuildAndQueryComponentAndFileTables(string file, bool isPackage = true, int? exitCode = null) + { + var folder = TestData.Get("TestData", "NakedFile"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var binFolder = Path.Combine(baseFolder, "bin"); + var msiPath = Path.Combine(binFolder, isPackage ? "test.msi" : "test.msm"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, file), + "-intermediateFolder", intermediateFolder, + "-bindpath", folder, + "-o", msiPath, + }); + + if (exitCode.HasValue) + { + Assert.Equal(exitCode.Value, result.ExitCode); + + return result.Messages.Select(m => m.Id.ToString()).ToArray(); + } + else + { + result.AssertSuccess(); + + return Query.QueryDatabase(msiPath, new[] { "Component", "File" }) + .OrderBy(s => s) + .ToArray(); + } + } + } + + private static void AssertFileComponentIds(int fileCount, string[] rows) + { + var componentRows = rows.Where(row => row.StartsWith("Component:")).ToArray(); + var fileRows = rows.Where(row => row.StartsWith("File:")).ToArray(); + + Assert.Equal(fileCount, componentRows.Length); + Assert.Equal(componentRows.Length, fileRows.Length); + + // Component id == Component keypath == File id + foreach (var componentRow in componentRows) + { + var columns = componentRow.Split(':', '\t'); + Assert.Equal(columns[1], columns[6]); + } + + foreach (var fileRow in fileRows) + { + var columns = fileRow.Split(':', '\t'); + Assert.Equal(columns[1], columns[2]); + } + } + } +} diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/MissingDirectoryWithSubdirectory.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/MissingDirectoryWithSubdirectory.wxs deleted file mode 100644 index cefa9abca..000000000 --- a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/MissingDirectoryWithSubdirectory.wxs +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/BadAttributes.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/BadAttributes.wxs new file mode 100644 index 000000000..269db3c07 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/BadAttributes.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/ComponentGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/ComponentGroup.wxs new file mode 100644 index 000000000..69a539a98 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/ComponentGroup.wxs @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Condition.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Condition.wxs new file mode 100644 index 000000000..3b5dae22d --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Condition.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Directory.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Directory.wxs new file mode 100644 index 000000000..85cdb0292 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Directory.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/DirectoryRef.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/DirectoryRef.wxs new file mode 100644 index 000000000..6de50ac44 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/DirectoryRef.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Feature.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Feature.wxs new file mode 100644 index 000000000..d4c2daa94 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Feature.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/FeatureGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/FeatureGroup.wxs new file mode 100644 index 000000000..94fdd0de2 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/FeatureGroup.wxs @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/FeatureRef.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/FeatureRef.wxs new file mode 100644 index 000000000..c92db44b9 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/FeatureRef.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Fragment.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Fragment.wxs new file mode 100644 index 000000000..6393bbc45 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Fragment.wxs @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Module.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Module.wxs new file mode 100644 index 000000000..c94f37200 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Module.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Package.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Package.wxs new file mode 100644 index 000000000..e5dd94e02 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/Package.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/PackageWithDefaultInstallFolder.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/PackageWithDefaultInstallFolder.wxs new file mode 100644 index 000000000..824f35014 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/PackageWithDefaultInstallFolder.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/PackageWithoutDefaultFeature.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/PackageWithoutDefaultFeature.wxs new file mode 100644 index 000000000..0dbf20458 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/PackageWithoutDefaultFeature.wxs @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/StandardDirectory.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/StandardDirectory.wxs new file mode 100644 index 000000000..5b6bdf2f3 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/StandardDirectory.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/WixlibComponentGroup.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/WixlibComponentGroup.wxs new file mode 100644 index 000000000..90ce0dc9a --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/WixlibComponentGroup.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/WixlibComponentGroupPackage.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/WixlibComponentGroupPackage.wxs new file mode 100644 index 000000000..69b920382 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/WixlibComponentGroupPackage.wxs @@ -0,0 +1,5 @@ + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/test.txt b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/test.txt new file mode 100644 index 000000000..d32727e04 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/NakedFile/test.txt @@ -0,0 +1 @@ +This is test.txt. diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs index b8e9f59c2..488e230f3 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs @@ -2,9 +2,8 @@ - - - + +