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 @@
-
-
-
+
+