@joebigelow / wix-1 / commits / 5fd1b7ff

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