@joebigelow / wix-1 / commits / e9ef9924

Convert Dependency elements to WiX v4 schema

Rob Mensching committed Feb 27, 2021 at 07:25 UTC e9ef9924b00fab9dc3988d10d50d1f11d1ddf097
4 files changed +316 -6
src/WixToolset.Converters/WixConverter.cs
+134 -2
@@ -32,6 +32,7 @@ namespace WixToolset.Converters
32 private static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
33 private static readonly XNamespace Wix3Namespace = "http://schemas.microsoft.com/wix/2006/wi";
34 private static readonly XNamespace WixBalNamespace = "http://wixtoolset.org/schemas/v4/wxs/bal";
35 + private static readonly XNamespace WixDependencyNamespace = "http://wixtoolset.org/schemas/v4/wxs/dependency";
36 private static readonly XNamespace WixUtilNamespace = "http://wixtoolset.org/schemas/v4/wxs/util";
37 private static readonly XNamespace WixFirewallNamespace = "http://wixtoolset.org/schemas/v4/wxs/firewall";
38
@@ -53,6 +54,9 @@ namespace WixToolset.Converters
54 private static readonly XName CreateFolderElementName = WixNamespace + "CreateFolder";
55 private static readonly XName CustomTableElementName = WixNamespace + "CustomTable";
56 private static readonly XName DataElementName = WixNamespace + "Data";
57 + private static readonly XName OldProvidesElementName = WixDependencyNamespace + "Provides";
58 + private static readonly XName OldRequiresElementName = WixDependencyNamespace + "Requires";
59 + private static readonly XName OldRequiresRefElementName = WixDependencyNamespace + "RequiresRef";
60 private static readonly XName DirectoryElementName = WixNamespace + "Directory";
61 private static readonly XName ErrorElementName = WixNamespace + "Error";
62 private static readonly XName FeatureElementName = WixNamespace + "Feature";
@@ -72,6 +76,9 @@ namespace WixToolset.Converters
76 private static readonly XName ProductElementName = WixNamespace + "Product";
77 private static readonly XName ProgressTextElementName = WixNamespace + "ProgressText";
78 private static readonly XName PublishElementName = WixNamespace + "Publish";
79 + private static readonly XName ProvidesElementName = WixNamespace + "Provides";
80 + private static readonly XName RequiresElementName = WixNamespace + "Requires";
81 + private static readonly XName RequiresRefElementName = WixNamespace + "RequiresRef";
82 private static readonly XName MultiStringValueElementName = WixNamespace + "MultiStringValue";
83 private static readonly XName RemotePayloadElementName = WixNamespace + "RemotePayload";
84 private static readonly XName RegistrySearchElementName = WixNamespace + "RegistrySearch";
@@ -106,11 +113,14 @@ namespace WixToolset.Converters
113 private static readonly XName SummaryInformationElementName = WixNamespace + "SummaryInformation";
114 private static readonly XName MediaTemplateElementName = WixNamespace + "MediaTemplate";
115
116 + private static readonly XName DependencyCheckAttributeName = WixDependencyNamespace + "Check";
117 + private static readonly XName DependencyEnforceAttributeName = WixDependencyNamespace + "Enforce";
118 +
119 private static readonly Dictionary<string, XNamespace> OldToNewNamespaceMapping = new Dictionary<string, XNamespace>()
120 {
121 { "http://schemas.microsoft.com/wix/BalExtension", "http://wixtoolset.org/schemas/v4/wxs/bal" },
122 { "http://schemas.microsoft.com/wix/ComPlusExtension", "http://wixtoolset.org/schemas/v4/wxs/complus" },
113 - { "http://schemas.microsoft.com/wix/DependencyExtension", "http://wixtoolset.org/schemas/v4/wxs/dependency" },
123 + { "http://schemas.microsoft.com/wix/DependencyExtension", WixDependencyNamespace },
124 { "http://schemas.microsoft.com/wix/DifxAppExtension", "http://wixtoolset.org/schemas/v4/wxs/difxapp" },
125 { "http://schemas.microsoft.com/wix/FirewallExtension", "http://wixtoolset.org/schemas/v4/wxs/firewall" },
126 { "http://schemas.microsoft.com/wix/HttpExtension", "http://wixtoolset.org/schemas/v4/wxs/http" },
@@ -174,6 +184,9 @@ namespace WixToolset.Converters
184 { WixConverter.MsiPackageElementName, this.ConvertWindowsInstallerPackageElement },
185 { WixConverter.MspPackageElementName, this.ConvertWindowsInstallerPackageElement },
186 { WixConverter.MsuPackageElementName, this.ConvertSuppressSignatureValidation },
187 + { WixConverter.OldProvidesElementName, this.ConvertProvidesElement },
188 + { WixConverter.OldRequiresElementName, this.ConvertRequiresElement },
189 + { WixConverter.OldRequiresRefElementName, this.ConvertRequiresRefElement },
190 { WixConverter.PayloadElementName, this.ConvertSuppressSignatureValidation },
191 { WixConverter.PermissionExElementName, this.ConvertPermissionExElement },
192 { WixConverter.ProductElementName, this.ConvertProductElement },
@@ -275,6 +288,7 @@ namespace WixToolset.Converters
288
289 // Start converting the nodes at the top.
290 this.ConvertNodes(document.Nodes(), 0);
291 + this.RemoveUnusedNamspaces(document.Root);
292
293 return this.Errors;
294 }
@@ -327,6 +341,7 @@ namespace WixToolset.Converters
341
342 // Start converting the nodes at the top.
343 this.ConvertNodes(document.Nodes(), 0);
344 + this.RemoveUnusedNamspaces(document.Root);
345
346 return this.Errors;
347 }
@@ -1136,6 +1151,50 @@ namespace WixToolset.Converters
1151
1152 private void ConvertShortcutPropertyElement(XElement element) => this.ConvertInnerTextToAttribute(element, "Value");
1153
1154 + private void ConvertProvidesElement(XElement element)
1155 + {
1156 + if (this.OnError(ConverterTestType.IntegratedDependencyNamespace, element, "The Provides element has been integrated into the WiX v4 namespace. Remove the namespace."))
1157 + {
1158 + element.Name = ProvidesElementName;
1159 + }
1160 +
1161 + if (element.Parent.Name == ComponentElementName &&
1162 + this.OnError(ConverterTestType.IntegratedDependencyNamespace, element, "The Provides element has been integrated into the WiX v4 namespace. Add the 'Check' attribute from the WixDependency.wixext to match v3 runtime behavior."))
1163 + {
1164 + element.Add(new XAttribute(DependencyCheckAttributeName, "yes"));
1165 + }
1166 + }
1167 +
1168 + private void ConvertRequiresElement(XElement element)
1169 + {
1170 + if (this.OnError(ConverterTestType.IntegratedDependencyNamespace, element, "The Requires element has been integrated into the WiX v4 namespace. Remove the namespace."))
1171 + {
1172 + element.Name = RequiresElementName;
1173 + }
1174 +
1175 + if (element.Parent.Name == ProvidesElementName &&
1176 + element.Parent.Parent?.Name == ComponentElementName &&
1177 + this.OnError(ConverterTestType.IntegratedDependencyNamespace, element, "The Requires element has been integrated into the WiX v4 namespace. Add the 'Enforce' attribute from the WixDependency.wixext to match v3 runtime behavior."))
1178 + {
1179 + element.Add(new XAttribute(DependencyEnforceAttributeName, "yes"));
1180 + }
1181 + }
1182 +
1183 + private void ConvertRequiresRefElement(XElement element)
1184 + {
1185 + if (this.OnError(ConverterTestType.IntegratedDependencyNamespace, element, "The RequiresRef element has been integrated into the WiX v4 namespace. Remove the namespace."))
1186 + {
1187 + element.Name = RequiresRefElementName;
1188 + }
1189 +
1190 + if (element.Parent.Name == ProvidesElementName &&
1191 + element.Parent.Parent?.Name == ComponentElementName &&
1192 + this.OnError(ConverterTestType.IntegratedDependencyNamespace, element, "The RequiresRef element has been integrated into the WiX v4 namespace. Add the 'Enforce' attribute from the WixDependency.wixext to match v3 runtime behavior."))
1193 + {
1194 + element.Add(new XAttribute(DependencyEnforceAttributeName, "yes"));
1195 + }
1196 + }
1197 +
1198 private void ConvertSuppressSignatureValidation(XElement element)
1199 {
1200 var suppressSignatureValidation = element.Attribute("SuppressSignatureValidation");
@@ -1149,7 +1208,7 @@ namespace WixToolset.Converters
1208
1209 private void ConvertTagElement(XElement element)
1210 {
1152 - if (this.OnError(ConverterTestType.TagElementRenamed, element, "The Tag element has been renamed. Use the element 'SoftwareTag' name."))
1211 + if (this.OnError(ConverterTestType.TagElementRenamed, element, "The Tag element has been renamed. Use the 'SoftwareTag' element instead."))
1212 {
1213 element.Name = SoftwareTagElementName;
1214 }
@@ -1311,6 +1370,10 @@ namespace WixToolset.Converters
1370 element.Add(new XAttribute("Bitness", value));
1371 win64.Remove();
1372 }
1373 + //else if (this.OnError(ConverterTestType.BitnessAttributeRequired, element, "Use the Bitness attribute instead."))
1374 + //{
1375 + // element.Add(new XAttribute("Bitness", "always32"));
1376 + //}
1377
1378 var result = element.Attribute("Result")?.Value;
1379 if (result == null || result == "value")
@@ -1460,6 +1523,50 @@ namespace WixToolset.Converters
1523 whitespace.Value = value.Append(' ', level * indentationAmount).ToString();
1524 }
1525
1526 + /// <summary>
1527 + /// Removes unused namespaces from the element and its children.
1528 + /// </summary>
1529 + /// <param name="root">Root element to start at.</param>
1530 + private void RemoveUnusedNamspaces(XElement root)
1531 + {
1532 + var declarations = new List<XAttribute>();
1533 + var namespaces = new HashSet<string>();
1534 +
1535 + VisitElement(root, x =>
1536 + {
1537 + if (x is XAttribute a && a.IsNamespaceDeclaration)
1538 + {
1539 + declarations.Add(a);
1540 + namespaces.Add(a.Value);
1541 + }
1542 + return true;
1543 + });
1544 +
1545 + foreach (var ns in namespaces.ToList())
1546 + {
1547 + VisitElement(root, x =>
1548 + {
1549 + if ((x is XElement e && e.Name.Namespace == ns) ||
1550 + (x is XAttribute a && !a.IsNamespaceDeclaration && a.Name.Namespace == ns))
1551 + {
1552 + namespaces.Remove(ns);
1553 + return false;
1554 + }
1555 +
1556 + return true;
1557 + });
1558 + }
1559 +
1560 + foreach (var declaration in declarations)
1561 + {
1562 + if (namespaces.Contains(declaration.Value) &&
1563 + this.OnError(ConverterTestType.RemoveUnusedNamespaces, declaration, "The namespace '{0}' is not used. Remove unused namespaces.", declaration.Value))
1564 + {
1565 + declaration.Remove();
1566 + }
1567 + }
1568 + }
1569 +
1570 /// <summary>
1571 /// Output an error message to the console.
1572 /// </summary>
@@ -1601,6 +1708,21 @@ namespace WixToolset.Converters
1708 }
1709 }
1710
1711 + private static bool VisitElement(XElement element, Func<XObject, bool> visitor)
1712 + {
1713 + if (!visitor(element))
1714 + {
1715 + return false;
1716 + }
1717 +
1718 + if (!element.Attributes().All(a => visitor(a)))
1719 + {
1720 + return false;
1721 + }
1722 +
1723 + return element.Elements().All(e => VisitElement(e, visitor));
1724 + }
1725 +
1726 private static bool WasImplicitlyStringTyped(string value)
1727 {
1728 if (value == null)
@@ -1835,6 +1957,16 @@ namespace WixToolset.Converters
1957 /// The Tag element has been renamed. Use the element 'SoftwareTag' name.
1958 /// </summary>
1959 TagElementRenamed,
1960 +
1961 + /// <summary>
1962 + /// The Dependency namespace has been incorporated into WiX v4 namespace.
1963 + /// </summary>
1964 + IntegratedDependencyNamespace,
1965 +
1966 + /// <summary>
1967 + /// Remove unused namespaces.
1968 + /// </summary>
1969 + RemoveUnusedNamespaces,
1970 }
1971 }
1972 }
src/test/WixToolsetTest.Converters/BootstrapperApplicationFixture.cs
+2 -2
@@ -396,7 +396,7 @@ namespace WixToolsetTest.Converters
396
397 var expected = new[]
398 {
399 - "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:bal=\"http://wixtoolset.org/schemas/v4/wxs/bal\">",
399 + "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
400 " <Fragment>",
401 " <BootstrapperApplication Id=\"ba\" />",
402 " </Fragment>",
@@ -409,7 +409,7 @@ namespace WixToolsetTest.Converters
409 var converter = new WixConverter(messaging, 2, null, null);
410
411 var errors = converter.ConvertDocument(document);
412 - Assert.Equal(4, errors);
412 + Assert.Equal(5, errors);
413
414 var actualLines = UnformattedDocumentLines(document);
415 WixAssert.CompareLineByLine(expected, actualLines);
src/test/WixToolsetTest.Converters/ConverterFixture.cs
+2 -2
@@ -166,7 +166,7 @@ namespace WixToolsetTest.Converters
166 "</Wix>");
167
168 var expected = String.Join(Environment.NewLine,
169 - "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:util=\"http://wixtoolset.org/schemas/v4/wxs/util\">",
169 + "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
170 " <Fragment />",
171 "</Wix>");
172
@@ -179,7 +179,7 @@ namespace WixToolsetTest.Converters
179
180 var actual = UnformattedDocumentString(document);
181
182 - Assert.Equal(3, errors);
182 + Assert.Equal(4, errors);
183 Assert.Equal(expected, actual);
184 Assert.Equal(Wix4Namespace, document.Root.GetDefaultNamespace());
185 }
src/test/WixToolsetTest.Converters/DependencyFixture.cs new
+178
@@ -0,0 +1,178 @@
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.Converters
4 +{
5 + using System;
6 + using System.Xml.Linq;
7 + using WixBuildTools.TestSupport;
8 + using WixToolset.Converters;
9 + using WixToolsetTest.Converters.Mocks;
10 + using Xunit;
11 +
12 + public class DependencyFixture : BaseConverterFixture
13 + {
14 + [Fact]
15 + public void FixPackageDependencyProvides()
16 + {
17 + var parse = String.Join(Environment.NewLine,
18 + "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
19 + "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:dep='http://schemas.microsoft.com/wix/DependencyExtension'>",
20 + " <Fragment>",
21 + " <ComponentGroup Id='Group1' Directory='INSTALLFOLDER'>",
22 + " <Component>",
23 + " <dep:Provides Key='abc' />",
24 + " </Component>",
25 + " </ComponentGroup>",
26 + " </Fragment>",
27 + "</Wix>");
28 +
29 + var expected = new[]
30 + {
31 + "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:dep=\"http://wixtoolset.org/schemas/v4/wxs/dependency\">",
32 + " <Fragment>",
33 + " <ComponentGroup Id=\"Group1\" Directory=\"INSTALLFOLDER\">",
34 + " <Component>",
35 + " <Provides Key=\"abc\" dep:Check=\"yes\" />",
36 + " </Component>",
37 + " </ComponentGroup>",
38 + " </Fragment>",
39 + "</Wix>"
40 + };
41 +
42 + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
43 +
44 + var messaging = new MockMessaging();
45 + var converter = new WixConverter(messaging, 2, null, null);
46 +
47 + var errors = converter.ConvertDocument(document);
48 + Assert.Equal(5, errors);
49 +
50 + var actualLines = UnformattedDocumentLines(document);
51 + WixAssert.CompareLineByLine(expected, actualLines);
52 + }
53 +
54 + [Fact]
55 + public void FixPackageDependencyRequires()
56 + {
57 + var parse = String.Join(Environment.NewLine,
58 + "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
59 + "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:dep='http://schemas.microsoft.com/wix/DependencyExtension'>",
60 + " <Fragment>",
61 + " <ComponentGroup Id='Group1' Directory='INSTALLFOLDER'>",
62 + " <Component>",
63 + " <dep:Provides Key='abc'>",
64 + " <dep:Requires Key='xyz' />",
65 + " </dep:Provides>",
66 + " </Component>",
67 + " </ComponentGroup>",
68 + " </Fragment>",
69 + "</Wix>");
70 +
71 + var expected = new[]
72 + {
73 + "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:dep=\"http://wixtoolset.org/schemas/v4/wxs/dependency\">",
74 + " <Fragment>",
75 + " <ComponentGroup Id=\"Group1\" Directory=\"INSTALLFOLDER\">",
76 + " <Component>",
77 + " <Provides Key=\"abc\" dep:Check=\"yes\">",
78 + " <Requires Key=\"xyz\" dep:Enforce=\"yes\" />",
79 + " </Provides>",
80 + " </Component>",
81 + " </ComponentGroup>",
82 + " </Fragment>",
83 + "</Wix>"
84 + };
85 +
86 + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
87 +
88 + var messaging = new MockMessaging();
89 + var converter = new WixConverter(messaging, 2, null, null);
90 +
91 + var errors = converter.ConvertDocument(document);
92 + Assert.Equal(7, errors);
93 +
94 + var actualLines = UnformattedDocumentLines(document);
95 + WixAssert.CompareLineByLine(expected, actualLines);
96 + }
97 +
98 + [Fact]
99 + public void FixPackageDependencyRequiresRef()
100 + {
101 + var parse = String.Join(Environment.NewLine,
102 + "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
103 + "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:dep='http://schemas.microsoft.com/wix/DependencyExtension'>",
104 + " <Fragment>",
105 + " <ComponentGroup Id='Group1' Directory='INSTALLFOLDER'>",
106 + " <Component>",
107 + " <dep:Provides Key='abc'>",
108 + " <dep:RequiresRef Id='OtherRequires' />",
109 + " </dep:Provides>",
110 + " </Component>",
111 + " </ComponentGroup>",
112 + " </Fragment>",
113 + "</Wix>");
114 +
115 + var expected = new[]
116 + {
117 + "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:dep=\"http://wixtoolset.org/schemas/v4/wxs/dependency\">",
118 + " <Fragment>",
119 + " <ComponentGroup Id=\"Group1\" Directory=\"INSTALLFOLDER\">",
120 + " <Component>",
121 + " <Provides Key=\"abc\" dep:Check=\"yes\">",
122 + " <RequiresRef Id=\"OtherRequires\" dep:Enforce=\"yes\" />",
123 + " </Provides>",
124 + " </Component>",
125 + " </ComponentGroup>",
126 + " </Fragment>",
127 + "</Wix>"
128 + };
129 +
130 + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
131 +
132 + var messaging = new MockMessaging();
133 + var converter = new WixConverter(messaging, 2, null, null);
134 +
135 + var errors = converter.ConvertDocument(document);
136 + Assert.Equal(7, errors);
137 +
138 + var actualLines = UnformattedDocumentLines(document);
139 + WixAssert.CompareLineByLine(expected, actualLines);
140 + }
141 +
142 + [Fact]
143 + public void FixBundleDependencyProvides()
144 + {
145 + var parse = String.Join(Environment.NewLine,
146 + "<?xml version=\"1.0\" encoding=\"utf-16\"?>",
147 + "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:dep='http://schemas.microsoft.com/wix/DependencyExtension'>",
148 + " <Fragment>",
149 + " <MsiPackage Id='Package1'>",
150 + " <dep:Provides Key='abc' />",
151 + " </MsiPackage>",
152 + " </Fragment>",
153 + "</Wix>");
154 +
155 + var expected = new[]
156 + {
157 + "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">",
158 + " <Fragment>",
159 + " <MsiPackage Id=\"Package1\">",
160 + " <Provides Key=\"abc\" />",
161 + " </MsiPackage>",
162 + " </Fragment>",
163 + "</Wix>"
164 + };
165 +
166 + var document = XDocument.Parse(parse, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
167 +
168 + var messaging = new MockMessaging();
169 + var converter = new WixConverter(messaging, 2, null, null);
170 +
171 + var errors = converter.ConvertDocument(document);
172 + Assert.Equal(5, errors);
173 +
174 + var actualLines = UnformattedDocumentLines(document);
175 + WixAssert.CompareLineByLine(expected, actualLines);
176 + }
177 + }
178 +}