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