@joebigelow / wix / commits / e22fd864

Implement IBurnBackendHelper and TryAddTupleToDataManifest.

Add GenerateManifestDataFromIRCommand, which allows the Burn backend to warn on unknown tuples.

Sean Hall committed Jun 19, 2020 at 13:28 UTC e22fd864bcb83d982441759719ba57fbc4391c95
8 files changed +404 -153
src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+11 -2
@@ -30,6 +30,7 @@ namespace WixToolset.Core.Burn
30 this.Messaging = context.ServiceProvider.GetService<IMessaging>();
31
32 this.BackendHelper = context.ServiceProvider.GetService<IBackendHelper>();
33 + this.InternalBurnBackendHelper = context.ServiceProvider.GetService<IInternalBurnBackendHelper>();
34
35 this.DefaultCompressionLevel = context.DefaultCompressionLevel;
36 this.DelayedFields = context.DelayedFields;
@@ -49,6 +50,8 @@ namespace WixToolset.Core.Burn
50
51 private IBackendHelper BackendHelper { get; }
52
53 + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; }
54 +
55 private CompressionLevel? DefaultCompressionLevel { get; }
56
57 public IEnumerable<IDelayedField> DelayedFields { get; }
@@ -386,6 +389,12 @@ namespace WixToolset.Core.Burn
389 // Update the bundle per-machine/per-user scope based on the chained packages.
390 this.ResolveBundleInstallScope(section, bundleTuple, orderedFacades);
391
392 + // Generate data for all manifests.
393 + {
394 + var command = new GenerateManifestDataFromIRCommand(this.Messaging, section, this.BackendExtensions, this.InternalBurnBackendHelper, extensionSearchTuplesById);
395 + command.Execute();
396 + }
397 +
398 // Give the extension one last hook before generating the output files.
399 foreach (var extension in this.BackendExtensions)
400 {
@@ -400,7 +409,7 @@ namespace WixToolset.Core.Burn
409 // Generate the core-defined BA manifest tables...
410 string baManifestPath;
411 {
403 - var command = new CreateBootstrapperApplicationManifestCommand(section, bundleTuple, orderedFacades, uxPayloadIndex, payloadTuples, this.IntermediateFolder);
412 + var command = new CreateBootstrapperApplicationManifestCommand(section, bundleTuple, orderedFacades, uxPayloadIndex, payloadTuples, this.IntermediateFolder, this.InternalBurnBackendHelper);
413 command.Execute();
414
415 var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow;
@@ -414,7 +423,7 @@ namespace WixToolset.Core.Burn
423 // Generate the bundle extension manifest...
424 string bextManifestPath;
425 {
417 - var command = new CreateBundleExtensionManifestCommand(section, bundleTuple, extensionSearchTuplesById, uxPayloadIndex, this.IntermediateFolder);
426 + var command = new CreateBundleExtensionManifestCommand(section, bundleTuple, uxPayloadIndex, this.IntermediateFolder, this.InternalBurnBackendHelper);
427 command.Execute();
428
429 var bextManifestPayload = command.BundleExtensionManifestPayloadRow;
src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs new
+198
@@ -0,0 +1,198 @@
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.Burn.Bind
4 +{
5 + using System.Collections.Generic;
6 + using System.Linq;
7 + using System.Text;
8 + using System.Xml;
9 + using WixToolset.Core.Burn.Bundles;
10 + using WixToolset.Core.Burn.ExtensibilityServices;
11 + using WixToolset.Data;
12 + using WixToolset.Data.Tuples;
13 + using WixToolset.Extensibility;
14 + using WixToolset.Extensibility.Services;
15 +
16 + internal class GenerateManifestDataFromIRCommand
17 + {
18 + public GenerateManifestDataFromIRCommand(IMessaging messaging, IntermediateSection section, IEnumerable<IBurnBackendExtension> backendExtensions, IBurnBackendHelper backendHelper, IDictionary<string, IList<IntermediateTuple>> extensionSearchTuplesById)
19 + {
20 + this.Messaging = messaging;
21 + this.Section = section;
22 + this.BackendExtensions = backendExtensions;
23 + this.BackendHelper = backendHelper;
24 + this.ExtensionSearchTuplesById = extensionSearchTuplesById;
25 + }
26 +
27 + private IEnumerable<IBurnBackendExtension> BackendExtensions { get; }
28 +
29 + private IBurnBackendHelper BackendHelper { get; }
30 +
31 + private IDictionary<string, IList<IntermediateTuple>> ExtensionSearchTuplesById { get; }
32 +
33 + private IMessaging Messaging { get; }
34 +
35 + private IntermediateSection Section { get; }
36 +
37 + public void Execute()
38 + {
39 + var tuples = this.Section.Tuples.ToList();
40 + var cellsByTableAndRowId = new Dictionary<string, List<WixCustomTableCellTuple>>();
41 + var customTablesById = new Dictionary<string, WixCustomTableTuple>();
42 +
43 + foreach (var kvp in this.ExtensionSearchTuplesById)
44 + {
45 + var extensionId = kvp.Key;
46 + var extensionSearchTuples = kvp.Value;
47 + foreach (var extensionSearchTuple in extensionSearchTuples)
48 + {
49 + this.BackendHelper.AddBundleExtensionData(extensionId, extensionSearchTuple, tupleIdIsIdAttribute: true);
50 + tuples.Remove(extensionSearchTuple);
51 + }
52 + }
53 +
54 + foreach (var tuple in tuples)
55 + {
56 + var unknownTuple = false;
57 + switch (tuple.Definition.Type)
58 + {
59 + // Tuples used internally and are not added to a data manifest.
60 + case TupleDefinitionType.ProvidesDependency:
61 + case TupleDefinitionType.WixApprovedExeForElevation:
62 + case TupleDefinitionType.WixBootstrapperApplication:
63 + case TupleDefinitionType.WixBundle:
64 + case TupleDefinitionType.WixBundleCatalog:
65 + case TupleDefinitionType.WixBundleContainer:
66 + case TupleDefinitionType.WixBundleExePackage:
67 + case TupleDefinitionType.WixBundleExtension:
68 + case TupleDefinitionType.WixBundleMsiFeature:
69 + case TupleDefinitionType.WixBundleMsiPackage:
70 + case TupleDefinitionType.WixBundleMsiProperty:
71 + case TupleDefinitionType.WixBundleMspPackage:
72 + case TupleDefinitionType.WixBundleMsuPackage:
73 + case TupleDefinitionType.WixBundlePackage:
74 + case TupleDefinitionType.WixBundlePackageCommandLine:
75 + case TupleDefinitionType.WixBundlePackageExitCode:
76 + case TupleDefinitionType.WixBundlePatchTargetCode:
77 + case TupleDefinitionType.WixBundlePayload:
78 + case TupleDefinitionType.WixBundleRelatedPackage:
79 + case TupleDefinitionType.WixBundleRollbackBoundary:
80 + case TupleDefinitionType.WixBundleSlipstreamMsp:
81 + case TupleDefinitionType.WixBundleUpdate:
82 + case TupleDefinitionType.WixBundleVariable:
83 + case TupleDefinitionType.WixChain:
84 + case TupleDefinitionType.WixComponentSearch:
85 + case TupleDefinitionType.WixCustomTableColumn:
86 + case TupleDefinitionType.WixDependencyProvider:
87 + case TupleDefinitionType.WixFileSearch:
88 + case TupleDefinitionType.WixGroup:
89 + case TupleDefinitionType.WixProductSearch:
90 + case TupleDefinitionType.WixRegistrySearch:
91 + case TupleDefinitionType.WixRelatedBundle:
92 + case TupleDefinitionType.WixSearch:
93 + case TupleDefinitionType.WixSearchRelation:
94 + case TupleDefinitionType.WixSetVariable:
95 + case TupleDefinitionType.WixUpdateRegistration:
96 + break;
97 +
98 + case TupleDefinitionType.WixCustomTable:
99 + unknownTuple = !this.IndexCustomTableTuple((WixCustomTableTuple)tuple, customTablesById);
100 + break;
101 +
102 + case TupleDefinitionType.WixCustomTableCell:
103 + this.IndexCustomTableCellTuple((WixCustomTableCellTuple)tuple, cellsByTableAndRowId);
104 + break;
105 +
106 + case TupleDefinitionType.MustBeFromAnExtension:
107 + unknownTuple = !this.AddTupleFromExtension(tuple);
108 + break;
109 +
110 + default:
111 + unknownTuple = true;
112 + break;
113 + }
114 +
115 + if (unknownTuple)
116 + {
117 + this.Messaging.Write(WarningMessages.TupleNotTranslatedToOutput(tuple));
118 + }
119 + }
120 +
121 + this.AddIndexedCellTuples(customTablesById, cellsByTableAndRowId);
122 + }
123 +
124 + private bool IndexCustomTableTuple(WixCustomTableTuple wixCustomTableTuple, Dictionary<string, WixCustomTableTuple> customTablesById)
125 + {
126 + if (!wixCustomTableTuple.Unreal)
127 + {
128 + return false;
129 + }
130 +
131 + var tableId = wixCustomTableTuple.Id.Id;
132 + customTablesById.Add(tableId, wixCustomTableTuple);
133 + return true;
134 + }
135 +
136 + private void IndexCustomTableCellTuple(WixCustomTableCellTuple wixCustomTableCellTuple, Dictionary<string, List<WixCustomTableCellTuple>> cellsByTableAndRowId)
137 + {
138 + var tableAndRowId = wixCustomTableCellTuple.TableRef + "/" + wixCustomTableCellTuple.RowId;
139 + if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells))
140 + {
141 + cells = new List<WixCustomTableCellTuple>();
142 + cellsByTableAndRowId.Add(tableAndRowId, cells);
143 + }
144 +
145 + cells.Add(wixCustomTableCellTuple);
146 + }
147 +
148 + private void AddIndexedCellTuples(Dictionary<string, WixCustomTableTuple> customTablesById, Dictionary<string, List<WixCustomTableCellTuple>> cellsByTableAndRowId)
149 + {
150 + var sb = new StringBuilder();
151 + using (var writer = XmlWriter.Create(sb, BurnBackendHelper.WriterSettings))
152 + {
153 + foreach (var rowOfCells in cellsByTableAndRowId.Values)
154 + {
155 + var tableId = rowOfCells[0].TableRef;
156 + var tableTuple = customTablesById[tableId];
157 +
158 + if (!tableTuple.Unreal)
159 + {
160 + return;
161 + }
162 +
163 + var columnNames = tableTuple.ColumnNamesSeparated;
164 +
165 + var rowDataByColumn = rowOfCells.ToDictionary(t => t.ColumnRef, t => t.Data);
166 +
167 + writer.WriteStartElement(tableId, BurnCommon.BADataNamespace);
168 +
169 + // Write all row data as attributes in table column order.
170 + foreach (var column in columnNames)
171 + {
172 + if (rowDataByColumn.TryGetValue(column, out var data))
173 + {
174 + writer.WriteAttributeString(column, data);
175 + }
176 + }
177 +
178 + writer.WriteEndElement();
179 + }
180 + }
181 +
182 + this.BackendHelper.AddBootstrapperApplicationData(sb.ToString());
183 + }
184 +
185 + private bool AddTupleFromExtension(IntermediateTuple tuple)
186 + {
187 + foreach (var extension in this.BackendExtensions)
188 + {
189 + if (extension.TryAddTupleToDataManifest(this.Section, tuple))
190 + {
191 + return true;
192 + }
193 + }
194 +
195 + return false;
196 + }
197 + }
198 +}
src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
+5 -89
@@ -4,7 +4,6 @@ namespace WixToolset.Core.Burn.Bundles
4 {
5 using System;
6 using System.Collections.Generic;
7 - using System.Diagnostics;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
@@ -16,9 +15,7 @@ namespace WixToolset.Core.Burn.Bundles
15
16 internal class CreateBootstrapperApplicationManifestCommand
17 {
19 - private static readonly char[] ColonCharacter = new[] { ':' };
20 -
21 - public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<PackageFacade> chainPackages, int lastUXPayloadIndex, Dictionary<string, WixBundlePayloadTuple> payloadTuples, string intermediateFolder)
18 + public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<PackageFacade> chainPackages, int lastUXPayloadIndex, Dictionary<string, WixBundlePayloadTuple> payloadTuples, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper)
19 {
20 this.Section = section;
21 this.BundleTuple = bundleTuple;
@@ -26,6 +23,7 @@ namespace WixToolset.Core.Burn.Bundles
23 this.LastUXPayloadIndex = lastUXPayloadIndex;
24 this.Payloads = payloadTuples;
25 this.IntermediateFolder = intermediateFolder;
26 + this.InternalBurnBackendHelper = internalBurnBackendHelper;
27 }
28
29 private IntermediateSection Section { get; }
@@ -34,6 +32,8 @@ namespace WixToolset.Core.Burn.Bundles
32
33 private IEnumerable<PackageFacade> ChainPackages { get; }
34
35 + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; }
36 +
37 private int LastUXPayloadIndex { get; }
38
39 private Dictionary<string, WixBundlePayloadTuple> Payloads { get; }
@@ -71,7 +71,7 @@ namespace WixToolset.Core.Burn.Bundles
71
72 this.WritePayloadInfo(writer);
73
74 - this.WriteCustomBootstrapperApplicationData(writer);
74 + this.InternalBurnBackendHelper.WriteBootstrapperApplicationData(writer);
75
76 writer.WriteEndElement();
77 writer.WriteEndDocument();
@@ -243,90 +243,6 @@ namespace WixToolset.Core.Burn.Bundles
243 }
244 }
245
246 - private void WriteCustomBootstrapperApplicationData(XmlTextWriter writer)
247 - {
248 - var dataTuplesGroupedByDefinitionName = this.Section.Tuples
249 - .Where(t => t.Definition.HasTag(BurnConstants.BootstrapperApplicationDataTupleDefinitionTag))
250 - .GroupBy(t => t.Definition);
251 -
252 - foreach (var group in dataTuplesGroupedByDefinitionName)
253 - {
254 - var definition = group.Key;
255 -
256 - // We simply assert that the table (and field) name is valid, because
257 - // this is up to the extension developer to get right. An author will
258 - // only affect the attribute value, and that will get properly escaped.
259 -#if DEBUG
260 - Debug.Assert(Common.IsIdentifier(definition.Name));
261 - foreach (var fieldDef in definition.FieldDefinitions)
262 - {
263 - Debug.Assert(Common.IsIdentifier(fieldDef.Name));
264 - }
265 -#endif // DEBUG
266 -
267 - foreach (var row in group)
268 - {
269 - writer.WriteStartElement(definition.Name);
270 -
271 - foreach (var field in row.Fields)
272 - {
273 - if (!field.IsNull())
274 - {
275 - writer.WriteAttributeString(field.Definition.Name, field.AsString());
276 - }
277 - }
278 -
279 - writer.WriteEndElement();
280 - }
281 - }
282 -
283 - var dataTablesById = this.Section.Tuples.OfType<WixCustomTableTuple>()
284 - .Where(t => t.Unreal && t.Id != null)
285 - .ToDictionary(t => t.Id.Id);
286 - var cellsByTable = this.Section.Tuples.OfType<WixCustomTableCellTuple>()
287 - .GroupBy(t => t.TableRef);
288 - foreach (var tableCells in cellsByTable)
289 - {
290 - var tableName = tableCells.Key;
291 - if (!dataTablesById.TryGetValue(tableName, out var tableTuple))
292 - {
293 - // This should have been a linker error.
294 - continue;
295 - }
296 -
297 - var columnNames = tableTuple.ColumnNamesSeparated;
298 -
299 - // We simply assert that the table (and field) name is valid, because
300 - // this is up to the extension developer to get right. An author will
301 - // only affect the attribute value, and that will get properly escaped.
302 -#if DEBUG
303 - Debug.Assert(Common.IsIdentifier(tableName));
304 - foreach (var columnName in columnNames)
305 - {
306 - Debug.Assert(Common.IsIdentifier(columnName));
307 - }
308 -#endif // DEBUG
309 -
310 - foreach (var rowCells in tableCells.GroupBy(t => t.RowId))
311 - {
312 - var rowDataByColumn = rowCells.ToDictionary(t => t.ColumnRef, t => t.Data);
313 -
314 - writer.WriteStartElement(tableName);
315 -
316 - // Write all row data as attributes in table column order.
317 - foreach (var column in columnNames)
318 - {
319 - if (rowDataByColumn.TryGetValue(column, out var data))
320 - {
321 - writer.WriteAttributeString(column, data);
322 - }
323 - }
324 -
325 - writer.WriteEndElement();
326 - }
327 - }
328 - }
329 -
246 private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath)
247 {
248 var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BADataFileName);
src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs
+4 -62
@@ -3,11 +3,8 @@
3 namespace WixToolset.Core.Burn.Bundles
4 {
5 using System;
6 - using System.Collections.Generic;
7 - using System.Diagnostics;
6 using System.Globalization;
7 using System.IO;
10 - using System.Linq;
8 using System.Text;
9 using System.Xml;
10 using WixToolset.Data;
@@ -16,20 +13,20 @@ namespace WixToolset.Core.Burn.Bundles
13
14 internal class CreateBundleExtensionManifestCommand
15 {
19 - public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IDictionary<string, IList<IntermediateTuple>> extensionSearchTuplesByExtensionId, int lastUXPayloadIndex, string intermediateFolder)
16 + public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, int lastUXPayloadIndex, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper)
17 {
18 this.Section = section;
19 this.BundleTuple = bundleTuple;
23 - this.ExtensionSearchTuplesByExtensionId = extensionSearchTuplesByExtensionId;
20 this.LastUXPayloadIndex = lastUXPayloadIndex;
21 this.IntermediateFolder = intermediateFolder;
22 + this.InternalBurnBackendHelper = internalBurnBackendHelper;
23 }
24
25 private IntermediateSection Section { get; }
26
27 private WixBundleTuple BundleTuple { get; }
28
32 - private IDictionary<string, IList<IntermediateTuple>> ExtensionSearchTuplesByExtensionId { get; }
29 + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; }
30
31 private int LastUXPayloadIndex { get; }
32
@@ -58,10 +55,7 @@ namespace WixToolset.Core.Burn.Bundles
55 writer.WriteStartDocument();
56 writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace);
57
61 - foreach (var kvp in this.ExtensionSearchTuplesByExtensionId)
62 - {
63 - this.WriteExtension(writer, kvp.Key, kvp.Value);
64 - }
58 + this.InternalBurnBackendHelper.WriteBundleExtensionData(writer);
59
60 writer.WriteEndElement();
61 writer.WriteEndDocument();
@@ -70,58 +64,6 @@ namespace WixToolset.Core.Burn.Bundles
64 return path;
65 }
66
73 - private void WriteExtension(XmlTextWriter writer, string extensionId, IEnumerable<IntermediateTuple> tuples)
74 - {
75 - writer.WriteStartElement("BundleExtension");
76 -
77 - writer.WriteAttributeString("Id", extensionId);
78 -
79 - this.WriteBundleExtensionDataTuples(writer, tuples);
80 -
81 - writer.WriteEndElement();
82 - }
83 -
84 - private void WriteBundleExtensionDataTuples(XmlTextWriter writer, IEnumerable<IntermediateTuple> tuples)
85 - {
86 - var dataTuplesGroupedByDefinitionName = tuples.GroupBy(t => t.Definition);
87 -
88 - foreach (var group in dataTuplesGroupedByDefinitionName)
89 - {
90 - var definition = group.Key;
91 -
92 - // We simply assert that the table (and field) name is valid, because
93 - // this is up to the extension developer to get right. An author will
94 - // only affect the attribute value, and that will get properly escaped.
95 -#if DEBUG
96 - Debug.Assert(Common.IsIdentifier(definition.Name));
97 - foreach (var fieldDef in definition.FieldDefinitions)
98 - {
99 - Debug.Assert(Common.IsIdentifier(fieldDef.Name));
100 - }
101 -#endif // DEBUG
102 -
103 - foreach (var tuple in group)
104 - {
105 - writer.WriteStartElement(definition.Name);
106 -
107 - if (tuple.Id != null)
108 - {
109 - writer.WriteAttributeString("Id", tuple.Id.Id);
110 - }
111 -
112 - foreach (var field in tuple.Fields)
113 - {
114 - if (!field.IsNull())
115 - {
116 - writer.WriteAttributeString(field.Definition.Name, field.AsString());
117 - }
118 - }
119 -
120 - writer.WriteEndElement();
121 - }
122 - }
123 - }
124 -
67 private WixBundlePayloadTuple CreateBundleExtensionManifestPayloadRow(string bextManifestPath)
68 {
69 var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName);
src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs new
+151
@@ -0,0 +1,151 @@
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.Burn.ExtensibilityServices
4 +{
5 + using System;
6 + using System.Collections.Generic;
7 + using System.IO;
8 + using System.Text;
9 + using System.Xml;
10 + using WixToolset.Core.Burn.Bundles;
11 + using WixToolset.Data;
12 +
13 + internal class BurnBackendHelper : IInternalBurnBackendHelper
14 + {
15 + public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment };
16 + public static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment };
17 +
18 + private ManifestData BootstrapperApplicationManifestData { get; } = new ManifestData();
19 +
20 + private Dictionary<string, ManifestData> BundleExtensionDataById { get; } = new Dictionary<string, ManifestData>();
21 +
22 + public void AddBootstrapperApplicationData(string xml)
23 + {
24 + this.BootstrapperApplicationManifestData.AddXml(xml);
25 + }
26 +
27 + public void AddBootstrapperApplicationData(IntermediateTuple tuple, bool tupleIdIsIdAttribute = false)
28 + {
29 + this.BootstrapperApplicationManifestData.AddTuple(tuple, tupleIdIsIdAttribute, BurnCommon.BADataNamespace);
30 + }
31 +
32 + public void AddBundleExtensionData(string extensionId, string xml)
33 + {
34 + var manifestData = this.GetBundleExtensionManifestData(extensionId);
35 + manifestData.AddXml(xml);
36 + }
37 +
38 + public void AddBundleExtensionData(string extensionId, IntermediateTuple tuple, bool tupleIdIsIdAttribute = false)
39 + {
40 + var manifestData = this.GetBundleExtensionManifestData(extensionId);
41 + manifestData.AddTuple(tuple, tupleIdIsIdAttribute, BurnCommon.BundleExtensionDataNamespace);
42 + }
43 +
44 + public void WriteBootstrapperApplicationData(XmlWriter writer)
45 + {
46 + this.BootstrapperApplicationManifestData.Write(writer);
47 + }
48 +
49 + public void WriteBundleExtensionData(XmlWriter writer)
50 + {
51 + foreach (var kvp in this.BundleExtensionDataById)
52 + {
53 + this.WriteExtension(writer, kvp.Key, kvp.Value);
54 + }
55 + }
56 +
57 + private ManifestData GetBundleExtensionManifestData(string extensionId)
58 + {
59 + if (!Common.IsIdentifier(extensionId))
60 + {
61 + throw new ArgumentException($"'{extensionId}' is not a valid extensionId");
62 + }
63 +
64 + if (!this.BundleExtensionDataById.TryGetValue(extensionId, out var manifestData))
65 + {
66 + manifestData = new ManifestData();
67 + this.BundleExtensionDataById.Add(extensionId, manifestData);
68 + }
69 +
70 + return manifestData;
71 + }
72 +
73 + private void WriteExtension(XmlWriter writer, string extensionId, ManifestData manifestData)
74 + {
75 + writer.WriteStartElement("BundleExtension");
76 +
77 + writer.WriteAttributeString("Id", extensionId);
78 +
79 + manifestData.Write(writer);
80 +
81 + writer.WriteEndElement();
82 + }
83 +
84 + private class ManifestData
85 + {
86 + public ManifestData()
87 + {
88 + this.Builder = new StringBuilder();
89 + }
90 +
91 + private StringBuilder Builder { get; }
92 +
93 + public void AddTuple(IntermediateTuple tuple, bool tupleIdIsIdAttribute, string ns)
94 + {
95 + // There might be a more efficient way to do this,
96 + // but this is an easy way to ensure we're creating valid XML.
97 + var sb = new StringBuilder();
98 + using (var writer = XmlWriter.Create(sb, WriterSettings))
99 + {
100 + writer.WriteStartElement(tuple.Definition.Name, ns);
101 +
102 + if (tupleIdIsIdAttribute && tuple.Id != null)
103 + {
104 + writer.WriteAttributeString("Id", tuple.Id.Id);
105 + }
106 +
107 + foreach (var field in tuple.Fields)
108 + {
109 + if (!field.IsNull())
110 + {
111 + writer.WriteAttributeString(field.Definition.Name, field.AsString());
112 + }
113 + }
114 +
115 + writer.WriteEndElement();
116 + }
117 +
118 + this.AddXml(sb.ToString());
119 + }
120 +
121 + public void AddXml(string xml)
122 + {
123 + // There might be a more efficient way to do this,
124 + // but this is an easy way to ensure we're given valid XML.
125 + var sb = new StringBuilder();
126 + using (var xmlWriter = XmlWriter.Create(sb, WriterSettings))
127 + {
128 + AddManifestDataFromString(xmlWriter, xml);
129 + }
130 + this.Builder.Append(sb.ToString());
131 + }
132 +
133 + public void Write(XmlWriter writer)
134 + {
135 + AddManifestDataFromString(writer, this.Builder.ToString());
136 + }
137 +
138 + private static void AddManifestDataFromString(XmlWriter xmlWriter, string xml)
139 + {
140 + using (var stringReader = new StringReader(xml))
141 + using (var xmlReader = XmlReader.Create(stringReader, ReaderSettings))
142 + {
143 + while (xmlReader.MoveToContent() != XmlNodeType.None)
144 + {
145 + xmlWriter.WriteNode(xmlReader, false);
146 + }
147 + }
148 + }
149 + }
150 + }
151 +}
src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs new
+14
@@ -0,0 +1,14 @@
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.Burn
4 +{
5 + using System.Xml;
6 + using WixToolset.Extensibility.Services;
7 +
8 + internal interface IInternalBurnBackendHelper : IBurnBackendHelper
9 + {
10 + void WriteBootstrapperApplicationData(XmlWriter writer);
11 +
12 + void WriteBundleExtensionData(XmlWriter writer);
13 + }
14 +}
src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs
+18
@@ -2,16 +2,34 @@
2
3 namespace WixToolset.Core.Burn
4 {
5 + using System;
6 + using System.Collections.Generic;
7 + using WixToolset.Core.Burn.ExtensibilityServices;
8 using WixToolset.Extensibility.Services;
9
10 public static class WixToolsetCoreServiceProviderExtensions
11 {
12 public static IWixToolsetCoreServiceProvider AddBundleBackend(this IWixToolsetCoreServiceProvider coreProvider)
13 {
14 + AddServices(coreProvider);
15 +
16 var extensionManager = coreProvider.GetService<IExtensionManager>();
17 extensionManager.Add(typeof(BurnExtensionFactory).Assembly);
18
19 return coreProvider;
20 }
21 +
22 + private static void AddServices(IWixToolsetCoreServiceProvider coreProvider)
23 + {
24 + // Singletons.
25 + coreProvider.AddService((provider, singletons) => AddSingleton<IInternalBurnBackendHelper>(singletons, new BurnBackendHelper()));
26 + coreProvider.AddService((provider, singletons) => AddSingleton<IBurnBackendHelper>(singletons, provider.GetService<IInternalBurnBackendHelper>()));
27 + }
28 +
29 + private static T AddSingleton<T>(Dictionary<Type, object> singletons, T service) where T : class
30 + {
31 + singletons.Add(typeof(T), service);
32 + return service;
33 + }
34 }
35 }
src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
+3
@@ -146,6 +146,9 @@ namespace WixToolsetTest.CoreIntegration
146 "<ExampleSearch Id='ExampleSearchBar' SearchFor='Bar' />" +
147 "<ExampleSearch Id='ExampleSearchFoo' SearchFor='Foo' />" +
148 "</BundleExtension>", bundleExtensionDatas[0].GetTestXml());
149 +
150 + var exampleSearches = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']/be:ExampleSearch");
151 + Assert.Equal(2, exampleSearches.Count);
152 }
153 }
154