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 ++ src/WixToolset.Core.TestPackage/BundleExtractor.cs | 34 ++++ .../ExtractBAContainerResult.cs | 32 ++++ src/WixToolset.Core/CompilerCore.cs | 5 + .../ExtensibilityServices/ParseHelper.cs | 37 ++++ src/test/Example.Extension/Data/example.wxs | 3 + .../Example.Extension/ExampleCompilerExtension.cs | 104 +++++++++++ src/test/Example.Extension/ExampleExtensionData.cs | 6 +- src/test/Example.Extension/ExampleSearchTuple.cs | 31 ++++ .../Example.Extension/ExampleTupleDefinitions.cs | 17 +- .../BundleManifestFixture.cs | 56 ++++++ .../BundleExtension/BundleExtensionSearches.wxs | 8 + .../BundleExtension/BundleWithSearches.wxs | 11 ++ .../WixToolsetTest.CoreIntegration.csproj | 2 + 24 files changed, 842 insertions(+), 231 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 create mode 100644 src/test/Example.Extension/ExampleSearchTuple.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs (limited to 'src') 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); + } +} diff --git a/src/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/WixToolset.Core.TestPackage/BundleExtractor.cs index 3d7b2932..8a56f117 100644 --- a/src/WixToolset.Core.TestPackage/BundleExtractor.cs +++ b/src/WixToolset.Core.TestPackage/BundleExtractor.cs @@ -22,11 +22,31 @@ namespace WixToolset.Core.TestPackage { result.ManifestDocument = LoadBurnManifest(destinationFolderPath); result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); + + result.BADataDocument = LoadBAData(destinationFolderPath); + result.BADataNamespaceManager = GetBADataNamespaceManager(result.BADataDocument, "ba"); + + result.BundleExtensionDataDocument = LoadBundleExtensionData(destinationFolderPath); + result.BundleExtensionDataNamespaceManager = GetBundleExtensionDataNamespaceManager(result.BundleExtensionDataDocument, "be"); } return result; } + public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace); + return namespaceManager; + } + + public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace); + return namespaceManager; + } + public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) { var namespaceManager = new XmlNamespaceManager(document.NameTable); @@ -34,6 +54,20 @@ namespace WixToolset.Core.TestPackage return namespaceManager; } + public static XmlDocument LoadBAData(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, BurnCommon.BADataFileName)); + return document; + } + + public static XmlDocument LoadBundleExtensionData(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, BurnCommon.BundleExtensionDataFileName)); + return document; + } + public static XmlDocument LoadBurnManifest(string baFolderPath) { var document = new XmlDocument(); diff --git a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs index 6d2ea943..63d7bb31 100644 --- a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs +++ b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs @@ -8,6 +8,10 @@ namespace WixToolset.Core.TestPackage public class ExtractBAContainerResult { + public XmlDocument BundleExtensionDataDocument { get; set; } + public XmlNamespaceManager BundleExtensionDataNamespaceManager { get; set; } + public XmlDocument BADataDocument { get; set; } + public XmlNamespaceManager BADataNamespaceManager { get; set; } public XmlDocument ManifestDocument { get; set; } public XmlNamespaceManager ManifestNamespaceManager { get; set; } public bool Success { get; set; } @@ -26,6 +30,34 @@ namespace WixToolset.Core.TestPackage return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); } + public string GetBundleExtensionFilePath(string extractedBAContainerFolderPath, string extensionId) + { + var uxPayloads = this.SelectManifestNodes($"/burn:BurnManifest/burn:UX/burn:Payload[@Id='{extensionId}']"); + var bextPayload = uxPayloads[0]; + var relativeBextPath = bextPayload.Attributes["FilePath"].Value; + return Path.Combine(extractedBAContainerFolderPath, relativeBextPath); + } + + /// + /// + /// + /// elements must have the 'ba' prefix + /// + public XmlNodeList SelectBADataNodes(string xpath) + { + return this.BADataDocument.SelectNodes(xpath, this.BADataNamespaceManager); + } + + /// + /// + /// + /// elements must have the 'be' prefix + /// + public XmlNodeList SelectBundleExtensionDataNodes(string xpath) + { + return this.BundleExtensionDataDocument.SelectNodes(xpath, this.BundleExtensionDataNamespaceManager); + } + /// /// /// diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index e87ad886..51828975 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs @@ -1026,6 +1026,11 @@ namespace WixToolset.Core return this.parseHelper.CreateDirectoryTuple(this.ActiveSection, sourceLineNumbers, id, parentId, name, this.activeSectionInlinedDirectoryIds, shortName, sourceName, shortSourceName); } + public void CreateWixSearchTuple(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) + { + this.parseHelper.CreateWixSearchTuple(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); + } + /// /// Gets the attribute value as inline directory syntax. /// diff --git a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs index 7447d420..2a851a21 100644 --- a/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs +++ b/src/WixToolset.Core/ExtensibilityServices/ParseHelper.cs @@ -260,6 +260,43 @@ namespace WixToolset.Core.ExtensibilityServices section.Tuples.Add(tuple); } + public void CreateWixSearchTuple(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId) + { + // TODO: verify variable is not a standard bundle variable + if (variable == null) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable")); + } + + section.Tuples.Add(new WixSearchTuple(sourceLineNumbers, id) + { + Variable = variable, + Condition = condition, + BundleExtensionRef = bundleExtensionId, + }); + + if (after != null) + { + this.CreateSimpleReference(section, sourceLineNumbers, "WixSearch", after); + // TODO: We're currently defaulting to "always run after", which we will need to change... + this.CreateWixSearchRelationTuple(section, sourceLineNumbers, id, after, 2); + } + + if (!String.IsNullOrEmpty(bundleExtensionId)) + { + this.CreateSimpleReference(section, sourceLineNumbers, "WixBundleExtension", bundleExtensionId); + } + } + + public void CreateWixSearchRelationTuple(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, int attributes) + { + section.Tuples.Add(new WixSearchRelationTuple(sourceLineNumbers, id) + { + ParentSearchRef = parentId, + Attributes = attributes, + }); + } + [Obsolete] public IntermediateTuple CreateRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName, Identifier identifier = null) { diff --git a/src/test/Example.Extension/Data/example.wxs b/src/test/Example.Extension/Data/example.wxs index cb100adf..cd17d478 100644 --- a/src/test/Example.Extension/Data/example.wxs +++ b/src/test/Example.Extension/Data/example.wxs @@ -8,4 +8,7 @@ + + + diff --git a/src/test/Example.Extension/ExampleCompilerExtension.cs b/src/test/Example.Extension/ExampleCompilerExtension.cs index 5efb428f..543b4165 100644 --- a/src/test/Example.Extension/ExampleCompilerExtension.cs +++ b/src/test/Example.Extension/ExampleCompilerExtension.cs @@ -11,6 +11,7 @@ namespace Example.Extension internal class ExampleCompilerExtension : BaseCompilerExtension { public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs"; + public string BundleExtensionId => "ExampleBundleExtension"; public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context) { @@ -18,6 +19,20 @@ namespace Example.Extension switch (parentElement.Name.LocalName) { + case "Bundle": + case "Fragment": + switch (element.Name.LocalName) + { + case "ExampleSearch": + this.ParseExampleSearchElement(intermediate, section, element); + processed = true; + break; + case "ExampleSearchRef": + this.ParseExampleSearchRefElement(intermediate, section, element); + processed = true; + break; + } + break; case "Component": switch (element.Name.LocalName) { @@ -77,5 +92,94 @@ namespace Example.Extension tuple.Set(1, value); } } + + private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + Identifier id = null; + string searchFor = null; + string variable = null; + string condition = null; + string after = null; + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); + break; + case "Variable": + variable = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "Condition": + condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "After": + after = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + case "SearchFor": + searchFor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); + break; + + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseAttribute(intermediate, section, element, attrib, null); + } + } + + if (null == id) + { + this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); + } + + if (!this.Messaging.EncounteredError) + { + this.ParseHelper.CreateWixSearchTuple(section, sourceLineNumbers, element.Name.LocalName, id, variable, condition, after, this.BundleExtensionId); + } + + if (!this.Messaging.EncounteredError) + { + + var tuple = new ExampleSearchTuple(sourceLineNumbers, id); + section.Tuples.Add(tuple); + tuple.SearchFor = searchFor; + } + } + + private void ParseExampleSearchRefElement(Intermediate intermediate, IntermediateSection section, XElement element) + { + var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); + + foreach (var attrib in element.Attributes()) + { + if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) + { + switch (attrib.Name.LocalName) + { + case "Id": + var refId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); + this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "ExampleSearch", refId); + break; + default: + this.ParseHelper.UnexpectedAttribute(element, attrib); + break; + } + } + else + { + this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); + } + } + + this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); + } } } diff --git a/src/test/Example.Extension/ExampleExtensionData.cs b/src/test/Example.Extension/ExampleExtensionData.cs index de0b8899..b38eb2a2 100644 --- a/src/test/Example.Extension/ExampleExtensionData.cs +++ b/src/test/Example.Extension/ExampleExtensionData.cs @@ -1,4 +1,4 @@ -// 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. +// 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 Example.Extension { @@ -22,6 +22,10 @@ namespace Example.Extension tupleDefinition = ExampleTupleDefinitions.Example; break; + case "ExampleSearch": + tupleDefinition = ExampleTupleDefinitions.ExampleSearch; + break; + default: tupleDefinition = null; break; diff --git a/src/test/Example.Extension/ExampleSearchTuple.cs b/src/test/Example.Extension/ExampleSearchTuple.cs new file mode 100644 index 00000000..df34f0af --- /dev/null +++ b/src/test/Example.Extension/ExampleSearchTuple.cs @@ -0,0 +1,31 @@ +// 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 Example.Extension +{ + using WixToolset.Data; + + public enum ExampleSearchTupleFields + { + Example, + SearchFor, + } + + public class ExampleSearchTuple : IntermediateTuple + { + public ExampleSearchTuple() : base(ExampleTupleDefinitions.ExampleSearch, null, null) + { + } + + public ExampleSearchTuple(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleTupleDefinitions.ExampleSearch, sourceLineNumber, id) + { + } + + public IntermediateField this[ExampleTupleFields index] => this.Fields[(int)index]; + + public string SearchFor + { + get => this.Fields[(int)ExampleSearchTupleFields.SearchFor]?.AsString(); + set => this.Set((int)ExampleSearchTupleFields.SearchFor, value); + } + } +} diff --git a/src/test/Example.Extension/ExampleTupleDefinitions.cs b/src/test/Example.Extension/ExampleTupleDefinitions.cs index 4775b827..b2c8c484 100644 --- a/src/test/Example.Extension/ExampleTupleDefinitions.cs +++ b/src/test/Example.Extension/ExampleTupleDefinitions.cs @@ -1,8 +1,9 @@ -// 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. +// 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 Example.Extension { using WixToolset.Data; + using WixToolset.Data.Burn; public static class ExampleTupleDefinitions { @@ -16,5 +17,19 @@ namespace Example.Extension new IntermediateFieldDefinition(nameof(ExampleTupleFields.Value), IntermediateFieldType.String), }, typeof(ExampleTuple)); + + public static readonly IntermediateTupleDefinition ExampleSearch = new IntermediateTupleDefinition( + nameof(ExampleSearch), + new[] + { + new IntermediateFieldDefinition(nameof(ExampleTupleFields.Example), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(ExampleSearchTupleFields.SearchFor), IntermediateFieldType.String), + }, + typeof(ExampleSearchTuple)); + + static ExampleTupleDefinitions() + { + ExampleSearch.AddTag(BurnConstants.BundleExtensionSearchTupleDefinitionTag); + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs index da4482ff..80f7b875 100644 --- a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -2,8 +2,10 @@ namespace WixToolsetTest.CoreIntegration { + using System; using System.Collections.Generic; using System.IO; + using Example.Extension; using WixBuildTools.TestSupport; using WixToolset.Core.TestPackage; using Xunit; @@ -57,5 +59,59 @@ namespace WixToolsetTest.CoreIntegration Assert.Equal("", bundleExtensionPayloads[0].GetTestXml(ignored)); } } + + [Fact] + public void PopulatesManifestWithBundleExtensionSearches() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); + var folder = TestData.Get(@"TestData"); + + using (var fs = new DisposableFileSystem()) + { + var baseFolder = fs.GetFolder(); + var intermediateFolder = Path.Combine(baseFolder, "obj"); + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); + var baFolderPath = Path.Combine(baseFolder, "ba"); + var extractFolderPath = Path.Combine(baseFolder, "extract"); + + var result = WixRunner.Execute(new[] + { + "build", + Path.Combine(folder, "BundleExtension", "BundleExtensionSearches.wxs"), + Path.Combine(folder, "BundleExtension", "BundleWithSearches.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-ext", extensionPath, + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), + "-intermediateFolder", intermediateFolder, + "-burnStub", burnStubPath, + "-o", bundlePath + }); + + result.AssertSuccess(); + + Assert.True(File.Exists(bundlePath)); + + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); + extractResult.AssertSuccess(); + + var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); + Assert.Equal(1, bundleExtensions.Count); + Assert.Equal("", bundleExtensions[0].GetTestXml()); + + var extensionSearches = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:ExtensionSearch"); + Assert.Equal(2, extensionSearches.Count); + Assert.Equal("", extensionSearches[0].GetTestXml()); + Assert.Equal("", extensionSearches[1].GetTestXml()); + + var bundleExtensionDatas = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']"); + Assert.Equal(1, bundleExtensionDatas.Count); + Assert.Equal("" + + "" + + "" + + "", bundleExtensionDatas[0].GetTestXml()); + } + } } } diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs new file mode 100644 index 00000000..fd8d3698 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs @@ -0,0 +1,8 @@ + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs new file mode 100644 index 00000000..c5a93eb3 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 85538b79..324d04ff 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -22,6 +22,8 @@ + + -- cgit v1.2.3-55-g6feb