@joebigelow / wix / commits / afbc6889

Add BundleExtension element. Add GetTestXml. Fix issue with building with current version of burn.

Add BundleExtension element. Add GetTestXml. Fix issue with building with current version of burn.

Sean Hall committed Mar 26, 2020 at 15:21 UTC afbc6889c73d58136cb8851858ca3c17f41dc2c5
15 files changed +416 -1
src/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs
+1 -1
@@ -125,7 +125,7 @@ namespace WixToolset.Core.Burn.Bundles
125 version.FileVersion = fourPartVersion;
126 version.ProductVersion = fourPartVersion;
127
128 - var strings = version[1033];
128 + var strings = version[1033] ?? version.Add(1033);
129 strings["LegalCopyright"] = bundleInfo.Copyright;
130 strings["OriginalFilename"] = Path.GetFileName(outputPath);
131 strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
+12
@@ -621,6 +621,18 @@ namespace WixToolset.Core.Burn.Bundles
621 writer.WriteEndElement();
622 }
623
624 + // Write the BundleExtension elements.
625 + var bundleExtensions = this.Section.Tuples.OfType<WixBundleExtensionTuple>();
626 +
627 + foreach (var bundleExtension in bundleExtensions)
628 + {
629 + writer.WriteStartElement("BundleExtension");
630 + writer.WriteAttributeString("Id", bundleExtension.Id.Id);
631 + writer.WriteAttributeString("EntryPayloadId", bundleExtension.PayloadRef);
632 +
633 + writer.WriteEndElement();
634 + }
635 +
636 writer.WriteEndDocument(); // </BurnManifest>
637 }
638 }
src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj
+6
@@ -10,6 +10,12 @@
10 <PublishRepositoryUrl>true</PublishRepositoryUrl>
11 </PropertyGroup>
12
13 + <ItemGroup>
14 + <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
15 + <_Parameter1>WixToolset.Core.TestPackage</_Parameter1>
16 + </AssemblyAttribute>
17 + </ItemGroup>
18 +
19 <ItemGroup>
20 <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" />
21 </ItemGroup>
src/WixToolset.Core.TestPackage/BundleExtractor.cs new
+44
@@ -0,0 +1,44 @@
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.TestPackage
4 +{
5 + using System.IO;
6 + using System.Xml;
7 + using WixToolset.Core.Burn.Bundles;
8 + using WixToolset.Extensibility.Services;
9 +
10 + public class BundleExtractor
11 + {
12 + public static ExtractBAContainerResult ExtractBAContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath)
13 + {
14 + var result = new ExtractBAContainerResult();
15 + Directory.CreateDirectory(tempFolderPath);
16 + using (var burnReader = BurnReader.Open(messaging, bundleFilePath))
17 + {
18 + result.Success = burnReader.ExtractUXContainer(destinationFolderPath, tempFolderPath);
19 + }
20 +
21 + if (result.Success)
22 + {
23 + result.ManifestDocument = LoadBurnManifest(destinationFolderPath);
24 + result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn");
25 + }
26 +
27 + return result;
28 + }
29 +
30 + public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix)
31 + {
32 + var namespaceManager = new XmlNamespaceManager(document.NameTable);
33 + namespaceManager.AddNamespace(prefix, BurnCommon.BurnNamespace);
34 + return namespaceManager;
35 + }
36 +
37 + public static XmlDocument LoadBurnManifest(string baFolderPath)
38 + {
39 + var document = new XmlDocument();
40 + document.Load(Path.Combine(baFolderPath, "manifest.xml"));
41 + return document;
42 + }
43 + }
44 +}
src/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs new
+39
@@ -0,0 +1,39 @@
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.TestPackage
4 +{
5 + using System.IO;
6 + using System.Xml;
7 + using Xunit;
8 +
9 + public class ExtractBAContainerResult
10 + {
11 + public XmlDocument ManifestDocument { get; set; }
12 + public XmlNamespaceManager ManifestNamespaceManager { get; set; }
13 + public bool Success { get; set; }
14 +
15 + public ExtractBAContainerResult AssertSuccess()
16 + {
17 + Assert.True(this.Success);
18 + return this;
19 + }
20 +
21 + public string GetBAFilePath(string extractedBAContainerFolderPath)
22 + {
23 + var uxPayloads = this.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload");
24 + var baPayload = uxPayloads[0];
25 + var relativeBAPath = baPayload.Attributes["FilePath"].Value;
26 + return Path.Combine(extractedBAContainerFolderPath, relativeBAPath);
27 + }
28 +
29 + /// <summary>
30 + ///
31 + /// </summary>
32 + /// <param name="xpath">elements must have the 'burn' prefix</param>
33 + /// <returns></returns>
34 + public XmlNodeList SelectManifestNodes(string xpath)
35 + {
36 + return this.ManifestDocument.SelectNodes(xpath, this.ManifestNamespaceManager);
37 + }
38 + }
39 +}
src/WixToolset.Core.TestPackage/XmlNodeExtensions.cs new
+75
@@ -0,0 +1,75 @@
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.TestPackage
4 +{
5 + using System.Collections.Generic;
6 + using System.IO;
7 + using System.Text.RegularExpressions;
8 + using System.Xml;
9 +
10 + public static class XmlNodeExtensions
11 + {
12 + public static string GetTestXml(this XmlNode node, Dictionary<string, List<string>> ignoredAttributesByElementName = null)
13 + {
14 + return node.OuterXml.GetTestXml(ignoredAttributesByElementName);
15 + }
16 +
17 + public static string GetTestXml(this string xml, Dictionary<string, List<string>> ignoredAttributesByElementName = null)
18 + {
19 + string formattedXml;
20 + using (var sw = new StringWriter())
21 + using (var writer = new TestXmlWriter(sw))
22 + {
23 + var doc = new XmlDocument();
24 + doc.LoadXml(xml);
25 +
26 + if (ignoredAttributesByElementName != null)
27 + {
28 + HandleIgnoredAttributes(doc, ignoredAttributesByElementName);
29 + }
30 +
31 + doc.Save(writer);
32 + formattedXml = sw.ToString();
33 + }
34 +
35 + return Regex.Replace(formattedXml, " xmlns(:[^=]+)?='[^']*'", "");
36 + }
37 +
38 + private static void HandleIgnoredAttributes(XmlNode node, Dictionary<string, List<string>> ignoredAttributesByElementName)
39 + {
40 + if (node.Attributes != null && ignoredAttributesByElementName.TryGetValue(node.LocalName, out var ignoredAttributes))
41 + {
42 + foreach (var ignoredAttribute in ignoredAttributes)
43 + {
44 + var attribute = node.Attributes[ignoredAttribute];
45 + if (attribute != null)
46 + {
47 + attribute.Value = "*";
48 + }
49 + }
50 + }
51 +
52 + if (node.ChildNodes != null)
53 + {
54 + foreach (XmlNode childNode in node.ChildNodes)
55 + {
56 + HandleIgnoredAttributes(childNode, ignoredAttributesByElementName);
57 + }
58 + }
59 + }
60 +
61 + private class TestXmlWriter : XmlTextWriter
62 + {
63 + public TestXmlWriter(TextWriter w)
64 + : base(w)
65 + {
66 + this.QuoteChar = '\'';
67 + }
68 +
69 + public override void WriteStartDocument()
70 + {
71 + //OmitXmlDeclaration
72 + }
73 + }
74 + }
75 +}
src/WixToolset.Core/Compiler.cs
+6
@@ -6121,6 +6121,12 @@ namespace WixToolset.Core
6121 case "BootstrapperApplicationRef":
6122 this.ParseBootstrapperApplicationRefElement(child);
6123 break;
6124 + case "BundleExtension":
6125 + this.ParseBundleExtensionElement(child);
6126 + break;
6127 + case "BundleExtensionRef":
6128 + this.ParseSimpleRefElement(child, "WixBundleExtension");
6129 + break;
6130 case "ComplianceCheck":
6131 this.ParseComplianceCheckElement(child);
6132 break;
src/WixToolset.Core/Compiler_Bundle.cs
+73
@@ -278,6 +278,12 @@ namespace WixToolset.Core
278 case "BootstrapperApplicationRef":
279 this.ParseBootstrapperApplicationRefElement(child);
280 break;
281 + case "BundleExtension":
282 + this.ParseBundleExtensionElement(child);
283 + break;
284 + case "BundleExtensionRef":
285 + this.ParseSimpleRefElement(child, "WixBundleExtension");
286 + break;
287 case "OptionalUpdateRegistration":
288 this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name);
289 break;
@@ -759,6 +765,73 @@ namespace WixToolset.Core
765 }
766 }
767
768 + /// <summary>
769 + /// Parse the BundleExtension element.
770 + /// </summary>
771 + /// <param name="node">Element to parse</param>
772 + private void ParseBundleExtensionElement(XElement node)
773 + {
774 + var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
775 + Identifier previousId = null;
776 + var previousType = ComplexReferenceChildType.Unknown;
777 +
778 + // The BundleExtension element acts like a Payload element so delegate to the "Payload" attribute parsing code to parse and create a Payload entry.
779 + var id = this.ParsePayloadElementContent(node, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId, false);
780 + if (null != id)
781 + {
782 + previousId = id;
783 + previousType = ComplexReferenceChildType.Payload;
784 + }
785 +
786 + foreach (var child in node.Elements())
787 + {
788 + if (CompilerCore.WixNamespace == child.Name.Namespace)
789 + {
790 + switch (child.Name.LocalName)
791 + {
792 + case "Payload":
793 + previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
794 + previousType = ComplexReferenceChildType.Payload;
795 + break;
796 + case "PayloadGroupRef":
797 + previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
798 + previousType = ComplexReferenceChildType.PayloadGroup;
799 + break;
800 + default:
801 + this.Core.UnexpectedElement(node, child);
802 + break;
803 + }
804 + }
805 + else
806 + {
807 + this.Core.ParseExtensionElement(node, child);
808 + }
809 + }
810 +
811 + if (null == previousId)
812 + {
813 + // We need *either* <Payload> or <PayloadGroupRef> or even just @SourceFile on the BundleExtension...
814 + // but we just say there's a missing <Payload>.
815 + // TODO: Is there a better message for this?
816 + this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Payload"));
817 + }
818 +
819 + if (null == id)
820 + {
821 + this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
822 + }
823 +
824 + // Add the BundleExtension.
825 + if (!this.Core.EncounteredError)
826 + {
827 + var tuple = new WixBundleExtensionTuple(sourceLineNumbers, id)
828 + {
829 + PayloadRef = id.Id,
830 + };
831 + this.Core.AddTuple(tuple);
832 + }
833 + }
834 +
835 /// <summary>
836 /// Parse the OptionalUpdateRegistration element.
837 /// </summary>
src/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs new
+61
@@ -0,0 +1,61 @@
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 WixToolsetTest.CoreIntegration
4 +{
5 + using System.Collections.Generic;
6 + using System.IO;
7 + using WixBuildTools.TestSupport;
8 + using WixToolset.Core.TestPackage;
9 + using Xunit;
10 +
11 + public class BundleManifestFixture
12 + {
13 + [Fact]
14 + public void PopulatesManifestWithBundleExtension()
15 + {
16 + var burnStubPath = TestData.Get(@"TestData\.Data\burn.exe");
17 + var folder = TestData.Get(@"TestData");
18 +
19 + using (var fs = new DisposableFileSystem())
20 + {
21 + var baseFolder = fs.GetFolder();
22 + var intermediateFolder = Path.Combine(baseFolder, "obj");
23 + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
24 + var baFolderPath = Path.Combine(baseFolder, "ba");
25 + var extractFolderPath = Path.Combine(baseFolder, "extract");
26 +
27 + var result = WixRunner.Execute(new[]
28 + {
29 + "build",
30 + Path.Combine(folder, "BundleExtension", "BundleExtension.wxs"),
31 + Path.Combine(folder, "BundleExtension", "SimpleBundleExtension.wxs"),
32 + Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
33 + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
34 + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
35 + "-intermediateFolder", intermediateFolder,
36 + "-burnStub", burnStubPath,
37 + "-o", bundlePath
38 + });
39 +
40 + result.AssertSuccess();
41 +
42 + Assert.True(File.Exists(bundlePath));
43 +
44 + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
45 + extractResult.AssertSuccess();
46 +
47 + var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension");
48 + Assert.Equal(1, bundleExtensions.Count);
49 + Assert.Equal("<BundleExtension Id='ExampleBext' EntryPayloadId='ExampleBext' />", bundleExtensions[0].GetTestXml());
50 +
51 + var bundleExtensionPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload[@Id='ExampleBext']");
52 + Assert.Equal(1, bundleExtensionPayloads.Count);
53 + var ignored = new Dictionary<string, List<string>>
54 + {
55 + { "Payload", new List<string> { "FileSize", "Hash", "SourcePath" } },
56 + };
57 + Assert.Equal("<Payload Id='ExampleBext' FilePath='fakebext.dll' FileSize='*' Hash='*' Packaging='embedded' SourcePath='*' />", bundleExtensionPayloads[0].GetTestXml(ignored));
58 + }
59 + }
60 + }
61 +}
src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs new
+6
@@ -0,0 +1,6 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 + <Fragment>
4 + <BundleExtension Id="ExampleBext" SourceFile="fakeba.dll" Name="fakebext.dll" />
5 + </Fragment>
6 +</Wix>
src/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs new
+10
@@ -0,0 +1,10 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 + <Fragment>
4 + <PackageGroup Id="BundlePackages">
5 + <PackageGroupRef Id="MinimalPackageGroup" />
6 + </PackageGroup>
7 +
8 + <BundleExtensionRef Id="ExampleBext" />
9 + </Fragment>
10 +</Wix>
src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs new
+9
@@ -0,0 +1,9 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 + <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC">
4 + <BootstrapperApplication SourceFile="fakeba.dll" />
5 + <Chain>
6 + <PackageGroupRef Id="BundlePackages" />
7 + </Chain>
8 + </Bundle>
9 +</Wix>
src/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs new
+8
@@ -0,0 +1,8 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 + <Fragment>
4 + <PackageGroup Id="MinimalPackageGroup">
5 + <MsiPackage SourceFile="test.msi" />
6 + </PackageGroup>
7 + </Fragment>
8 +</Wix>
src/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs new
+62
@@ -0,0 +1,62 @@
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 WixToolsetTest.CoreIntegration
4 +{
5 + using System.Collections.Generic;
6 + using WixToolset.Core.TestPackage;
7 + using Xunit;
8 +
9 + public class TestXmlFixture
10 + {
11 + [Fact]
12 + public void ChangesIgnoredAttributesToStarToHelpMakeTestsLessFragile()
13 + {
14 + var original = @"<Top One='f'>
15 + <First Two='t'>
16 + <Target One='a' Two='b' Three='c' />
17 + </First>
18 + <Target One='z' Two='x' Three = 'y' />
19 +</Top>";
20 + var expected = "<Top One='f'><First Two='t'><Target One='*' Two='*' Three='c' /></First><Target One='*' Two='*' Three='y' /></Top>";
21 + var ignored = new Dictionary<string, List<string>> { { "Target", new List<string> { "One", "Two", "Missing" } } };
22 + Assert.Equal(expected, original.GetTestXml(ignored));
23 + }
24 +
25 + [Fact]
26 + public void OutputsSingleQuotesSinceDoubleQuotesInCsharpLiteralStringsArePainful()
27 + {
28 + var original = "<Test Simple=\"\" EscapedDoubleQuote=\"&quot;\" SingleQuoteValue=\"'test'\" Alternating='\"' AlternatingEscaped='&quot;' />";
29 + var expected = "<Test Simple='' EscapedDoubleQuote='\"' SingleQuoteValue='&apos;test&apos;' Alternating='\"' AlternatingEscaped='\"' />";
30 + Assert.Equal(expected, original.GetTestXml());
31 + }
32 +
33 + [Fact]
34 + public void RemovesAllNamespacesToReduceTyping()
35 + {
36 + var original = "<Test xmlns='a'><Child xmlns:b='b'><Grandchild xmlns:c='c' /><Grandchild /></Child></Test>";
37 + var expected = "<Test><Child><Grandchild /><Grandchild /></Child></Test>";
38 + Assert.Equal(expected, original.GetTestXml());
39 + }
40 +
41 + [Fact]
42 + public void RemovesUnnecessaryWhitespaceToAvoidLineEndingIssues()
43 + {
44 + var original = @"<Test>
45 + <Child>
46 + <Grandchild />
47 + <Grandchild />
48 + </Child>
49 +</Test>";
50 + var expected = "<Test><Child><Grandchild /><Grandchild /></Child></Test>";
51 + Assert.Equal(expected, original.GetTestXml());
52 + }
53 +
54 + [Fact]
55 + public void RemovesXmlDeclarationToReduceTyping()
56 + {
57 + var original = "<?xml version='1.0'?><Test />";
58 + var expected = "<Test />";
59 + Assert.Equal(expected, original.GetTestXml());
60 + }
61 + }
62 +}
src/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj
+4
@@ -21,6 +21,10 @@
21 <Content Include="TestData\AppSearch\FileSearch.wxs" CopyToOutputDirectory="PreserveNewest" />
22 <Content Include="TestData\AppSearch\NestedDirSearchUnderRegSearch.msi" CopyToOutputDirectory="PreserveNewest" />
23 <Content Include="TestData\AppSearch\RegistrySearch.wxs" CopyToOutputDirectory="PreserveNewest" />
24 + <Content Include="TestData\BundleExtension\BundleExtension.wxs" CopyToOutputDirectory="PreserveNewest" />
25 + <Content Include="TestData\BundleExtension\SimpleBundleExtension.wxs" CopyToOutputDirectory="PreserveNewest" />
26 + <Content Include="TestData\BundleWithPackageGroupRef\Bundle.wxs" CopyToOutputDirectory="PreserveNewest" />
27 + <Content Include="TestData\BundleWithPackageGroupRef\MinimalPackageGroup.wxs" CopyToOutputDirectory="PreserveNewest" />
28 <Content Include="TestData\Class\DecompiledOldClassTableDef.wxs" CopyToOutputDirectory="PreserveNewest" />
29 <Content Include="TestData\Class\IconIndex0.wxs" CopyToOutputDirectory="PreserveNewest" />
30 <Content Include="TestData\Class\OldClassTableDef.msi" CopyToOutputDirectory="PreserveNewest" />