diff options
| author | Sean Hall <r.sean.hall@gmail.com> | 2020-03-27 13:54:56 +1000 |
|---|---|---|
| committer | Sean Hall <r.sean.hall@gmail.com> | 2020-03-30 21:30:04 +1000 |
| commit | 0baf6e26ec7ab2ff0b6ad36e9d44f3d68819b5d6 (patch) | |
| tree | 1ef577d0246b662f4a8bda0ed935e987f03562a0 /src | |
| parent | afbc6889c73d58136cb8851858ca3c17f41dc2c5 (diff) | |
| download | wix-0baf6e26ec7ab2ff0b6ad36e9d44f3d68819b5d6.tar.gz wix-0baf6e26ec7ab2ff0b6ad36e9d44f3d68819b5d6.tar.bz2 wix-0baf6e26ec7ab2ff0b6ad36e9d44f3d68819b5d6.zip | |
Add ability for extensions to create custom bundle searches.
This required creating BundleExtensionData.xml.
Diffstat (limited to 'src')
23 files changed, 688 insertions, 77 deletions
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 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml; | ||
| 7 | using WixToolset.Data.Tuples; | ||
| 8 | |||
| 9 | internal abstract class BaseSearchFacade : ISearchFacade | ||
| 10 | { | ||
| 11 | protected WixSearchTuple SearchTuple { get; set; } | ||
| 12 | |||
| 13 | public virtual void WriteXml(XmlTextWriter writer) | ||
| 14 | { | ||
| 15 | writer.WriteAttributeString("Id", this.SearchTuple.Id.Id); | ||
| 16 | writer.WriteAttributeString("Variable", this.SearchTuple.Variable); | ||
| 17 | if (!String.IsNullOrEmpty(this.SearchTuple.Condition)) | ||
| 18 | { | ||
| 19 | writer.WriteAttributeString("Condition", this.SearchTuple.Condition); | ||
| 20 | } | ||
| 21 | if (!String.IsNullOrEmpty(this.SearchTuple.BundleExtensionRef)) | ||
| 22 | { | ||
| 23 | writer.WriteAttributeString("ExtensionId", this.SearchTuple.BundleExtensionRef); | ||
| 24 | } | ||
| 25 | } | ||
| 26 | } | ||
| 27 | } | ||
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 | |||
| 117 | // If there are any fields to resolve later, create the cache to populate during bind. | 117 | // If there are any fields to resolve later, create the cache to populate during bind. |
| 118 | var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null; | 118 | var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null; |
| 119 | 119 | ||
| 120 | // TODO: Although the WixSearch tables are defined in the Util extension, | 120 | var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section); |
| 121 | // the Bundle Binder has to know all about them. We hope to revisit all | 121 | orderSearchesCommand.Execute(); |
| 122 | // of this in the 4.0 timeframe. | 122 | var orderedSearches = orderSearchesCommand.OrderedSearchFacades; |
| 123 | var orderedSearches = this.OrderSearches(section); | 123 | var extensionSearchTuplesById = orderSearchesCommand.ExtensionSearchTuplesByExtensionId; |
| 124 | 124 | ||
| 125 | // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). | 125 | // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules). |
| 126 | { | 126 | { |
| @@ -387,6 +387,17 @@ namespace WixToolset.Core.Burn | |||
| 387 | 387 | ||
| 388 | var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; | 388 | var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; |
| 389 | payloadTuples.Add(baManifestPayload.Id.Id, baManifestPayload); | 389 | payloadTuples.Add(baManifestPayload.Id.Id, baManifestPayload); |
| 390 | ++uxPayloadIndex; | ||
| 391 | } | ||
| 392 | |||
| 393 | // Generate the bundle extension manifest... | ||
| 394 | { | ||
| 395 | var command = new CreateBundleExtensionManifestCommand(section, bundleTuple, extensionSearchTuplesById, uxPayloadIndex, this.IntermediateFolder); | ||
| 396 | command.Execute(); | ||
| 397 | |||
| 398 | var bextManifestPayload = command.BundleExtensionManifestPayloadRow; | ||
| 399 | payloadTuples.Add(bextManifestPayload.Id.Id, bextManifestPayload); | ||
| 400 | ++uxPayloadIndex; | ||
| 390 | } | 401 | } |
| 391 | 402 | ||
| 392 | #if TODO | 403 | #if TODO |
| @@ -464,28 +475,6 @@ namespace WixToolset.Core.Burn | |||
| 464 | trackedFiles.Add(trackIntermediate); | 475 | trackedFiles.Add(trackIntermediate); |
| 465 | } | 476 | } |
| 466 | 477 | ||
| 467 | private IEnumerable<SearchFacade> OrderSearches(IntermediateSection section) | ||
| 468 | { | ||
| 469 | var searchesById = section.Tuples | ||
| 470 | .Where(t => t.Definition.Type == TupleDefinitionType.WixComponentSearch || | ||
| 471 | t.Definition.Type == TupleDefinitionType.WixFileSearch || | ||
| 472 | t.Definition.Type == TupleDefinitionType.WixProductSearch || | ||
| 473 | t.Definition.Type == TupleDefinitionType.WixRegistrySearch) | ||
| 474 | .ToDictionary(t => t.Id.Id); | ||
| 475 | |||
| 476 | var orderedSearches = new List<SearchFacade>(searchesById.Keys.Count); | ||
| 477 | |||
| 478 | foreach (var searchTuple in section.Tuples.OfType<WixSearchTuple>()) | ||
| 479 | { | ||
| 480 | if (searchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple)) | ||
| 481 | { | ||
| 482 | orderedSearches.Add(new SearchFacade(searchTuple, specificSearchTuple)); | ||
| 483 | } | ||
| 484 | } | ||
| 485 | |||
| 486 | return orderedSearches; | ||
| 487 | } | ||
| 488 | |||
| 489 | /// <summary> | 478 | /// <summary> |
| 490 | /// Populates the variable cache with specific package properties. | 479 | /// Populates the variable cache with specific package properties. |
| 491 | /// </summary> | 480 | /// </summary> |
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 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System.Xml; | ||
| 6 | using WixToolset.Data.Tuples; | ||
| 7 | |||
| 8 | internal class ExtensionSearchFacade : BaseSearchFacade | ||
| 9 | { | ||
| 10 | public ExtensionSearchFacade(WixSearchTuple searchTuple) | ||
| 11 | { | ||
| 12 | this.SearchTuple = searchTuple; | ||
| 13 | } | ||
| 14 | |||
| 15 | public override void WriteXml(XmlTextWriter writer) | ||
| 16 | { | ||
| 17 | writer.WriteStartElement("ExtensionSearch"); | ||
| 18 | |||
| 19 | base.WriteXml(writer); | ||
| 20 | |||
| 21 | writer.WriteEndElement(); | ||
| 22 | } | ||
| 23 | } | ||
| 24 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bind/SearchFacade.cs b/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs index 65f3cb5b..0a80760d 100644 --- a/src/WixToolset.Core.Burn/Bind/SearchFacade.cs +++ b/src/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs | |||
| @@ -7,48 +7,36 @@ namespace WixToolset.Core.Burn | |||
| 7 | using WixToolset.Data; | 7 | using WixToolset.Data; |
| 8 | using WixToolset.Data.Tuples; | 8 | using WixToolset.Data.Tuples; |
| 9 | 9 | ||
| 10 | internal class SearchFacade | 10 | internal class LegacySearchFacade : BaseSearchFacade |
| 11 | { | 11 | { |
| 12 | public SearchFacade(WixSearchTuple searchTuple, IntermediateTuple searchSpecificTuple) | 12 | public LegacySearchFacade(WixSearchTuple searchTuple, IntermediateTuple searchSpecificTuple) |
| 13 | { | 13 | { |
| 14 | this.SearchTuple = searchTuple; | 14 | this.SearchTuple = searchTuple; |
| 15 | this.SearchSpecificTuple = searchSpecificTuple; | 15 | this.SearchSpecificTuple = searchSpecificTuple; |
| 16 | } | 16 | } |
| 17 | 17 | ||
| 18 | public WixSearchTuple SearchTuple { get; } | ||
| 19 | |||
| 20 | public IntermediateTuple SearchSpecificTuple { get; } | 18 | public IntermediateTuple SearchSpecificTuple { get; } |
| 21 | 19 | ||
| 22 | /// <summary> | 20 | /// <summary> |
| 23 | /// Generates Burn manifest and ParameterInfo-style markup a search. | 21 | /// Generates Burn manifest and ParameterInfo-style markup a search. |
| 24 | /// </summary> | 22 | /// </summary> |
| 25 | /// <param name="writer"></param> | 23 | /// <param name="writer"></param> |
| 26 | public void WriteXml(XmlTextWriter writer) | 24 | public override void WriteXml(XmlTextWriter writer) |
| 27 | { | 25 | { |
| 28 | switch (this.SearchSpecificTuple) | 26 | switch (this.SearchSpecificTuple) |
| 29 | { | 27 | { |
| 30 | case WixComponentSearchTuple tuple: | 28 | case WixComponentSearchTuple tuple: |
| 31 | this.WriteComponentSearchXml(writer, tuple); | 29 | this.WriteComponentSearchXml(writer, tuple); |
| 32 | break; | 30 | break; |
| 33 | case WixFileSearchTuple tuple: | 31 | case WixFileSearchTuple tuple: |
| 34 | this.WriteFileSearchXml(writer, tuple); | 32 | this.WriteFileSearchXml(writer, tuple); |
| 35 | break; | 33 | break; |
| 36 | case WixProductSearchTuple tuple: | 34 | case WixProductSearchTuple tuple: |
| 37 | this.WriteProductSearchXml(writer, tuple); | 35 | this.WriteProductSearchXml(writer, tuple); |
| 38 | break; | 36 | break; |
| 39 | case WixRegistrySearchTuple tuple: | 37 | case WixRegistrySearchTuple tuple: |
| 40 | this.WriteRegistrySearchXml(writer, tuple); | 38 | this.WriteRegistrySearchXml(writer, tuple); |
| 41 | break; | 39 | break; |
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | private void WriteCommonAttributes(XmlTextWriter writer) | ||
| 46 | { | ||
| 47 | writer.WriteAttributeString("Id", this.SearchTuple.Id.Id); | ||
| 48 | writer.WriteAttributeString("Variable", this.SearchTuple.Variable); | ||
| 49 | if (!String.IsNullOrEmpty(this.SearchTuple.Condition)) | ||
| 50 | { | ||
| 51 | writer.WriteAttributeString("Condition", this.SearchTuple.Condition); | ||
| 52 | } | 40 | } |
| 53 | } | 41 | } |
| 54 | 42 | ||
| @@ -56,7 +44,7 @@ namespace WixToolset.Core.Burn | |||
| 56 | { | 44 | { |
| 57 | writer.WriteStartElement("MsiComponentSearch"); | 45 | writer.WriteStartElement("MsiComponentSearch"); |
| 58 | 46 | ||
| 59 | this.WriteCommonAttributes(writer); | 47 | base.WriteXml(writer); |
| 60 | 48 | ||
| 61 | writer.WriteAttributeString("ComponentId", searchTuple.Guid); | 49 | writer.WriteAttributeString("ComponentId", searchTuple.Guid); |
| 62 | 50 | ||
| @@ -85,7 +73,7 @@ namespace WixToolset.Core.Burn | |||
| 85 | { | 73 | { |
| 86 | writer.WriteStartElement((0 == (searchTuple.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); | 74 | writer.WriteStartElement((0 == (searchTuple.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); |
| 87 | 75 | ||
| 88 | this.WriteCommonAttributes(writer); | 76 | base.WriteXml(writer); |
| 89 | 77 | ||
| 90 | writer.WriteAttributeString("Path", searchTuple.Path); | 78 | writer.WriteAttributeString("Path", searchTuple.Path); |
| 91 | if (WixFileSearchAttributes.WantExists == (searchTuple.Attributes & WixFileSearchAttributes.WantExists)) | 79 | if (WixFileSearchAttributes.WantExists == (searchTuple.Attributes & WixFileSearchAttributes.WantExists)) |
| @@ -108,7 +96,7 @@ namespace WixToolset.Core.Burn | |||
| 108 | { | 96 | { |
| 109 | writer.WriteStartElement("MsiProductSearch"); | 97 | writer.WriteStartElement("MsiProductSearch"); |
| 110 | 98 | ||
| 111 | this.WriteCommonAttributes(writer); | 99 | base.WriteXml(writer); |
| 112 | 100 | ||
| 113 | if (0 != (tuple.Attributes & WixProductSearchAttributes.UpgradeCode)) | 101 | if (0 != (tuple.Attributes & WixProductSearchAttributes.UpgradeCode)) |
| 114 | { | 102 | { |
| @@ -143,22 +131,22 @@ namespace WixToolset.Core.Burn | |||
| 143 | { | 131 | { |
| 144 | writer.WriteStartElement("RegistrySearch"); | 132 | writer.WriteStartElement("RegistrySearch"); |
| 145 | 133 | ||
| 146 | this.WriteCommonAttributes(writer); | 134 | base.WriteXml(writer); |
| 147 | 135 | ||
| 148 | switch (tuple.Root) | 136 | switch (tuple.Root) |
| 149 | { | 137 | { |
| 150 | case RegistryRootType.ClassesRoot: | 138 | case RegistryRootType.ClassesRoot: |
| 151 | writer.WriteAttributeString("Root", "HKCR"); | 139 | writer.WriteAttributeString("Root", "HKCR"); |
| 152 | break; | 140 | break; |
| 153 | case RegistryRootType.CurrentUser: | 141 | case RegistryRootType.CurrentUser: |
| 154 | writer.WriteAttributeString("Root", "HKCU"); | 142 | writer.WriteAttributeString("Root", "HKCU"); |
| 155 | break; | 143 | break; |
| 156 | case RegistryRootType.LocalMachine: | 144 | case RegistryRootType.LocalMachine: |
| 157 | writer.WriteAttributeString("Root", "HKLM"); | 145 | writer.WriteAttributeString("Root", "HKLM"); |
| 158 | break; | 146 | break; |
| 159 | case RegistryRootType.Users: | 147 | case RegistryRootType.Users: |
| 160 | writer.WriteAttributeString("Root", "HKU"); | 148 | writer.WriteAttributeString("Root", "HKU"); |
| 161 | break; | 149 | break; |
| 162 | } | 150 | } |
| 163 | 151 | ||
| 164 | writer.WriteAttributeString("Key", tuple.Key); | 152 | writer.WriteAttributeString("Key", tuple.Key); |
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 | |||
| 22 | public const string BurnUXContainerPayloadIdFormat = "p{0}"; | 22 | public const string BurnUXContainerPayloadIdFormat = "p{0}"; |
| 23 | public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}"; | 23 | public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}"; |
| 24 | 24 | ||
| 25 | public const string BADataFileName = "BootstrapperApplicationData.xml"; | ||
| 26 | public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"; | ||
| 27 | |||
| 28 | public const string BundleExtensionDataFileName = "BundleExtensionData.xml"; | ||
| 29 | public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData"; | ||
| 30 | |||
| 25 | // See WinNT.h for details about the PE format, including the | 31 | // See WinNT.h for details about the PE format, including the |
| 26 | // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, | 32 | // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, |
| 27 | // IMAGE_FILE_HEADER, etc. | 33 | // IMAGE_FILE_HEADER, etc. |
| @@ -167,7 +173,7 @@ namespace WixToolset.Core.Burn.Bundles | |||
| 167 | /// <returns>True if initialized.</returns> | 173 | /// <returns>True if initialized.</returns> |
| 168 | protected bool Initialize(BinaryReader reader) | 174 | protected bool Initialize(BinaryReader reader) |
| 169 | { | 175 | { |
| 170 | if (!GetWixburnSectionInfo(reader)) | 176 | if (!this.GetWixburnSectionInfo(reader)) |
| 171 | { | 177 | { |
| 172 | return false; | 178 | return false; |
| 173 | } | 179 | } |
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 | |||
| 57 | { | 57 | { |
| 58 | writer.Formatting = Formatting.Indented; | 58 | writer.Formatting = Formatting.Indented; |
| 59 | writer.WriteStartDocument(); | 59 | writer.WriteStartDocument(); |
| 60 | writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData"); | 60 | writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace); |
| 61 | 61 | ||
| 62 | this.WriteBundleInfo(writer); | 62 | this.WriteBundleInfo(writer); |
| 63 | 63 | ||
| @@ -247,11 +247,11 @@ namespace WixToolset.Core.Burn.Bundles | |||
| 247 | 247 | ||
| 248 | private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) | 248 | private WixBundlePayloadTuple CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) |
| 249 | { | 249 | { |
| 250 | var generatedId = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); | 250 | var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BADataFileName); |
| 251 | 251 | ||
| 252 | var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) | 252 | var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) |
| 253 | { | 253 | { |
| 254 | Name = "BootstrapperApplicationData.xml", | 254 | Name = BurnCommon.BADataFileName, |
| 255 | SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, | 255 | SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, |
| 256 | Compressed = true, | 256 | Compressed = true, |
| 257 | UnresolvedSourceFile = baManifestPath, | 257 | 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 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using System.Text; | ||
| 12 | using System.Xml; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.Burn; | ||
| 15 | using WixToolset.Data.Tuples; | ||
| 16 | |||
| 17 | internal class CreateBundleExtensionManifestCommand | ||
| 18 | { | ||
| 19 | public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleTuple bundleTuple, IDictionary<string, IList<IntermediateTuple>> extensionSearchTuplesByExtensionId, int lastUXPayloadIndex, string intermediateFolder) | ||
| 20 | { | ||
| 21 | this.Section = section; | ||
| 22 | this.BundleTuple = bundleTuple; | ||
| 23 | this.ExtensionSearchTuplesByExtensionId = extensionSearchTuplesByExtensionId; | ||
| 24 | this.LastUXPayloadIndex = lastUXPayloadIndex; | ||
| 25 | this.IntermediateFolder = intermediateFolder; | ||
| 26 | } | ||
| 27 | |||
| 28 | private IntermediateSection Section { get; } | ||
| 29 | |||
| 30 | private WixBundleTuple BundleTuple { get; } | ||
| 31 | |||
| 32 | private IDictionary<string, IList<IntermediateTuple>> ExtensionSearchTuplesByExtensionId { get; } | ||
| 33 | |||
| 34 | private int LastUXPayloadIndex { get; } | ||
| 35 | |||
| 36 | private string IntermediateFolder { get; } | ||
| 37 | |||
| 38 | public WixBundlePayloadTuple BundleExtensionManifestPayloadRow { get; private set; } | ||
| 39 | |||
| 40 | public void Execute() | ||
| 41 | { | ||
| 42 | var bextManifestPath = this.CreateBundleExtensionManifest(); | ||
| 43 | |||
| 44 | this.BundleExtensionManifestPayloadRow = this.CreateBundleExtensionManifestPayloadRow(bextManifestPath); | ||
| 45 | } | ||
| 46 | |||
| 47 | private string CreateBundleExtensionManifest() | ||
| 48 | { | ||
| 49 | var path = Path.Combine(this.IntermediateFolder, "wix-bextdata.xml"); | ||
| 50 | |||
| 51 | Directory.CreateDirectory(Path.GetDirectoryName(path)); | ||
| 52 | |||
| 53 | using (var writer = new XmlTextWriter(path, Encoding.Unicode)) | ||
| 54 | { | ||
| 55 | writer.Formatting = Formatting.Indented; | ||
| 56 | writer.WriteStartDocument(); | ||
| 57 | writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace); | ||
| 58 | |||
| 59 | foreach (var kvp in this.ExtensionSearchTuplesByExtensionId) | ||
| 60 | { | ||
| 61 | this.WriteExtension(writer, kvp.Key, kvp.Value); | ||
| 62 | } | ||
| 63 | |||
| 64 | writer.WriteEndElement(); | ||
| 65 | writer.WriteEndDocument(); | ||
| 66 | } | ||
| 67 | |||
| 68 | return path; | ||
| 69 | } | ||
| 70 | |||
| 71 | private void WriteExtension(XmlTextWriter writer, string extensionId, IEnumerable<IntermediateTuple> tuples) | ||
| 72 | { | ||
| 73 | writer.WriteStartElement("BundleExtension"); | ||
| 74 | |||
| 75 | writer.WriteAttributeString("Id", extensionId); | ||
| 76 | |||
| 77 | this.WriteBundleExtensionDataTuples(writer, tuples); | ||
| 78 | |||
| 79 | writer.WriteEndElement(); | ||
| 80 | } | ||
| 81 | |||
| 82 | private void WriteBundleExtensionDataTuples(XmlTextWriter writer, IEnumerable<IntermediateTuple> tuples) | ||
| 83 | { | ||
| 84 | var dataTuplesGroupedByDefinitionName = tuples.GroupBy(t => t.Definition); | ||
| 85 | |||
| 86 | foreach (var group in dataTuplesGroupedByDefinitionName) | ||
| 87 | { | ||
| 88 | var definition = group.Key; | ||
| 89 | |||
| 90 | // We simply assert that the table (and field) name is valid, because | ||
| 91 | // this is up to the extension developer to get right. An author will | ||
| 92 | // only affect the attribute value, and that will get properly escaped. | ||
| 93 | #if DEBUG | ||
| 94 | Debug.Assert(Common.IsIdentifier(definition.Name)); | ||
| 95 | foreach (var fieldDef in definition.FieldDefinitions) | ||
| 96 | { | ||
| 97 | Debug.Assert(Common.IsIdentifier(fieldDef.Name)); | ||
| 98 | } | ||
| 99 | #endif // DEBUG | ||
| 100 | |||
| 101 | foreach (var tuple in group) | ||
| 102 | { | ||
| 103 | writer.WriteStartElement(definition.Name); | ||
| 104 | |||
| 105 | if (tuple.Id != null) | ||
| 106 | { | ||
| 107 | writer.WriteAttributeString("Id", tuple.Id.Id); | ||
| 108 | } | ||
| 109 | |||
| 110 | foreach (var field in tuple.Fields) | ||
| 111 | { | ||
| 112 | if (!field.IsNull()) | ||
| 113 | { | ||
| 114 | writer.WriteAttributeString(field.Definition.Name, field.AsString()); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | writer.WriteEndElement(); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | private WixBundlePayloadTuple CreateBundleExtensionManifestPayloadRow(string bextManifestPath) | ||
| 124 | { | ||
| 125 | var generatedId = Common.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName); | ||
| 126 | |||
| 127 | var tuple = new WixBundlePayloadTuple(this.BundleTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, generatedId)) | ||
| 128 | { | ||
| 129 | Name = BurnCommon.BundleExtensionDataFileName, | ||
| 130 | SourceFile = new IntermediateFieldPathValue { Path = bextManifestPath }, | ||
| 131 | Compressed = true, | ||
| 132 | UnresolvedSourceFile = bextManifestPath, | ||
| 133 | ContainerRef = BurnConstants.BurnUXContainerName, | ||
| 134 | EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), | ||
| 135 | Packaging = PackagingType.Embedded, | ||
| 136 | }; | ||
| 137 | |||
| 138 | var fileInfo = new FileInfo(bextManifestPath); | ||
| 139 | |||
| 140 | tuple.FileSize = (int)fileInfo.Length; | ||
| 141 | |||
| 142 | tuple.Hash = BundleHashAlgorithm.Hash(fileInfo); | ||
| 143 | |||
| 144 | this.Section.Tuples.Add(tuple); | ||
| 145 | |||
| 146 | return tuple; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
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 | |||
| 18 | 18 | ||
| 19 | internal class CreateBurnManifestCommand | 19 | internal class CreateBurnManifestCommand |
| 20 | { | 20 | { |
| 21 | public CreateBurnManifestCommand(IMessaging messaging, IEnumerable<IBurnBackendExtension> backendExtensions, string executableName, IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<WixBundleContainerTuple> containers, WixChainTuple chainTuple, IEnumerable<PackageFacade> orderedPackages, IEnumerable<WixBundleRollbackBoundaryTuple> boundaries, IEnumerable<WixBundlePayloadTuple> uxPayloads, Dictionary<string, WixBundlePayloadTuple> allPayloadsById, IEnumerable<SearchFacade> orderedSearches, IEnumerable<WixBundleCatalogTuple> catalogs, string intermediateFolder) | 21 | public CreateBurnManifestCommand(IMessaging messaging, IEnumerable<IBurnBackendExtension> backendExtensions, string executableName, IntermediateSection section, WixBundleTuple bundleTuple, IEnumerable<WixBundleContainerTuple> containers, WixChainTuple chainTuple, IEnumerable<PackageFacade> orderedPackages, IEnumerable<WixBundleRollbackBoundaryTuple> boundaries, IEnumerable<WixBundlePayloadTuple> uxPayloads, Dictionary<string, WixBundlePayloadTuple> allPayloadsById, IEnumerable<ISearchFacade> orderedSearches, IEnumerable<WixBundleCatalogTuple> catalogs, string intermediateFolder) |
| 22 | { | 22 | { |
| 23 | this.Messaging = messaging; | 23 | this.Messaging = messaging; |
| 24 | this.BackendExtensions = backendExtensions; | 24 | this.BackendExtensions = backendExtensions; |
| @@ -54,7 +54,7 @@ namespace WixToolset.Core.Burn.Bundles | |||
| 54 | 54 | ||
| 55 | private IEnumerable<PackageFacade> OrderedPackages { get; } | 55 | private IEnumerable<PackageFacade> OrderedPackages { get; } |
| 56 | 56 | ||
| 57 | private IEnumerable<SearchFacade> OrderedSearches { get; } | 57 | private IEnumerable<ISearchFacade> OrderedSearches { get; } |
| 58 | 58 | ||
| 59 | private Dictionary<string, WixBundlePayloadTuple> Payloads { get; } | 59 | private Dictionary<string, WixBundlePayloadTuple> Payloads { get; } |
| 60 | 60 | ||
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 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using System.Linq; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Burn; | ||
| 9 | using WixToolset.Data.Tuples; | ||
| 10 | using WixToolset.Extensibility.Services; | ||
| 11 | |||
| 12 | internal class OrderSearchesCommand | ||
| 13 | { | ||
| 14 | public OrderSearchesCommand(IMessaging messaging, IntermediateSection section) | ||
| 15 | { | ||
| 16 | this.Messaging = messaging; | ||
| 17 | this.Section = section; | ||
| 18 | } | ||
| 19 | |||
| 20 | private IMessaging Messaging { get; } | ||
| 21 | |||
| 22 | private IntermediateSection Section { get; } | ||
| 23 | |||
| 24 | public IDictionary<string, IList<IntermediateTuple>> ExtensionSearchTuplesByExtensionId { get; private set; } | ||
| 25 | |||
| 26 | public IList<ISearchFacade> OrderedSearchFacades { get; private set; } | ||
| 27 | |||
| 28 | public void Execute() | ||
| 29 | { | ||
| 30 | // TODO: Although the WixSearch tables are defined in the Util extension, | ||
| 31 | // the Bundle Binder has to know all about them. We hope to revisit all | ||
| 32 | // of this in the 4.0 timeframe. | ||
| 33 | var legacySearchesById = this.Section.Tuples | ||
| 34 | .Where(t => t.Definition.Type == TupleDefinitionType.WixComponentSearch || | ||
| 35 | t.Definition.Type == TupleDefinitionType.WixFileSearch || | ||
| 36 | t.Definition.Type == TupleDefinitionType.WixProductSearch || | ||
| 37 | t.Definition.Type == TupleDefinitionType.WixRegistrySearch) | ||
| 38 | .ToDictionary(t => t.Id.Id); | ||
| 39 | var extensionSearchesById = this.Section.Tuples | ||
| 40 | .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchTupleDefinitionTag)) | ||
| 41 | .ToDictionary(t => t.Id.Id); | ||
| 42 | var searchTuples = this.Section.Tuples.OfType<WixSearchTuple>().ToList(); | ||
| 43 | |||
| 44 | this.ExtensionSearchTuplesByExtensionId = new Dictionary<string, IList<IntermediateTuple>>(); | ||
| 45 | this.OrderedSearchFacades = new List<ISearchFacade>(legacySearchesById.Keys.Count + extensionSearchesById.Keys.Count); | ||
| 46 | |||
| 47 | foreach (var searchTuple in searchTuples) | ||
| 48 | { | ||
| 49 | if (legacySearchesById.TryGetValue(searchTuple.Id.Id, out var specificSearchTuple)) | ||
| 50 | { | ||
| 51 | this.OrderedSearchFacades.Add(new LegacySearchFacade(searchTuple, specificSearchTuple)); | ||
| 52 | } | ||
| 53 | else if (extensionSearchesById.TryGetValue(searchTuple.Id.Id, out var extensionSearchTuple)) | ||
| 54 | { | ||
| 55 | this.OrderedSearchFacades.Add(new ExtensionSearchFacade(searchTuple)); | ||
| 56 | |||
| 57 | if (!this.ExtensionSearchTuplesByExtensionId.TryGetValue(searchTuple.BundleExtensionRef, out var extensionSearchTuples)) | ||
| 58 | { | ||
| 59 | extensionSearchTuples = new List<IntermediateTuple>(); | ||
| 60 | this.ExtensionSearchTuplesByExtensionId[searchTuple.BundleExtensionRef] = extensionSearchTuples; | ||
| 61 | } | ||
| 62 | extensionSearchTuples.Add(extensionSearchTuple); | ||
| 63 | } | ||
| 64 | else | ||
| 65 | { | ||
| 66 | this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchTuple.SourceLineNumbers, searchTuple.Id.Id)); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } | ||
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 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System.Xml; | ||
| 6 | |||
| 7 | internal interface ISearchFacade | ||
| 8 | { | ||
| 9 | /// <summary> | ||
| 10 | /// Writes the search to the Burn manifest. | ||
| 11 | /// </summary> | ||
| 12 | /// <param name="writer"></param> | ||
| 13 | void WriteXml(XmlTextWriter writer); | ||
| 14 | } | ||
| 15 | } | ||
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 | |||
| 22 | { | 22 | { |
| 23 | result.ManifestDocument = LoadBurnManifest(destinationFolderPath); | 23 | result.ManifestDocument = LoadBurnManifest(destinationFolderPath); |
| 24 | result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); | 24 | result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); |
| 25 | |||
| 26 | result.BADataDocument = LoadBAData(destinationFolderPath); | ||
| 27 | result.BADataNamespaceManager = GetBADataNamespaceManager(result.BADataDocument, "ba"); | ||
| 28 | |||
| 29 | result.BundleExtensionDataDocument = LoadBundleExtensionData(destinationFolderPath); | ||
| 30 | result.BundleExtensionDataNamespaceManager = GetBundleExtensionDataNamespaceManager(result.BundleExtensionDataDocument, "be"); | ||
| 25 | } | 31 | } |
| 26 | 32 | ||
| 27 | return result; | 33 | return result; |
| 28 | } | 34 | } |
| 29 | 35 | ||
| 36 | public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix) | ||
| 37 | { | ||
| 38 | var namespaceManager = new XmlNamespaceManager(document.NameTable); | ||
| 39 | namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace); | ||
| 40 | return namespaceManager; | ||
| 41 | } | ||
| 42 | |||
| 43 | public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix) | ||
| 44 | { | ||
| 45 | var namespaceManager = new XmlNamespaceManager(document.NameTable); | ||
| 46 | namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace); | ||
| 47 | return namespaceManager; | ||
| 48 | } | ||
| 49 | |||
| 30 | public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) | 50 | public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) |
| 31 | { | 51 | { |
| 32 | var namespaceManager = new XmlNamespaceManager(document.NameTable); | 52 | var namespaceManager = new XmlNamespaceManager(document.NameTable); |
| @@ -34,6 +54,20 @@ namespace WixToolset.Core.TestPackage | |||
| 34 | return namespaceManager; | 54 | return namespaceManager; |
| 35 | } | 55 | } |
| 36 | 56 | ||
| 57 | public static XmlDocument LoadBAData(string baFolderPath) | ||
| 58 | { | ||
| 59 | var document = new XmlDocument(); | ||
| 60 | document.Load(Path.Combine(baFolderPath, BurnCommon.BADataFileName)); | ||
| 61 | return document; | ||
| 62 | } | ||
| 63 | |||
| 64 | public static XmlDocument LoadBundleExtensionData(string baFolderPath) | ||
| 65 | { | ||
| 66 | var document = new XmlDocument(); | ||
| 67 | document.Load(Path.Combine(baFolderPath, BurnCommon.BundleExtensionDataFileName)); | ||
| 68 | return document; | ||
| 69 | } | ||
| 70 | |||
| 37 | public static XmlDocument LoadBurnManifest(string baFolderPath) | 71 | public static XmlDocument LoadBurnManifest(string baFolderPath) |
| 38 | { | 72 | { |
| 39 | var document = new XmlDocument(); | 73 | 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 | |||
| 8 | 8 | ||
| 9 | public class ExtractBAContainerResult | 9 | public class ExtractBAContainerResult |
| 10 | { | 10 | { |
| 11 | public XmlDocument BundleExtensionDataDocument { get; set; } | ||
| 12 | public XmlNamespaceManager BundleExtensionDataNamespaceManager { get; set; } | ||
| 13 | public XmlDocument BADataDocument { get; set; } | ||
| 14 | public XmlNamespaceManager BADataNamespaceManager { get; set; } | ||
| 11 | public XmlDocument ManifestDocument { get; set; } | 15 | public XmlDocument ManifestDocument { get; set; } |
| 12 | public XmlNamespaceManager ManifestNamespaceManager { get; set; } | 16 | public XmlNamespaceManager ManifestNamespaceManager { get; set; } |
| 13 | public bool Success { get; set; } | 17 | public bool Success { get; set; } |
| @@ -26,6 +30,34 @@ namespace WixToolset.Core.TestPackage | |||
| 26 | return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); | 30 | return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); |
| 27 | } | 31 | } |
| 28 | 32 | ||
| 33 | public string GetBundleExtensionFilePath(string extractedBAContainerFolderPath, string extensionId) | ||
| 34 | { | ||
| 35 | var uxPayloads = this.SelectManifestNodes($"/burn:BurnManifest/burn:UX/burn:Payload[@Id='{extensionId}']"); | ||
| 36 | var bextPayload = uxPayloads[0]; | ||
| 37 | var relativeBextPath = bextPayload.Attributes["FilePath"].Value; | ||
| 38 | return Path.Combine(extractedBAContainerFolderPath, relativeBextPath); | ||
| 39 | } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// | ||
| 43 | /// </summary> | ||
| 44 | /// <param name="xpath">elements must have the 'ba' prefix</param> | ||
| 45 | /// <returns></returns> | ||
| 46 | public XmlNodeList SelectBADataNodes(string xpath) | ||
| 47 | { | ||
| 48 | return this.BADataDocument.SelectNodes(xpath, this.BADataNamespaceManager); | ||
| 49 | } | ||
| 50 | |||
| 51 | /// <summary> | ||
| 52 | /// | ||
| 53 | /// </summary> | ||
| 54 | /// <param name="xpath">elements must have the 'be' prefix</param> | ||
| 55 | /// <returns></returns> | ||
| 56 | public XmlNodeList SelectBundleExtensionDataNodes(string xpath) | ||
| 57 | { | ||
| 58 | return this.BundleExtensionDataDocument.SelectNodes(xpath, this.BundleExtensionDataNamespaceManager); | ||
| 59 | } | ||
| 60 | |||
| 29 | /// <summary> | 61 | /// <summary> |
| 30 | /// | 62 | /// |
| 31 | /// </summary> | 63 | /// </summary> |
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 | |||
| 1026 | return this.parseHelper.CreateDirectoryTuple(this.ActiveSection, sourceLineNumbers, id, parentId, name, this.activeSectionInlinedDirectoryIds, shortName, sourceName, shortSourceName); | 1026 | return this.parseHelper.CreateDirectoryTuple(this.ActiveSection, sourceLineNumbers, id, parentId, name, this.activeSectionInlinedDirectoryIds, shortName, sourceName, shortSourceName); |
| 1027 | } | 1027 | } |
| 1028 | 1028 | ||
| 1029 | public void CreateWixSearchTuple(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after) | ||
| 1030 | { | ||
| 1031 | this.parseHelper.CreateWixSearchTuple(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null); | ||
| 1032 | } | ||
| 1033 | |||
| 1029 | /// <summary> | 1034 | /// <summary> |
| 1030 | /// Gets the attribute value as inline directory syntax. | 1035 | /// Gets the attribute value as inline directory syntax. |
| 1031 | /// </summary> | 1036 | /// </summary> |
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 | |||
| 260 | section.Tuples.Add(tuple); | 260 | section.Tuples.Add(tuple); |
| 261 | } | 261 | } |
| 262 | 262 | ||
| 263 | public void CreateWixSearchTuple(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId) | ||
| 264 | { | ||
| 265 | // TODO: verify variable is not a standard bundle variable | ||
| 266 | if (variable == null) | ||
| 267 | { | ||
| 268 | this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable")); | ||
| 269 | } | ||
| 270 | |||
| 271 | section.Tuples.Add(new WixSearchTuple(sourceLineNumbers, id) | ||
| 272 | { | ||
| 273 | Variable = variable, | ||
| 274 | Condition = condition, | ||
| 275 | BundleExtensionRef = bundleExtensionId, | ||
| 276 | }); | ||
| 277 | |||
| 278 | if (after != null) | ||
| 279 | { | ||
| 280 | this.CreateSimpleReference(section, sourceLineNumbers, "WixSearch", after); | ||
| 281 | // TODO: We're currently defaulting to "always run after", which we will need to change... | ||
| 282 | this.CreateWixSearchRelationTuple(section, sourceLineNumbers, id, after, 2); | ||
| 283 | } | ||
| 284 | |||
| 285 | if (!String.IsNullOrEmpty(bundleExtensionId)) | ||
| 286 | { | ||
| 287 | this.CreateSimpleReference(section, sourceLineNumbers, "WixBundleExtension", bundleExtensionId); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | public void CreateWixSearchRelationTuple(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, int attributes) | ||
| 292 | { | ||
| 293 | section.Tuples.Add(new WixSearchRelationTuple(sourceLineNumbers, id) | ||
| 294 | { | ||
| 295 | ParentSearchRef = parentId, | ||
| 296 | Attributes = attributes, | ||
| 297 | }); | ||
| 298 | } | ||
| 299 | |||
| 263 | [Obsolete] | 300 | [Obsolete] |
| 264 | public IntermediateTuple CreateRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName, Identifier identifier = null) | 301 | public IntermediateTuple CreateRow(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName, Identifier identifier = null) |
| 265 | { | 302 | { |
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 @@ | |||
| 8 | <Fragment> | 8 | <Fragment> |
| 9 | <BootstrapperApplication Id="fakeba" SourceFile="example.txt" /> | 9 | <BootstrapperApplication Id="fakeba" SourceFile="example.txt" /> |
| 10 | </Fragment> | 10 | </Fragment> |
| 11 | <Fragment> | ||
| 12 | <BundleExtension Id="ExampleBundleExtension" SourceFile="example.txt" /> | ||
| 13 | </Fragment> | ||
| 11 | </Wix> | 14 | </Wix> |
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 | |||
| 11 | internal class ExampleCompilerExtension : BaseCompilerExtension | 11 | internal class ExampleCompilerExtension : BaseCompilerExtension |
| 12 | { | 12 | { |
| 13 | public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs"; | 13 | public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs"; |
| 14 | public string BundleExtensionId => "ExampleBundleExtension"; | ||
| 14 | 15 | ||
| 15 | public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context) | 16 | public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context) |
| 16 | { | 17 | { |
| @@ -18,6 +19,20 @@ namespace Example.Extension | |||
| 18 | 19 | ||
| 19 | switch (parentElement.Name.LocalName) | 20 | switch (parentElement.Name.LocalName) |
| 20 | { | 21 | { |
| 22 | case "Bundle": | ||
| 23 | case "Fragment": | ||
| 24 | switch (element.Name.LocalName) | ||
| 25 | { | ||
| 26 | case "ExampleSearch": | ||
| 27 | this.ParseExampleSearchElement(intermediate, section, element); | ||
| 28 | processed = true; | ||
| 29 | break; | ||
| 30 | case "ExampleSearchRef": | ||
| 31 | this.ParseExampleSearchRefElement(intermediate, section, element); | ||
| 32 | processed = true; | ||
| 33 | break; | ||
| 34 | } | ||
| 35 | break; | ||
| 21 | case "Component": | 36 | case "Component": |
| 22 | switch (element.Name.LocalName) | 37 | switch (element.Name.LocalName) |
| 23 | { | 38 | { |
| @@ -77,5 +92,94 @@ namespace Example.Extension | |||
| 77 | tuple.Set(1, value); | 92 | tuple.Set(1, value); |
| 78 | } | 93 | } |
| 79 | } | 94 | } |
| 95 | |||
| 96 | private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element) | ||
| 97 | { | ||
| 98 | var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); | ||
| 99 | Identifier id = null; | ||
| 100 | string searchFor = null; | ||
| 101 | string variable = null; | ||
| 102 | string condition = null; | ||
| 103 | string after = null; | ||
| 104 | |||
| 105 | foreach (var attrib in element.Attributes()) | ||
| 106 | { | ||
| 107 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) | ||
| 108 | { | ||
| 109 | switch (attrib.Name.LocalName) | ||
| 110 | { | ||
| 111 | case "Id": | ||
| 112 | id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
| 113 | break; | ||
| 114 | case "Variable": | ||
| 115 | variable = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 116 | break; | ||
| 117 | case "Condition": | ||
| 118 | condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 119 | break; | ||
| 120 | case "After": | ||
| 121 | after = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 122 | break; | ||
| 123 | case "SearchFor": | ||
| 124 | searchFor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
| 125 | break; | ||
| 126 | |||
| 127 | default: | ||
| 128 | this.ParseHelper.UnexpectedAttribute(element, attrib); | ||
| 129 | break; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | else | ||
| 133 | { | ||
| 134 | this.ParseAttribute(intermediate, section, element, attrib, null); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | if (null == id) | ||
| 139 | { | ||
| 140 | this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id")); | ||
| 141 | } | ||
| 142 | |||
| 143 | if (!this.Messaging.EncounteredError) | ||
| 144 | { | ||
| 145 | this.ParseHelper.CreateWixSearchTuple(section, sourceLineNumbers, element.Name.LocalName, id, variable, condition, after, this.BundleExtensionId); | ||
| 146 | } | ||
| 147 | |||
| 148 | if (!this.Messaging.EncounteredError) | ||
| 149 | { | ||
| 150 | |||
| 151 | var tuple = new ExampleSearchTuple(sourceLineNumbers, id); | ||
| 152 | section.Tuples.Add(tuple); | ||
| 153 | tuple.SearchFor = searchFor; | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | private void ParseExampleSearchRefElement(Intermediate intermediate, IntermediateSection section, XElement element) | ||
| 158 | { | ||
| 159 | var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); | ||
| 160 | |||
| 161 | foreach (var attrib in element.Attributes()) | ||
| 162 | { | ||
| 163 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) | ||
| 164 | { | ||
| 165 | switch (attrib.Name.LocalName) | ||
| 166 | { | ||
| 167 | case "Id": | ||
| 168 | var refId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
| 169 | this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "ExampleSearch", refId); | ||
| 170 | break; | ||
| 171 | default: | ||
| 172 | this.ParseHelper.UnexpectedAttribute(element, attrib); | ||
| 173 | break; | ||
| 174 | } | ||
| 175 | } | ||
| 176 | else | ||
| 177 | { | ||
| 178 | this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); | ||
| 183 | } | ||
| 80 | } | 184 | } |
| 81 | } | 185 | } |
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 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. |
| 2 | 2 | ||
| 3 | namespace Example.Extension | 3 | namespace Example.Extension |
| 4 | { | 4 | { |
| @@ -22,6 +22,10 @@ namespace Example.Extension | |||
| 22 | tupleDefinition = ExampleTupleDefinitions.Example; | 22 | tupleDefinition = ExampleTupleDefinitions.Example; |
| 23 | break; | 23 | break; |
| 24 | 24 | ||
| 25 | case "ExampleSearch": | ||
| 26 | tupleDefinition = ExampleTupleDefinitions.ExampleSearch; | ||
| 27 | break; | ||
| 28 | |||
| 25 | default: | 29 | default: |
| 26 | tupleDefinition = null; | 30 | tupleDefinition = null; |
| 27 | break; | 31 | 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 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace Example.Extension | ||
| 4 | { | ||
| 5 | using WixToolset.Data; | ||
| 6 | |||
| 7 | public enum ExampleSearchTupleFields | ||
| 8 | { | ||
| 9 | Example, | ||
| 10 | SearchFor, | ||
| 11 | } | ||
| 12 | |||
| 13 | public class ExampleSearchTuple : IntermediateTuple | ||
| 14 | { | ||
| 15 | public ExampleSearchTuple() : base(ExampleTupleDefinitions.ExampleSearch, null, null) | ||
| 16 | { | ||
| 17 | } | ||
| 18 | |||
| 19 | public ExampleSearchTuple(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleTupleDefinitions.ExampleSearch, sourceLineNumber, id) | ||
| 20 | { | ||
| 21 | } | ||
| 22 | |||
| 23 | public IntermediateField this[ExampleTupleFields index] => this.Fields[(int)index]; | ||
| 24 | |||
| 25 | public string SearchFor | ||
| 26 | { | ||
| 27 | get => this.Fields[(int)ExampleSearchTupleFields.SearchFor]?.AsString(); | ||
| 28 | set => this.Set((int)ExampleSearchTupleFields.SearchFor, value); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | } | ||
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 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. |
| 2 | 2 | ||
| 3 | namespace Example.Extension | 3 | namespace Example.Extension |
| 4 | { | 4 | { |
| 5 | using WixToolset.Data; | 5 | using WixToolset.Data; |
| 6 | using WixToolset.Data.Burn; | ||
| 6 | 7 | ||
| 7 | public static class ExampleTupleDefinitions | 8 | public static class ExampleTupleDefinitions |
| 8 | { | 9 | { |
| @@ -16,5 +17,19 @@ namespace Example.Extension | |||
| 16 | new IntermediateFieldDefinition(nameof(ExampleTupleFields.Value), IntermediateFieldType.String), | 17 | new IntermediateFieldDefinition(nameof(ExampleTupleFields.Value), IntermediateFieldType.String), |
| 17 | }, | 18 | }, |
| 18 | typeof(ExampleTuple)); | 19 | typeof(ExampleTuple)); |
| 20 | |||
| 21 | public static readonly IntermediateTupleDefinition ExampleSearch = new IntermediateTupleDefinition( | ||
| 22 | nameof(ExampleSearch), | ||
| 23 | new[] | ||
| 24 | { | ||
| 25 | new IntermediateFieldDefinition(nameof(ExampleTupleFields.Example), IntermediateFieldType.String), | ||
| 26 | new IntermediateFieldDefinition(nameof(ExampleSearchTupleFields.SearchFor), IntermediateFieldType.String), | ||
| 27 | }, | ||
| 28 | typeof(ExampleSearchTuple)); | ||
| 29 | |||
| 30 | static ExampleTupleDefinitions() | ||
| 31 | { | ||
| 32 | ExampleSearch.AddTag(BurnConstants.BundleExtensionSearchTupleDefinitionTag); | ||
| 33 | } | ||
| 19 | } | 34 | } |
| 20 | } | 35 | } |
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 @@ | |||
| 2 | 2 | ||
| 3 | namespace WixToolsetTest.CoreIntegration | 3 | namespace WixToolsetTest.CoreIntegration |
| 4 | { | 4 | { |
| 5 | using System; | ||
| 5 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
| 6 | using System.IO; | 7 | using System.IO; |
| 8 | using Example.Extension; | ||
| 7 | using WixBuildTools.TestSupport; | 9 | using WixBuildTools.TestSupport; |
| 8 | using WixToolset.Core.TestPackage; | 10 | using WixToolset.Core.TestPackage; |
| 9 | using Xunit; | 11 | using Xunit; |
| @@ -57,5 +59,59 @@ namespace WixToolsetTest.CoreIntegration | |||
| 57 | Assert.Equal("<Payload Id='ExampleBext' FilePath='fakebext.dll' FileSize='*' Hash='*' Packaging='embedded' SourcePath='*' />", bundleExtensionPayloads[0].GetTestXml(ignored)); | 59 | Assert.Equal("<Payload Id='ExampleBext' FilePath='fakebext.dll' FileSize='*' Hash='*' Packaging='embedded' SourcePath='*' />", bundleExtensionPayloads[0].GetTestXml(ignored)); |
| 58 | } | 60 | } |
| 59 | } | 61 | } |
| 62 | |||
| 63 | [Fact] | ||
| 64 | public void PopulatesManifestWithBundleExtensionSearches() | ||
| 65 | { | ||
| 66 | var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); | ||
| 67 | var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath); | ||
| 68 | var folder = TestData.Get(@"TestData"); | ||
| 69 | |||
| 70 | using (var fs = new DisposableFileSystem()) | ||
| 71 | { | ||
| 72 | var baseFolder = fs.GetFolder(); | ||
| 73 | var intermediateFolder = Path.Combine(baseFolder, "obj"); | ||
| 74 | var bundlePath = Path.Combine(baseFolder, @"bin\test.exe"); | ||
| 75 | var baFolderPath = Path.Combine(baseFolder, "ba"); | ||
| 76 | var extractFolderPath = Path.Combine(baseFolder, "extract"); | ||
| 77 | |||
| 78 | var result = WixRunner.Execute(new[] | ||
| 79 | { | ||
| 80 | "build", | ||
| 81 | Path.Combine(folder, "BundleExtension", "BundleExtensionSearches.wxs"), | ||
| 82 | Path.Combine(folder, "BundleExtension", "BundleWithSearches.wxs"), | ||
| 83 | Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), | ||
| 84 | Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), | ||
| 85 | "-ext", extensionPath, | ||
| 86 | "-bindpath", Path.Combine(folder, "SimpleBundle", "data"), | ||
| 87 | "-intermediateFolder", intermediateFolder, | ||
| 88 | "-burnStub", burnStubPath, | ||
| 89 | "-o", bundlePath | ||
| 90 | }); | ||
| 91 | |||
| 92 | result.AssertSuccess(); | ||
| 93 | |||
| 94 | Assert.True(File.Exists(bundlePath)); | ||
| 95 | |||
| 96 | var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath); | ||
| 97 | extractResult.AssertSuccess(); | ||
| 98 | |||
| 99 | var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension"); | ||
| 100 | Assert.Equal(1, bundleExtensions.Count); | ||
| 101 | Assert.Equal("<BundleExtension Id='ExampleBundleExtension' EntryPayloadId='ExampleBundleExtension' />", bundleExtensions[0].GetTestXml()); | ||
| 102 | |||
| 103 | var extensionSearches = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:ExtensionSearch"); | ||
| 104 | Assert.Equal(2, extensionSearches.Count); | ||
| 105 | Assert.Equal("<ExtensionSearch Id='ExampleSearchBar' Variable='SearchBar' Condition='WixBundleInstalled' ExtensionId='ExampleBundleExtension' />", extensionSearches[0].GetTestXml()); | ||
| 106 | Assert.Equal("<ExtensionSearch Id='ExampleSearchFoo' Variable='SearchFoo' ExtensionId='ExampleBundleExtension' />", extensionSearches[1].GetTestXml()); | ||
| 107 | |||
| 108 | var bundleExtensionDatas = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']"); | ||
| 109 | Assert.Equal(1, bundleExtensionDatas.Count); | ||
| 110 | Assert.Equal("<BundleExtension Id='ExampleBundleExtension'>" + | ||
| 111 | "<ExampleSearch Id='ExampleSearchBar' SearchFor='Bar' />" + | ||
| 112 | "<ExampleSearch Id='ExampleSearchFoo' SearchFor='Foo' />" + | ||
| 113 | "</BundleExtension>", bundleExtensionDatas[0].GetTestXml()); | ||
| 114 | } | ||
| 115 | } | ||
| 60 | } | 116 | } |
| 61 | } | 117 | } |
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 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" | ||
| 3 | xmlns:ex="http://www.example.com/scheams/v1/wxs"> | ||
| 4 | <Fragment> | ||
| 5 | <ex:ExampleSearch Id="ExampleSearchBar" Variable="SearchBar" Condition="WixBundleInstalled" SearchFor="Bar" /> | ||
| 6 | <ex:ExampleSearch Id="ExampleSearchFoo" Variable="SearchFoo" SearchFor="Foo" /> | ||
| 7 | </Fragment> | ||
| 8 | </Wix> | ||
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 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" | ||
| 3 | xmlns:ex="http://www.example.com/scheams/v1/wxs"> | ||
| 4 | <Fragment> | ||
| 5 | <PackageGroup Id="BundlePackages"> | ||
| 6 | <PackageGroupRef Id="MinimalPackageGroup" /> | ||
| 7 | </PackageGroup> | ||
| 8 | |||
| 9 | <ex:ExampleSearchRef Id="ExampleSearchFoo" /> | ||
| 10 | </Fragment> | ||
| 11 | </Wix> | ||
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 @@ | |||
| 22 | <Content Include="TestData\AppSearch\NestedDirSearchUnderRegSearch.msi" CopyToOutputDirectory="PreserveNewest" /> | 22 | <Content Include="TestData\AppSearch\NestedDirSearchUnderRegSearch.msi" CopyToOutputDirectory="PreserveNewest" /> |
| 23 | <Content Include="TestData\AppSearch\RegistrySearch.wxs" CopyToOutputDirectory="PreserveNewest" /> | 23 | <Content Include="TestData\AppSearch\RegistrySearch.wxs" CopyToOutputDirectory="PreserveNewest" /> |
| 24 | <Content Include="TestData\BundleExtension\BundleExtension.wxs" CopyToOutputDirectory="PreserveNewest" /> | 24 | <Content Include="TestData\BundleExtension\BundleExtension.wxs" CopyToOutputDirectory="PreserveNewest" /> |
| 25 | <Content Include="TestData\BundleExtension\BundleExtensionSearches.wxs" CopyToOutputDirectory="PreserveNewest" /> | ||
| 26 | <Content Include="TestData\BundleExtension\BundleWithSearches.wxs" CopyToOutputDirectory="PreserveNewest" /> | ||
| 25 | <Content Include="TestData\BundleExtension\SimpleBundleExtension.wxs" CopyToOutputDirectory="PreserveNewest" /> | 27 | <Content Include="TestData\BundleExtension\SimpleBundleExtension.wxs" CopyToOutputDirectory="PreserveNewest" /> |
| 26 | <Content Include="TestData\BundleWithPackageGroupRef\Bundle.wxs" CopyToOutputDirectory="PreserveNewest" /> | 28 | <Content Include="TestData\BundleWithPackageGroupRef\Bundle.wxs" CopyToOutputDirectory="PreserveNewest" /> |
| 27 | <Content Include="TestData\BundleWithPackageGroupRef\MinimalPackageGroup.wxs" CopyToOutputDirectory="PreserveNewest" /> | 29 | <Content Include="TestData\BundleWithPackageGroupRef\MinimalPackageGroup.wxs" CopyToOutputDirectory="PreserveNewest" /> |
