From afbc6889c73d58136cb8851858ca3c17f41dc2c5 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 26 Mar 2020 15:21:06 +1000 Subject: Add BundleExtension element. Add GetTestXml. Fix issue with building with current version of burn. --- .../Bundles/CreateBundleExeCommand.cs | 2 +- .../Bundles/CreateBurnManifestCommand.cs | 12 ++++ .../WixToolset.Core.Burn.csproj | 6 ++ src/WixToolset.Core.TestPackage/BundleExtractor.cs | 44 +++++++++++++ .../ExtractBAContainerResult.cs | 39 +++++++++++ .../XmlNodeExtensions.cs | 75 ++++++++++++++++++++++ src/WixToolset.Core/Compiler.cs | 6 ++ src/WixToolset.Core/Compiler_Bundle.cs | 73 +++++++++++++++++++++ .../BundleManifestFixture.cs | 61 ++++++++++++++++++ .../TestData/BundleExtension/BundleExtension.wxs | 6 ++ .../BundleExtension/SimpleBundleExtension.wxs | 10 +++ .../TestData/BundleWithPackageGroupRef/Bundle.wxs | 9 +++ .../MinimalPackageGroup.wxs | 8 +++ .../TestXmlFixture.cs | 62 ++++++++++++++++++ .../WixToolsetTest.CoreIntegration.csproj | 4 ++ 15 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 src/WixToolset.Core.TestPackage/BundleExtractor.cs create mode 100644 src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs create mode 100644 src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs (limited to 'src') diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs index bf0473d2..53636509 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs @@ -125,7 +125,7 @@ namespace WixToolset.Core.Burn.Bundles version.FileVersion = fourPartVersion; version.ProductVersion = fourPartVersion; - var strings = version[1033]; + var strings = version[1033] ?? version.Add(1033); strings["LegalCopyright"] = bundleInfo.Copyright; strings["OriginalFilename"] = Path.GetFileName(outputPath); strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index b7ea4116..64a01794 100644 --- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -621,6 +621,18 @@ namespace WixToolset.Core.Burn.Bundles writer.WriteEndElement(); } + // Write the BundleExtension elements. + var bundleExtensions = this.Section.Tuples.OfType(); + + foreach (var bundleExtension in bundleExtensions) + { + writer.WriteStartElement("BundleExtension"); + writer.WriteAttributeString("Id", bundleExtension.Id.Id); + writer.WriteAttributeString("EntryPayloadId", bundleExtension.PayloadRef); + + writer.WriteEndElement(); + } + writer.WriteEndDocument(); // } } diff --git a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj index 3542f85a..ae0e7023 100644 --- a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj +++ b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj @@ -10,6 +10,12 @@ true + + + <_Parameter1>WixToolset.Core.TestPackage + + + diff --git a/src/WixToolset.Core.TestPackage/BundleExtractor.cs b/src/WixToolset.Core.TestPackage/BundleExtractor.cs new file mode 100644 index 00000000..3d7b2932 --- /dev/null +++ b/src/WixToolset.Core.TestPackage/BundleExtractor.cs @@ -0,0 +1,44 @@ +// 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.TestPackage +{ + using System.IO; + using System.Xml; + using WixToolset.Core.Burn.Bundles; + using WixToolset.Extensibility.Services; + + public class BundleExtractor + { + public static ExtractBAContainerResult ExtractBAContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath) + { + var result = new ExtractBAContainerResult(); + Directory.CreateDirectory(tempFolderPath); + using (var burnReader = BurnReader.Open(messaging, bundleFilePath)) + { + result.Success = burnReader.ExtractUXContainer(destinationFolderPath, tempFolderPath); + } + + if (result.Success) + { + result.ManifestDocument = LoadBurnManifest(destinationFolderPath); + result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn"); + } + + return result; + } + + public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix) + { + var namespaceManager = new XmlNamespaceManager(document.NameTable); + namespaceManager.AddNamespace(prefix, BurnCommon.BurnNamespace); + return namespaceManager; + } + + public static XmlDocument LoadBurnManifest(string baFolderPath) + { + var document = new XmlDocument(); + document.Load(Path.Combine(baFolderPath, "manifest.xml")); + return document; + } + } +} diff --git a/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs new file mode 100644 index 00000000..6d2ea943 --- /dev/null +++ b/src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs @@ -0,0 +1,39 @@ +// 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.TestPackage +{ + using System.IO; + using System.Xml; + using Xunit; + + public class ExtractBAContainerResult + { + public XmlDocument ManifestDocument { get; set; } + public XmlNamespaceManager ManifestNamespaceManager { get; set; } + public bool Success { get; set; } + + public ExtractBAContainerResult AssertSuccess() + { + Assert.True(this.Success); + return this; + } + + public string GetBAFilePath(string extractedBAContainerFolderPath) + { + var uxPayloads = this.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload"); + var baPayload = uxPayloads[0]; + var relativeBAPath = baPayload.Attributes["FilePath"].Value; + return Path.Combine(extractedBAContainerFolderPath, relativeBAPath); + } + + /// + /// + /// + /// elements must have the 'burn' prefix + /// + public XmlNodeList SelectManifestNodes(string xpath) + { + return this.ManifestDocument.SelectNodes(xpath, this.ManifestNamespaceManager); + } + } +} diff --git a/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs b/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs new file mode 100644 index 00000000..a7f04508 --- /dev/null +++ b/src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs @@ -0,0 +1,75 @@ +// 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.TestPackage +{ + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using System.Xml; + + public static class XmlNodeExtensions + { + public static string GetTestXml(this XmlNode node, Dictionary> ignoredAttributesByElementName = null) + { + return node.OuterXml.GetTestXml(ignoredAttributesByElementName); + } + + public static string GetTestXml(this string xml, Dictionary> ignoredAttributesByElementName = null) + { + string formattedXml; + using (var sw = new StringWriter()) + using (var writer = new TestXmlWriter(sw)) + { + var doc = new XmlDocument(); + doc.LoadXml(xml); + + if (ignoredAttributesByElementName != null) + { + HandleIgnoredAttributes(doc, ignoredAttributesByElementName); + } + + doc.Save(writer); + formattedXml = sw.ToString(); + } + + return Regex.Replace(formattedXml, " xmlns(:[^=]+)?='[^']*'", ""); + } + + private static void HandleIgnoredAttributes(XmlNode node, Dictionary> ignoredAttributesByElementName) + { + if (node.Attributes != null && ignoredAttributesByElementName.TryGetValue(node.LocalName, out var ignoredAttributes)) + { + foreach (var ignoredAttribute in ignoredAttributes) + { + var attribute = node.Attributes[ignoredAttribute]; + if (attribute != null) + { + attribute.Value = "*"; + } + } + } + + if (node.ChildNodes != null) + { + foreach (XmlNode childNode in node.ChildNodes) + { + HandleIgnoredAttributes(childNode, ignoredAttributesByElementName); + } + } + } + + private class TestXmlWriter : XmlTextWriter + { + public TestXmlWriter(TextWriter w) + : base(w) + { + this.QuoteChar = '\''; + } + + public override void WriteStartDocument() + { + //OmitXmlDeclaration + } + } + } +} diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index 8c0c4a39..6f122f7b 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs @@ -6121,6 +6121,12 @@ namespace WixToolset.Core case "BootstrapperApplicationRef": this.ParseBootstrapperApplicationRefElement(child); break; + case "BundleExtension": + this.ParseBundleExtensionElement(child); + break; + case "BundleExtensionRef": + this.ParseSimpleRefElement(child, "WixBundleExtension"); + break; case "ComplianceCheck": this.ParseComplianceCheckElement(child); break; diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs index 3be7d0c5..a840e448 100644 --- a/src/WixToolset.Core/Compiler_Bundle.cs +++ b/src/WixToolset.Core/Compiler_Bundle.cs @@ -278,6 +278,12 @@ namespace WixToolset.Core case "BootstrapperApplicationRef": this.ParseBootstrapperApplicationRefElement(child); break; + case "BundleExtension": + this.ParseBundleExtensionElement(child); + break; + case "BundleExtensionRef": + this.ParseSimpleRefElement(child, "WixBundleExtension"); + break; case "OptionalUpdateRegistration": this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name); break; @@ -759,6 +765,73 @@ namespace WixToolset.Core } } + /// + /// Parse the BundleExtension element. + /// + /// Element to parse + private void ParseBundleExtensionElement(XElement node) + { + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); + Identifier previousId = null; + var previousType = ComplexReferenceChildType.Unknown; + + // The BundleExtension element acts like a Payload element so delegate to the "Payload" attribute parsing code to parse and create a Payload entry. + var id = this.ParsePayloadElementContent(node, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId, false); + if (null != id) + { + previousId = id; + previousType = ComplexReferenceChildType.Payload; + } + + foreach (var child in node.Elements()) + { + if (CompilerCore.WixNamespace == child.Name.Namespace) + { + switch (child.Name.LocalName) + { + case "Payload": + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.Payload; + break; + case "PayloadGroupRef": + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId); + previousType = ComplexReferenceChildType.PayloadGroup; + break; + default: + this.Core.UnexpectedElement(node, child); + break; + } + } + else + { + this.Core.ParseExtensionElement(node, child); + } + } + + if (null == previousId) + { + // We need *either* or or even just @SourceFile on the BundleExtension... + // but we just say there's a missing . + // TODO: Is there a better message for this? + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Payload")); + } + + if (null == id) + { + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id")); + } + + // Add the BundleExtension. + if (!this.Core.EncounteredError) + { + var tuple = new WixBundleExtensionTuple(sourceLineNumbers, id) + { + PayloadRef = id.Id, + }; + this.Core.AddTuple(tuple); + } + } + /// /// Parse the OptionalUpdateRegistration element. /// diff --git a/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs new file mode 100644 index 00000000..da4482ff --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs @@ -0,0 +1,61 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using System.IO; + using WixBuildTools.TestSupport; + using WixToolset.Core.TestPackage; + using Xunit; + + public class BundleManifestFixture + { + [Fact] + public void PopulatesManifestWithBundleExtension() + { + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe"); + 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", "BundleExtension.wxs"), + Path.Combine(folder, "BundleExtension", "SimpleBundleExtension.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"), + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"), + "-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 bundleExtensionPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload[@Id='ExampleBext']"); + Assert.Equal(1, bundleExtensionPayloads.Count); + var ignored = new Dictionary> + { + { "Payload", new List { "FileSize", "Hash", "SourcePath" } }, + }; + Assert.Equal("", bundleExtensionPayloads[0].GetTestXml(ignored)); + } + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs new file mode 100644 index 00000000..eefae822 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs new file mode 100644 index 00000000..7303a05a --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs new file mode 100644 index 00000000..207a8de1 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs new file mode 100644 index 00000000..b0bde4f6 --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs b/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs new file mode 100644 index 00000000..5330305e --- /dev/null +++ b/src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs @@ -0,0 +1,62 @@ +// 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 WixToolsetTest.CoreIntegration +{ + using System.Collections.Generic; + using WixToolset.Core.TestPackage; + using Xunit; + + public class TestXmlFixture + { + [Fact] + public void ChangesIgnoredAttributesToStarToHelpMakeTestsLessFragile() + { + var original = @" + + + + +"; + var expected = ""; + var ignored = new Dictionary> { { "Target", new List { "One", "Two", "Missing" } } }; + Assert.Equal(expected, original.GetTestXml(ignored)); + } + + [Fact] + public void OutputsSingleQuotesSinceDoubleQuotesInCsharpLiteralStringsArePainful() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesAllNamespacesToReduceTyping() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesUnnecessaryWhitespaceToAvoidLineEndingIssues() + { + var original = @" + + + + +"; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + + [Fact] + public void RemovesXmlDeclarationToReduceTyping() + { + var original = ""; + var expected = ""; + Assert.Equal(expected, original.GetTestXml()); + } + } +} diff --git a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj index 7f21fde1..85538b79 100644 --- a/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj +++ b/src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj @@ -21,6 +21,10 @@ + + + + -- cgit v1.2.3-55-g6feb