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>