aboutsummaryrefslogtreecommitdiff
path: root/src/wixext/DependencyCompiler.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/wixext/DependencyCompiler.cs')
-rw-r--r--src/wixext/DependencyCompiler.cs615
1 files changed, 615 insertions, 0 deletions
diff --git a/src/wixext/DependencyCompiler.cs b/src/wixext/DependencyCompiler.cs
new file mode 100644
index 00000000..a138c047
--- /dev/null
+++ b/src/wixext/DependencyCompiler.cs
@@ -0,0 +1,615 @@
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
3namespace WixToolset.Extensions
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Text;
9 using System.Xml.Linq;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12
13 /// <summary>
14 /// The compiler for the WiX toolset dependency extension.
15 /// </summary>
16 public sealed class DependencyCompiler : CompilerExtension
17 {
18 /// <summary>
19 /// Package type when parsing the Provides element.
20 /// </summary>
21 private enum PackageType
22 {
23 None,
24 ExePackage,
25 MsiPackage,
26 MspPackage,
27 MsuPackage
28 }
29
30 public DependencyCompiler()
31 {
32 this.Namespace = "http://wixtoolset.org/schemas/v4/wxs/dependency";
33 }
34
35 /// <summary>
36 /// Processes an attribute for the Compiler.
37 /// </summary>
38 /// <param name="sourceLineNumbers">Source line number for the parent element.</param>
39 /// <param name="parentElement">Parent element of attribute.</param>
40 /// <param name="attribute">Attribute to process.</param>
41 public override void ParseAttribute(XElement parentElement, XAttribute attribute, IDictionary<string, string> context)
42 {
43 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(parentElement);
44 switch (parentElement.Name.LocalName)
45 {
46 case "Bundle":
47 switch (attribute.Name.LocalName)
48 {
49 case "ProviderKey":
50 this.ParseProviderKeyAttribute(sourceLineNumbers, parentElement, attribute);
51 break;
52 default:
53 this.Core.UnexpectedAttribute(parentElement, attribute);
54 break;
55 }
56 break;
57 default:
58 this.Core.UnexpectedAttribute(parentElement, attribute);
59 break;
60 }
61 }
62
63 /// <summary>
64 /// Processes an element for the Compiler.
65 /// </summary>
66 /// <param name="sourceLineNumbers">Source line number for the parent element.</param>
67 /// <param name="parentElement">Parent element of element to process.</param>
68 /// <param name="element">Element to process.</param>
69 /// <param name="contextValues">Extra information about the context in which this element is being parsed.</param>
70 public override void ParseElement(XElement parentElement, XElement element, IDictionary<string, string> context)
71 {
72 PackageType packageType = PackageType.None;
73
74 switch (parentElement.Name.LocalName)
75 {
76 case "Bundle":
77 case "Fragment":
78 case "Module":
79 case "Product":
80 switch (element.Name.LocalName)
81 {
82 case "Requires":
83 this.ParseRequiresElement(element, null, false);
84 break;
85 default:
86 this.Core.UnexpectedElement(parentElement, element);
87 break;
88 }
89 break;
90 case "ExePackage":
91 packageType = PackageType.ExePackage;
92 break;
93 case "MsiPackage":
94 packageType = PackageType.MsiPackage;
95 break;
96 case "MspPackage":
97 packageType = PackageType.MspPackage;
98 break;
99 case "MsuPackage":
100 packageType = PackageType.MsuPackage;
101 break;
102 default:
103 this.Core.UnexpectedElement(parentElement, element);
104 break;
105 }
106
107 if (PackageType.None != packageType)
108 {
109 string packageId = context["PackageId"];
110
111 switch (element.Name.LocalName)
112 {
113 case "Provides":
114 this.ParseProvidesElement(element, packageType, packageId);
115 break;
116 default:
117 this.Core.UnexpectedElement(parentElement, element);
118 break;
119 }
120 }
121 }
122
123 /// <summary>
124 /// Processes a child element of a Component for the Compiler.
125 /// </summary>
126 /// <param name="parentElement">Parent element of element to process.</param>
127 /// <param name="element">Element to process.</param>
128 /// <param name="context">Extra information about the context in which this element is being parsed.</param>
129 /// <returns>The component key path type if set.</returns>
130 public override ComponentKeyPath ParsePossibleKeyPathElement(XElement parentElement, XElement element, IDictionary<string, string> context)
131 {
132 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(parentElement);
133 ComponentKeyPath keyPath = null;
134
135 switch (parentElement.Name.LocalName)
136 {
137 case "Component":
138 string componentId = context["ComponentId"];
139
140 // 64-bit components may cause issues downlevel.
141 bool win64 = false;
142 Boolean.TryParse(context["Win64"], out win64);
143
144 switch (element.Name.LocalName)
145 {
146 case "Provides":
147 if (win64)
148 {
149 this.Core.OnMessage(DependencyWarnings.Win64Component(sourceLineNumbers, componentId));
150 }
151
152 keyPath = this.ParseProvidesElement(element, PackageType.None, componentId);
153 break;
154 default:
155 this.Core.UnexpectedElement(parentElement, element);
156 break;
157 }
158 break;
159 default:
160 this.Core.UnexpectedElement(parentElement, element);
161 break;
162 }
163
164 return keyPath;
165 }
166
167 /// <summary>
168 /// Processes the ProviderKey bundle attribute.
169 /// </summary>
170 /// <param name="sourceLineNumbers">Source line number for the parent element.</param>
171 /// <param name="parentElement">Parent element of attribute.</param>
172 /// <param name="attribute">The XML attribute for the ProviderKey attribute.</param>
173 private void ParseProviderKeyAttribute(SourceLineNumber sourceLineNumbers, XElement parentElement, XAttribute attribute)
174 {
175 Identifier id = null;
176 string providerKey = null;
177 int illegalChar = -1;
178
179 switch (attribute.Name.LocalName)
180 {
181 case "ProviderKey":
182 providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attribute);
183 break;
184 default:
185 this.Core.UnexpectedAttribute(parentElement, attribute);
186 break;
187 }
188
189 // Make sure the key does not contain any illegal characters or values.
190 if (String.IsNullOrEmpty(providerKey))
191 {
192 this.Core.OnMessage(WixErrors.IllegalEmptyAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, attribute.Name.LocalName));
193 }
194 else if (0 <= (illegalChar = providerKey.IndexOfAny(DependencyCommon.InvalidCharacters)))
195 {
196 StringBuilder sb = new StringBuilder(DependencyCommon.InvalidCharacters.Length * 2);
197 Array.ForEach<char>(DependencyCommon.InvalidCharacters, c => sb.Append(c).Append(" "));
198
199 this.Core.OnMessage(DependencyErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], sb.ToString()));
200 }
201 else if ("ALL" == providerKey)
202 {
203 this.Core.OnMessage(DependencyErrors.ReservedValue(sourceLineNumbers, parentElement.Name.LocalName, "ProviderKey", providerKey));
204 }
205
206 // Generate the primary key for the row.
207 id = this.Core.CreateIdentifier("dep", attribute.Name.LocalName, providerKey);
208
209 if (!this.Core.EncounteredError)
210 {
211 // Create the provider row for the bundle. The Component_ field is required
212 // in the table definition but unused for bundles, so just set it to the valid ID.
213 Row row = this.Core.CreateRow(sourceLineNumbers, "WixDependencyProvider", id);
214 row[1] = id.Id;
215 row[2] = providerKey;
216 row[5] = DependencyCommon.ProvidesAttributesBundle;
217 }
218 }
219
220 /// <summary>
221 /// Processes the Provides element.
222 /// </summary>
223 /// <param name="node">The XML node for the Provides element.</param>
224 /// <param name="packageType">The type of the package being chained into a bundle, or "None" if building an MSI package.</param>
225 /// <param name="keyPath">Explicit key path.</param>
226 /// <param name="parentId">The identifier of the parent component or package.</param>
227 /// <returns>The type of key path if set.</returns>
228 private ComponentKeyPath ParseProvidesElement(XElement node, PackageType packageType, string parentId)
229 {
230 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
231 ComponentKeyPath keyPath = null;
232 Identifier id = null;
233 string key = null;
234 string version = null;
235 string displayName = null;
236 int attributes = 0;
237 int illegalChar = -1;
238
239 foreach (XAttribute attrib in node.Attributes())
240 {
241 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
242 {
243 switch (attrib.Name.LocalName)
244 {
245 case "Id":
246 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
247 break;
248 case "Key":
249 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
250 break;
251 case "Version":
252 version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
253 break;
254 case "DisplayName":
255 displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
256 break;
257 default:
258 this.Core.UnexpectedAttribute(node, attrib);
259 break;
260 }
261 }
262 else
263 {
264 this.Core.ParseExtensionAttribute(node, attrib);
265 }
266 }
267
268 // Make sure the key is valid. The key will default to the ProductCode for MSI packages
269 // and the package code for MSP packages in the binder if not specified.
270 if (!String.IsNullOrEmpty(key))
271 {
272 // Make sure the key does not contain any illegal characters or values.
273 if (0 <= (illegalChar = key.IndexOfAny(DependencyCommon.InvalidCharacters)))
274 {
275 StringBuilder sb = new StringBuilder(DependencyCommon.InvalidCharacters.Length * 2);
276 Array.ForEach<char>(DependencyCommon.InvalidCharacters, c => sb.Append(c).Append(" "));
277
278 this.Core.OnMessage(DependencyErrors.IllegalCharactersInProvider(sourceLineNumbers, "Key", key[illegalChar], sb.ToString()));
279 }
280 else if ("ALL" == key)
281 {
282 this.Core.OnMessage(DependencyErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Key", key));
283 }
284 }
285 else if (PackageType.ExePackage == packageType || PackageType.MsuPackage == packageType)
286 {
287 // Must specify the provider key when authored for a package.
288 this.Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
289 }
290 else if (PackageType.None == packageType)
291 {
292 // Make sure the ProductCode is authored and set the key.
293 this.Core.CreateSimpleReference(sourceLineNumbers, "Property", "ProductCode");
294 key = "!(bind.property.ProductCode)";
295 }
296
297 // The Version attribute should not be authored in or for an MSI package.
298 if (!String.IsNullOrEmpty(version))
299 {
300 switch (packageType)
301 {
302 case PackageType.None:
303 this.Core.OnMessage(DependencyWarnings.DiscouragedVersionAttribute(sourceLineNumbers));
304 break;
305 case PackageType.MsiPackage:
306 this.Core.OnMessage(DependencyWarnings.DiscouragedVersionAttribute(sourceLineNumbers, parentId));
307 break;
308 }
309 }
310 else if (PackageType.MspPackage == packageType || PackageType.MsuPackage == packageType)
311 {
312 // Must specify the Version when authored for packages that do not contain a version.
313 this.Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
314 }
315
316 // Need the element ID for child element processing, so generate now if not authored.
317 if (null == id)
318 {
319 id = this.Core.CreateIdentifier("dep", node.Name.LocalName, parentId, key);
320 }
321
322 foreach (XElement child in node.Elements())
323 {
324 if (this.Namespace == child.Name.Namespace)
325 {
326 switch (child.Name.LocalName)
327 {
328 case "Requires":
329 this.ParseRequiresElement(child, id.Id, PackageType.None == packageType);
330 break;
331 case "RequiresRef":
332 this.ParseRequiresRefElement(child, id.Id, PackageType.None == packageType);
333 break;
334 default:
335 this.Core.UnexpectedElement(node, child);
336 break;
337 }
338 }
339 else
340 {
341 this.Core.ParseExtensionElement(node, child);
342 }
343 }
344
345 if (!this.Core.EncounteredError)
346 {
347 // Create the row in the provider table.
348 Row row = this.Core.CreateRow(sourceLineNumbers, "WixDependencyProvider", id);
349 row[1] = parentId;
350 row[2] = key;
351
352 if (!String.IsNullOrEmpty(version))
353 {
354 row[3] = version;
355 }
356
357 if (!String.IsNullOrEmpty(displayName))
358 {
359 row[4] = displayName;
360 }
361
362 if (0 != attributes)
363 {
364 row[5] = attributes;
365 }
366
367 if (PackageType.None == packageType)
368 {
369 // Reference the Check custom action to check for dependencies on the current provider.
370 if (Platform.ARM == this.Core.CurrentPlatform)
371 {
372 // Ensure the ARM version of the CA is referenced.
373 this.Core.CreateSimpleReference(sourceLineNumbers, "CustomAction", "WixDependencyCheck_ARM");
374 }
375 else
376 {
377 // All other supported platforms use x86.
378 this.Core.CreateSimpleReference(sourceLineNumbers, "CustomAction", "WixDependencyCheck");
379 }
380
381 // Generate registry rows for the provider using binder properties.
382 string keyProvides = String.Concat(DependencyCommon.RegistryRoot, key);
383
384 row = this.Core.CreateRow(sourceLineNumbers, "Registry", this.Core.CreateIdentifier("reg", id.Id, "(Default)"));
385 row[1] = -1;
386 row[2] = keyProvides;
387 row[3] = null;
388 row[4] = "[ProductCode]";
389 row[5] = parentId;
390
391 // Use the Version registry value and use that as a potential key path.
392 Identifier idVersion = this.Core.CreateIdentifier("reg", id.Id, "Version");
393 keyPath = new ComponentKeyPath() { Id = idVersion.Id, Explicit = false, Type = ComponentKeyPathType.Registry };
394
395 row = this.Core.CreateRow(sourceLineNumbers, "Registry", idVersion);
396 row[1] = -1;
397 row[2] = keyProvides;
398 row[3] = "Version";
399 row[4] = !String.IsNullOrEmpty(version) ? version : "[ProductVersion]";
400 row[5] = parentId;
401
402 row = this.Core.CreateRow(sourceLineNumbers, "Registry", this.Core.CreateIdentifier("reg", id.Id, "DisplayName"));
403 row[1] = -1;
404 row[2] = keyProvides;
405 row[3] = "DisplayName";
406 row[4] = !String.IsNullOrEmpty(displayName) ? displayName : "[ProductName]";
407 row[5] = parentId;
408
409 if (0 != attributes)
410 {
411 row = this.Core.CreateRow(sourceLineNumbers, "Registry", this.Core.CreateIdentifier("reg", id.Id, "Attributes"));
412 row[1] = -1;
413 row[2] = keyProvides;
414 row[3] = "Attributes";
415 row[4] = String.Concat("#", attributes.ToString(CultureInfo.InvariantCulture.NumberFormat));
416 row[5] = parentId;
417 }
418 }
419 }
420
421 return keyPath;
422 }
423
424 /// <summary>
425 /// Processes the Requires element.
426 /// </summary>
427 /// <param name="node">The XML node for the Requires element.</param>
428 /// <param name="providerId">The parent provider identifier.</param>
429 /// <param name="requiresAction">Whether the Requires custom action should be referenced.</param>
430 private void ParseRequiresElement(XElement node, string providerId, bool requiresAction)
431 {
432 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
433 Identifier id = null;
434 string providerKey = null;
435 string minVersion = null;
436 string maxVersion = null;
437 int attributes = 0;
438 int illegalChar = -1;
439
440 foreach (XAttribute attrib in node.Attributes())
441 {
442 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
443 {
444 switch (attrib.Name.LocalName)
445 {
446 case "Id":
447 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
448 break;
449 case "ProviderKey":
450 providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
451 break;
452 case "Minimum":
453 minVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
454 break;
455 case "Maximum":
456 maxVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
457 break;
458 case "IncludeMinimum":
459 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
460 {
461 attributes |= DependencyCommon.RequiresAttributesMinVersionInclusive;
462 }
463 break;
464 case "IncludeMaximum":
465 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
466 {
467 attributes |= DependencyCommon.RequiresAttributesMaxVersionInclusive;
468 }
469 break;
470 default:
471 this.Core.UnexpectedAttribute(node, attrib);
472 break;
473 }
474 }
475 else
476 {
477 this.Core.ParseExtensionAttribute(node, attrib);
478 }
479 }
480
481 this.Core.ParseForExtensionElements(node);
482
483 if (null == id)
484 {
485 // Generate an ID only if this element is authored under a Provides element; otherwise, a RequiresRef
486 // element will be necessary and the Id attribute will be required.
487 if (!String.IsNullOrEmpty(providerId))
488 {
489 id = this.Core.CreateIdentifier("dep", node.Name.LocalName, providerKey);
490 }
491 else
492 {
493 this.Core.OnMessage(WixErrors.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Id", "Provides"));
494 id = Identifier.Invalid;
495 }
496 }
497
498 if (String.IsNullOrEmpty(providerKey))
499 {
500 this.Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProviderKey"));
501 }
502 // Make sure the key does not contain any illegal characters.
503 else if (0 <= (illegalChar = providerKey.IndexOfAny(DependencyCommon.InvalidCharacters)))
504 {
505 StringBuilder sb = new StringBuilder(DependencyCommon.InvalidCharacters.Length * 2);
506 Array.ForEach<char>(DependencyCommon.InvalidCharacters, c => sb.Append(c).Append(" "));
507
508 this.Core.OnMessage(DependencyErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], sb.ToString()));
509 }
510
511
512 if (!this.Core.EncounteredError)
513 {
514 // Reference the Require custom action if required.
515 if (requiresAction)
516 {
517 if (Platform.ARM == this.Core.CurrentPlatform)
518 {
519 // Ensure the ARM version of the CA is referenced.
520 this.Core.CreateSimpleReference(sourceLineNumbers, "CustomAction", "WixDependencyRequire_ARM");
521 }
522 else
523 {
524 // All other supported platforms use x86.
525 this.Core.CreateSimpleReference(sourceLineNumbers, "CustomAction", "WixDependencyRequire");
526 }
527 }
528
529 Row row = this.Core.CreateRow(sourceLineNumbers, "WixDependency", id);
530 row[1] = providerKey;
531 row[2] = minVersion;
532 row[3] = maxVersion;
533
534 if (0 != attributes)
535 {
536 row[4] = attributes;
537 }
538
539 // Create the relationship between this WixDependency row and the WixDependencyProvider row.
540 if (!String.IsNullOrEmpty(providerId))
541 {
542 // Create the relationship between the WixDependency row and the parent WixDependencyProvider row.
543 row = this.Core.CreateRow(sourceLineNumbers, "WixDependencyRef");
544 row[0] = providerId;
545 row[1] = id.Id;
546 }
547 }
548 }
549
550 /// <summary>
551 /// Processes the RequiresRef element.
552 /// </summary>
553 /// <param name="node">The XML node for the RequiresRef element.</param>
554 /// <param name="providerId">The parent provider identifier.</param>
555 /// <param name="requiresAction">Whether the Requires custom action should be referenced.</param>
556 private void ParseRequiresRefElement(XElement node, string providerId, bool requiresAction)
557 {
558 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
559 string id = null;
560
561 foreach (XAttribute attrib in node.Attributes())
562 {
563 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
564 {
565 switch (attrib.Name.LocalName)
566 {
567 case "Id":
568 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
569 break;
570 default:
571 this.Core.UnexpectedAttribute(node, attrib);
572 break;
573 }
574 }
575 else
576 {
577 this.Core.ParseExtensionAttribute(node, attrib);
578 }
579 }
580
581 this.Core.ParseForExtensionElements(node);
582
583 if (String.IsNullOrEmpty(id))
584 {
585 this.Core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
586 }
587
588 if (!this.Core.EncounteredError)
589 {
590 // Reference the Require custom action if required.
591 if (requiresAction)
592 {
593 if (Platform.ARM == this.Core.CurrentPlatform)
594 {
595 // Ensure the ARM version of the CA is referenced.
596 this.Core.CreateSimpleReference(sourceLineNumbers, "CustomAction", "WixDependencyRequire_ARM");
597 }
598 else
599 {
600 // All other supported platforms use x86.
601 this.Core.CreateSimpleReference(sourceLineNumbers, "CustomAction", "WixDependencyRequire");
602 }
603 }
604
605 // Create a link dependency on the row that contains information we'll need during bind.
606 this.Core.CreateSimpleReference(sourceLineNumbers, "WixDependency", id);
607
608 // Create the relationship between the WixDependency row and the parent WixDependencyProvider row.
609 Row row = this.Core.CreateRow(sourceLineNumbers, "WixDependencyRef");
610 row[0] = providerId;
611 row[1] = id;
612 }
613 }
614 }
615}