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 | ||
