From e22fd864bcb83d982441759719ba57fbc4391c95 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 19 Jun 2020 13:28:50 +1000 Subject: Implement IBurnBackendHelper and TryAddTupleToDataManifest. Add GenerateManifestDataFromIRCommand, which allows the Burn backend to warn on unknown tuples. --- src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 13 +- .../Bind/GenerateManifestDataFromIRCommand.cs | 198 +++++++++++++++++++++ ...CreateBootstrapperApplicationManifestCommand.cs | 94 +--------- .../CreateBundleExtensionManifestCommand.cs | 66 +------ .../ExtensibilityServices/BurnBackendHelper.cs | 151 ++++++++++++++++ .../IInternalBurnBackendHelper.cs | 14 ++ .../WixToolsetCoreServiceProviderExtensions.cs | 18 ++ .../BundleManifestFixture.cs | 3 + 8 files changed, 404 insertions(+), 153 deletions(-) create mode 100644 src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs create mode 100644 src/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs create mode 100644 src/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs 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 this.Messaging = context.ServiceProvider.GetService(); this.BackendHelper = context.ServiceProvider.GetService(); + this.InternalBurnBackendHelper = context.ServiceProvider.GetService(); this.DefaultCompressionLevel = context.DefaultCompressionLevel; this.DelayedFields = context.DelayedFields; @@ -49,6 +50,8 @@ namespace WixToolset.Core.Burn private IBackendHelper BackendHelper { get; } + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } + private CompressionLevel? DefaultCompressionLevel { get; } public IEnumerable DelayedFields { get; } @@ -386,6 +389,12 @@ namespace WixToolset.Core.Burn // Update the bundle per-machine/per-user scope based on the chained packages. this.ResolveBundleInstallScope(section, bundleTuple, orderedFacades); + // Generate data for all manifests. + { + var command = new GenerateManifestDataFromIRCommand(this.Messaging, section, this.BackendExtensions, this.InternalBurnBackendHelper, extensionSearchTuplesById); + command.Execute(); + } + // Give the extension one last hook before generating the output files. foreach (var extension in this.BackendExtensions) { @@ -400,7 +409,7 @@ namespace WixToolset.Core.Burn // Generate the core-defined BA manifest tables... string baManifestPath; { - var command = new CreateBootstrapperApplicationManifestCommand(section, bundleTuple, orderedFacades, uxPayloadIndex, payloadTuples, this.IntermediateFolder); + var command = new CreateBootstrapperApplicationManifestCommand(section, bundleTuple, orderedFacades, uxPayloadIndex, payloadTuples, this.IntermediateFolder, this.InternalBurnBackendHelper); command.Execute(); var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; @@ -414,7 +423,7 @@ namespace WixToolset.Core.Burn // Generate the bundle extension manifest... string bextManifestPath; { - var command = new CreateBundleExtensionManifestCommand(section, bundleTuple, extensionSearchTuplesById, uxPayloadIndex, this.IntermediateFolder); + var command = new CreateBundleExtensionManifestCommand(section, bundleTuple, uxPayloadIndex, this.IntermediateFolder, this.InternalBurnBackendHelper); command.Execute(); 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 @@ +// 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. + +namespace WixToolset.Core.Burn.Bind +{ + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Xml; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Core.Burn.ExtensibilityServices; + using WixToolset.Data; + using WixToolset.Data.Tuples; + using WixToolset.Extensibility; + using WixToolset.Extensibility.Services; + + internal class GenerateManifestDataFromIRCommand + { + public GenerateManifestDataFromIRCommand(IMessaging messaging, IntermediateSection section, IEnumerable backendExtensions, IBurnBackendHelper backendHelper, IDictionary> extensionSearchTuplesById) + { + this.Messaging = messaging; + this.Section = section; + this.BackendExtensions = backendExtensions; + this.BackendHelper = backendHelper; + this.ExtensionSearchTuplesById = extensionSearchTuplesById; + } + + private IEnumerable BackendExtensions { get; } + + private IBurnBackendHelper BackendHelper { get; } + + private IDictionary> ExtensionSearchTuplesById { get; } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public void Execute() + { + var tuples = this.Section.Tuples.ToList(); + var cellsByTableAndRowId = new Dictionary>(); + var customTablesById = new Dictionary(); + + foreach (var kvp in this.ExtensionSearchTuplesById) + { + var extensionId = kvp.Key; + var extensionSearchTuples = kvp.Value; + foreach (var extensionSearchTuple in extensionSearchTuples) + { + this.BackendHelper.AddBundleExtensionData(extensionId, extensionSearchTuple, tupleIdIsIdAttribute: true); + tuples.Remove(extensionSearchTuple); + } + } + + foreach (var tuple in tuples) + { + var unknownTuple = false; + switch (tuple.Definition.Type) + { + // Tuples used internally and are not added to a data manifest. + case TupleDefinitionType.ProvidesDependency: + case TupleDefinitionType.WixApprovedExeForElevation: + case TupleDefinitionType.WixBootstrapperApplication: + case TupleDefinitionType.WixBundle: + case TupleDefinitionType.WixBundleCatalog: + case TupleDefinitionType.WixBundleContainer: + case TupleDefinitionType.WixBundleExePackage: + case TupleDefinitionType.WixBundleExtension: + case TupleDefinitionType.WixBundleMsiFeature: + case TupleDefinitionType.WixBundleMsiPackage: + case TupleDefinitionType.WixBundleMsiProperty: + case TupleDefinitionType.WixBundleMspPackage: + case TupleDefinitionType.WixBundleMsuPackage: + case TupleDefinitionType.WixBundlePackage: + case TupleDefinitionType.WixBundlePackageCommandLine: + case TupleDefinitionType.WixBundlePackageExitCode: + case TupleDefinitionType.WixBundlePatchTargetCode: + case TupleDefinitionType.WixBundlePayload: + case TupleDefinitionType.WixBundleRelatedPackage: + case TupleDefinitionType.WixBundleRollbackBoundary: + case TupleDefinitionType.WixBundleSlipstreamMsp: + case TupleDefinitionType.WixBundleUpdate: + case TupleDefinitionType.WixBundleVariable: + case TupleDefinitionType.WixChain: + case TupleDefinitionType.WixComponentSearch: + case TupleDefinitionType.WixCustomTableColumn: + case TupleDefinitionType.WixDependencyProvider: + case TupleDefinitionType.WixFileSearch: + case TupleDefinitionType.WixGroup: + case TupleDefinitionType.WixProductSearch: + case TupleDefinitionType.WixRegistrySearch: + case TupleDefinitionType.WixRelatedBundle: + case TupleDefinitionType.WixSearch: + case TupleDefinitionType.WixSearchRelation: + case TupleDefinitionType.WixSetVariable: + case TupleDefinitionType.WixUpdateRegistration: + break; + + case TupleDefinitionType.WixCustomTable: + unknownTuple = !this.IndexCustomTableTuple((WixCustomTableTuple)tuple, customTablesById); + break; + + case TupleDefinitionType.WixCustomTableCell: + this.IndexCustomTableCellTuple((WixCustomTableCellTuple)tuple, cellsByTableAndRowId); + break; + + case TupleDefinitionType.MustBeFromAnExtension: + unknownTuple = !this.AddTupleFromExtension(tuple); + break; + + default: + unknownTuple = true; + break; + } + + if (unknownTuple) + { + this.Messaging.Write(WarningMessages.TupleNotTranslatedToOutput(tuple)); + } + } + + this.AddIndexedCellTuples(customTablesById, cellsByTableAndRowId); + } + + private bool IndexCustomTableTuple(WixCustomTableTuple wixCustomTableTuple, Dictionary customTablesById) + { + if (!wixCustomTableTuple.Unreal) + { + return false; + } + + var tableId = wixCustomTableTuple.Id.Id; + customTablesById.Add(tableId, wixCustomTableTuple); + return true; + } + + private void IndexCustomTableCellTuple(WixCustomTableCellTuple wixCustomTableCellTuple, Dictionary> cellsByTableAndRowId) + { + var tableAndRowId = wixCustomTableCellTuple.TableRef + "/" + wixCustomTableCellTuple.RowId; + if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells)) + { + cells = new List(); + cellsByTableAndRowId.Add(tableAndRowId, cells); + } + + cells.Add(wixCustomTableCellTuple); + } + + private void AddIndexedCellTuples(Dictionary customTablesById, Dictionary> cellsByTableAndRowId) + { + var sb = new StringBuilder(); + using (var writer = XmlWriter.Create(sb, BurnBackendHelper.WriterSettings)) + { + foreach (var rowOfCells in cellsByTableAndRowId.Values) + { + var tableId = rowOfCells[0].TableRef; + var tableTuple = customTablesById[tableId]; + + if (!tableTuple.Unreal) + { + return; + } + + var columnNames = tableTuple.ColumnNamesSeparated; + + var rowDataByColumn = rowOfCells.ToDictionary(t => t.ColumnRef, t => t.Data); + + writer.WriteStartElement(tableId, BurnCommon.BADataNamespace); + + // Write all row data as attributes in table column order. + foreach (var column in columnNames) + { + if (rowDataByColumn.TryGetValue(column, out var data)) + { + writer.WriteAttributeString(column, data); + } + } + + writer.WriteEndElement(); + } + } + + this.BackendHelper.AddBootstrapperApplicationData(sb.ToString()); + } + + private bool AddTupleFromExtension(IntermediateTuple tuple) + { + foreach (var extension in this.BackendExtensions) + { + if (extension.TryAddTupleToDataManifest(this.Section, tuple)) + { + return true; + } + } + + return false; + } + } +} 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 { using System; using System.Collections.Generic; - using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -16,9 +15,7 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateBootstrapperApplicationManifestCommand { - private static readonly char[] ColonCharacter = new[] { ':' }; - - public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable chainPackages, int lastUXPayloadIndex, Dictionary payloadTuples, string intermediateFolder) + public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable chainPackages, int lastUXPayloadIndex, Dictionary payloadTuples, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) { this.Section = section; this.BundleTuple = bundleTuple; @@ -26,6 +23,7 @@ namespace WixToolset.Core.Burn.Bundles this.LastUXPayloadIndex = lastUXPayloadIndex; this.Payloads = payloadTuples; this.IntermediateFolder = intermediateFolder; + this.InternalBurnBackendHelper = internalBurnBackendHelper; } private IntermediateSection Section { get; } @@ -34,6 +32,8 @@ namespace WixToolset.Core.Burn.Bundles private IEnumerable ChainPackages { get; } + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } + private int LastUXPayloadIndex { get; } private Dictionary Payloads { get; } @@ -71,7 +71,7 @@ namespace WixToolset.Core.Burn.Bundles this.WritePayloadInfo(writer); - this.WriteCustomBootstrapperApplicationData(writer); + this.InternalBurnBackendHelper.WriteBootstrapperApplicationData(writer); writer.WriteEndElement(); writer.WriteEndDocument(); @@ -243,90 +243,6 @@ namespace WixToolset.Core.Burn.Bundles } } - private void WriteCustomBootstrapperApplicationData(XmlTextWriter writer) - { - var dataTuplesGroupedByDefinitionName = this.Section.Tuples - .Where(t => t.Definition.HasTag(BurnConstants.BootstrapperApplicationDataTupleDefinitionTag)) - .GroupBy(t => t.Definition); - - foreach (var group in dataTuplesGroupedByDefinitionName) - { - var definition = group.Key; - - // We simply assert that the table (and field) name is valid, because - // this is up to the extension developer to get right. An author will - // only affect the attribute value, and that will get properly escaped. -#if DEBUG - Debug.Assert(Common.IsIdentifier(definition.Name)); - foreach (var fieldDef in definition.FieldDefinitions) - { - Debug.Assert(Common.IsIdentifier(fieldDef.Name)); - } -#endif // DEBUG - - foreach (var row in group) - { - writer.WriteStartElement(definition.Name); - - foreach (var field in row.Fields) - { - if (!field.IsNull()) - { - writer.WriteAttributeString(field.Definition.Name, field.AsString()); - } - } - - writer.WriteEndElement(); - } - } - - var dataTablesById = this.Section.Tuples.OfType() - .Where(t => t.Unreal && t.Id != null) - .ToDictionary(t => t.Id.Id); - var cellsByTable = this.Section.Tuples.OfType() - .GroupBy(t => t.TableRef); - foreach (var tableCells in cellsByTable) - { - var tableName = tableCells.Key; - if (!dataTablesById.TryGetValue(tableName, out var tableTuple)) - { - // This should have been a linker error. - continue; - } - - var columnNames = tableTuple.ColumnNamesSeparated; - - // We simply assert that the table (and field) name is valid, because - // this is up to the extension developer to get right. An author will - // only affect the attribute value, and that will get properly escaped. -#if DEBUG - Debug.Assert(Common.IsIdentifier(tableName)); - foreach (var columnName in columnNames) - { - Debug.Assert(Common.IsIdentifier(columnName)); - } -#endif // DEBUG - - foreach (var rowCells in tableCells.GroupBy(t => t.RowId)) - { - var rowDataByColumn = rowCells.ToDictionary(t => t.ColumnRef, t => t.Data); - - writer.WriteStartElement(tableName); - - // Write all row data as attributes in table column order. - foreach (var column in columnNames) - { - if (rowDataByColumn.TryGetValue(column, out var data)) - { - writer.WriteAttributeString(column, data); - } - } - - writer.WriteEndElement(); - } - } - } - private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) { 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 @@ namespace WixToolset.Core.Burn.Bundles { using System; - using System.Collections.Generic; - using System.Diagnostics; using System.Globalization; using System.IO; - using System.Linq; using System.Text; using System.Xml; using WixToolset.Data; @@ -16,20 +13,20 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateBundleExtensionManifestCommand { - public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IDictionary> extensionSearchTuplesByExtensionId, int lastUXPayloadIndex, string intermediateFolder) + public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, int lastUXPayloadIndex, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) { this.Section = section; this.BundleTuple = bundleTuple; - this.ExtensionSearchTuplesByExtensionId = extensionSearchTuplesByExtensionId; this.LastUXPayloadIndex = lastUXPayloadIndex; this.IntermediateFolder = intermediateFolder; + this.InternalBurnBackendHelper = internalBurnBackendHelper; } private IntermediateSection Section { get; } private WixBundleTuple BundleTuple { get; } - private IDictionary> ExtensionSearchTuplesByExtensionId { get; } + private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } private int LastUXPayloadIndex { get; } @@ -58,10 +55,7 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteStartDocument(); writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); - foreach (var kvp in this.ExtensionSearchTuplesByExtensionId) - { - this.WriteExtension(writer, kvp.Key, kvp.Value); - } + this.InternalBurnBackendHelper.WriteBundleExtensionData(writer); writer.WriteEndElement(); writer.WriteEndDocument(); @@ -70,58 +64,6 @@ namespace WixToolset.Core.Burn.Bundles return path; } - private void WriteExtension(XmlTextWriter writer, string extensionId, IEnumerable tuples) - { - writer.WriteStartElement("BundleExtension"); - - writer.WriteAttributeString("Id", extensionId); - - this.WriteBundleExtensionDataTuples(writer, tuples); - - writer.WriteEndElement(); - } - - private void WriteBundleExtensionDataTuples(XmlTextWriter writer, IEnumerable tuples) - { - var dataTuplesGroupedByDefinitionName = tuples.GroupBy(t => t.Definition); - - foreach (var group in dataTuplesGroupedByDefinitionName) - { - var definition = group.Key; - - // We simply assert that the table (and field) name is valid, because - // this is up to the extension developer to get right. An author will - // only affect the attribute value, and that will get properly escaped. -#if DEBUG - Debug.Assert(Common.IsIdentifier(definition.Name)); - foreach (var fieldDef in definition.FieldDefinitions) - { - Debug.Assert(Common.IsIdentifier(fieldDef.Name)); - } -#endif // DEBUG - - foreach (var tuple in group) - { - writer.WriteStartElement(definition.Name); - - if (tuple.Id != null) - { - writer.WriteAttributeString("Id", tuple.Id.Id); - } - - foreach (var field in tuple.Fields) - { - if (!field.IsNull()) - { - writer.WriteAttributeString(field.Definition.Name, field.AsString()); - } - } - - writer.WriteEndElement(); - } - } - } - private WixBundlePayloadTuple CreateBundleExtensionManifestPayloadRow(string bextManifestPath) { 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 @@ +// 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. + +namespace WixToolset.Core.Burn.ExtensibilityServices +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Xml; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Data; + + internal class BurnBackendHelper : IInternalBurnBackendHelper + { + public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment }; + public static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment }; + + private ManifestData BootstrapperApplicationManifestData { get; } = new ManifestData(); + + private Dictionary BundleExtensionDataById { get; } = new Dictionary(); + + public void AddBootstrapperApplicationData(string xml) + { + this.BootstrapperApplicationManifestData.AddXml(xml); + } + + public void AddBootstrapperApplicationData(IntermediateTuple tuple, bool tupleIdIsIdAttribute = false) + { + this.BootstrapperApplicationManifestData.AddTuple(tuple, tupleIdIsIdAttribute, BurnCommon.BADataNamespace); + } + + public void AddBundleExtensionData(string extensionId, string xml) + { + var manifestData = this.GetBundleExtensionManifestData(extensionId); + manifestData.AddXml(xml); + } + + public void AddBundleExtensionData(string extensionId, IntermediateTuple tuple, bool tupleIdIsIdAttribute = false) + { + var manifestData = this.GetBundleExtensionManifestData(extensionId); + manifestData.AddTuple(tuple, tupleIdIsIdAttribute, BurnCommon.BundleExtensionDataNamespace); + } + + public void WriteBootstrapperApplicationData(XmlWriter writer) + { + this.BootstrapperApplicationManifestData.Write(writer); + } + + public void WriteBundleExtensionData(XmlWriter writer) + { + foreach (var kvp in this.BundleExtensionDataById) + { + this.WriteExtension(writer, kvp.Key, kvp.Value); + } + } + + private ManifestData GetBundleExtensionManifestData(string extensionId) + { + if (!Common.IsIdentifier(extensionId)) + { + throw new ArgumentException($"'{extensionId}' is not a valid extensionId"); + } + + if (!this.BundleExtensionDataById.TryGetValue(extensionId, out var manifestData)) + { + manifestData = new ManifestData(); + this.BundleExtensionDataById.Add(extensionId, manifestData); + } + + return manifestData; + } + + private void WriteExtension(XmlWriter writer, string extensionId, ManifestData manifestData) + { + writer.WriteStartElement("BundleExtension"); + + writer.WriteAttributeString("Id", extensionId); + + manifestData.Write(writer); + + writer.WriteEndElement(); + } + + private class ManifestData + { + public ManifestData() + { + this.Builder = new StringBuilder(); + } + + private StringBuilder Builder { get; } + + public void AddTuple(IntermediateTuple tuple, bool tupleIdIsIdAttribute, string ns) + { + // There might be a more efficient way to do this, + // but this is an easy way to ensure we're creating valid XML. + var sb = new StringBuilder(); + using (var writer = XmlWriter.Create(sb, WriterSettings)) + { + writer.WriteStartElement(tuple.Definition.Name, ns); + + if (tupleIdIsIdAttribute && tuple.Id != null) + { + writer.WriteAttributeString("Id", tuple.Id.Id); + } + + foreach (var field in tuple.Fields) + { + if (!field.IsNull()) + { + writer.WriteAttributeString(field.Definition.Name, field.AsString()); + } + } + + writer.WriteEndElement(); + } + + this.AddXml(sb.ToString()); + } + + public void AddXml(string xml) + { + // There might be a more efficient way to do this, + // but this is an easy way to ensure we're given valid XML. + var sb = new StringBuilder(); + using (var xmlWriter = XmlWriter.Create(sb, WriterSettings)) + { + AddManifestDataFromString(xmlWriter, xml); + } + this.Builder.Append(sb.ToString()); + } + + public void Write(XmlWriter writer) + { + AddManifestDataFromString(writer, this.Builder.ToString()); + } + + private static void AddManifestDataFromString(XmlWriter xmlWriter, string xml) + { + using (var stringReader = new StringReader(xml)) + using (var xmlReader = XmlReader.Create(stringReader, ReaderSettings)) + { + while (xmlReader.MoveToContent() != XmlNodeType.None) + { + xmlWriter.WriteNode(xmlReader, false); + } + } + } + } + } +} 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 @@ +// 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. + +namespace WixToolset.Core.Burn +{ + using System.Xml; + using WixToolset.Extensibility.Services; + + internal interface IInternalBurnBackendHelper : IBurnBackendHelper + { + void WriteBootstrapperApplicationData(XmlWriter writer); + + void WriteBundleExtensionData(XmlWriter writer); + } +} 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 @@ namespace WixToolset.Core.Burn { + using System; + using System.Collections.Generic; + using WixToolset.Core.Burn.ExtensibilityServices; using WixToolset.Extensibility.Services; public static class WixToolsetCoreServiceProviderExtensions { public static IWixToolsetCoreServiceProvider AddBundleBackend(this IWixToolsetCoreServiceProvider coreProvider) { + AddServices(coreProvider); + var extensionManager = coreProvider.GetService(); extensionManager.Add(typeof(BurnExtensionFactory).Assembly); return coreProvider; } + + private static void AddServices(IWixToolsetCoreServiceProvider coreProvider) + { + // Singletons. + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, new BurnBackendHelper())); + coreProvider.AddService((provider, singletons) => AddSingleton(singletons, provider.GetService())); + } + + private static T AddSingleton(Dictionary singletons, T service) where T : class + { + singletons.Add(typeof(T), service); + return service; + } } } 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 "" + "" + "", bundleExtensionDatas[0].GetTestXml()); + + var exampleSearches = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']/be:ExampleSearch"); + Assert.Equal(2, exampleSearches.Count); } } -- cgit v1.2.3-55-g6feb