aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob Arnson <bob@firegiant.com>2023-12-17 21:06:30 -0500
committerBob Arnson <github@bobs.org>2023-12-26 14:56:27 -0500
commit1c249dde054a6dd261c2b8f55e2173fdbfe64c41 (patch)
tree5f6d0e72bbbd59197fa4f35a11c9d3f7db5b631d
parent6f4fda58d9303c889024e20ee39b1374ed0f02e1 (diff)
downloadwix-1c249dde054a6dd261c2b8f55e2173fdbfe64c41.tar.gz
wix-1c249dde054a6dd261c2b8f55e2173fdbfe64c41.tar.bz2
wix-1c249dde054a6dd261c2b8f55e2173fdbfe64c41.zip
Implement default-feature feature.
Use the WiX stdlib. See WIP at wixtoolset/issues#7581.
-rw-r--r--src/api/wix/WixToolset.Data/WixStandardLibrary.cs14
-rw-r--r--src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs5
-rw-r--r--src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs57
-rw-r--r--src/wix/WixToolset.Core/Compiler.cs4
-rw-r--r--src/wix/WixToolset.Core/Compiler_Package.cs10
-rw-r--r--src/wix/WixToolset.Core/Linker.cs17
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs74
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageBadDefaultFeature.wxs27
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageDefaultFeature.wxs50
9 files changed, 250 insertions, 8 deletions
diff --git a/src/api/wix/WixToolset.Data/WixStandardLibrary.cs b/src/api/wix/WixToolset.Data/WixStandardLibrary.cs
index c5c9d8d4..3758e5b1 100644
--- a/src/api/wix/WixToolset.Data/WixStandardLibrary.cs
+++ b/src/api/wix/WixToolset.Data/WixStandardLibrary.cs
@@ -83,6 +83,20 @@ namespace WixToolset.Data
83 yield return section; 83 yield return section;
84 } 84 }
85 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
86 // Package References. 100 // Package References.
87 { 101 {
88 var section = CreateSection(WixStandardLibraryIdentifiers.WixStandardPackageReferences); 102 var section = CreateSection(WixStandardLibraryIdentifiers.WixStandardPackageReferences);
diff --git a/src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs b/src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs
index 8c4ac08e..6579a42b 100644
--- a/src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs
+++ b/src/api/wix/WixToolset.Data/WixStandardLibraryIdentifiers.cs
@@ -16,5 +16,10 @@ namespace WixToolset.Data
16 /// WiX Standard references for modules. 16 /// WiX Standard references for modules.
17 /// </summary> 17 /// </summary>
18 public static readonly string WixStandardModuleReferences = "WixStandardModuleReferences"; 18 public static readonly string WixStandardModuleReferences = "WixStandardModuleReferences";
19
20 /// <summary>
21 /// Default feature name.
22 /// </summary>
23 public static readonly string DefaultFeatureName = "WixDefaultFeature";
19 } 24 }
20} 25}
diff --git a/src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs b/src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs
new file mode 100644
index 00000000..9904d740
--- /dev/null
+++ b/src/wix/WixToolset.Core/AssignDefaultFeatureCommand.cs
@@ -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
3namespace 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}
diff --git a/src/wix/WixToolset.Core/Compiler.cs b/src/wix/WixToolset.Core/Compiler.cs
index 6a01b0ef..bfdf4fe8 100644
--- a/src/wix/WixToolset.Core/Compiler.cs
+++ b/src/wix/WixToolset.Core/Compiler.cs
@@ -2681,7 +2681,7 @@ namespace WixToolset.Core
2681 /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param> 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) 2682 private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
2683 { 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 2685
2686 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); 2686 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2687 string id = null; 2687 string id = null;
@@ -2730,7 +2730,7 @@ namespace WixToolset.Core
2730 /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param> 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) 2731 private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
2732 { 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 2734
2735 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); 2735 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2736 string id = null; 2736 string id = null;
diff --git a/src/wix/WixToolset.Core/Compiler_Package.cs b/src/wix/WixToolset.Core/Compiler_Package.cs
index 02ed9092..17a6a913 100644
--- a/src/wix/WixToolset.Core/Compiler_Package.cs
+++ b/src/wix/WixToolset.Core/Compiler_Package.cs
@@ -240,10 +240,16 @@ namespace WixToolset.Core
240 this.ParseComplianceCheckElement(child); 240 this.ParseComplianceCheckElement(child);
241 break; 241 break;
242 case "Component": 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);
244 break; 247 break;
245 case "ComponentGroup": 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);
247 break; 253 break;
248 case "CustomAction": 254 case "CustomAction":
249 this.ParseCustomActionElement(child); 255 this.ParseCustomActionElement(child);
diff --git a/src/wix/WixToolset.Core/Linker.cs b/src/wix/WixToolset.Core/Linker.cs
index 0cef88d2..ce5612c3 100644
--- a/src/wix/WixToolset.Core/Linker.cs
+++ b/src/wix/WixToolset.Core/Linker.cs
@@ -126,6 +126,14 @@ namespace WixToolset.Core
126 } 126 }
127 } 127 }
128 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
129 // Resolve the symbol references to find the set of sections we care about for linking. 137 // Resolve the symbol references to find the set of sections we care about for linking.
130 // Of course, we start with the entry section (that's how it got its name after all). 138 // Of course, we start with the entry section (that's how it got its name after all).
131 var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName); 139 var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName);
@@ -162,7 +170,7 @@ namespace WixToolset.Core
162 return null; 170 return null;
163 } 171 }
164 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.
166 foreach (var component in sections.SelectMany(s => s.Symbols.Where(y => y.Definition.Type == SymbolDefinitionType.Component))) 174 foreach (var component in sections.SelectMany(s => s.Symbols.Where(y => y.Definition.Type == SymbolDefinitionType.Component)))
167 { 175 {
168 if (!referencedComponents.Contains(component.Id.Id)) 176 if (!referencedComponents.Contains(component.Id.Id))
@@ -370,7 +378,8 @@ namespace WixToolset.Core
370 foreach (var section in sections) 378 foreach (var section in sections)
371 { 379 {
372 // Need ToList since we might want to add symbols while processing. 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)
374 { 383 {
375 ConnectToFeature connection; 384 ConnectToFeature connection;
376 switch (wixComplexReferenceRow.ParentType) 385 switch (wixComplexReferenceRow.ParentType)
@@ -515,6 +524,10 @@ namespace WixToolset.Core
515 featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary)); 524 featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary));
516 break; 525 break;
517 526
527 case ComplexReferenceChildType.Component:
528 case ComplexReferenceChildType.ComponentGroup:
529 break;
530
518 default: 531 default:
519 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); 532 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
520 } 533 }
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs
index c6da19eb..00fea67e 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs
@@ -2,11 +2,10 @@
2 2
3namespace WixToolsetTest.CoreIntegration 3namespace WixToolsetTest.CoreIntegration
4{ 4{
5 using System;
6 using System.IO; 5 using System.IO;
7 using System.Linq; 6 using System.Linq;
8 using WixInternal.TestSupport;
9 using WixInternal.Core.TestPackage; 7 using WixInternal.Core.TestPackage;
8 using WixInternal.TestSupport;
10 using WixToolset.Data; 9 using WixToolset.Data;
11 using Xunit; 10 using Xunit;
12 11
@@ -45,6 +44,77 @@ namespace WixToolsetTest.CoreIntegration
45 } 44 }
46 45
47 [Fact] 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]
48 public void CannotBuildMsiWithTooLargeFeatureDepth() 118 public void CannotBuildMsiWithTooLargeFeatureDepth()
49 { 119 {
50 var folder = TestData.Get(@"TestData"); 120 var folder = TestData.Get(@"TestData");
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 00000000..0f8a078a
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageBadDefaultFeature.wxs
@@ -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>
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 00000000..bd93e53c
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageDefaultFeature.wxs
@@ -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>