Skip to content

Commit b4d2173

Browse files
committed
Implement default-feature feature.
Use the WiX stdlib. See WIP at wixtoolset/issues#7581.
1 parent f02ef4a commit b4d2173

File tree

9 files changed

+250
-8
lines changed

9 files changed

+250
-8
lines changed

src/api/wix/WixToolset.Data/WixStandardLibrary.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,20 @@ private static IEnumerable<IntermediateSection> YieldSections(Platform platform)
8383
yield return section;
8484
}
8585

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+
86100
// Package References.
87101
{
88102
var section = CreateSection(WixStandardLibraryIdentifiers.WixStandardPackageReferences);

src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,10 @@ public static class WixStandardLibraryIdentifiers
1616
/// WiX Standard references for modules.
1717
/// </summary>
1818
public static readonly string WixStandardModuleReferences = "WixStandardModuleReferences";
19+
20+
/// <summary>
21+
/// Default feature name.
22+
/// </summary>
23+
public static readonly string DefaultFeatureName = "WixDefaultFeature";
1924
}
2025
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2681,7 +2681,7 @@ private void ParseComponentGroupElement(XElement node, ComplexReferenceParentTyp
26812681
/// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param>
26822682
private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
26832683
{
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);
26852685

26862686
var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
26872687
string id = null;
@@ -2730,7 +2730,7 @@ private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParent
27302730
/// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param>
27312731
private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
27322732
{
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);
27342734

27352735
var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
27362736
string id = null;

src/wix/WixToolset.Core/Compiler_Package.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,16 @@ private void ParsePackageElement(XElement node)
240240
this.ParseComplianceCheckElement(child);
241241
break;
242242
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);
244247
break;
245248
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);
247253
break;
248254
case "CustomAction":
249255
this.ParseCustomActionElement(child);

src/wix/WixToolset.Core/Linker.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,14 @@ public Intermediate Link(ILinkContext context)
126126
}
127127
}
128128

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+
129137
// Resolve the symbol references to find the set of sections we care about for linking.
130138
// Of course, we start with the entry section (that's how it got its name after all).
131139
var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName);
@@ -162,7 +170,7 @@ public Intermediate Link(ILinkContext context)
162170
return null;
163171
}
164172

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.
166174
foreach (var component in sections.SelectMany(s => s.Symbols.Where(y => y.Definition.Type == SymbolDefinitionType.Component)))
167175
{
168176
if (!referencedComponents.Contains(component.Id.Id))
@@ -370,7 +378,8 @@ private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnum
370378
foreach (var section in sections)
371379
{
372380
// 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)
374383
{
375384
ConnectToFeature connection;
376385
switch (wixComplexReferenceRow.ParentType)
@@ -515,6 +524,10 @@ private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnum
515524
featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary));
516525
break;
517526

527+
case ComplexReferenceChildType.Component:
528+
case ComplexReferenceChildType.ComponentGroup:
529+
break;
530+
518531
default:
519532
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
520533
}

src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
namespace WixToolsetTest.CoreIntegration
44
{
5-
using System;
65
using System.IO;
76
using System.Linq;
8-
using WixInternal.TestSupport;
97
using WixInternal.Core.TestPackage;
8+
using WixInternal.TestSupport;
109
using WixToolset.Data;
1110
using Xunit;
1211

@@ -44,6 +43,77 @@ public void CanDetectMissingFeatureComponentMapping()
4443
}
4544
}
4645

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+
47117
[Fact]
48118
public void CannotBuildMsiWithTooLargeFeatureDepth()
49119
{
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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>

0 commit comments

Comments
 (0)