From 0baf6e26ec7ab2ff0b6ad36e9d44f3d68819b5d6 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Fri, 27 Mar 2020 13:54:56 +1000 Subject: Add ability for extensions to create custom bundle searches. This required creating BundleExtensionData.xml. --- src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs | 27 +++ src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | 41 ++--- .../Bind/ExtensionSearchFacade.cs | 24 +++ .../Bind/LegacySearchFacade.cs | 185 +++++++++++++++++++ src/WixToolset.Core.Burn/Bind/SearchFacade.cs | 197 --------------------- src/WixToolset.Core.Burn/Bundles/BurnCommon.cs | 8 +- ...CreateBootstrapperApplicationManifestCommand.cs | 6 +- .../CreateBundleExtensionManifestCommand.cs | 149 ++++++++++++++++ .../Bundles/CreateBurnManifestCommand.cs | 4 +- .../Bundles/OrderSearchesCommand.cs | 71 ++++++++ src/WixToolset.Core.Burn/ISearchFacade.cs | 15 ++ 11 files changed, 498 insertions(+), 229 deletions(-) create mode 100644 src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs create mode 100644 src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs create mode 100644 src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs delete mode 100644 src/WixToolset.Core.Burn/Bind/SearchFacade.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs create mode 100644 src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs create mode 100644 src/WixToolset.Core.Burn/ISearchFacade.cs (limited to 'src/WixToolset.Core.Burn') diff --git a/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs new file mode 100644 index 00000000..d00c5778 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs @@ -0,0 +1,27 @@ +// 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; + using System.Xml; + using WixToolset.Data.Tuples; + + internal abstract class BaseSearchFacade : ISearchFacade + { + protected WixSearchTuple SearchTuple { get; set; } + + public virtual void WriteXml(XmlTextWriter writer) + { + writer.WriteAttributeString("Id", this.SearchTuple.Id.Id); + writer.WriteAttributeString("Variable", this.SearchTuple.Variable); + if (!String.IsNullOrEmpty(this.SearchTuple.Condition)) + { + writer.WriteAttributeString("Condition", this.SearchTuple.Condition); + } + if (!String.IsNullOrEmpty(this.SearchTuple.BundleExtensionRef)) + { + writer.WriteAttributeString("ExtensionId", this.SearchTuple.BundleExtensionRef); + } + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 9f98483f..2cb5ed64 100644 --- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -117,10 +117,10 @@ namespace WixToolset.Core.Burn // If there are any fields to resolve later, create the cache to populate during bind. var variableCache = this.DelayedFields.Any() ? new Dictionary(StringComparer.InvariantCultureIgnoreCase) : null; - // TODO: Although the WixSearch tables are defined in the Util extension, - // the Bundle Binder has to know all about them. We hope to revisit all - // of this in the 4.0 timeframe. - var orderedSearches = this.OrderSearches(section); + var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section); + orderSearchesCommand.Execute(); + var orderedSearches = orderSearchesCommand.OrderedSearchFacades; + var extensionSearchTuplesById = orderSearchesCommand.ExtensionSearchTuplesByExtensionId; // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). { @@ -387,6 +387,17 @@ namespace WixToolset.Core.Burn var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; payloadTuples.Add(baManifestPayload.Id.Id, baManifestPayload); + ++uxPayloadIndex; + } + + // Generate the bundle extension manifest... + { + var command = new CreateBundleExtensionManifestCommand(section, bundleTuple, extensionSearchTuplesById, uxPayloadIndex, this.IntermediateFolder); + command.Execute(); + + var bextManifestPayload = command.BundleExtensionManifestPayloadRow; + payloadTuples.Add(bextManifestPayload.Id.Id, bextManifestPayload); + ++uxPayloadIndex; } #if TODO @@ -464,28 +475,6 @@ namespace WixToolset.Core.Burn trackedFiles.Add(trackIntermediate); } - private IEnumerable OrderSearches(IntermediateSection section) - { - var searchesById = section.Tuples - .Where(t => t.Definition.Type == TupleDefinitionType.WixComponentSearch || - t.Definition.Type == TupleDefinitionType.WixFileSearch || - t.Definition.Type == TupleDefinitionType.WixProductSearch || - t.Definition.Type == TupleDefinitionType.WixRegistrySearch) - .ToDictionary(t => t.Id.Id); - - var orderedSearches = new List(searchesById.Keys.Count); - - foreach (var searchTuple in section.Tuples.OfType()) - { - if (searchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple)) - { - orderedSearches.Add(new SearchFacade(searchTuple, specificSearchTuple)); - } - } - - return orderedSearches; - } - /// /// Populates the variable cache with specific package properties. /// diff --git a/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs b/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs new file mode 100644 index 00000000..6a830a28 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs @@ -0,0 +1,24 @@ +// 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.Data.Tuples; + + internal class ExtensionSearchFacade : BaseSearchFacade + { + public ExtensionSearchFacade(WixSearchTuple searchTuple) + { + this.SearchTuple = searchTuple; + } + + public override void WriteXml(XmlTextWriter writer) + { + writer.WriteStartElement("ExtensionSearch"); + + base.WriteXml(writer); + + writer.WriteEndElement(); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs b/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs new file mode 100644 index 00000000..0a80760d --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs @@ -0,0 +1,185 @@ +// 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; + using System.Xml; + using WixToolset.Data; + using WixToolset.Data.Tuples; + + internal class LegacySearchFacade : BaseSearchFacade + { + public LegacySearchFacade(WixSearchTuple searchTuple, IntermediateTuple searchSpecificTuple) + { + this.SearchTuple = searchTuple; + this.SearchSpecificTuple = searchSpecificTuple; + } + + public IntermediateTuple SearchSpecificTuple { get; } + + /// + /// Generates Burn manifest and ParameterInfo-style markup a search. + /// + /// + public override void WriteXml(XmlTextWriter writer) + { + switch (this.SearchSpecificTuple) + { + case WixComponentSearchTuple tuple: + this.WriteComponentSearchXml(writer, tuple); + break; + case WixFileSearchTuple tuple: + this.WriteFileSearchXml(writer, tuple); + break; + case WixProductSearchTuple tuple: + this.WriteProductSearchXml(writer, tuple); + break; + case WixRegistrySearchTuple tuple: + this.WriteRegistrySearchXml(writer, tuple); + break; + } + } + + private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchTuple searchTuple) + { + writer.WriteStartElement("MsiComponentSearch"); + + base.WriteXml(writer); + + writer.WriteAttributeString("ComponentId", searchTuple.Guid); + + if (!String.IsNullOrEmpty(searchTuple.ProductCode)) + { + writer.WriteAttributeString("ProductCode", searchTuple.ProductCode); + } + + if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.KeyPath)) + { + writer.WriteAttributeString("Type", "keyPath"); + } + else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.WantDirectory)) + { + writer.WriteAttributeString("Type", "directory"); + } + + writer.WriteEndElement(); + } + + private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchTuple searchTuple) + { + writer.WriteStartElement((0 == (searchTuple.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); + + base.WriteXml(writer); + + writer.WriteAttributeString("Path", searchTuple.Path); + if (WixFileSearchAttributes.WantExists == (searchTuple.Attributes & WixFileSearchAttributes.WantExists)) + { + writer.WriteAttributeString("Type", "exists"); + } + else if (WixFileSearchAttributes.WantVersion == (searchTuple.Attributes & WixFileSearchAttributes.WantVersion)) + { + // Can never get here for DirectorySearch. + writer.WriteAttributeString("Type", "version"); + } + else + { + writer.WriteAttributeString("Type", "path"); + } + writer.WriteEndElement(); + } + + private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchTuple tuple) + { + writer.WriteStartElement("MsiProductSearch"); + + base.WriteXml(writer); + + if (0 != (tuple.Attributes & WixProductSearchAttributes.UpgradeCode)) + { + writer.WriteAttributeString("UpgradeCode", tuple.Guid); + } + else + { + writer.WriteAttributeString("ProductCode", tuple.Guid); + } + + if (0 != (tuple.Attributes & WixProductSearchAttributes.Version)) + { + writer.WriteAttributeString("Type", "version"); + } + else if (0 != (tuple.Attributes & WixProductSearchAttributes.Language)) + { + writer.WriteAttributeString("Type", "language"); + } + else if (0 != (tuple.Attributes & WixProductSearchAttributes.State)) + { + writer.WriteAttributeString("Type", "state"); + } + else if (0 != (tuple.Attributes & WixProductSearchAttributes.Assignment)) + { + writer.WriteAttributeString("Type", "assignment"); + } + + writer.WriteEndElement(); + } + + private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchTuple tuple) + { + writer.WriteStartElement("RegistrySearch"); + + base.WriteXml(writer); + + switch (tuple.Root) + { + case RegistryRootType.ClassesRoot: + writer.WriteAttributeString("Root", "HKCR"); + break; + case RegistryRootType.CurrentUser: + writer.WriteAttributeString("Root", "HKCU"); + break; + case RegistryRootType.LocalMachine: + writer.WriteAttributeString("Root", "HKLM"); + break; + case RegistryRootType.Users: + writer.WriteAttributeString("Root", "HKU"); + break; + } + + writer.WriteAttributeString("Key", tuple.Key); + + if (!String.IsNullOrEmpty(tuple.Value)) + { + writer.WriteAttributeString("Value", tuple.Value); + } + + var existenceOnly = 0 != (tuple.Attributes & WixRegistrySearchAttributes.WantExists); + + writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); + + if (0 != (tuple.Attributes & WixRegistrySearchAttributes.Win64)) + { + writer.WriteAttributeString("Win64", "yes"); + } + + if (!existenceOnly) + { + if (0 != (tuple.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) + { + writer.WriteAttributeString("ExpandEnvironment", "yes"); + } + + // We *always* say this is VariableType="string". If we end up + // needing to be more specific, we will have to expand the "Format" + // attribute to allow "number" and "version". + + writer.WriteAttributeString("VariableType", "string"); + } + + writer.WriteEndElement(); + } + } +} diff --git a/src/WixToolset.Core.Burn/Bind/SearchFacade.cs b/src/WixToolset.Core.Burn/Bind/SearchFacade.cs deleted file mode 100644 index 65f3cb5b..00000000 --- a/src/WixToolset.Core.Burn/Bind/SearchFacade.cs +++ /dev/null @@ -1,197 +0,0 @@ -// 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; - using System.Xml; - using WixToolset.Data; - using WixToolset.Data.Tuples; - - internal class SearchFacade - { - public SearchFacade(WixSearchTuple searchTuple, IntermediateTuple searchSpecificTuple) - { - this.SearchTuple = searchTuple; - this.SearchSpecificTuple = searchSpecificTuple; - } - - public WixSearchTuple SearchTuple { get; } - - public IntermediateTuple SearchSpecificTuple { get; } - - /// - /// Generates Burn manifest and ParameterInfo-style markup a search. - /// - /// - public void WriteXml(XmlTextWriter writer) - { - switch (this.SearchSpecificTuple) - { - case WixComponentSearchTuple tuple: - this.WriteComponentSearchXml(writer, tuple); - break; - case WixFileSearchTuple tuple: - this.WriteFileSearchXml(writer, tuple); - break; - case WixProductSearchTuple tuple: - this.WriteProductSearchXml(writer, tuple); - break; - case WixRegistrySearchTuple tuple: - this.WriteRegistrySearchXml(writer, tuple); - break; - } - } - - private void WriteCommonAttributes(XmlTextWriter writer) - { - writer.WriteAttributeString("Id", this.SearchTuple.Id.Id); - writer.WriteAttributeString("Variable", this.SearchTuple.Variable); - if (!String.IsNullOrEmpty(this.SearchTuple.Condition)) - { - writer.WriteAttributeString("Condition", this.SearchTuple.Condition); - } - } - - private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchTuple searchTuple) - { - writer.WriteStartElement("MsiComponentSearch"); - - this.WriteCommonAttributes(writer); - - writer.WriteAttributeString("ComponentId", searchTuple.Guid); - - if (!String.IsNullOrEmpty(searchTuple.ProductCode)) - { - writer.WriteAttributeString("ProductCode", searchTuple.ProductCode); - } - - if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.KeyPath)) - { - writer.WriteAttributeString("Type", "keyPath"); - } - else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (searchTuple.Attributes & WixComponentSearchAttributes.WantDirectory)) - { - writer.WriteAttributeString("Type", "directory"); - } - - writer.WriteEndElement(); - } - - private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchTuple searchTuple) - { - writer.WriteStartElement((0 == (searchTuple.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); - - this.WriteCommonAttributes(writer); - - writer.WriteAttributeString("Path", searchTuple.Path); - if (WixFileSearchAttributes.WantExists == (searchTuple.Attributes & WixFileSearchAttributes.WantExists)) - { - writer.WriteAttributeString("Type", "exists"); - } - else if (WixFileSearchAttributes.WantVersion == (searchTuple.Attributes & WixFileSearchAttributes.WantVersion)) - { - // Can never get here for DirectorySearch. - writer.WriteAttributeString("Type", "version"); - } - else - { - writer.WriteAttributeString("Type", "path"); - } - writer.WriteEndElement(); - } - - private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchTuple tuple) - { - writer.WriteStartElement("MsiProductSearch"); - - this.WriteCommonAttributes(writer); - - if (0 != (tuple.Attributes & WixProductSearchAttributes.UpgradeCode)) - { - writer.WriteAttributeString("UpgradeCode", tuple.Guid); - } - else - { - writer.WriteAttributeString("ProductCode", tuple.Guid); - } - - if (0 != (tuple.Attributes & WixProductSearchAttributes.Version)) - { - writer.WriteAttributeString("Type", "version"); - } - else if (0 != (tuple.Attributes & WixProductSearchAttributes.Language)) - { - writer.WriteAttributeString("Type", "language"); - } - else if (0 != (tuple.Attributes & WixProductSearchAttributes.State)) - { - writer.WriteAttributeString("Type", "state"); - } - else if (0 != (tuple.Attributes & WixProductSearchAttributes.Assignment)) - { - writer.WriteAttributeString("Type", "assignment"); - } - - writer.WriteEndElement(); - } - - private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchTuple tuple) - { - writer.WriteStartElement("RegistrySearch"); - - this.WriteCommonAttributes(writer); - - switch (tuple.Root) - { - case RegistryRootType.ClassesRoot: - writer.WriteAttributeString("Root", "HKCR"); - break; - case RegistryRootType.CurrentUser: - writer.WriteAttributeString("Root", "HKCU"); - break; - case RegistryRootType.LocalMachine: - writer.WriteAttributeString("Root", "HKLM"); - break; - case RegistryRootType.Users: - writer.WriteAttributeString("Root", "HKU"); - break; - } - - writer.WriteAttributeString("Key", tuple.Key); - - if (!String.IsNullOrEmpty(tuple.Value)) - { - writer.WriteAttributeString("Value", tuple.Value); - } - - var existenceOnly = 0 != (tuple.Attributes & WixRegistrySearchAttributes.WantExists); - - writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); - - if (0 != (tuple.Attributes & WixRegistrySearchAttributes.Win64)) - { - writer.WriteAttributeString("Win64", "yes"); - } - - if (!existenceOnly) - { - if (0 != (tuple.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) - { - writer.WriteAttributeString("ExpandEnvironment", "yes"); - } - - // We *always* say this is VariableType="string". If we end up - // needing to be more specific, we will have to expand the "Format" - // attribute to allow "number" and "version". - - writer.WriteAttributeString("VariableType", "string"); - } - - writer.WriteEndElement(); - } - } -} diff --git a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs index 78b95bf4..5cff0b5a 100644 --- a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs +++ b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs @@ -22,6 +22,12 @@ namespace WixToolset.Core.Burn.Bundles public const string BurnUXContainerPayloadIdFormat = "p{0}"; public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}"; + public const string BADataFileName = "BootstrapperApplicationData.xml"; + public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; + + public const string BundleExtensionDataFileName = "BundleExtensionData.xml"; + public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData"; + // See WinNT.h for details about the PE format, including the // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, // IMAGE_FILE_HEADER, etc. @@ -167,7 +173,7 @@ namespace WixToolset.Core.Burn.Bundles /// True if initialized. protected bool Initialize(BinaryReader reader) { - if (!GetWixburnSectionInfo(reader)) + if (!this.GetWixburnSectionInfo(reader)) { return false; } diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs index 5cd1f7e8..be8227f2 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs @@ -57,7 +57,7 @@ namespace WixToolset.Core.Burn.Bundles { writer.Formatting = Formatting.Indented; writer.WriteStartDocument(); - writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"); + writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace); this.WriteBundleInfo(writer); @@ -247,11 +247,11 @@ namespace WixToolset.Core.Burn.Bundles private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) { - var generatedId = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); + var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BADataFileName); var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) { - Name = "BootstrapperApplicationData.xml", + Name = BurnCommon.BADataFileName, SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, Compressed = true, UnresolvedSourceFile = baManifestPath, diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs new file mode 100644 index 00000000..b608c03d --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs @@ -0,0 +1,149 @@ +// 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.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; + using WixToolset.Data.Burn; + using WixToolset.Data.Tuples; + + internal class CreateBundleExtensionManifestCommand + { + public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IDictionary> extensionSearchTuplesByExtensionId, int lastUXPayloadIndex, string intermediateFolder) + { + this.Section = section; + this.BundleTuple = bundleTuple; + this.ExtensionSearchTuplesByExtensionId = extensionSearchTuplesByExtensionId; + this.LastUXPayloadIndex = lastUXPayloadIndex; + this.IntermediateFolder = intermediateFolder; + } + + private IntermediateSection Section { get; } + + private WixBundleTuple BundleTuple { get; } + + private IDictionary> ExtensionSearchTuplesByExtensionId { get; } + + private int LastUXPayloadIndex { get; } + + private string IntermediateFolder { get; } + + public WixBundlePayloadTuple BundleExtensionManifestPayloadRow { get; private set; } + + public void Execute() + { + var bextManifestPath = this.CreateBundleExtensionManifest(); + + this.BundleExtensionManifestPayloadRow = this.CreateBundleExtensionManifestPayloadRow(bextManifestPath); + } + + private string CreateBundleExtensionManifest() + { + var path = Path.Combine(this.IntermediateFolder, "wix-bextdata.xml"); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + using (var writer = new XmlTextWriter(path, Encoding.Unicode)) + { + writer.Formatting = Formatting.Indented; + writer.WriteStartDocument(); + writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); + + foreach (var kvp in this.ExtensionSearchTuplesByExtensionId) + { + this.WriteExtension(writer, kvp.Key, kvp.Value); + } + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + + 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); + + var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) + { + Name = BurnCommon.BundleExtensionDataFileName, + SourceFile = new IntermediateFieldPathValue { Path = bextManifestPath }, + Compressed = true, + UnresolvedSourceFile = bextManifestPath, + ContainerRef = BurnConstants.BurnUXContainerName, + EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), + Packaging = PackagingType.Embedded, + }; + + var fileInfo = new FileInfo(bextManifestPath); + + tuple.FileSize = (int)fileInfo.Length; + + tuple.Hash = BundleHashAlgorithm.Hash(fileInfo); + + this.Section.Tuples.Add(tuple); + + return tuple; + } + } +} diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 64a01794..58133d38 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -18,7 +18,7 @@ namespace WixToolset.Core.Burn.Bundles internal class CreateBurnManifestCommand { - public CreateBurnManifestCommand(IMessaging messaging, IEnumerable backendExtensions, string executableName, IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable containers, WixChainTuple chainTuple, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, IEnumerable orderedSearches, IEnumerable catalogs, string intermediateFolder) + public CreateBurnManifestCommand(IMessaging messaging, IEnumerable backendExtensions, string executableName, IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable containers, WixChainTuple chainTuple, IEnumerable orderedPackages, IEnumerable boundaries, IEnumerable uxPayloads, Dictionary allPayloadsById, IEnumerable orderedSearches, IEnumerable catalogs, string intermediateFolder) { this.Messaging = messaging; this.BackendExtensions = backendExtensions; @@ -54,7 +54,7 @@ namespace WixToolset.Core.Burn.Bundles private IEnumerable OrderedPackages { get; } - private IEnumerable OrderedSearches { get; } + private IEnumerable OrderedSearches { get; } private Dictionary Payloads { get; } diff --git a/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs new file mode 100644 index 00000000..55b31ed3 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs @@ -0,0 +1,71 @@ +// 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.Bundles +{ + using System.Collections.Generic; + using System.Linq; + using WixToolset.Data; + using WixToolset.Data.Burn; + using WixToolset.Data.Tuples; + using WixToolset.Extensibility.Services; + + internal class OrderSearchesCommand + { + public OrderSearchesCommand(IMessaging messaging, IntermediateSection section) + { + this.Messaging = messaging; + this.Section = section; + } + + private IMessaging Messaging { get; } + + private IntermediateSection Section { get; } + + public IDictionary> ExtensionSearchTuplesByExtensionId { get; private set; } + + public IList OrderedSearchFacades { get; private set; } + + public void Execute() + { + // TODO: Although the WixSearch tables are defined in the Util extension, + // the Bundle Binder has to know all about them. We hope to revisit all + // of this in the 4.0 timeframe. + var legacySearchesById = this.Section.Tuples + .Where(t => t.Definition.Type == TupleDefinitionType.WixComponentSearch || + t.Definition.Type == TupleDefinitionType.WixFileSearch || + t.Definition.Type == TupleDefinitionType.WixProductSearch || + t.Definition.Type == TupleDefinitionType.WixRegistrySearch) + .ToDictionary(t => t.Id.Id); + var extensionSearchesById = this.Section.Tuples + .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchTupleDefinitionTag)) + .ToDictionary(t => t.Id.Id); + var searchTuples = this.Section.Tuples.OfType().ToList(); + + this.ExtensionSearchTuplesByExtensionId = new Dictionary>(); + this.OrderedSearchFacades = new List(legacySearchesById.Keys.Count + extensionSearchesById.Keys.Count); + + foreach (var searchTuple in searchTuples) + { + if (legacySearchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple)) + { + this.OrderedSearchFacades.Add(new LegacySearchFacade(searchTuple, specificSearchTuple)); + } + else if (extensionSearchesById.TryGetValue(searchTuple.Id.Id, out var extensionSearchTuple)) + { + this.OrderedSearchFacades.Add(new ExtensionSearchFacade(searchTuple)); + + if (!this.ExtensionSearchTuplesByExtensionId.TryGetValue(searchTuple.BundleExtensionRef, out var extensionSearchTuples)) + { + extensionSearchTuples = new List(); + this.ExtensionSearchTuplesByExtensionId[searchTuple.BundleExtensionRef] = extensionSearchTuples; + } + extensionSearchTuples.Add(extensionSearchTuple); + } + else + { + this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchTuple.SourceLineNumbers, searchTuple.Id.Id)); + } + } + } + } +} diff --git a/src/WixToolset.Core.Burn/ISearchFacade.cs b/src/WixToolset.Core.Burn/ISearchFacade.cs new file mode 100644 index 00000000..b9ad8649 --- /dev/null +++ b/src/WixToolset.Core.Burn/ISearchFacade.cs @@ -0,0 +1,15 @@ +// 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; + + internal interface ISearchFacade + { + /// + /// Writes the search to the Burn manifest. + /// + /// + void WriteXml(XmlTextWriter writer); + } +} -- cgit v1.2.3-55-g6feb