diff --git a/src/api/wix/WixToolset.Data/WixStandardLibrary.cs b/src/api/wix/WixToolset.Data/WixStandardLibrary.cs index c5c9d8d42..3758e5b15 100644 --- a/src/api/wix/WixToolset.Data/WixStandardLibrary.cs +++ b/src/api/wix/WixToolset.Data/WixStandardLibrary.cs @@ -83,6 +83,20 @@ private static IEnumerable YieldSections(Platform platform) yield return section; } + // Default feature. + { + var symbol = new FeatureSymbol(sourceLineNumber, new Identifier(AccessModifier.Virtual, WixStandardLibraryIdentifiers.DefaultFeatureName)) + { + Level = 1, + Display = 0, + InstallDefault = FeatureInstallDefault.Local, + }; + + var section = CreateSectionAroundSymbol(symbol); + + yield return section; + } + // Package References. { var section = CreateSection(WixStandardLibraryIdentifiers.WixStandardPackageReferences); diff --git a/src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs b/src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs index 8c4ac08e3..6579a42b9 100644 --- a/src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs +++ b/src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs @@ -16,5 +16,10 @@ public static class WixStandardLibraryIdentifiers /// WiX Standard references for modules. /// public static readonly string WixStandardModuleReferences = "WixStandardModuleReferences"; + + /// + /// Default feature name. + /// + public static readonly string DefaultFeatureName = "WixDefaultFeature"; } } diff --git a/src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs b/src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs new file mode 100644 index 000000000..9904d740a --- /dev/null +++ b/src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs @@ -0,0 +1,57 @@ +// 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 +{ + using System.Collections.Generic; + using System.ComponentModel; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Symbols; + + internal class AssignDefaultFeatureCommand + { + public AssignDefaultFeatureCommand(IntermediateSection entrySection, IEnumerable sections) + { + this.EntrySection = entrySection; + this.Sections = sections; + } + + public IntermediateSection EntrySection { get; } + + public IEnumerable Sections { get; } + + public void Execute() + { + foreach (var section in this.Sections) + { + var components = section.Symbols.OfType().ToList(); + foreach (var component in components) + { + this.EntrySection.AddSymbol(new WixComplexReferenceSymbol(component.SourceLineNumbers) + { + Parent = WixStandardLibraryIdentifiers.DefaultFeatureName, + ParentType = ComplexReferenceParentType.Feature, + ParentLanguage = null, + Child = component.Id.Id, + ChildType = ComplexReferenceChildType.Component, + IsPrimary = true, + }); + + this.EntrySection.AddSymbol(new WixGroupSymbol(component.SourceLineNumbers) + { + ParentId = WixStandardLibraryIdentifiers.DefaultFeatureName, + ParentType = ComplexReferenceParentType.Feature, + ChildId = component.Id.Id, + ChildType = ComplexReferenceChildType.Component, + }); + } + } + + this.EntrySection.AddSymbol(new WixSimpleReferenceSymbol() + { + Table = "Feature", + PrimaryKeys = WixStandardLibraryIdentifiers.DefaultFeatureName, + }); + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler.cs b/src/wix/WixToolset.Core/Compiler.cs index 6a01b0ef9..bfdf4fe84 100644 --- a/src/wix/WixToolset.Core/Compiler.cs +++ b/src/wix/WixToolset.Core/Compiler.cs @@ -2681,7 +2681,7 @@ private void ParseComponentGroupElement(XElement node, ComplexReferenceParentTyp /// Optional language of parent (only useful for Modules). private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) { - Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); + Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType || ComplexReferenceParentType.Product == parentType); var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string id = null; @@ -2730,7 +2730,7 @@ private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParent /// Optional language of parent (only useful for Modules). private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage) { - Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType); + Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType || ComplexReferenceParentType.Product == parentType); var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); string id = null; diff --git a/src/wix/WixToolset.Core/Compiler_Package.cs b/src/wix/WixToolset.Core/Compiler_Package.cs index 02ed90921..2035dd128 100644 --- a/src/wix/WixToolset.Core/Compiler_Package.cs +++ b/src/wix/WixToolset.Core/Compiler_Package.cs @@ -242,9 +242,15 @@ private void ParsePackageElement(XElement node) case "Component": this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null); break; + case "ComponentRef": + this.ParseComponentRefElement(child, ComplexReferenceParentType.Product, null, null); + break; case "ComponentGroup": this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null); break; + case "ComponentGroupRef": + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Product, null, null); + break; case "CustomAction": this.ParseCustomActionElement(child); break; diff --git a/src/wix/WixToolset.Core/Linker.cs b/src/wix/WixToolset.Core/Linker.cs index 0cef88d22..ce5612c34 100644 --- a/src/wix/WixToolset.Core/Linker.cs +++ b/src/wix/WixToolset.Core/Linker.cs @@ -126,6 +126,14 @@ public Intermediate Link(ILinkContext context) } } + // If there are no authored features, create a default feature and assign the components to it. + if (find.EntrySection.Type == SectionType.Package + && !sections.Where(s => s.Id != WixStandardLibraryIdentifiers.DefaultFeatureName).SelectMany(s => s.Symbols).OfType().Any()) + { + var command = new AssignDefaultFeatureCommand(find.EntrySection, sections); + command.Execute(); + } + // Resolve the symbol references to find the set of sections we care about for linking. // Of course, we start with the entry section (that's how it got its name after all). var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName); @@ -162,7 +170,7 @@ public Intermediate Link(ILinkContext context) return null; } - // Display an error message for Components that were not referenced by a Feature. + // If there are authored features, error for any referenced components that aren't assigned to a feature. foreach (var component in sections.SelectMany(s => s.Symbols.Where(y => y.Definition.Type == SymbolDefinitionType.Component))) { if (!referencedComponents.Contains(component.Id.Id)) @@ -370,7 +378,8 @@ private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnum foreach (var section in sections) { // Need ToList since we might want to add symbols while processing. - foreach (var wixComplexReferenceRow in section.Symbols.OfType().ToList()) + var wixComplexReferences = section.Symbols.OfType().ToList(); + foreach (var wixComplexReferenceRow in wixComplexReferences) { ConnectToFeature connection; switch (wixComplexReferenceRow.ParentType) @@ -515,6 +524,10 @@ private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnum featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary)); break; + case ComplexReferenceChildType.Component: + case ComplexReferenceChildType.ComponentGroup: + break; + default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs index c6da19eb4..00fea67e7 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs @@ -2,11 +2,10 @@ namespace WixToolsetTest.CoreIntegration { - using System; using System.IO; using System.Linq; - using WixInternal.TestSupport; using WixInternal.Core.TestPackage; + using WixInternal.TestSupport; using WixToolset.Data; using Xunit; @@ -44,6 +43,77 @@ public void CanDetectMissingFeatureComponentMapping() } } + [Fact] + public void CanAutomaticallyCreateDefaultFeature() + { + 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, "Feature", "PackageDefaultFeature.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + Assert.Empty(result.Messages); + + Assert.True(File.Exists(msiPath)); + var results = Query.QueryDatabase(msiPath, new[] { "Feature", "FeatureComponents", "Shortcut" }); + WixAssert.CompareLineByLine(new[] + { + "Feature:WixDefaultFeature\t\t\t\t0\t1\t\t0", + "FeatureComponents:WixDefaultFeature\tAnotherComponentInAFragment", + "FeatureComponents:WixDefaultFeature\tComponentInAFragment", + "FeatureComponents:WixDefaultFeature\tfil6J6CHYPBCOMYclNjnqn0afimmzM", + "FeatureComponents:WixDefaultFeature\tfilcV1yrx0x8wJWj4qMzcH21jwkPko", + "FeatureComponents:WixDefaultFeature\tfilj.cb0sFWqIPHPFSKJSEEaPDuAQ4", + "Shortcut:AdvertisedShortcut\tINSTALLFOLDER\tShortcut\tAnotherComponentInAFragment\tWixDefaultFeature\t\t\t\t\t\t\t\t\t\t\t", + }, results); + } + } + + [Fact] + public void WontAutomaticallyCreateDefaultFeature() + { + 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, "Feature", "PackageBadDefaultFeature.wxs"), + "-bindpath", Path.Combine(folder, "SingleFile", "data"), + "-intermediateFolder", intermediateFolder, + "-o", msiPath + }); + + var messages = result.Messages.Select(m => m.ToString()).ToList(); + messages.Sort(); + + WixAssert.CompareLineByLine(new[] + { + "Found orphaned Component 'fil6J6CHYPBCOMYclNjnqn0afimmzM'. If this is a Package, every Component must have at least one parent Feature. To include a Component in a Module, you must include it directly as a Component element of the Module element or indirectly via ComponentRef, ComponentGroup, or ComponentGroupRef elements.", + "Found orphaned Component 'filcV1yrx0x8wJWj4qMzcH21jwkPko'. If this is a Package, every Component must have at least one parent Feature. To include a Component in a Module, you must include it directly as a Component element of the Module element or indirectly via ComponentRef, ComponentGroup, or ComponentGroupRef elements.", + "Found orphaned Component 'filj.cb0sFWqIPHPFSKJSEEaPDuAQ4'. If this is a Package, every Component must have at least one parent Feature. To include a Component in a Module, you must include it directly as a Component element of the Module element or indirectly via ComponentRef, ComponentGroup, or ComponentGroupRef elements.", + }, messages.ToArray()); + + Assert.Equal(267, result.ExitCode); + } + } + [Fact] public void CannotBuildMsiWithTooLargeFeatureDepth() { diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageBadDefaultFeature.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageBadDefaultFeature.wxs new file mode 100644 index 000000000..0f8a078af --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageBadDefaultFeature.wxs @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageDefaultFeature.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageDefaultFeature.wxs new file mode 100644 index 000000000..a855f26f9 --- /dev/null +++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageDefaultFeature.wxs @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +