aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs13
-rw-r--r--src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs198
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs94
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs66
-rw-r--r--src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs151
-rw-r--r--src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs14
-rw-r--r--src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs18
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs3
8 files changed, 404 insertions, 153 deletions
diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
index 540c6288..943625ec 100644
--- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
@@ -30,6 +30,7 @@ namespace WixToolset.Core.Burn
30 this.Messaging = context.ServiceProvider.GetService<IMessaging>(); 30 this.Messaging = context.ServiceProvider.GetService<IMessaging>();
31 31
32 this.BackendHelper = context.ServiceProvider.GetService<IBackendHelper>(); 32 this.BackendHelper = context.ServiceProvider.GetService<IBackendHelper>();
33 this.InternalBurnBackendHelper = context.ServiceProvider.GetService<IInternalBurnBackendHelper>();
33 34
34 this.DefaultCompressionLevel = context.DefaultCompressionLevel; 35 this.DefaultCompressionLevel = context.DefaultCompressionLevel;
35 this.DelayedFields = context.DelayedFields; 36 this.DelayedFields = context.DelayedFields;
@@ -49,6 +50,8 @@ namespace WixToolset.Core.Burn
49 50
50 private IBackendHelper BackendHelper { get; } 51 private IBackendHelper BackendHelper { get; }
51 52
53 private IInternalBurnBackendHelper InternalBurnBackendHelper { get; }
54
52 private CompressionLevel? DefaultCompressionLevel { get; } 55 private CompressionLevel? DefaultCompressionLevel { get; }
53 56
54 public IEnumerable<IDelayedField> DelayedFields { get; } 57 public IEnumerable<IDelayedField> DelayedFields { get; }
@@ -386,6 +389,12 @@ namespace WixToolset.Core.Burn
386 // Update the bundle per-machine/per-user scope based on the chained packages. 389 // Update the bundle per-machine/per-user scope based on the chained packages.
387 this.ResolveBundleInstallScope(section, bundleTuple, orderedFacades); 390 this.ResolveBundleInstallScope(section, bundleTuple, orderedFacades);
388 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
389 // Give the extension one last hook before generating the output files. 398 // Give the extension one last hook before generating the output files.
390 foreach (var extension in this.BackendExtensions) 399 foreach (var extension in this.BackendExtensions)
391 { 400 {
@@ -400,7 +409,7 @@ namespace WixToolset.Core.Burn
400 // Generate the core-defined BA manifest tables... 409 // Generate the core-defined BA manifest tables...
401 string baManifestPath; 410 string baManifestPath;
402 { 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);
404 command.Execute(); 413 command.Execute();
405 414
406 var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; 415 var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow;
@@ -414,7 +423,7 @@ namespace WixToolset.Core.Burn
414 // Generate the bundle extension manifest... 423 // Generate the bundle extension manifest...
415 string bextManifestPath; 424 string bextManifestPath;
416 { 425 {
417 var command = new CreateBundleExtensionManifestCommand(section, bundleTuple, extensionSearchTuplesById, uxPayloadIndex, this.IntermediateFolder); 426 var command = new CreateBundleExtensionManifestCommand(section, bundleTuple, uxPayloadIndex, this.IntermediateFolder, this.InternalBurnBackendHelper);
418 command.Execute(); 427 command.Execute();
419 428
420 var bextManifestPayload = command.BundleExtensionManifestPayloadRow; 429 var bextManifestPayload = command.BundleExtensionManifestPayloadRow;
diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs
new file mode 100644
index 00000000..20ecd157
--- /dev/null
+++ b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs
@@ -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
3namespace 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}
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
index dba2a9ba..4468fee5 100644
--- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
+++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs
@@ -4,7 +4,6 @@ namespace WixToolset.Core.Burn.Bundles
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization; 7 using System.Globalization;
9 using System.IO; 8 using System.IO;
10 using System.Linq; 9 using System.Linq;
@@ -16,9 +15,7 @@ namespace WixToolset.Core.Burn.Bundles
16 15
17 internal class CreateBootstrapperApplicationManifestCommand 16 internal class CreateBootstrapperApplicationManifestCommand
18 { 17 {
19 private static readonly char[] ColonCharacter = new[] { ':' }; 18 public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<PackageFacade> chainPackages, int lastUXPayloadIndex, Dictionary<string, WixBundlePayloadTuple> payloadTuples, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper)
20
21 public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<PackageFacade> chainPackages, int lastUXPayloadIndex, Dictionary<string, WixBundlePayloadTuple> payloadTuples, string intermediateFolder)
22 { 19 {
23 this.Section = section; 20 this.Section = section;
24 this.BundleTuple = bundleTuple; 21 this.BundleTuple = bundleTuple;
@@ -26,6 +23,7 @@ namespace WixToolset.Core.Burn.Bundles
26 this.LastUXPayloadIndex = lastUXPayloadIndex; 23 this.LastUXPayloadIndex = lastUXPayloadIndex;
27 this.Payloads = payloadTuples; 24 this.Payloads = payloadTuples;
28 this.IntermediateFolder = intermediateFolder; 25 this.IntermediateFolder = intermediateFolder;
26 this.InternalBurnBackendHelper = internalBurnBackendHelper;
29 } 27 }
30 28
31 private IntermediateSection Section { get; } 29 private IntermediateSection Section { get; }
@@ -34,6 +32,8 @@ namespace WixToolset.Core.Burn.Bundles
34 32
35 private IEnumerable<PackageFacade> ChainPackages { get; } 33 private IEnumerable<PackageFacade> ChainPackages { get; }
36 34
35 private IInternalBurnBackendHelper InternalBurnBackendHelper { get; }
36
37 private int LastUXPayloadIndex { get; } 37 private int LastUXPayloadIndex { get; }
38 38
39 private Dictionary<string, WixBundlePayloadTuple> Payloads { get; } 39 private Dictionary<string, WixBundlePayloadTuple> Payloads { get; }
@@ -71,7 +71,7 @@ namespace WixToolset.Core.Burn.Bundles
71 71
72 this.WritePayloadInfo(writer); 72 this.WritePayloadInfo(writer);
73 73
74 this.WriteCustomBootstrapperApplicationData(writer); 74 this.InternalBurnBackendHelper.WriteBootstrapperApplicationData(writer);
75 75
76 writer.WriteEndElement(); 76 writer.WriteEndElement();
77 writer.WriteEndDocument(); 77 writer.WriteEndDocument();
@@ -243,90 +243,6 @@ namespace WixToolset.Core.Burn.Bundles
243 } 243 }
244 } 244 }
245 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
330 private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) 246 private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath)
331 { 247 {
332 var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BADataFileName); 248 var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BADataFileName);
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs
index b4739775..f7acd54c 100644
--- a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs
+++ b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs
@@ -3,11 +3,8 @@
3namespace WixToolset.Core.Burn.Bundles 3namespace WixToolset.Core.Burn.Bundles
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization; 6 using System.Globalization;
9 using System.IO; 7 using System.IO;
10 using System.Linq;
11 using System.Text; 8 using System.Text;
12 using System.Xml; 9 using System.Xml;
13 using WixToolset.Data; 10 using WixToolset.Data;
@@ -16,20 +13,20 @@ namespace WixToolset.Core.Burn.Bundles
16 13
17 internal class CreateBundleExtensionManifestCommand 14 internal class CreateBundleExtensionManifestCommand
18 { 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)
20 { 17 {
21 this.Section = section; 18 this.Section = section;
22 this.BundleTuple = bundleTuple; 19 this.BundleTuple = bundleTuple;
23 this.ExtensionSearchTuplesByExtensionId = extensionSearchTuplesByExtensionId;
24 this.LastUXPayloadIndex = lastUXPayloadIndex; 20 this.LastUXPayloadIndex = lastUXPayloadIndex;
25 this.IntermediateFolder = intermediateFolder; 21 this.IntermediateFolder = intermediateFolder;
22 this.InternalBurnBackendHelper = internalBurnBackendHelper;
26 } 23 }
27 24
28 private IntermediateSection Section { get; } 25 private IntermediateSection Section { get; }
29 26
30 private WixBundleTuple BundleTuple { get; } 27 private WixBundleTuple BundleTuple { get; }
31 28
32 private IDictionary<string, IList<IntermediateTuple>> ExtensionSearchTuplesByExtensionId { get; } 29 private IInternalBurnBackendHelper InternalBurnBackendHelper { get; }
33 30
34 private int LastUXPayloadIndex { get; } 31 private int LastUXPayloadIndex { get; }
35 32
@@ -58,10 +55,7 @@ namespace WixToolset.Core.Burn.Bundles
58 writer.WriteStartDocument(); 55 writer.WriteStartDocument();
59 writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); 56 writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace);
60 57
61 foreach (var kvp in this.ExtensionSearchTuplesByExtensionId) 58 this.InternalBurnBackendHelper.WriteBundleExtensionData(writer);
62 {
63 this.WriteExtension(writer, kvp.Key, kvp.Value);
64 }
65 59
66 writer.WriteEndElement(); 60 writer.WriteEndElement();
67 writer.WriteEndDocument(); 61 writer.WriteEndDocument();
@@ -70,58 +64,6 @@ namespace WixToolset.Core.Burn.Bundles
70 return path; 64 return path;
71 } 65 }
72 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
125 private WixBundlePayloadTuple CreateBundleExtensionManifestPayloadRow(string bextManifestPath) 67 private WixBundlePayloadTuple CreateBundleExtensionManifestPayloadRow(string bextManifestPath)
126 { 68 {
127 var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName); 69 var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName);
diff --git a/src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs b/src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs
new file mode 100644
index 00000000..10ac9931
--- /dev/null
+++ b/src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs
@@ -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
3namespace 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}
diff --git a/src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs b/src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs
new file mode 100644
index 00000000..59c4f20f
--- /dev/null
+++ b/src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs
@@ -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
3namespace 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}
diff --git a/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs b/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs
index 5c3fd449..04fa4daf 100644
--- a/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs
+++ b/src/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs
@@ -2,16 +2,34 @@
2 2
3namespace WixToolset.Core.Burn 3namespace WixToolset.Core.Burn
4{ 4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Core.Burn.ExtensibilityServices;
5 using WixToolset.Extensibility.Services; 8 using WixToolset.Extensibility.Services;
6 9
7 public static class WixToolsetCoreServiceProviderExtensions 10 public static class WixToolsetCoreServiceProviderExtensions
8 { 11 {
9 public static IWixToolsetCoreServiceProvider AddBundleBackend(this IWixToolsetCoreServiceProvider coreProvider) 12 public static IWixToolsetCoreServiceProvider AddBundleBackend(this IWixToolsetCoreServiceProvider coreProvider)
10 { 13 {
14 AddServices(coreProvider);
15
11 var extensionManager = coreProvider.GetService<IExtensionManager>(); 16 var extensionManager = coreProvider.GetService<IExtensionManager>();
12 extensionManager.Add(typeof(BurnExtensionFactory).Assembly); 17 extensionManager.Add(typeof(BurnExtensionFactory).Assembly);
13 18
14 return coreProvider; 19 return coreProvider;
15 } 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 }
16 } 34 }
17} 35}
diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
index 80c00ef1..4b1ec718 100644
--- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs
@@ -146,6 +146,9 @@ namespace WixToolsetTest.CoreIntegration
146 "<ExampleSearch Id='ExampleSearchBar' SearchFor='Bar' />" + 146 "<ExampleSearch Id='ExampleSearchBar' SearchFor='Bar' />" +
147 "<ExampleSearch Id='ExampleSearchFoo' SearchFor='Foo' />" + 147 "<ExampleSearch Id='ExampleSearchFoo' SearchFor='Foo' />" +
148 "</BundleExtension>", bundleExtensionDatas[0].GetTestXml()); 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);
149 } 152 }
150 } 153 }
151 154