From e5562692a535e1275b51106048817812d24fbb74 Mon Sep 17 00:00:00 2001 From: Bob Arnson Date: Sun, 2 Oct 2022 10:05:00 -0400 Subject: [PATCH] Implement default-feature feature. See WIP at https://github.com/wixtoolset/issues/issues/7581. --- .../AssignDefaultFeatureCommand.cs | 70 ++++++++++++++++++ src/wix/WixToolset.Core/Compiler.cs | 4 +- src/wix/WixToolset.Core/Compiler_Package.cs | 6 ++ src/wix/WixToolset.Core/Linker.cs | 21 ++++-- .../FeatureFixture.cs | 71 +++++++++++++++++++ .../Feature/PackageBadDefaultFeature.wxs | 27 +++++++ .../Feature/PackageDefaultFeature.wxs | 46 ++++++++++++ 7 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageBadDefaultFeature.wxs create mode 100644 src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageDefaultFeature.wxs diff --git a/src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs b/src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs new file mode 100644 index 000000000..87ab14645 --- /dev/null +++ b/src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs @@ -0,0 +1,70 @@ +// 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.Linq; + using WixToolset.Core.Link; + using WixToolset.Data; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Services; + using static System.Collections.Specialized.BitVector32; + + internal class AssignDefaultFeatureCommand + { + private const string DefaultFeatureName = "WixDefaultFeature"; + + public AssignDefaultFeatureCommand(IMessaging messaging, IntermediateSection entrySection, IEnumerable sections, HashSet referencedComponents, Link.ConnectToFeatureCollection componentsToFeatures) + { + this.Messaging = messaging; + this.EntrySection = entrySection; + this.Sections = sections; + this.ReferencedComponents = referencedComponents; + this.ComponentsToFeatures = componentsToFeatures; + } + + public IMessaging Messaging { get; } + + public IntermediateSection EntrySection { get; } + + public IEnumerable Sections { get; } + + public HashSet ReferencedComponents { get; } + + public ConnectToFeatureCollection ComponentsToFeatures { get; } + + public void Execute() + { + var assignedComponents = false; + + foreach (var section in this.Sections) + { + foreach (var component in section.Symbols.OfType().ToList()) + { + if (!this.ReferencedComponents.Contains(component.Id.Id)) + { + assignedComponents = true; + + this.ComponentsToFeatures.Add(new ConnectToFeature(section, component.Id.Id, DefaultFeatureName, explicitPrimaryFeature: true)); + + section.AddSymbol(new FeatureComponentsSymbol + { + FeatureRef = DefaultFeatureName, + ComponentRef = component.Id.Id, + }); + } + } + } + + if (assignedComponents) + { + this.EntrySection.AddSymbol(new FeatureSymbol(null, new Identifier(AccessModifier.Global, DefaultFeatureName)) + { + Level = 1, + Display = 0, + InstallDefault = FeatureInstallDefault.Local, + }); + } + } + } +} diff --git a/src/wix/WixToolset.Core/Compiler.cs b/src/wix/WixToolset.Core/Compiler.cs index 36fd35b22..d9be25004 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 9c1b316aa..120a8ec2f 100644 --- a/src/wix/WixToolset.Core/Compiler_Package.cs +++ b/src/wix/WixToolset.Core/Compiler_Package.cs @@ -247,9 +247,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 a3d990392..21cc67347 100644 --- a/src/wix/WixToolset.Core/Linker.cs +++ b/src/wix/WixToolset.Core/Linker.cs @@ -150,14 +150,23 @@ public Intermediate Link(ILinkContext context) return null; } - // Display an error message for Components that were not referenced by a Feature. - foreach (var component in sections.SelectMany(s => s.Symbols.Where(y => y.Definition.Type == SymbolDefinitionType.Component))) + // If there are authored features, error for any referenced components that aren't assigned to a feature. + // If not, create a default feature and assign the components to it. + if (sections.SelectMany(s => s.Symbols).OfType().Any()) { - if (!referencedComponents.Contains(component.Id.Id)) + foreach (var component in sections.SelectMany(s => s.Symbols.Where(y => y.Definition.Type == SymbolDefinitionType.Component))) { - this.Messaging.Write(ErrorMessages.OrphanedComponent(component.SourceLineNumbers, component.Id.Id)); + if (!referencedComponents.Contains(component.Id.Id)) + { + this.Messaging.Write(ErrorMessages.OrphanedComponent(component.SourceLineNumbers, component.Id.Id)); + } } } + else + { + var command = new AssignDefaultFeatureCommand(this.Messaging, find.EntrySection, sections, referencedComponents, componentsToFeatures); + command.Execute(); + } // Report duplicates that would ultimately end up being primary key collisions. { @@ -532,6 +541,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..6121c3fcd 100644 --- a/src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs +++ b/src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs @@ -44,6 +44,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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +