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
+}