Absorb Dependency.wixext into Core
Partly resolves wixtoolset/issues#5949
Rob Mensching committed
Feb 26, 2021 at 11:24 UTC
5fd1b7ff82f17d55c8357fe76898a1bdc5953476
14 files changed
+668
src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+12
@@ -325,6 +325,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind
325
command.Execute();
326
}
327
328
+ // Process dependency references.
329
+ if (SectionType.Product == section.Type || SectionType.Module == section.Type)
330
+ {
331
+ var dependencyRefs = section.Symbols.OfType<WixDependencyRefSymbol>().ToList();
332
+
333
+ if (dependencyRefs.Any())
334
+ {
335
+ var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs);
336
+ command.Execute();
337
+ }
338
+ }
339
+
340
// If there are any backend extensions, give them the opportunity to process
341
// the section now that the fields have all be resolved.
342
//
src/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs
new
+111
@@ -0,0 +1,111 @@
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.WindowsInstaller.Bind
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.Linq;
8
+ using WixToolset.Data;
9
+ using WixToolset.Data.Symbols;
10
+ using WixToolset.Extensibility.Services;
11
+
12
+ internal class ProcessDependencyReferencesCommand
13
+ {
14
+ // The root registry key for the dependency extension. We write to Software\Classes explicitly
15
+ // based on the current security context instead of HKCR. See
16
+ // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information.
17
+ private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\";
18
+ private const string RegistryDependents = "Dependents";
19
+
20
+ public ProcessDependencyReferencesCommand(IWindowsInstallerBackendHelper backendHelper, IntermediateSection section, IEnumerable<WixDependencyRefSymbol> dependencyRefSymbols)
21
+ {
22
+ this.Section = section;
23
+ this.DependencyRefSymbols = dependencyRefSymbols;
24
+ }
25
+
26
+ private IntermediateSection Section { get; }
27
+
28
+ private IEnumerable<WixDependencyRefSymbol> DependencyRefSymbols { get; }
29
+
30
+ public void Execute()
31
+ {
32
+ var wixDependencyRows = this.Section.Symbols.OfType<WixDependencySymbol>().ToDictionary(d => d.Id.Id);
33
+ var wixDependencyProviderRows = this.Section.Symbols.OfType<WixDependencyProviderSymbol>().ToDictionary(d => d.Id.Id);
34
+
35
+ // For each relationship, get the provides and requires rows to generate registry values.
36
+ foreach (var wixDependencyRefRow in this.DependencyRefSymbols)
37
+ {
38
+ var providesId = wixDependencyRefRow.WixDependencyProviderRef;
39
+ var requiresId = wixDependencyRefRow.WixDependencyRef;
40
+
41
+ // If we do not find both symbols, skip the registry key generation.
42
+ if (!wixDependencyRows.TryGetValue(requiresId, out var wixDependencyRow))
43
+ {
44
+ continue;
45
+ }
46
+
47
+ if (!wixDependencyProviderRows.TryGetValue(providesId, out var wixDependencyProviderRow))
48
+ {
49
+ continue;
50
+ }
51
+
52
+ // Format the root registry key using the required provider key and the current provider key.
53
+ var requiresKey = wixDependencyRow.Id.Id;
54
+ var providesKey = wixDependencyRow.ProviderKey;
55
+ var keyRequires = String.Format(@"{0}{1}\{2}\{3}", DependencyRegistryRoot, requiresKey, RegistryDependents, providesKey);
56
+
57
+ // Get the component ID from the provider.
58
+ var componentId = wixDependencyProviderRow.ComponentRef;
59
+
60
+ var id = Common.GenerateIdentifier("reg", providesId, requiresId, "(Default)");
61
+ this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Private, id))
62
+ {
63
+ ComponentRef = componentId,
64
+ Root = RegistryRootType.MachineUser,
65
+ Key = keyRequires,
66
+ Name = "*",
67
+ });
68
+
69
+ if (!String.IsNullOrEmpty(wixDependencyRow.MinVersion))
70
+ {
71
+ id = Common.GenerateIdentifier("reg", providesId, requiresId, "MinVersion");
72
+ this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Private, id))
73
+ {
74
+ ComponentRef = componentId,
75
+ Root = RegistryRootType.MachineUser,
76
+ Key = keyRequires,
77
+ Name = "MinVersion",
78
+ Value = wixDependencyRow.MinVersion
79
+ });
80
+ }
81
+
82
+ string maxVersion = (string)wixDependencyRow[3];
83
+ if (!String.IsNullOrEmpty(wixDependencyRow.MaxVersion))
84
+ {
85
+ id = Common.GenerateIdentifier("reg", providesId, requiresId, "MaxVersion");
86
+ this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Private, id))
87
+ {
88
+ ComponentRef = componentId,
89
+ Root = RegistryRootType.MachineUser,
90
+ Key = keyRequires,
91
+ Name = "MaxVersion",
92
+ Value = wixDependencyRow.MaxVersion
93
+ });
94
+ }
95
+
96
+ if (wixDependencyRow.Attributes != WixDependencySymbolAttributes.None)
97
+ {
98
+ id = Common.GenerateIdentifier("reg", providesId, requiresId, "Attributes");
99
+ this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Private, id))
100
+ {
101
+ ComponentRef = componentId,
102
+ Root = RegistryRootType.MachineUser,
103
+ Key = keyRequires,
104
+ Name = "Attributes",
105
+ Value = String.Concat("#", (int)wixDependencyRow.Attributes)
106
+ });
107
+ }
108
+ }
109
+ }
110
+ }
111
+}
src/WixToolset.Core/Compiler.cs
+13
@@ -2366,6 +2366,16 @@ namespace WixToolset.Core
2366
var foundExtension = false;
2367
this.ParseProgIdElement(child, id.Id, YesNoType.NotSet, null, null, null, ref foundExtension, YesNoType.NotSet);
2368
break;
2369
+ case "Provides":
2370
+ if (win64)
2371
+ {
2372
+ this.Messaging.Write(CompilerWarnings.Win64Component(sourceLineNumbers, id.Id));
2373
+ }
2374
+
2375
+ keyPathSet = this.ParseProvidesElement(child, null, id.Id, out keyPossible);
2376
+ keyBit = ComponentKeyPathType.Registry;
2377
+ break;
2378
+
2379
case "RegistryKey":
2380
keyPathSet = this.ParseRegistryKeyElement(child, id.Id, null, null, win64, out keyPossible);
2381
keyBit = ComponentKeyPathType.Registry;
@@ -6290,6 +6300,9 @@ namespace WixToolset.Core
6300
case "RelatedBundle":
6301
this.ParseRelatedBundleElement(child);
6302
break;
6303
+ case "Requires":
6304
+ this.ParseRequiresElement(child, null, false);
6305
+ break;
6306
case "SetDirectory":
6307
this.ParseSetDirectoryElement(child);
6308
break;
src/WixToolset.Core/CompilerErrors.cs
+13
@@ -6,6 +6,16 @@ namespace WixToolset.Core
6
7
internal static class CompilerErrors
8
{
9
+ public static Message IllegalCharactersInProvider(SourceLineNumber sourceLineNumbers, string attributeName, char illegalChar, string illegalChars)
10
+ {
11
+ return Message(sourceLineNumbers, Ids.IllegalCharactersInProvider, "The provider key authored into the {0} attribute contains an illegal character, '{1}'. Please author the provider key without any of the following characters: {2}", attributeName, illegalChar, illegalChars);
12
+ }
13
+
14
+ public static Message ReservedValue(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string attributeValue)
15
+ {
16
+ return Message(sourceLineNumbers, Ids.ReservedValue, "The {0}/@{1} attribute value '{2}' is reserved and cannot be used here. Please choose a different value.", elementName, attributeName, attributeValue);
17
+ }
18
+
19
public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name)
20
{
21
return Message(sourceLineNumbers, Ids.IllegalName, "The Tag/@Name attribute value, '{1}', contains invalid filename identifiers. The Tag/@Name may have defaulted from the {0}/@Name attrbute. If so, use the Tag/@Name attribute to provide a valid filename. Any character except for the follow may be used: \\ ? | > < : / * \".", parentElement, name);
@@ -23,6 +33,9 @@ namespace WixToolset.Core
33
34
public enum Ids
35
{
36
+ IllegalCharactersInProvider = 5400,
37
+ ReservedValue = 5401,
38
+
39
IllegalName = 6601,
40
ExampleRegid = 6602,
41
}
src/WixToolset.Core/CompilerWarnings.cs
new
+53
@@ -0,0 +1,53 @@
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
4
+{
5
+ using WixToolset.Data;
6
+
7
+ internal static class CompilerWarnings
8
+ {
9
+ public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers)
10
+ {
11
+ return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified in an MSI package. The ProductVersion will be used by default.");
12
+ }
13
+
14
+ public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers, string id)
15
+ {
16
+ return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified for MSI package {0}. The ProductVersion will be used by default.", id);
17
+ }
18
+
19
+ public static Message PropertyRemoved(string name)
20
+ {
21
+ return Message(null, Ids.PropertyRemoved, "The property {0} was authored in the package with a value and will be removed. The property should not be authored.", name);
22
+ }
23
+
24
+ public static Message ProvidesKeyNotFound(SourceLineNumber sourceLineNumbers, string id)
25
+ {
26
+ return Message(sourceLineNumbers, Ids.ProvidesKeyNotFound, "The provider key with identifier {0} was not found in the WixDependencyProvider table. Related registry rows will not be removed from authoring.", id);
27
+ }
28
+
29
+ public static Message RequiresKeyNotFound(SourceLineNumber sourceLineNumbers, string id)
30
+ {
31
+ return Message(sourceLineNumbers, Ids.RequiresKeyNotFound, "The dependency key with identifier {0} was not found in the WixDependency table. Related registry rows will not be removed from authoring.", id);
32
+ }
33
+
34
+ public static Message Win64Component(SourceLineNumber sourceLineNumbers, string componentId)
35
+ {
36
+ return Message(sourceLineNumbers, Ids.Win64Component, "The Provides element should not be authored in the 64-bit component with identifier {0}. The dependency feature may not work if installing this package on 64-bit Windows operating systems prior to Windows 7 and Windows Server 2008 R2. Set the Component/@Bitness attribute to \"always32\" to ensure the dependency feature works correctly on legacy operating systems.", componentId);
37
+ }
38
+
39
+ private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
40
+ {
41
+ return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args);
42
+ }
43
+
44
+ public enum Ids
45
+ {
46
+ ProvidesKeyNotFound = 5431,
47
+ RequiresKeyNotFound = 5432,
48
+ PropertyRemoved = 5433,
49
+ DiscouragedVersionAttribute = 5434,
50
+ Win64Component = 5435,
51
+ }
52
+ }
53
+}
src/WixToolset.Core/Compiler_Bundle.cs
+9
@@ -196,6 +196,9 @@ namespace WixToolset.Core
196
case "ParentName":
197
parentName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
198
break;
199
+ case "ProviderKey":
200
+ this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib);
201
+ break;
202
case "SplashScreenSourceFile":
203
splashScreenSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
204
break;
@@ -340,6 +343,9 @@ namespace WixToolset.Core
343
case "RelatedBundle":
344
this.ParseRelatedBundleElement(child);
345
break;
346
+ case "Requires":
347
+ this.ParseRequiresElement(child, null, false);
348
+ break;
349
case "SetVariable":
350
this.ParseSetVariableElement(child);
351
break;
@@ -2386,6 +2392,9 @@ namespace WixToolset.Core
2392
case "PayloadGroupRef":
2393
this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null);
2394
break;
2395
+ case "Provides":
2396
+ this.ParseProvidesElement(child, packageType, id.Id, out _);
2397
+ break;
2398
case "ExitCode":
2399
allowed = (packageType == WixBundlePackageType.Exe);
2400
if (allowed)
src/WixToolset.Core/Compiler_Dependency.cs
new
+385
@@ -0,0 +1,385 @@
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
4
+{
5
+ using System;
6
+ using System.Xml.Linq;
7
+ using WixToolset.Data;
8
+ using WixToolset.Data.Symbols;
9
+
10
+ /// <summary>
11
+ /// Compiler of the WiX toolset.
12
+ /// </summary>
13
+ internal partial class Compiler : ICompiler
14
+ {
15
+ // The root registry key for the dependency extension. We write to Software\Classes explicitly
16
+ // based on the current security context instead of HKCR. See
17
+ // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information.
18
+ private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\";
19
+
20
+ private static readonly char[] InvalidDependencyCharacters = new char[] { ' ', '\"', ';', '\\' };
21
+
22
+ /// <summary>
23
+ /// Processes the ProviderKey bundle attribute.
24
+ /// </summary>
25
+ /// <param name="sourceLineNumbers">Source line number for the parent element.</param>
26
+ /// <param name="parentElement">Parent element of attribute.</param>
27
+ /// <param name="attribute">The XML attribute for the ProviderKey attribute.</param>
28
+ private void ParseBundleProviderKeyAttribute(SourceLineNumber sourceLineNumbers, XElement parentElement, XAttribute attribute)
29
+ {
30
+ var providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attribute);
31
+ int illegalChar;
32
+
33
+ // Make sure the key does not contain any illegal characters or values.
34
+ if (String.IsNullOrEmpty(providerKey))
35
+ {
36
+ this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, attribute.Name.LocalName));
37
+ }
38
+ else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters)))
39
+ {
40
+ this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters)));
41
+ }
42
+ else if ("ALL" == providerKey)
43
+ {
44
+ this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, parentElement.Name.LocalName, "ProviderKey", providerKey));
45
+ }
46
+
47
+ if (!this.Messaging.EncounteredError)
48
+ {
49
+ // Generate the primary key for the row.
50
+ var id = this.Core.CreateIdentifier("dep", attribute.Name.LocalName, providerKey);
51
+
52
+ // Create the provider symbol for the bundle. The Component_ field is required
53
+ // in the table definition but unused for bundles, so just set it to the valid ID.
54
+ this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id)
55
+ {
56
+ ComponentRef = id.Id,
57
+ ProviderKey = providerKey,
58
+ Attributes = WixDependencyProviderAttributes.ProvidesAttributesBundle,
59
+ });
60
+ }
61
+ }
62
+
63
+ /// <summary>
64
+ /// Processes the Provides element.
65
+ /// </summary>
66
+ /// <param name="node">The XML node for the Provides element.</param>
67
+ /// <param name="packageType">The type of the package being chained into a bundle, or null if building an MSI package.</param>
68
+ /// <param name="parentId">The identifier of the parent component or package.</param>
69
+ /// <param name="possibleKeyPath">Possible KeyPath identifier.</param>
70
+ /// <returns>Yes if this is the keypath.</returns>
71
+ private YesNoType ParseProvidesElement(XElement node, WixBundlePackageType? packageType, string parentId, out string possibleKeyPath)
72
+ {
73
+ possibleKeyPath = null;
74
+
75
+ var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
76
+ Identifier id = null;
77
+ string key = null;
78
+ string version = null;
79
+ string displayName = null;
80
+
81
+ foreach (var attrib in node.Attributes())
82
+ {
83
+ if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
84
+ {
85
+ switch (attrib.Name.LocalName)
86
+ {
87
+ case "Id":
88
+ id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
89
+ break;
90
+ case "Key":
91
+ key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
92
+ break;
93
+ case "Version":
94
+ version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
95
+ break;
96
+ case "DisplayName":
97
+ displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
98
+ break;
99
+ default:
100
+ this.Core.UnexpectedAttribute(node, attrib);
101
+ break;
102
+ }
103
+ }
104
+ else
105
+ {
106
+ this.Core.ParseExtensionAttribute(node, attrib);
107
+ }
108
+ }
109
+
110
+ // Make sure the key is valid. The key will default to the ProductCode for MSI packages
111
+ // and the package code for MSP packages in the binder if not specified.
112
+ if (!String.IsNullOrEmpty(key))
113
+ {
114
+ int illegalChar;
115
+
116
+ // Make sure the key does not contain any illegal characters or values.
117
+ if (0 <= (illegalChar = key.IndexOfAny(InvalidDependencyCharacters)))
118
+ {
119
+ this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "Key", key[illegalChar], String.Join(" ", InvalidDependencyCharacters)));
120
+ }
121
+ else if ("ALL" == key)
122
+ {
123
+ this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Key", key));
124
+ }
125
+ }
126
+ else if (!packageType.HasValue)
127
+ {
128
+ // Make sure the ProductCode is authored and set the key.
129
+ this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "ProductCode");
130
+ key = "!(bind.property.ProductCode)";
131
+ }
132
+ else if (WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msu == packageType)
133
+ {
134
+ // Must specify the provider key when authored for a package.
135
+ this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
136
+ }
137
+
138
+ // The Version attribute should not be authored in or for an MSI package.
139
+ if (!String.IsNullOrEmpty(version))
140
+ {
141
+ switch (packageType)
142
+ {
143
+ case null:
144
+ this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers));
145
+ break;
146
+ case WixBundlePackageType.Msi:
147
+ this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers, parentId));
148
+ break;
149
+ }
150
+ }
151
+ else if (WixBundlePackageType.Msp == packageType || WixBundlePackageType.Msu == packageType)
152
+ {
153
+ // Must specify the Version when authored for packages that do not contain a version.
154
+ this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
155
+ }
156
+
157
+ // Need the element ID for child element processing, so generate now if not authored.
158
+ if (null == id)
159
+ {
160
+ id = this.Core.CreateIdentifier("dep", node.Name.LocalName, parentId, key);
161
+ }
162
+
163
+ foreach (var child in node.Elements())
164
+ {
165
+ if (CompilerCore.WixNamespace == child.Name.Namespace)
166
+ {
167
+ switch (child.Name.LocalName)
168
+ {
169
+ case "Requires":
170
+ this.ParseRequiresElement(child, id.Id, requiresAction: !packageType.HasValue);
171
+ break;
172
+ case "RequiresRef":
173
+ this.ParseRequiresRefElement(child, id.Id, requiresAction: !packageType.HasValue);
174
+ break;
175
+ default:
176
+ this.Core.UnexpectedElement(node, child);
177
+ break;
178
+ }
179
+ }
180
+ else
181
+ {
182
+ this.Core.ParseExtensionElement(node, child);
183
+ }
184
+ }
185
+
186
+ if (!this.Messaging.EncounteredError)
187
+ {
188
+ var symbol = this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id)
189
+ {
190
+ ComponentRef = parentId,
191
+ ProviderKey = key,
192
+ });
193
+
194
+ if (!String.IsNullOrEmpty(version))
195
+ {
196
+ symbol.Version = version;
197
+ }
198
+
199
+ if (!String.IsNullOrEmpty(displayName))
200
+ {
201
+ symbol.DisplayName = displayName;
202
+ }
203
+
204
+ if (!packageType.HasValue)
205
+ {
206
+ // Generate registry rows for the provider using binder properties.
207
+ var keyProvides = String.Concat(DependencyRegistryRoot, key);
208
+ var root = RegistryRootType.MachineUser;
209
+
210
+ var value = "[ProductCode]";
211
+ this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, null, value, parentId);
212
+
213
+ value = !String.IsNullOrEmpty(version) ? version : "[ProductVersion]";
214
+ var versionRegistrySymbol = this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "Version", value, parentId);
215
+
216
+ value = !String.IsNullOrEmpty(displayName) ? displayName : "[ProductName]";
217
+ this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "DisplayName", value, parentId);
218
+
219
+ // Use the Version registry value and use that as a potential key path.
220
+ possibleKeyPath = versionRegistrySymbol.Id;
221
+ }
222
+ }
223
+
224
+ return YesNoType.NotSet;
225
+ }
226
+
227
+ /// <summary>
228
+ /// Processes the Requires element.
229
+ /// </summary>
230
+ /// <param name="node">The XML node for the Requires element.</param>
231
+ /// <param name="providerId">The parent provider identifier.</param>
232
+ /// <param name="requiresAction">Whether the Requires custom action should be referenced.</param>
233
+ private void ParseRequiresElement(XElement node, string providerId, bool requiresAction)
234
+ {
235
+ var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
236
+ Identifier id = null;
237
+ string providerKey = null;
238
+ string minVersion = null;
239
+ string maxVersion = null;
240
+ var attributes = WixDependencySymbolAttributes.None;
241
+ var illegalChar = -1;
242
+
243
+ foreach (var attrib in node.Attributes())
244
+ {
245
+ if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
246
+ {
247
+ switch (attrib.Name.LocalName)
248
+ {
249
+ case "Id":
250
+ id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
251
+ break;
252
+ case "ProviderKey":
253
+ providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
254
+ break;
255
+ case "Minimum":
256
+ minVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
257
+ break;
258
+ case "Maximum":
259
+ maxVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
260
+ break;
261
+ case "IncludeMinimum":
262
+ if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
263
+ {
264
+ attributes |= WixDependencySymbolAttributes.RequiresAttributesMinVersionInclusive;
265
+ }
266
+ break;
267
+ case "IncludeMaximum":
268
+ if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
269
+ {
270
+ attributes |= WixDependencySymbolAttributes.RequiresAttributesMaxVersionInclusive;
271
+ }
272
+ break;
273
+ default:
274
+ this.Core.UnexpectedAttribute(node, attrib);
275
+ break;
276
+ }
277
+ }
278
+ else
279
+ {
280
+ this.Core.ParseExtensionAttribute(node, attrib);
281
+ }
282
+ }
283
+
284
+ this.Core.ParseForExtensionElements(node);
285
+
286
+ if (null == id)
287
+ {
288
+ // Generate an ID only if this element is authored under a Provides element; otherwise, a RequiresRef
289
+ // element will be necessary and the Id attribute will be required.
290
+ if (!String.IsNullOrEmpty(providerId))
291
+ {
292
+ id = this.Core.CreateIdentifier("dep", node.Name.LocalName, providerKey);
293
+ }
294
+ else
295
+ {
296
+ this.Messaging.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Id", "Provides"));
297
+ id = Identifier.Invalid;
298
+ }
299
+ }
300
+
301
+ if (String.IsNullOrEmpty(providerKey))
302
+ {
303
+ this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProviderKey"));
304
+ }
305
+ // Make sure the key does not contain any illegal characters.
306
+ else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters)))
307
+ {
308
+ this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters)));
309
+ }
310
+
311
+ if (!this.Messaging.EncounteredError)
312
+ {
313
+ var symbol = this.Core.AddSymbol(new WixDependencySymbol(sourceLineNumbers, id)
314
+ {
315
+ ProviderKey = providerKey,
316
+ MinVersion = minVersion,
317
+ MaxVersion = maxVersion,
318
+ Attributes = attributes
319
+ });
320
+
321
+ // Create the relationship between this WixDependency symbol and the WixDependencyProvider symbol.
322
+ if (!String.IsNullOrEmpty(providerId))
323
+ {
324
+ this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers)
325
+ {
326
+ WixDependencyProviderRef = providerId,
327
+ WixDependencyRef = id.Id,
328
+ });
329
+ }
330
+ }
331
+ }
332
+
333
+ /// <summary>
334
+ /// Processes the RequiresRef element.
335
+ /// </summary>
336
+ /// <param name="node">The XML node for the RequiresRef element.</param>
337
+ /// <param name="providerId">The parent provider identifier.</param>
338
+ /// <param name="requiresAction">Whether the Requires custom action should be referenced.</param>
339
+ private void ParseRequiresRefElement(XElement node, string providerId, bool requiresAction)
340
+ {
341
+ var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
342
+ string id = null;
343
+
344
+ foreach (var attrib in node.Attributes())
345
+ {
346
+ if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
347
+ {
348
+ switch (attrib.Name.LocalName)
349
+ {
350
+ case "Id":
351
+ id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
352
+ break;
353
+ default:
354
+ this.Core.UnexpectedAttribute(node, attrib);
355
+ break;
356
+ }
357
+ }
358
+ else
359
+ {
360
+ this.Core.ParseExtensionAttribute(node, attrib);
361
+ }
362
+ }
363
+
364
+ this.Core.ParseForExtensionElements(node);
365
+
366
+ if (String.IsNullOrEmpty(id))
367
+ {
368
+ this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
369
+ }
370
+
371
+ if (!this.Messaging.EncounteredError)
372
+ {
373
+ // Create a link dependency on the row that contains information we'll need during bind.
374
+ this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixDependency, id);
375
+
376
+ // Create the relationship between the WixDependency row and the parent WixDependencyProvider row.
377
+ this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers)
378
+ {
379
+ WixDependencyProviderRef = providerId,
380
+ WixDependencyRef = id,
381
+ });
382
+ }
383
+ }
384
+ }
385
+}
src/WixToolset.Core/Compiler_Module.cs
+3
@@ -190,6 +190,9 @@ namespace WixToolset.Core
190
case "PropertyRef":
191
this.ParseSimpleRefElement(child, SymbolDefinitions.Property);
192
break;
193
+ case "Requires":
194
+ this.ParseRequiresElement(child, null, false);
195
+ break;
196
case "SetDirectory":
197
this.ParseSetDirectoryElement(child);
198
break;
src/WixToolset.Core/Compiler_Package.cs
renamed
+3
@@ -306,6 +306,9 @@ namespace WixToolset.Core
306
case "PropertyRef":
307
this.ParseSimpleRefElement(child, SymbolDefinitions.Property);
308
break;
309
+ case "Requires":
310
+ this.ParseRequiresElement(child, null, false);
311
+ break;
312
case "SetDirectory":
313
this.ParseSetDirectoryElement(child);
314
break;
src/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs
new
+30
@@ -0,0 +1,30 @@
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 WixBuildTools.TestSupport;
6
+ using WixToolset.Core.TestPackage;
7
+ using Xunit;
8
+
9
+ public class DependencyExtensionFixture
10
+ {
11
+ [Fact]
12
+ public void CanBuildUsingProvides()
13
+ {
14
+ var folder = TestData.Get(@"TestData\UsingProvides");
15
+ var build = new Builder(folder, null, new[] { folder });
16
+
17
+ var results = build.BuildAndQuery(Build, "WixDependencyProvider");
18
+ Assert.Equal(new[]
19
+ {
20
+ "WixDependencyProvider:dep74OfIcniaqxA7EprRGBw4Oyy3r8\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tUsingProvides\t\t\t",
21
+ }, results);
22
+ }
23
+
24
+ private static void Build(string[] args)
25
+ {
26
+ var result = WixRunner.Execute(args)
27
+ .AssertSuccess();
28
+ }
29
+ }
30
+}
src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl
new
+11
@@ -0,0 +1,11 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+
3
+<!--
4
+This file contains the declaration of all the localizable strings.
5
+-->
6
+<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
+
8
+ <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9
+ <String Id="FeatureTitle">MsiPackage</String>
10
+
11
+</WixLocalization>
src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs
new
+14
@@ -0,0 +1,14 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+
4
+ <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
5
+
6
+ <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
7
+ <ComponentGroupRef Id="ProductComponents" />
8
+ </Feature>
9
+ </Package>
10
+
11
+ <Fragment>
12
+ <Directory Id="INSTALLFOLDER" Name="ProgramFilesFolder:\MsiPackage" />
13
+ </Fragment>
14
+</Wix>
src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs
new
+10
@@ -0,0 +1,10 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Fragment>
3
+ <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
4
+ <Component>
5
+ <File Source="example.txt" />
6
+ <Provides Key="UsingProvides" />
7
+ </Component>
8
+ </ComponentGroup>
9
+ </Fragment>
10
+</Wix>
src/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt
new
+1
@@ -0,0 +1 @@
1
+This is example.txt.
\ No newline at end of file