@joebigelow / wix-1 / commits / 1c249dde

Implement default-feature feature.

Use the WiX stdlib. See WIP at wixtoolset/issues#7581.

Bob Arnson committed Dec 17, 2023 at 21:06 UTC 1c249dde054a6dd261c2b8f55e2173fdbfe64c41
9 files changed +250 -8
src/api/wix/WixToolset.Data/WixStandardLibrary.cs
+14
@@ -83,6 +83,20 @@ namespace WixToolset.Data
83 yield return section;
84 }
85
86 + // Default feature.
87 + {
88 + var symbol = new FeatureSymbol(sourceLineNumber, new Identifier(AccessModifier.Virtual, WixStandardLibraryIdentifiers.DefaultFeatureName))
89 + {
90 + Level = 1,
91 + Display = 0,
92 + InstallDefault = FeatureInstallDefault.Local,
93 + };
94 +
95 + var section = CreateSectionAroundSymbol(symbol);
96 +
97 + yield return section;
98 + }
99 +
100 // Package References.
101 {
102 var section = CreateSection(WixStandardLibraryIdentifiers.WixStandardPackageReferences);
src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs
+5
@@ -16,5 +16,10 @@ namespace WixToolset.Data
16 /// WiX Standard references for modules.
17 /// </summary>
18 public static readonly string WixStandardModuleReferences = "WixStandardModuleReferences";
19 +
20 + /// <summary>
21 + /// Default feature name.
22 + /// </summary>
23 + public static readonly string DefaultFeatureName = "WixDefaultFeature";
24 }
25 }
src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs new
+57
@@ -0,0 +1,57 @@
1 +// 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.
2 +
3 +namespace WixToolset.Core
4 +{
5 + using System.Collections.Generic;
6 + using System.ComponentModel;
7 + using System.Linq;
8 + using WixToolset.Data;
9 + using WixToolset.Data.Symbols;
10 +
11 + internal class AssignDefaultFeatureCommand
12 + {
13 + public AssignDefaultFeatureCommand(IntermediateSection entrySection, IEnumerable<IntermediateSection> sections)
14 + {
15 + this.EntrySection = entrySection;
16 + this.Sections = sections;
17 + }
18 +
19 + public IntermediateSection EntrySection { get; }
20 +
21 + public IEnumerable<IntermediateSection> Sections { get; }
22 +
23 + public void Execute()
24 + {
25 + foreach (var section in this.Sections)
26 + {
27 + var components = section.Symbols.OfType<ComponentSymbol>().ToList();
28 + foreach (var component in components)
29 + {
30 + this.EntrySection.AddSymbol(new WixComplexReferenceSymbol(component.SourceLineNumbers)
31 + {
32 + Parent = WixStandardLibraryIdentifiers.DefaultFeatureName,
33 + ParentType = ComplexReferenceParentType.Feature,
34 + ParentLanguage = null,
35 + Child = component.Id.Id,
36 + ChildType = ComplexReferenceChildType.Component,
37 + IsPrimary = true,
38 + });
39 +
40 + this.EntrySection.AddSymbol(new WixGroupSymbol(component.SourceLineNumbers)
41 + {
42 + ParentId = WixStandardLibraryIdentifiers.DefaultFeatureName,
43 + ParentType = ComplexReferenceParentType.Feature,
44 + ChildId = component.Id.Id,
45 + ChildType = ComplexReferenceChildType.Component,
46 + });
47 + }
48 + }
49 +
50 + this.EntrySection.AddSymbol(new WixSimpleReferenceSymbol()
51 + {
52 + Table = "Feature",
53 + PrimaryKeys = WixStandardLibraryIdentifiers.DefaultFeatureName,
54 + });
55 + }
56 + }
57 +}
src/wix/WixToolset.Core/Compiler.cs
+2 -2
@@ -2681,7 +2681,7 @@ namespace WixToolset.Core
2681 /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param>
2682 private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
2683 {
2684 - Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType);
2684 + Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType || ComplexReferenceParentType.Product == parentType);
2685
2686 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2687 string id = null;
@@ -2730,7 +2730,7 @@ namespace WixToolset.Core
2730 /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param>
2731 private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
2732 {
2733 - Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType);
2733 + Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType || ComplexReferenceParentType.Product == parentType);
2734
2735 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2736 string id = null;
src/wix/WixToolset.Core/Compiler_Package.cs
+8 -2
@@ -240,10 +240,16 @@ namespace WixToolset.Core
240 this.ParseComplianceCheckElement(child);
241 break;
242 case "Component":
243 - this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null);
243 + this.ParseComponentElement(child, ComplexReferenceParentType.Product, null, null, CompilerConstants.IntegerNotSet, null, null);
244 + break;
245 + case "ComponentRef":
246 + this.ParseComponentRefElement(child, ComplexReferenceParentType.Product, null, null);
247 break;
248 case "ComponentGroup":
246 - this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null);
249 + this.ParseComponentGroupElement(child, ComplexReferenceParentType.Product, null);
250 + break;
251 + case "ComponentGroupRef":
252 + this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Product, null, null);
253 break;
254 case "CustomAction":
255 this.ParseCustomActionElement(child);
src/wix/WixToolset.Core/Linker.cs
+15 -2
@@ -126,6 +126,14 @@ namespace WixToolset.Core
126 }
127 }
128
129 + // If there are no authored features, create a default feature and assign the components to it.
130 + if (find.EntrySection.Type == SectionType.Package
131 + && !sections.Where(s => s.Id != WixStandardLibraryIdentifiers.DefaultFeatureName).SelectMany(s => s.Symbols).OfType<FeatureSymbol>().Any())
132 + {
133 + var command = new AssignDefaultFeatureCommand(find.EntrySection, sections);
134 + command.Execute();
135 + }
136 +
137 // Resolve the symbol references to find the set of sections we care about for linking.
138 // Of course, we start with the entry section (that's how it got its name after all).
139 var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName);
@@ -162,7 +170,7 @@ namespace WixToolset.Core
170 return null;
171 }
172
165 - // Display an error message for Components that were not referenced by a Feature.
173 + // If there are authored features, error for any referenced components that aren't assigned to a feature.
174 foreach (var component in sections.SelectMany(s => s.Symbols.Where(y => y.Definition.Type == SymbolDefinitionType.Component)))
175 {
176 if (!referencedComponents.Contains(component.Id.Id))
@@ -370,7 +378,8 @@ namespace WixToolset.Core
378 foreach (var section in sections)
379 {
380 // Need ToList since we might want to add symbols while processing.
373 - foreach (var wixComplexReferenceRow in section.Symbols.OfType<WixComplexReferenceSymbol>().ToList())
381 + var wixComplexReferences = section.Symbols.OfType<WixComplexReferenceSymbol>().ToList();
382 + foreach (var wixComplexReferenceRow in wixComplexReferences)
383 {
384 ConnectToFeature connection;
385 switch (wixComplexReferenceRow.ParentType)
@@ -515,6 +524,10 @@ namespace WixToolset.Core
524 featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary));
525 break;
526
527 + case ComplexReferenceChildType.Component:
528 + case ComplexReferenceChildType.ComponentGroup:
529 + break;
530 +
531 default:
532 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
533 }
src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs
+72 -2
@@ -2,11 +2,10 @@
2
3 namespace WixToolsetTest.CoreIntegration
4 {
5 - using System;
5 using System.IO;
6 using System.Linq;
8 - using WixInternal.TestSupport;
7 using WixInternal.Core.TestPackage;
8 + using WixInternal.TestSupport;
9 using WixToolset.Data;
10 using Xunit;
11
@@ -44,6 +43,77 @@ namespace WixToolsetTest.CoreIntegration
43 }
44 }
45
46 + [Fact]
47 + public void CanAutomaticallyCreateDefaultFeature()
48 + {
49 + var folder = TestData.Get(@"TestData");
50 +
51 + using (var fs = new DisposableFileSystem())
52 + {
53 + var baseFolder = fs.GetFolder();
54 + var intermediateFolder = Path.Combine(baseFolder, "obj");
55 + var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
56 +
57 + var result = WixRunner.Execute(new[]
58 + {
59 + "build",
60 + Path.Combine(folder, "Feature", "PackageDefaultFeature.wxs"),
61 + "-bindpath", Path.Combine(folder, "SingleFile", "data"),
62 + "-intermediateFolder", intermediateFolder,
63 + "-o", msiPath
64 + });
65 +
66 + Assert.Empty(result.Messages);
67 +
68 + Assert.True(File.Exists(msiPath));
69 + var results = Query.QueryDatabase(msiPath, new[] { "Feature", "FeatureComponents", "Shortcut" });
70 + WixAssert.CompareLineByLine(new[]
71 + {
72 + "Feature:WixDefaultFeature\t\t\t\t0\t1\t\t0",
73 + "FeatureComponents:WixDefaultFeature\tAnotherComponentInAFragment",
74 + "FeatureComponents:WixDefaultFeature\tComponentInAFragment",
75 + "FeatureComponents:WixDefaultFeature\tfil6J6CHYPBCOMYclNjnqn0afimmzM",
76 + "FeatureComponents:WixDefaultFeature\tfilcV1yrx0x8wJWj4qMzcH21jwkPko",
77 + "FeatureComponents:WixDefaultFeature\tfilj.cb0sFWqIPHPFSKJSEEaPDuAQ4",
78 + "Shortcut:AdvertisedShortcut\tINSTALLFOLDER\tShortcut\tAnotherComponentInAFragment\tWixDefaultFeature\t\t\t\t\t\t\t\t\t\t\t",
79 + }, results);
80 + }
81 + }
82 +
83 + [Fact]
84 + public void WontAutomaticallyCreateDefaultFeature()
85 + {
86 + var folder = TestData.Get(@"TestData");
87 +
88 + using (var fs = new DisposableFileSystem())
89 + {
90 + var baseFolder = fs.GetFolder();
91 + var intermediateFolder = Path.Combine(baseFolder, "obj");
92 + var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
93 +
94 + var result = WixRunner.Execute(new[]
95 + {
96 + "build",
97 + Path.Combine(folder, "Feature", "PackageBadDefaultFeature.wxs"),
98 + "-bindpath", Path.Combine(folder, "SingleFile", "data"),
99 + "-intermediateFolder", intermediateFolder,
100 + "-o", msiPath
101 + });
102 +
103 + var messages = result.Messages.Select(m => m.ToString()).ToList();
104 + messages.Sort();
105 +
106 + WixAssert.CompareLineByLine(new[]
107 + {
108 + "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.",
109 + "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.",
110 + "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.",
111 + }, messages.ToArray());
112 +
113 + Assert.Equal(267, result.ExitCode);
114 + }
115 + }
116 +
117 [Fact]
118 public void CannotBuildMsiWithTooLargeFeatureDepth()
119 {
src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageBadDefaultFeature.wxs new
+27
@@ -0,0 +1,27 @@
1 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 + <Package Name="PackageMissingFeatureComponentMapping" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="12E4699F-E774-4D05-8A01-5BDD41BBA127">
3 + <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
4 +
5 + <StandardDirectory Id="ProgramFiles6432Folder">
6 + <Directory Id="INSTALLFOLDER" Name="PackageMissingFeatureComponentMapping">
7 + <Directory Id="SubFolder" Name="NotMapped">
8 + <Component>
9 + <File Source="test.txt" />
10 + </Component>
11 + </Directory>
12 + </Directory>
13 + </StandardDirectory>
14 +
15 + <Feature Id="MissingComponentFeature" />
16 +
17 + <Component Directory="INSTALLFOLDER">
18 + <File Source="test.txt" />
19 + </Component>
20 +
21 + <ComponentGroup Id="ImplicitFeatureComponentGroup" Directory="INSTALLFOLDER">
22 + <Component>
23 + <File Name="test2.txt" Source="test.txt" />
24 + </Component>
25 + </ComponentGroup>
26 + </Package>
27 +</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageDefaultFeature.wxs new
+50
@@ -0,0 +1,50 @@
1 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 + <Package Name="PackageMissingFeatureComponentMapping" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="12E4699F-E774-4D05-8A01-5BDD41BBA127">
3 + <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
4 +
5 + <StandardDirectory Id="ProgramFiles6432Folder">
6 + <Directory Id="INSTALLFOLDER" Name="PackageMissingFeatureComponentMapping">
7 + <Directory Id="SubFolder" Name="NotMapped">
8 + <Component>
9 + <File Source="test.txt" />
10 + </Component>
11 + </Directory>
12 + </Directory>
13 + </StandardDirectory>
14 +
15 + <Component Directory="INSTALLFOLDER">
16 + <File Source="test.txt" />
17 + </Component>
18 +
19 + <ComponentRef Id="ComponentInAFragment" />
20 + <ComponentGroupRef Id="ComponentGroupInAFragment" />
21 + <FeatureGroupRef Id="FeatureGroupInAFragment" />
22 + </Package>
23 +
24 + <Fragment>
25 + <ComponentGroup Id="ComponentGroupInAFragment" Directory="INSTALLFOLDER">
26 + <Component>
27 + <File Name="test2.txt" Source="test.txt" />
28 + </Component>
29 + </ComponentGroup>
30 + </Fragment>
31 +
32 + <Fragment>
33 + <!--
34 + Keeping the component outside the feature group, to ensure the component
35 + comes along for the ride when the empty feature group is referenced.
36 + -->
37 + <FeatureGroup Id="FeatureGroupInAFragment" />
38 +
39 + <Component Id="AnotherComponentInAFragment" Directory="INSTALLFOLDER">
40 + <File Name="test3.txt" Source="test.txt" />
41 + <Shortcut Id="AdvertisedShortcut" Advertise="yes" Name="Shortcut" />
42 + </Component>
43 + </Fragment>
44 +
45 + <Fragment>
46 + <Component Id="ComponentInAFragment" Directory="INSTALLFOLDER">
47 + <File Name="test4.txt" Source="test.txt" />
48 + </Component>
49 + </Fragment>
50 +</Wix>