aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Hall <r.sean.hall@gmail.com>2020-03-27 13:54:56 +1000
committerSean Hall <r.sean.hall@gmail.com>2020-03-30 21:30:04 +1000
commit0baf6e26ec7ab2ff0b6ad36e9d44f3d68819b5d6 (patch)
tree1ef577d0246b662f4a8bda0ed935e987f03562a0
parentafbc6889c73d58136cb8851858ca3c17f41dc2c5 (diff)
downloadwix-0baf6e26ec7ab2ff0b6ad36e9d44f3d68819b5d6.tar.gz
wix-0baf6e26ec7ab2ff0b6ad36e9d44f3d68819b5d6.tar.bz2
wix-0baf6e26ec7ab2ff0b6ad36e9d44f3d68819b5d6.zip
Add ability for extensions to create custom bundle searches.
This required creating BundleExtensionData.xml.
-rw-r--r--src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs27
-rw-r--r--src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs41
-rw-r--r--src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs24
-rw-r--r--src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs (renamed from src/WixToolset.Core.Burn/Bind/SearchFacade.cs)74
-rw-r--r--src/WixToolset.Core.Burn/Bundles/BurnCommon.cs8
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs6
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs149
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs4
-rw-r--r--src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs71
-rw-r--r--src/WixToolset.Core.Burn/ISearchFacade.cs15
-rw-r--r--src/WixToolset.Core.TestPackage/BundleExtractor.cs34
-rw-r--r--src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs32
-rw-r--r--src/WixToolset.Core/CompilerCore.cs5
-rw-r--r--src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs37
-rw-r--r--src/test/Example.Extension/Data/example.wxs3
-rw-r--r--src/test/Example.Extension/ExampleCompilerExtension.cs104
-rw-r--r--src/test/Example.Extension/ExampleExtensionData.cs6
-rw-r--r--src/test/Example.Extension/ExampleSearchTuple.cs31
-rw-r--r--src/test/Example.Extension/ExampleTupleDefinitions.cs17
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs56
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs8
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs11
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj2
23 files changed, 688 insertions, 77 deletions
diff --git a/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs
new file mode 100644
index 00000000..d00c5778
--- /dev/null
+++ b/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs
@@ -0,0 +1,27 @@
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.Burn
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data.Tuples;
8
9 internal abstract class BaseSearchFacade : ISearchFacade
10 {
11 protected WixSearchTuple SearchTuple { get; set; }
12
13 public virtual void WriteXml(XmlTextWriter writer)
14 {
15 writer.WriteAttributeString("Id", this.SearchTuple.Id.Id);
16 writer.WriteAttributeString("Variable", this.SearchTuple.Variable);
17 if (!String.IsNullOrEmpty(this.SearchTuple.Condition))
18 {
19 writer.WriteAttributeString("Condition", this.SearchTuple.Condition);
20 }
21 if (!String.IsNullOrEmpty(this.SearchTuple.BundleExtensionRef))
22 {
23 writer.WriteAttributeString("ExtensionId", this.SearchTuple.BundleExtensionRef);
24 }
25 }
26 }
27}
diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
index 9f98483f..2cb5ed64 100644
--- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
@@ -117,10 +117,10 @@ namespace WixToolset.Core.Burn
117 // If there are any fields to resolve later, create the cache to populate during bind. 117 // If there are any fields to resolve later, create the cache to populate during bind.
118 var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null; 118 var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null;
119 119
120 // TODO: Although the WixSearch tables are defined in the Util extension, 120 var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section);
121 // the Bundle Binder has to know all about them. We hope to revisit all 121 orderSearchesCommand.Execute();
122 // of this in the 4.0 timeframe. 122 var orderedSearches = orderSearchesCommand.OrderedSearchFacades;
123 var orderedSearches = this.OrderSearches(section); 123 var extensionSearchTuplesById = orderSearchesCommand.ExtensionSearchTuplesByExtensionId;
124 124
125 // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). 125 // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules).
126 { 126 {
@@ -387,6 +387,17 @@ namespace WixToolset.Core.Burn
387 387
388 var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; 388 var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow;
389 payloadTuples.Add(baManifestPayload.Id.Id, baManifestPayload); 389 payloadTuples.Add(baManifestPayload.Id.Id, baManifestPayload);
390 ++uxPayloadIndex;
391 }
392
393 // Generate the bundle extension manifest...
394 {
395 var command = new CreateBundleExtensionManifestCommand(section, bundleTuple, extensionSearchTuplesById, uxPayloadIndex, this.IntermediateFolder);
396 command.Execute();
397
398 var bextManifestPayload = command.BundleExtensionManifestPayloadRow;
399 payloadTuples.Add(bextManifestPayload.Id.Id, bextManifestPayload);
400 ++uxPayloadIndex;
390 } 401 }
391 402
392#if TODO 403#if TODO
@@ -464,28 +475,6 @@ namespace WixToolset.Core.Burn
464 trackedFiles.Add(trackIntermediate); 475 trackedFiles.Add(trackIntermediate);
465 } 476 }
466 477
467 private IEnumerable<SearchFacade> OrderSearches(IntermediateSection section)
468 {
469 var searchesById = section.Tuples
470 .Where(t => t.Definition.Type == TupleDefinitionType.WixComponentSearch ||
471 t.Definition.Type == TupleDefinitionType.WixFileSearch ||
472 t.Definition.Type == TupleDefinitionType.WixProductSearch ||
473 t.Definition.Type == TupleDefinitionType.WixRegistrySearch)
474 .ToDictionary(t => t.Id.Id);
475
476 var orderedSearches = new List<SearchFacade>(searchesById.Keys.Count);
477
478 foreach (var searchTuple in section.Tuples.OfType<WixSearchTuple>())
479 {
480 if (searchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple))
481 {
482 orderedSearches.Add(new SearchFacade(searchTuple, specificSearchTuple));
483 }
484 }
485
486 return orderedSearches;
487 }
488
489 /// <summary> 478 /// <summary>
490 /// Populates the variable cache with specific package properties. 479 /// Populates the variable cache with specific package properties.
491 /// </summary> 480 /// </summary>
diff --git a/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs
new file mode 100644
index 00000000..6a830a28
--- /dev/null
+++ b/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs
@@ -0,0 +1,24 @@
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.Burn
4{
5 using System.Xml;
6 using WixToolset.Data.Tuples;
7
8 internal class ExtensionSearchFacade : BaseSearchFacade
9 {
10 public ExtensionSearchFacade(WixSearchTuple searchTuple)
11 {
12 this.SearchTuple = searchTuple;
13 }
14
15 public override void WriteXml(XmlTextWriter writer)
16 {
17 writer.WriteStartElement("ExtensionSearch");
18
19 base.WriteXml(writer);
20
21 writer.WriteEndElement();
22 }
23 }
24}
diff --git a/src/WixToolset.Core.Burn/Bind/SearchFacade.cs b/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs
index 65f3cb5b..0a80760d 100644
--- a/src/WixToolset.Core.Burn/Bind/SearchFacade.cs
+++ b/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs
@@ -7,48 +7,36 @@ namespace WixToolset.Core.Burn
7 using WixToolset.Data; 7 using WixToolset.Data;
8 using WixToolset.Data.Tuples; 8 using WixToolset.Data.Tuples;
9 9
10 internal class SearchFacade 10 internal class LegacySearchFacade : BaseSearchFacade
11 { 11 {
12 public SearchFacade(WixSearchTuple searchTuple, IntermediateTuple searchSpecificTuple) 12 public LegacySearchFacade(WixSearchTuple searchTuple, IntermediateTuple searchSpecificTuple)
13 { 13 {
14 this.SearchTuple = searchTuple; 14 this.SearchTuple = searchTuple;
15 this.SearchSpecificTuple = searchSpecificTuple; 15 this.SearchSpecificTuple = searchSpecificTuple;
16 } 16 }
17 17
18 public WixSearchTuple SearchTuple { get; }
19
20 public IntermediateTuple SearchSpecificTuple { get; } 18 public IntermediateTuple SearchSpecificTuple { get; }
21 19
22 /// <summary> 20 /// <summary>
23 /// Generates Burn manifest and ParameterInfo-style markup a search. 21 /// Generates Burn manifest and ParameterInfo-style markup a search.
24 /// </summary> 22 /// </summary>
25 /// <param name="writer"></param> 23 /// <param name="writer"></param>
26 public void WriteXml(XmlTextWriter writer) 24 public override void WriteXml(XmlTextWriter writer)
27 { 25 {
28 switch (this.SearchSpecificTuple) 26 switch (this.SearchSpecificTuple)
29 { 27 {
30 case WixComponentSearchTuple tuple: 28 case WixComponentSearchTuple tuple:
31 this.WriteComponentSearchXml(writer, tuple); 29 this.WriteComponentSearchXml(writer, tuple);
32 break; 30 break;
33 case WixFileSearchTuple tuple: 31 case WixFileSearchTuple tuple:
34 this.WriteFileSearchXml(writer, tuple); 32 this.WriteFileSearchXml(writer, tuple);
35 break; 33 break;
36 case WixProductSearchTuple tuple: 34 case WixProductSearchTuple tuple:
37 this.WriteProductSearchXml(writer, tuple); 35 this.WriteProductSearchXml(writer, tuple);
38 break; 36 break;
39 case WixRegistrySearchTuple tuple: 37 case WixRegistrySearchTuple tuple:
40 this.WriteRegistrySearchXml(writer, tuple); 38 this.WriteRegistrySearchXml(writer, tuple);
41 break; 39 break;
42 }
43 }
44
45 private void WriteCommonAttributes(XmlTextWriter writer)
46 {
47 writer.WriteAttributeString("Id", this.SearchTuple.Id.Id);
48 writer.WriteAttributeString("Variable", this.SearchTuple.Variable);
49 if (!String.IsNullOrEmpty(this.SearchTuple.Condition))
50 {
51 writer.WriteAttributeString("Condition", this.SearchTuple.Condition);
52 } 40 }
53 } 41 }
54 42
@@ -56,7 +44,7 @@ namespace WixToolset.Core.Burn
56 { 44 {
57 writer.WriteStartElement("MsiComponentSearch"); 45 writer.WriteStartElement("MsiComponentSearch");
58 46
59 this.WriteCommonAttributes(writer); 47 base.WriteXml(writer);
60 48
61 writer.WriteAttributeString("ComponentId", searchTuple.Guid); 49 writer.WriteAttributeString("ComponentId", searchTuple.Guid);
62 50
@@ -85,7 +73,7 @@ namespace WixToolset.Core.Burn
85 { 73 {
86 writer.WriteStartElement((0 == (searchTuple.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); 74 writer.WriteStartElement((0 == (searchTuple.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch");
87 75
88 this.WriteCommonAttributes(writer); 76 base.WriteXml(writer);
89 77
90 writer.WriteAttributeString("Path", searchTuple.Path); 78 writer.WriteAttributeString("Path", searchTuple.Path);
91 if (WixFileSearchAttributes.WantExists == (searchTuple.Attributes & WixFileSearchAttributes.WantExists)) 79 if (WixFileSearchAttributes.WantExists == (searchTuple.Attributes & WixFileSearchAttributes.WantExists))
@@ -108,7 +96,7 @@ namespace WixToolset.Core.Burn
108 { 96 {
109 writer.WriteStartElement("MsiProductSearch"); 97 writer.WriteStartElement("MsiProductSearch");
110 98
111 this.WriteCommonAttributes(writer); 99 base.WriteXml(writer);
112 100
113 if (0 != (tuple.Attributes & WixProductSearchAttributes.UpgradeCode)) 101 if (0 != (tuple.Attributes & WixProductSearchAttributes.UpgradeCode))
114 { 102 {
@@ -143,22 +131,22 @@ namespace WixToolset.Core.Burn
143 { 131 {
144 writer.WriteStartElement("RegistrySearch"); 132 writer.WriteStartElement("RegistrySearch");
145 133
146 this.WriteCommonAttributes(writer); 134 base.WriteXml(writer);
147 135
148 switch (tuple.Root) 136 switch (tuple.Root)
149 { 137 {
150 case RegistryRootType.ClassesRoot: 138 case RegistryRootType.ClassesRoot:
151 writer.WriteAttributeString("Root", "HKCR"); 139 writer.WriteAttributeString("Root", "HKCR");
152 break; 140 break;
153 case RegistryRootType.CurrentUser: 141 case RegistryRootType.CurrentUser:
154 writer.WriteAttributeString("Root", "HKCU"); 142 writer.WriteAttributeString("Root", "HKCU");
155 break; 143 break;
156 case RegistryRootType.LocalMachine: 144 case RegistryRootType.LocalMachine:
157 writer.WriteAttributeString("Root", "HKLM"); 145 writer.WriteAttributeString("Root", "HKLM");
158 break; 146 break;
159 case RegistryRootType.Users: 147 case RegistryRootType.Users:
160 writer.WriteAttributeString("Root", "HKU"); 148 writer.WriteAttributeString("Root", "HKU");
161 break; 149 break;
162 } 150 }
163 151
164 writer.WriteAttributeString("Key", tuple.Key); 152 writer.WriteAttributeString("Key", tuple.Key);
diff --git a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs
index 78b95bf4..5cff0b5a 100644
--- a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs
+++ b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs
@@ -22,6 +22,12 @@ namespace WixToolset.Core.Burn.Bundles
22 public const string BurnUXContainerPayloadIdFormat = "p{0}"; 22 public const string BurnUXContainerPayloadIdFormat = "p{0}";
23 public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}"; 23 public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}";
24 24
25 public const string BADataFileName = "BootstrapperApplicationData.xml";
26 public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData";
27
28 public const string BundleExtensionDataFileName = "BundleExtensionData.xml";
29 public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData";
30
25 // See WinNT.h for details about the PE format, including the 31 // See WinNT.h for details about the PE format, including the
26 // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, 32 // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32,
27 // IMAGE_FILE_HEADER, etc. 33 // IMAGE_FILE_HEADER, etc.
@@ -167,7 +173,7 @@ namespace WixToolset.Core.Burn.Bundles
167 /// <returns>True if initialized.</returns> 173 /// <returns>True if initialized.</returns>
168 protected bool Initialize(BinaryReader reader) 174 protected bool Initialize(BinaryReader reader)
169 { 175 {
170 if (!GetWixburnSectionInfo(reader)) 176 if (!this.GetWixburnSectionInfo(reader))
171 { 177 {
172 return false; 178 return false;
173 } 179 }
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
index 5cd1f7e8..be8227f2 100644
--- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
+++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
@@ -57,7 +57,7 @@ namespace WixToolset.Core.Burn.Bundles
57 { 57 {
58 writer.Formatting = Formatting.Indented; 58 writer.Formatting = Formatting.Indented;
59 writer.WriteStartDocument(); 59 writer.WriteStartDocument();
60 writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"); 60 writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace);
61 61
62 this.WriteBundleInfo(writer); 62 this.WriteBundleInfo(writer);
63 63
@@ -247,11 +247,11 @@ namespace WixToolset.Core.Burn.Bundles
247 247
248 private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) 248 private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath)
249 { 249 {
250 var generatedId = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); 250 var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BADataFileName);
251 251
252 var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) 252 var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId))
253 { 253 {
254 Name = "BootstrapperApplicationData.xml", 254 Name = BurnCommon.BADataFileName,
255 SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, 255 SourceFile = new IntermediateFieldPathValue { Path = baManifestPath },
256 Compressed = true, 256 Compressed = true,
257 UnresolvedSourceFile = baManifestPath, 257 UnresolvedSourceFile = baManifestPath,
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs
new file mode 100644
index 00000000..b608c03d
--- /dev/null
+++ b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs
@@ -0,0 +1,149 @@
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.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using System.Text;
12 using System.Xml;
13 using WixToolset.Data;
14 using WixToolset.Data.Burn;
15 using WixToolset.Data.Tuples;
16
17 internal class CreateBundleExtensionManifestCommand
18 {
19 public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IDictionary<string, IList<IntermediateTuple>> extensionSearchTuplesByExtensionId, int lastUXPayloadIndex, string intermediateFolder)
20 {
21 this.Section = section;
22 this.BundleTuple = bundleTuple;
23 this.ExtensionSearchTuplesByExtensionId = extensionSearchTuplesByExtensionId;
24 this.LastUXPayloadIndex = lastUXPayloadIndex;
25 this.IntermediateFolder = intermediateFolder;
26 }
27
28 private IntermediateSection Section { get; }
29
30 private WixBundleTuple BundleTuple { get; }
31
32 private IDictionary<string, IList<IntermediateTuple>> ExtensionSearchTuplesByExtensionId { get; }
33
34 private int LastUXPayloadIndex { get; }
35
36 private string IntermediateFolder { get; }
37
38 public WixBundlePayloadTuple BundleExtensionManifestPayloadRow { get; private set; }
39
40 public void Execute()
41 {
42 var bextManifestPath = this.CreateBundleExtensionManifest();
43
44 this.BundleExtensionManifestPayloadRow = this.CreateBundleExtensionManifestPayloadRow(bextManifestPath);
45 }
46
47 private string CreateBundleExtensionManifest()
48 {
49 var path = Path.Combine(this.IntermediateFolder, "wix-bextdata.xml");
50
51 Directory.CreateDirectory(Path.GetDirectoryName(path));
52
53 using (var writer = new XmlTextWriter(path, Encoding.Unicode))
54 {
55 writer.Formatting = Formatting.Indented;
56 writer.WriteStartDocument();
57 writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace);
58
59 foreach (var kvp in this.ExtensionSearchTuplesByExtensionId)
60 {
61 this.WriteExtension(writer, kvp.Key, kvp.Value);
62 }
63
64 writer.WriteEndElement();
65 writer.WriteEndDocument();
66 }
67
68 return path;
69 }
70
71 private void WriteExtension(XmlTextWriter writer, string extensionId, IEnumerable<IntermediateTuple> tuples)
72 {
73 writer.WriteStartElement("BundleExtension");
74
75 writer.WriteAttributeString("Id", extensionId);
76
77 this.WriteBundleExtensionDataTuples(writer, tuples);
78
79 writer.WriteEndElement();
80 }
81
82 private void WriteBundleExtensionDataTuples(XmlTextWriter writer, IEnumerable<IntermediateTuple> tuples)
83 {
84 var dataTuplesGroupedByDefinitionName = tuples.GroupBy(t => t.Definition);
85
86 foreach (var group in dataTuplesGroupedByDefinitionName)
87 {
88 var definition = group.Key;
89
90 // We simply assert that the table (and field) name is valid, because
91 // this is up to the extension developer to get right. An author will
92 // only affect the attribute value, and that will get properly escaped.
93#if DEBUG
94 Debug.Assert(Common.IsIdentifier(definition.Name));
95 foreach (var fieldDef in definition.FieldDefinitions)
96 {
97 Debug.Assert(Common.IsIdentifier(fieldDef.Name));
98 }
99#endif // DEBUG
100
101 foreach (var tuple in group)
102 {
103 writer.WriteStartElement(definition.Name);
104
105 if (tuple.Id != null)
106 {
107 writer.WriteAttributeString("Id", tuple.Id.Id);
108 }
109
110 foreach (var field in tuple.Fields)
111 {
112 if (!field.IsNull())
113 {
114 writer.WriteAttributeString(field.Definition.Name, field.AsString());
115 }
116 }
117
118 writer.WriteEndElement();
119 }
120 }
121 }
122
123 private WixBundlePayloadTuple CreateBundleExtensionManifestPayloadRow(string bextManifestPath)
124 {
125 var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName);
126
127 var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId))
128 {
129 Name = BurnCommon.BundleExtensionDataFileName,
130 SourceFile = new IntermediateFieldPathValue { Path = bextManifestPath },
131 Compressed = true,
132 UnresolvedSourceFile = bextManifestPath,
133 ContainerRef = BurnConstants.BurnUXContainerName,
134 EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex),
135 Packaging = PackagingType.Embedded,
136 };
137
138 var fileInfo = new FileInfo(bextManifestPath);
139
140 tuple.FileSize = (int)fileInfo.Length;
141
142 tuple.Hash = BundleHashAlgorithm.Hash(fileInfo);
143
144 this.Section.Tuples.Add(tuple);
145
146 return tuple;
147 }
148 }
149}
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
index 64a01794..58133d38 100644
--- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
+++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
@@ -18,7 +18,7 @@ namespace WixToolset.Core.Burn.Bundles
18 18
19 internal class CreateBurnManifestCommand 19 internal class CreateBurnManifestCommand
20 { 20 {
21 public CreateBurnManifestCommand(IMessaging messaging, IEnumerable<IBurnBackendExtension> backendExtensions, string executableName, IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<WixBundleContainerTuple> containers, WixChainTuple chainTuple, IEnumerable<PackageFacade> orderedPackages, IEnumerable<WixBundleRollbackBoundaryTuple> boundaries, IEnumerable<WixBundlePayloadTuple> uxPayloads, Dictionary<string, WixBundlePayloadTuple> allPayloadsById, IEnumerable<SearchFacade> orderedSearches, IEnumerable<WixBundleCatalogTuple> catalogs, string intermediateFolder) 21 public CreateBurnManifestCommand(IMessaging messaging, IEnumerable<IBurnBackendExtension> backendExtensions, string executableName, IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<WixBundleContainerTuple> containers, WixChainTuple chainTuple, IEnumerable<PackageFacade> orderedPackages, IEnumerable<WixBundleRollbackBoundaryTuple> boundaries, IEnumerable<WixBundlePayloadTuple> uxPayloads, Dictionary<string, WixBundlePayloadTuple> allPayloadsById, IEnumerable<ISearchFacade> orderedSearches, IEnumerable<WixBundleCatalogTuple> catalogs, string intermediateFolder)
22 { 22 {
23 this.Messaging = messaging; 23 this.Messaging = messaging;
24 this.BackendExtensions = backendExtensions; 24 this.BackendExtensions = backendExtensions;
@@ -54,7 +54,7 @@ namespace WixToolset.Core.Burn.Bundles
54 54
55 private IEnumerable<PackageFacade> OrderedPackages { get; } 55 private IEnumerable<PackageFacade> OrderedPackages { get; }
56 56
57 private IEnumerable<SearchFacade> OrderedSearches { get; } 57 private IEnumerable<ISearchFacade> OrderedSearches { get; }
58 58
59 private Dictionary<string, WixBundlePayloadTuple> Payloads { get; } 59 private Dictionary<string, WixBundlePayloadTuple> Payloads { get; }
60 60
diff --git a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs
new file mode 100644
index 00000000..55b31ed3
--- /dev/null
+++ b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs
@@ -0,0 +1,71 @@
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.Burn.Bundles
4{
5 using System.Collections.Generic;
6 using System.Linq;
7 using WixToolset.Data;
8 using WixToolset.Data.Burn;
9 using WixToolset.Data.Tuples;
10 using WixToolset.Extensibility.Services;
11
12 internal class OrderSearchesCommand
13 {
14 public OrderSearchesCommand(IMessaging messaging, IntermediateSection section)
15 {
16 this.Messaging = messaging;
17 this.Section = section;
18 }
19
20 private IMessaging Messaging { get; }
21
22 private IntermediateSection Section { get; }
23
24 public IDictionary<string, IList<IntermediateTuple>> ExtensionSearchTuplesByExtensionId { get; private set; }
25
26 public IList<ISearchFacade> OrderedSearchFacades { get; private set; }
27
28 public void Execute()
29 {
30 // TODO: Although the WixSearch tables are defined in the Util extension,
31 // the Bundle Binder has to know all about them. We hope to revisit all
32 // of this in the 4.0 timeframe.
33 var legacySearchesById = this.Section.Tuples
34 .Where(t => t.Definition.Type == TupleDefinitionType.WixComponentSearch ||
35 t.Definition.Type == TupleDefinitionType.WixFileSearch ||
36 t.Definition.Type == TupleDefinitionType.WixProductSearch ||
37 t.Definition.Type == TupleDefinitionType.WixRegistrySearch)
38 .ToDictionary(t => t.Id.Id);
39 var extensionSearchesById = this.Section.Tuples
40 .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchTupleDefinitionTag))
41 .ToDictionary(t => t.Id.Id);
42 var searchTuples = this.Section.Tuples.OfType<WixSearchTuple>().ToList();
43
44 this.ExtensionSearchTuplesByExtensionId = new Dictionary<string, IList<IntermediateTuple>>();
45 this.OrderedSearchFacades = new List<ISearchFacade>(legacySearchesById.Keys.Count + extensionSearchesById.Keys.Count);
46
47 foreach (var searchTuple in searchTuples)
48 {
49 if (legacySearchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple))
50 {
51 this.OrderedSearchFacades.Add(new LegacySearchFacade(searchTuple, specificSearchTuple));
52 }
53 else if (extensionSearchesById.TryGetValue(searchTuple.Id.Id, out var extensionSearchTuple))
54 {
55 this.OrderedSearchFacades.Add(new ExtensionSearchFacade(searchTuple));
56
57 if (!this.ExtensionSearchTuplesByExtensionId.TryGetValue(searchTuple.BundleExtensionRef, out var extensionSearchTuples))
58 {
59 extensionSearchTuples = new List<IntermediateTuple>();
60 this.ExtensionSearchTuplesByExtensionId[searchTuple.BundleExtensionRef] = extensionSearchTuples;
61 }
62 extensionSearchTuples.Add(extensionSearchTuple);
63 }
64 else
65 {
66 this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchTuple.SourceLineNumbers, searchTuple.Id.Id));
67 }
68 }
69 }
70 }
71}
diff --git a/src/WixToolset.Core.Burn/ISearchFacade.cs b/src/WixToolset.Core.Burn/ISearchFacade.cs
new file mode 100644
index 00000000..b9ad8649
--- /dev/null
+++ b/src/WixToolset.Core.Burn/ISearchFacade.cs
@@ -0,0 +1,15 @@
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.Burn
4{
5 using System.Xml;
6
7 internal interface ISearchFacade
8 {
9 /// <summary>
10 /// Writes the search to the Burn manifest.
11 /// </summary>
12 /// <param name="writer"></param>
13 void WriteXml(XmlTextWriter writer);
14 }
15}
diff --git a/src/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/WixToolset.Core.TestPackage/BundleExtractor.cs
index 3d7b2932..8a56f117 100644
--- a/src/WixToolset.Core.TestPackage/BundleExtractor.cs
+++ b/src/WixToolset.Core.TestPackage/BundleExtractor.cs
@@ -22,11 +22,31 @@ namespace WixToolset.Core.TestPackage
22 { 22 {
23 result.ManifestDocument = LoadBurnManifest(destinationFolderPath); 23 result.ManifestDocument = LoadBurnManifest(destinationFolderPath);
24 result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); 24 result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn");
25
26 result.BADataDocument = LoadBAData(destinationFolderPath);
27 result.BADataNamespaceManager = GetBADataNamespaceManager(result.BADataDocument, "ba");
28
29 result.BundleExtensionDataDocument = LoadBundleExtensionData(destinationFolderPath);
30 result.BundleExtensionDataNamespaceManager = GetBundleExtensionDataNamespaceManager(result.BundleExtensionDataDocument, "be");
25 } 31 }
26 32
27 return result; 33 return result;
28 } 34 }
29 35
36 public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix)
37 {
38 var namespaceManager = new XmlNamespaceManager(document.NameTable);
39 namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace);
40 return namespaceManager;
41 }
42
43 public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix)
44 {
45 var namespaceManager = new XmlNamespaceManager(document.NameTable);
46 namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace);
47 return namespaceManager;
48 }
49
30 public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) 50 public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix)
31 { 51 {
32 var namespaceManager = new XmlNamespaceManager(document.NameTable); 52 var namespaceManager = new XmlNamespaceManager(document.NameTable);
@@ -34,6 +54,20 @@ namespace WixToolset.Core.TestPackage
34 return namespaceManager; 54 return namespaceManager;
35 } 55 }
36 56
57 public static XmlDocument LoadBAData(string baFolderPath)
58 {
59 var document = new XmlDocument();
60 document.Load(Path.Combine(baFolderPath, BurnCommon.BADataFileName));
61 return document;
62 }
63
64 public static XmlDocument LoadBundleExtensionData(string baFolderPath)
65 {
66 var document = new XmlDocument();
67 document.Load(Path.Combine(baFolderPath, BurnCommon.BundleExtensionDataFileName));
68 return document;
69 }
70
37 public static XmlDocument LoadBurnManifest(string baFolderPath) 71 public static XmlDocument LoadBurnManifest(string baFolderPath)
38 { 72 {
39 var document = new XmlDocument(); 73 var document = new XmlDocument();
diff --git a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs
index 6d2ea943..63d7bb31 100644
--- a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs
+++ b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs
@@ -8,6 +8,10 @@ namespace WixToolset.Core.TestPackage
8 8
9 public class ExtractBAContainerResult 9 public class ExtractBAContainerResult
10 { 10 {
11 public XmlDocument BundleExtensionDataDocument { get; set; }
12 public XmlNamespaceManager BundleExtensionDataNamespaceManager { get; set; }
13 public XmlDocument BADataDocument { get; set; }
14 public XmlNamespaceManager BADataNamespaceManager { get; set; }
11 public XmlDocument ManifestDocument { get; set; } 15 public XmlDocument ManifestDocument { get; set; }
12 public XmlNamespaceManager ManifestNamespaceManager { get; set; } 16 public XmlNamespaceManager ManifestNamespaceManager { get; set; }
13 public bool Success { get; set; } 17 public bool Success { get; set; }
@@ -26,6 +30,34 @@ namespace WixToolset.Core.TestPackage
26 return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); 30 return Path.Combine(extractedBAContainerFolderPath, relativeBAPath);
27 } 31 }
28 32
33 public string GetBundleExtensionFilePath(string extractedBAContainerFolderPath, string extensionId)
34 {
35 var uxPayloads = this.SelectManifestNodes($"/burn:BurnManifest/burn:UX/burn:Payload[@Id='{extensionId}']");
36 var bextPayload = uxPayloads[0];
37 var relativeBextPath = bextPayload.Attributes["FilePath"].Value;
38 return Path.Combine(extractedBAContainerFolderPath, relativeBextPath);
39 }
40
41 /// <summary>
42 ///
43 /// </summary>
44 /// <param name="xpath">elements must have the 'ba' prefix</param>
45 /// <returns></returns>
46 public XmlNodeList SelectBADataNodes(string xpath)
47 {
48 return this.BADataDocument.SelectNodes(xpath, this.BADataNamespaceManager);
49 }
50
51 /// <summary>
52 ///
53 /// </summary>
54 /// <param name="xpath">elements must have the 'be' prefix</param>
55 /// <returns></returns>
56 public XmlNodeList SelectBundleExtensionDataNodes(string xpath)
57 {
58 return this.BundleExtensionDataDocument.SelectNodes(xpath, this.BundleExtensionDataNamespaceManager);
59 }
60
29 /// <summary> 61 /// <summary>
30 /// 62 ///
31 /// </summary> 63 /// </summary>
diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs
index e87ad886..51828975 100644
--- a/src/WixToolset.Core/CompilerCore.cs
+++ b/src/WixToolset.Core/CompilerCore.cs
@@ -1026,6 +1026,11 @@ namespace WixToolset.Core
1026 return this.parseHelper.CreateDirectoryTuple(this.ActiveSection, sourceLineNumbers, id, parentId, name, this.activeSectionInlinedDirectoryIds, shortName, sourceName, shortSourceName); 1026 return this.parseHelper.CreateDirectoryTuple(this.ActiveSection, sourceLineNumbers, id, parentId, name, this.activeSectionInlinedDirectoryIds, shortName, sourceName, shortSourceName);
1027 } 1027 }
1028 1028
1029 public void CreateWixSearchTuple(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after)
1030 {
1031 this.parseHelper.CreateWixSearchTuple(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null);
1032 }
1033
1029 /// <summary> 1034 /// <summary>
1030 /// Gets the attribute value as inline directory syntax. 1035 /// Gets the attribute value as inline directory syntax.
1031 /// </summary> 1036 /// </summary>
diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
index 7447d420..2a851a21 100644
--- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
+++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs
@@ -260,6 +260,43 @@ namespace WixToolset.Core.ExtensibilityServices
260 section.Tuples.Add(tuple); 260 section.Tuples.Add(tuple);
261 } 261 }
262 262
263 public void CreateWixSearchTuple(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId)
264 {
265 // TODO: verify variable is not a standard bundle variable
266 if (variable == null)
267 {
268 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable"));
269 }
270
271 section.Tuples.Add(new WixSearchTuple(sourceLineNumbers, id)
272 {
273 Variable = variable,
274 Condition = condition,
275 BundleExtensionRef = bundleExtensionId,
276 });
277
278 if (after != null)
279 {
280 this.CreateSimpleReference(section, sourceLineNumbers, "WixSearch", after);
281 // TODO: We're currently defaulting to "always run after", which we will need to change...
282 this.CreateWixSearchRelationTuple(section, sourceLineNumbers, id, after, 2);
283 }
284
285 if (!String.IsNullOrEmpty(bundleExtensionId))
286 {
287 this.CreateSimpleReference(section, sourceLineNumbers, "WixBundleExtension", bundleExtensionId);
288 }
289 }
290
291 public void CreateWixSearchRelationTuple(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, int attributes)
292 {
293 section.Tuples.Add(new WixSearchRelationTuple(sourceLineNumbers, id)
294 {
295 ParentSearchRef = parentId,
296 Attributes = attributes,
297 });
298 }
299
263 [Obsolete] 300 [Obsolete]
264 public IntermediateTuple CreateRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName, Identifier identifier = null) 301 public IntermediateTuple CreateRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName, Identifier identifier = null)
265 { 302 {
diff --git a/src/test/Example.Extension/Data/example.wxs b/src/test/Example.Extension/Data/example.wxs
index cb100adf..cd17d478 100644
--- a/src/test/Example.Extension/Data/example.wxs
+++ b/src/test/Example.Extension/Data/example.wxs
@@ -8,4 +8,7 @@
8 <Fragment> 8 <Fragment>
9 <BootstrapperApplication Id="fakeba" SourceFile="example.txt" /> 9 <BootstrapperApplication Id="fakeba" SourceFile="example.txt" />
10 </Fragment> 10 </Fragment>
11 <Fragment>
12 <BundleExtension Id="ExampleBundleExtension" SourceFile="example.txt" />
13 </Fragment>
11</Wix> 14</Wix>
diff --git a/src/test/Example.Extension/ExampleCompilerExtension.cs b/src/test/Example.Extension/ExampleCompilerExtension.cs
index 5efb428f..543b4165 100644
--- a/src/test/Example.Extension/ExampleCompilerExtension.cs
+++ b/src/test/Example.Extension/ExampleCompilerExtension.cs
@@ -11,6 +11,7 @@ namespace Example.Extension
11 internal class ExampleCompilerExtension : BaseCompilerExtension 11 internal class ExampleCompilerExtension : BaseCompilerExtension
12 { 12 {
13 public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs"; 13 public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs";
14 public string BundleExtensionId => "ExampleBundleExtension";
14 15
15 public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context) 16 public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context)
16 { 17 {
@@ -18,6 +19,20 @@ namespace Example.Extension
18 19
19 switch (parentElement.Name.LocalName) 20 switch (parentElement.Name.LocalName)
20 { 21 {
22 case "Bundle":
23 case "Fragment":
24 switch (element.Name.LocalName)
25 {
26 case "ExampleSearch":
27 this.ParseExampleSearchElement(intermediate, section, element);
28 processed = true;
29 break;
30 case "ExampleSearchRef":
31 this.ParseExampleSearchRefElement(intermediate, section, element);
32 processed = true;
33 break;
34 }
35 break;
21 case "Component": 36 case "Component":
22 switch (element.Name.LocalName) 37 switch (element.Name.LocalName)
23 { 38 {
@@ -77,5 +92,94 @@ namespace Example.Extension
77 tuple.Set(1, value); 92 tuple.Set(1, value);
78 } 93 }
79 } 94 }
95
96 private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element)
97 {
98 var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
99 Identifier id = null;
100 string searchFor = null;
101 string variable = null;
102 string condition = null;
103 string after = null;
104
105 foreach (var attrib in element.Attributes())
106 {
107 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
108 {
109 switch (attrib.Name.LocalName)
110 {
111 case "Id":
112 id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib);
113 break;
114 case "Variable":
115 variable = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
116 break;
117 case "Condition":
118 condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
119 break;
120 case "After":
121 after = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
122 break;
123 case "SearchFor":
124 searchFor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
125 break;
126
127 default:
128 this.ParseHelper.UnexpectedAttribute(element, attrib);
129 break;
130 }
131 }
132 else
133 {
134 this.ParseAttribute(intermediate, section, element, attrib, null);
135 }
136 }
137
138 if (null == id)
139 {
140 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id"));
141 }
142
143 if (!this.Messaging.EncounteredError)
144 {
145 this.ParseHelper.CreateWixSearchTuple(section, sourceLineNumbers, element.Name.LocalName, id, variable, condition, after, this.BundleExtensionId);
146 }
147
148 if (!this.Messaging.EncounteredError)
149 {
150
151 var tuple = new ExampleSearchTuple(sourceLineNumbers, id);
152 section.Tuples.Add(tuple);
153 tuple.SearchFor = searchFor;
154 }
155 }
156
157 private void ParseExampleSearchRefElement(Intermediate intermediate, IntermediateSection section, XElement element)
158 {
159 var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
160
161 foreach (var attrib in element.Attributes())
162 {
163 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
164 {
165 switch (attrib.Name.LocalName)
166 {
167 case "Id":
168 var refId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
169 this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "ExampleSearch", refId);
170 break;
171 default:
172 this.ParseHelper.UnexpectedAttribute(element, attrib);
173 break;
174 }
175 }
176 else
177 {
178 this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib);
179 }
180 }
181
182 this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element);
183 }
80 } 184 }
81} 185}
diff --git a/src/test/Example.Extension/ExampleExtensionData.cs b/src/test/Example.Extension/ExampleExtensionData.cs
index de0b8899..b38eb2a2 100644
--- a/src/test/Example.Extension/ExampleExtensionData.cs
+++ b/src/test/Example.Extension/ExampleExtensionData.cs
@@ -1,4 +1,4 @@
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. 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 2
3namespace Example.Extension 3namespace Example.Extension
4{ 4{
@@ -22,6 +22,10 @@ namespace Example.Extension
22 tupleDefinition = ExampleTupleDefinitions.Example; 22 tupleDefinition = ExampleTupleDefinitions.Example;
23 break; 23 break;
24 24
25 case "ExampleSearch":
26 tupleDefinition = ExampleTupleDefinitions.ExampleSearch;
27 break;
28
25 default: 29 default:
26 tupleDefinition = null; 30 tupleDefinition = null;
27 break; 31 break;
diff --git a/src/test/Example.Extension/ExampleSearchTuple.cs b/src/test/Example.Extension/ExampleSearchTuple.cs
new file mode 100644
index 00000000..df34f0af
--- /dev/null
+++ b/src/test/Example.Extension/ExampleSearchTuple.cs
@@ -0,0 +1,31 @@
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 Example.Extension
4{
5 using WixToolset.Data;
6
7 public enum ExampleSearchTupleFields
8 {
9 Example,
10 SearchFor,
11 }
12
13 public class ExampleSearchTuple : IntermediateTuple
14 {
15 public ExampleSearchTuple() : base(ExampleTupleDefinitions.ExampleSearch, null, null)
16 {
17 }
18
19 public ExampleSearchTuple(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleTupleDefinitions.ExampleSearch, sourceLineNumber, id)
20 {
21 }
22
23 public IntermediateField this[ExampleTupleFields index] => this.Fields[(int)index];
24
25 public string SearchFor
26 {
27 get => this.Fields[(int)ExampleSearchTupleFields.SearchFor]?.AsString();
28 set => this.Set((int)ExampleSearchTupleFields.SearchFor, value);
29 }
30 }
31}
diff --git a/src/test/Example.Extension/ExampleTupleDefinitions.cs b/src/test/Example.Extension/ExampleTupleDefinitions.cs
index 4775b827..b2c8c484 100644
--- a/src/test/Example.Extension/ExampleTupleDefinitions.cs
+++ b/src/test/Example.Extension/ExampleTupleDefinitions.cs
@@ -1,8 +1,9 @@
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. 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 2
3namespace Example.Extension 3namespace Example.Extension
4{ 4{
5 using WixToolset.Data; 5 using WixToolset.Data;
6 using WixToolset.Data.Burn;
6 7
7 public static class ExampleTupleDefinitions 8 public static class ExampleTupleDefinitions
8 { 9 {
@@ -16,5 +17,19 @@ namespace Example.Extension
16 new IntermediateFieldDefinition(nameof(ExampleTupleFields.Value), IntermediateFieldType.String), 17 new IntermediateFieldDefinition(nameof(ExampleTupleFields.Value), IntermediateFieldType.String),
17 }, 18 },
18 typeof(ExampleTuple)); 19 typeof(ExampleTuple));
20
21 public static readonly IntermediateTupleDefinition ExampleSearch = new IntermediateTupleDefinition(
22 nameof(ExampleSearch),
23 new[]
24 {
25 new IntermediateFieldDefinition(nameof(ExampleTupleFields.Example), IntermediateFieldType.String),
26 new IntermediateFieldDefinition(nameof(ExampleSearchTupleFields.SearchFor), IntermediateFieldType.String),
27 },
28 typeof(ExampleSearchTuple));
29
30 static ExampleTupleDefinitions()
31 {
32 ExampleSearch.AddTag(BurnConstants.BundleExtensionSearchTupleDefinitionTag);
33 }
19 } 34 }
20} 35}
diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
index da4482ff..80f7b875 100644
--- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
@@ -2,8 +2,10 @@
2 2
3namespace WixToolsetTest.CoreIntegration 3namespace WixToolsetTest.CoreIntegration
4{ 4{
5 using System;
5 using System.Collections.Generic; 6 using System.Collections.Generic;
6 using System.IO; 7 using System.IO;
8 using Example.Extension;
7 using WixBuildTools.TestSupport; 9 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage; 10 using WixToolset.Core.TestPackage;
9 using Xunit; 11 using Xunit;
@@ -57,5 +59,59 @@ namespace WixToolsetTest.CoreIntegration
57 Assert.Equal("<Payload Id='ExampleBext' FilePath='fakebext.dll' FileSize='*' Hash='*' Packaging='embedded' SourcePath='*' />", bundleExtensionPayloads[0].GetTestXml(ignored)); 59 Assert.Equal("<Payload Id='ExampleBext' FilePath='fakebext.dll' FileSize='*' Hash='*' Packaging='embedded' SourcePath='*' />", bundleExtensionPayloads[0].GetTestXml(ignored));
58 } 60 }
59 } 61 }
62
63 [Fact]
64 public void PopulatesManifestWithBundleExtensionSearches()
65 {
66 var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe");
67 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
68 var folder = TestData.Get(@"TestData");
69
70 using (var fs = new DisposableFileSystem())
71 {
72 var baseFolder = fs.GetFolder();
73 var intermediateFolder = Path.Combine(baseFolder, "obj");
74 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
75 var baFolderPath = Path.Combine(baseFolder, "ba");
76 var extractFolderPath = Path.Combine(baseFolder, "extract");
77
78 var result = WixRunner.Execute(new[]
79 {
80 "build",
81 Path.Combine(folder, "BundleExtension", "BundleExtensionSearches.wxs"),
82 Path.Combine(folder, "BundleExtension", "BundleWithSearches.wxs"),
83 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
84 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
85 "-ext", extensionPath,
86 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
87 "-intermediateFolder", intermediateFolder,
88 "-burnStub", burnStubPath,
89 "-o", bundlePath
90 });
91
92 result.AssertSuccess();
93
94 Assert.True(File.Exists(bundlePath));
95
96 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
97 extractResult.AssertSuccess();
98
99 var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension");
100 Assert.Equal(1, bundleExtensions.Count);
101 Assert.Equal("<BundleExtension Id='ExampleBundleExtension' EntryPayloadId='ExampleBundleExtension' />", bundleExtensions[0].GetTestXml());
102
103 var extensionSearches = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:ExtensionSearch");
104 Assert.Equal(2, extensionSearches.Count);
105 Assert.Equal("<ExtensionSearch Id='ExampleSearchBar' Variable='SearchBar' Condition='WixBundleInstalled' ExtensionId='ExampleBundleExtension' />", extensionSearches[0].GetTestXml());
106 Assert.Equal("<ExtensionSearch Id='ExampleSearchFoo' Variable='SearchFoo' ExtensionId='ExampleBundleExtension' />", extensionSearches[1].GetTestXml());
107
108 var bundleExtensionDatas = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']");
109 Assert.Equal(1, bundleExtensionDatas.Count);
110 Assert.Equal("<BundleExtension Id='ExampleBundleExtension'>" +
111 "<ExampleSearch Id='ExampleSearchBar' SearchFor='Bar' />" +
112 "<ExampleSearch Id='ExampleSearchFoo' SearchFor='Foo' />" +
113 "</BundleExtension>", bundleExtensionDatas[0].GetTestXml());
114 }
115 }
60 } 116 }
61} 117}
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs
new file mode 100644
index 00000000..fd8d3698
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
3 xmlns:ex="http://www.example.com/scheams/v1/wxs">
4 <Fragment>
5 <ex:ExampleSearch Id="ExampleSearchBar" Variable="SearchBar" Condition="WixBundleInstalled" SearchFor="Bar" />
6 <ex:ExampleSearch Id="ExampleSearchFoo" Variable="SearchFoo" SearchFor="Foo" />
7 </Fragment>
8</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs
new file mode 100644
index 00000000..c5a93eb3
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
3 xmlns:ex="http://www.example.com/scheams/v1/wxs">
4 <Fragment>
5 <PackageGroup Id="BundlePackages">
6 <PackageGroupRef Id="MinimalPackageGroup" />
7 </PackageGroup>
8
9 <ex:ExampleSearchRef Id="ExampleSearchFoo" />
10 </Fragment>
11</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
index 85538b79..324d04ff 100644
--- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
+++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
@@ -22,6 +22,8 @@
22 <Content Include="TestData\AppSearch\NestedDirSearchUnderRegSearch.msi" CopyToOutputDirectory="PreserveNewest" /> 22 <Content Include="TestData\AppSearch\NestedDirSearchUnderRegSearch.msi" CopyToOutputDirectory="PreserveNewest" />
23 <Content Include="TestData\AppSearch\RegistrySearch.wxs" CopyToOutputDirectory="PreserveNewest" /> 23 <Content Include="TestData\AppSearch\RegistrySearch.wxs" CopyToOutputDirectory="PreserveNewest" />
24 <Content Include="TestData\BundleExtension\BundleExtension.wxs" CopyToOutputDirectory="PreserveNewest" /> 24 <Content Include="TestData\BundleExtension\BundleExtension.wxs" CopyToOutputDirectory="PreserveNewest" />
25 <Content Include="TestData\BundleExtension\BundleExtensionSearches.wxs" CopyToOutputDirectory="PreserveNewest" />
26 <Content Include="TestData\BundleExtension\BundleWithSearches.wxs" CopyToOutputDirectory="PreserveNewest" />
25 <Content Include="TestData\BundleExtension\SimpleBundleExtension.wxs" CopyToOutputDirectory="PreserveNewest" /> 27 <Content Include="TestData\BundleExtension\SimpleBundleExtension.wxs" CopyToOutputDirectory="PreserveNewest" />
26 <Content Include="TestData\BundleWithPackageGroupRef\Bundle.wxs" CopyToOutputDirectory="PreserveNewest" /> 28 <Content Include="TestData\BundleWithPackageGroupRef\Bundle.wxs" CopyToOutputDirectory="PreserveNewest" />
27 <Content Include="TestData\BundleWithPackageGroupRef\MinimalPackageGroup.wxs" CopyToOutputDirectory="PreserveNewest" /> 29 <Content Include="TestData\BundleWithPackageGroupRef\MinimalPackageGroup.wxs" CopyToOutputDirectory="PreserveNewest" />