diff options
Diffstat (limited to 'src')
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 | |||
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 | } | ||
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 @@ | |||
3 | namespace WixToolset.Core.Burn.Bundles | 3 | namespace 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 | |||
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 | } | ||
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 | |||
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 | } | ||
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 | ||
3 | namespace WixToolset.Core.Burn | 3 | namespace 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 | ||