main
cs 487 lines 22.4 KB
Raw
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.BootstrapperApplications
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Text;
9 using System.Xml;
10 using WixToolset.BootstrapperApplications.Symbols;
11 using WixToolset.Data;
12 using WixToolset.Data.Burn;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Extensibility;
15 using WixToolset.Extensibility.Data;
16
17 public class BalBurnBackendExtension : BaseBurnBackendBinderExtension
18 {
19 private static readonly IntermediateSymbolDefinition[] BurnSymbolDefinitions =
20 {
21 #pragma warning disable 0612 // obsolete
22 BalSymbolDefinitions.WixBalBAFactoryAssembly,
23 #pragma warning restore 0612
24 BalSymbolDefinitions.WixBalBAFunctions,
25 BalSymbolDefinitions.WixBalCondition,
26 BalSymbolDefinitions.WixBalPackageInfo,
27 BalSymbolDefinitions.WixPrereqInformation,
28 BalSymbolDefinitions.WixStdbaCommandLine,
29 BalSymbolDefinitions.WixStdbaOptions,
30 BalSymbolDefinitions.WixStdbaOverridableVariable,
31 BalSymbolDefinitions.WixPrereqOptions,
32 };
33
34 protected override IReadOnlyCollection<IntermediateSymbolDefinition> SymbolDefinitions => BurnSymbolDefinitions;
35
36 public override bool TryProcessSymbol(IntermediateSection section, IntermediateSymbol symbol)
37 {
38 if (symbol is WixBalPackageInfoSymbol balPackageInfoSymbol)
39 {
40 // There might be a more efficient way to do this,
41 // but this is an easy way to ensure we're creating valid XML.
42 var sb = new StringBuilder();
43 using (var writer = XmlWriter.Create(sb))
44 {
45 writer.WriteStartElement(symbol.Definition.Name, BurnConstants.BootstrapperApplicationDataNamespace);
46
47 writer.WriteAttributeString("PackageId", balPackageInfoSymbol.PackageId);
48
49 if (balPackageInfoSymbol.DisplayInternalUICondition != null)
50 {
51 writer.WriteAttributeString("DisplayInternalUICondition", balPackageInfoSymbol.DisplayInternalUICondition);
52 }
53
54 if (balPackageInfoSymbol.PrimaryPackageType != BalPrimaryPackageType.None)
55 {
56 writer.WriteAttributeString("PrimaryPackageType", balPackageInfoSymbol.PrimaryPackageType.ToString().ToLower());
57 }
58
59 writer.WriteEndElement();
60 }
61
62 this.BackendHelper.AddBootstrapperApplicationData(sb.ToString());
63
64 return true;
65 }
66 else if (symbol is WixStdbaCommandLineSymbol stdbaCommandLineSymbol)
67 {
68 var sb = new StringBuilder();
69 using (var writer = XmlWriter.Create(sb))
70 {
71 writer.WriteStartElement(symbol.Definition.Name, BurnConstants.BootstrapperApplicationDataNamespace);
72
73 switch (stdbaCommandLineSymbol.VariableType)
74 {
75 case WixStdbaCommandLineVariableType.CaseInsensitive:
76 writer.WriteAttributeString("VariableType", "caseInsensitive");
77 break;
78 default:
79 writer.WriteAttributeString("VariableType", "caseSensitive");
80 break;
81 }
82
83 writer.WriteEndElement();
84 }
85
86 this.BackendHelper.AddBootstrapperApplicationData(sb.ToString());
87
88 return true;
89 }
90 else if (symbol is WixBalBootstrapperApplicationSymbol)
91 {
92 // This symbol is only for the processing in SymbolsFinalized.
93 return true;
94 }
95 else
96 {
97 return base.TryProcessSymbol(section, symbol);
98 }
99 }
100
101 public override void SymbolsFinalized(IntermediateSection section)
102 {
103 base.SymbolsFinalized(section);
104
105 this.VerifyBalConditions(section);
106 this.VerifyDisplayInternalUICondition(section);
107 this.VerifyOverridableVariables(section);
108
109 var balBaSymbol = section.Symbols.OfType<WixBalBootstrapperApplicationSymbol>().SingleOrDefault();
110 if (balBaSymbol == null)
111 {
112 return;
113 }
114
115 var isIuiBA = balBaSymbol.Type == WixBalBootstrapperApplicationType.InternalUi;
116 var isPreqBA = balBaSymbol.Type == WixBalBootstrapperApplicationType.Prerequisite;
117 var isStdBA = balBaSymbol.Type == WixBalBootstrapperApplicationType.Standard;
118
119 if (!isIuiBA && !isPreqBA && !isStdBA)
120 {
121 throw new WixException($"Invalid WixBalBootstrapperApplicationType: '{balBaSymbol.Type}'");
122 }
123
124 this.VerifyBAFunctions(section);
125
126 if (isIuiBA)
127 {
128 // This needs to happen before VerifyPrereqPackages because it can add prereq packages.
129 this.VerifyPrimaryPackages(section, balBaSymbol.SourceLineNumbers);
130 }
131
132 if (isIuiBA || isPreqBA)
133 {
134 this.VerifyPrereqPackages(section, balBaSymbol.SourceLineNumbers, isIuiBA);
135 }
136 }
137
138 private void VerifyBAFunctions(IntermediateSection section)
139 {
140 WixBalBAFunctionsSymbol baFunctionsSymbol = null;
141 foreach (var symbol in section.Symbols.OfType<WixBalBAFunctionsSymbol>())
142 {
143 if (null == baFunctionsSymbol)
144 {
145 baFunctionsSymbol = symbol;
146 }
147 else
148 {
149 this.Messaging.Write(BalErrors.MultipleBAFunctions(symbol.SourceLineNumbers));
150 }
151 }
152
153 var payloadPropertiesSymbols = section.Symbols.OfType<WixBundlePayloadSymbol>().ToList();
154 if (null == baFunctionsSymbol)
155 {
156 foreach (var payloadPropertiesSymbol in payloadPropertiesSymbols)
157 {
158 if (String.Equals(payloadPropertiesSymbol.Name, "bafunctions.dll", StringComparison.OrdinalIgnoreCase) &&
159 BurnConstants.BurnUXContainerName == payloadPropertiesSymbol.ContainerRef)
160 {
161 this.Messaging.Write(BalWarnings.UnmarkedBAFunctionsDLL(payloadPropertiesSymbol.SourceLineNumbers));
162 }
163 }
164 }
165 else
166 {
167 var payloadId = baFunctionsSymbol.PayloadId;
168 var bundlePayloadSymbol = payloadPropertiesSymbols.Single(x => payloadId == x.Id.Id);
169 if (BurnConstants.BurnUXContainerName != bundlePayloadSymbol.ContainerRef)
170 {
171 this.Messaging.Write(BalErrors.BAFunctionsPayloadRequiredInUXContainer(baFunctionsSymbol.SourceLineNumbers));
172 }
173
174 baFunctionsSymbol.FilePath = bundlePayloadSymbol.Name;
175 }
176 }
177
178 private void VerifyBalConditions(IntermediateSection section)
179 {
180 var balConditionSymbols = section.Symbols.OfType<WixBalConditionSymbol>().ToList();
181 foreach (var balConditionSymbol in balConditionSymbols)
182 {
183 this.BackendHelper.ValidateBundleCondition(balConditionSymbol.SourceLineNumbers, "bal:Condition", "Condition", balConditionSymbol.Condition, BundleConditionPhase.Detect);
184 }
185 }
186
187 private void VerifyDisplayInternalUICondition(IntermediateSection section)
188 {
189 foreach (var balPackageInfoSymbol in section.Symbols.OfType<WixBalPackageInfoSymbol>().ToList())
190 {
191 if (balPackageInfoSymbol.DisplayInternalUICondition != null)
192 {
193 this.BackendHelper.ValidateBundleCondition(balPackageInfoSymbol.SourceLineNumbers, "*Package", "bal:DisplayInternalUICondition", balPackageInfoSymbol.DisplayInternalUICondition, BundleConditionPhase.Plan);
194 }
195 }
196 }
197
198 private void VerifyPrimaryPackages(IntermediateSection section, SourceLineNumber baSourceLineNumbers)
199 {
200 WixBalPackageInfoSymbol defaultPrimaryPackage = null;
201 WixBalPackageInfoSymbol x86PrimaryPackage = null;
202 WixBalPackageInfoSymbol x64PrimaryPackage = null;
203 WixBalPackageInfoSymbol arm64PrimaryPackage = null;
204 var nonPermanentNonPrimaryPackages = new List<WixBundlePackageSymbol>();
205
206 var balPackageInfoSymbolsByPackageId = section.Symbols.OfType<WixBalPackageInfoSymbol>().ToDictionary(x => x.PackageId);
207 var mbaPrereqInfoSymbolsByPackageId = section.Symbols.OfType<WixPrereqInformationSymbol>().ToDictionary(x => x.PackageId);
208 var msiPackageSymbolsByPackageId = section.Symbols.OfType<WixBundleMsiPackageSymbol>().ToDictionary(x => x.Id.Id);
209 var packageSymbols = section.Symbols.OfType<WixBundlePackageSymbol>().ToList();
210 foreach (var packageSymbol in packageSymbols)
211 {
212 var packageId = packageSymbol.Id?.Id;
213 var isPrereq = false;
214 var primaryPackageType = BalPrimaryPackageType.None;
215
216 if (mbaPrereqInfoSymbolsByPackageId.TryGetValue(packageId, out var _))
217 {
218 isPrereq = true;
219 }
220
221 if (balPackageInfoSymbolsByPackageId.TryGetValue(packageId, out var balPackageInfoSymbol))
222 {
223 primaryPackageType = balPackageInfoSymbol.PrimaryPackageType;
224 }
225
226 if (packageSymbol.Permanent)
227 {
228 if (primaryPackageType != BalPrimaryPackageType.None)
229 {
230 this.Messaging.Write(BalErrors.IuibaPermanentPrimaryPackageType(packageSymbol.SourceLineNumbers));
231 }
232 else
233 {
234 if (!isPrereq)
235 {
236 var prereqInfoSymbol = section.AddSymbol(new WixPrereqInformationSymbol(packageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Global, packageId))
237 {
238 PackageId = packageId,
239 });
240
241 mbaPrereqInfoSymbolsByPackageId.Add(packageId, prereqInfoSymbol);
242 }
243
244 this.VerifyIuibaPrereqPackage(packageSymbol);
245 }
246 }
247 else
248 {
249 if (isPrereq)
250 {
251 if (primaryPackageType == BalPrimaryPackageType.None)
252 {
253 this.Messaging.Write(BalErrors.IuibaNonPermanentPrereqPackage(packageSymbol.SourceLineNumbers));
254 }
255 else
256 {
257 this.Messaging.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(
258 packageSymbol.SourceLineNumbers,
259 packageSymbol.Type + "Package",
260 "PrereqPackage",
261 "yes",
262 "PrimaryPackageType"));
263 }
264 }
265 else if (primaryPackageType == BalPrimaryPackageType.None)
266 {
267 nonPermanentNonPrimaryPackages.Add(packageSymbol);
268 }
269 else if (packageSymbol.Type != WixBundlePackageType.Msi)
270 {
271 this.Messaging.Write(BalErrors.IuibaNonMsiPrimaryPackage(packageSymbol.SourceLineNumbers));
272 }
273 else if (!msiPackageSymbolsByPackageId.TryGetValue(packageId, out var msiPackageSymbol))
274 {
275 throw new WixException($"Missing WixBundleMsiPackageSymbol for package '{packageId}'");
276 }
277 else if (msiPackageSymbol.EnableFeatureSelection)
278 {
279 this.Messaging.Write(BalErrors.IuibaPrimaryPackageEnableFeatureSelection(packageSymbol.SourceLineNumbers));
280 }
281 else
282 {
283 if (primaryPackageType == BalPrimaryPackageType.Default)
284 {
285 if (defaultPrimaryPackage == null)
286 {
287 defaultPrimaryPackage = balPackageInfoSymbol;
288 }
289 else
290 {
291 this.Messaging.Write(BalErrors.MultiplePrimaryPackageType(balPackageInfoSymbol.SourceLineNumbers, "default"));
292 this.Messaging.Write(BalErrors.MultiplePrimaryPackageType2(defaultPrimaryPackage.SourceLineNumbers));
293 }
294 }
295 else if (balPackageInfoSymbol.PrimaryPackageType == BalPrimaryPackageType.X86)
296 {
297 if (x86PrimaryPackage == null)
298 {
299 x86PrimaryPackage = balPackageInfoSymbol;
300 }
301 else
302 {
303 this.Messaging.Write(BalErrors.MultiplePrimaryPackageType(balPackageInfoSymbol.SourceLineNumbers, "x86"));
304 this.Messaging.Write(BalErrors.MultiplePrimaryPackageType2(x86PrimaryPackage.SourceLineNumbers));
305 }
306 }
307 else if (balPackageInfoSymbol.PrimaryPackageType == BalPrimaryPackageType.X64)
308 {
309 if (x64PrimaryPackage == null)
310 {
311 x64PrimaryPackage = balPackageInfoSymbol;
312 }
313 else
314 {
315 this.Messaging.Write(BalErrors.MultiplePrimaryPackageType(balPackageInfoSymbol.SourceLineNumbers, "x64"));
316 this.Messaging.Write(BalErrors.MultiplePrimaryPackageType2(x64PrimaryPackage.SourceLineNumbers));
317 }
318 }
319 else if (balPackageInfoSymbol.PrimaryPackageType == BalPrimaryPackageType.ARM64)
320 {
321 if (arm64PrimaryPackage == null)
322 {
323 arm64PrimaryPackage = balPackageInfoSymbol;
324 }
325 else
326 {
327 this.Messaging.Write(BalErrors.MultiplePrimaryPackageType(balPackageInfoSymbol.SourceLineNumbers, "arm64"));
328 this.Messaging.Write(BalErrors.MultiplePrimaryPackageType2(arm64PrimaryPackage.SourceLineNumbers));
329 }
330 }
331 else
332 {
333 throw new NotImplementedException();
334 }
335
336 this.VerifyIuibaPrimaryPackage(packageSymbol, balPackageInfoSymbol);
337 }
338 }
339 }
340
341 if (defaultPrimaryPackage == null && nonPermanentNonPrimaryPackages.Count == 1)
342 {
343 var packageSymbol = nonPermanentNonPrimaryPackages[0];
344
345 if (packageSymbol.Type == WixBundlePackageType.Msi)
346 {
347 var packageId = packageSymbol.Id?.Id;
348 var msiPackageSymbol = section.Symbols.OfType<WixBundleMsiPackageSymbol>()
349 .SingleOrDefault(x => x.Id.Id == packageId);
350 if (!msiPackageSymbol.EnableFeatureSelection)
351 {
352 if (!balPackageInfoSymbolsByPackageId.TryGetValue(packageId, out var balPackageInfoSymbol))
353 {
354 balPackageInfoSymbol = section.AddSymbol(new WixBalPackageInfoSymbol(packageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Global, packageId))
355 {
356 PackageId = packageId,
357 });
358
359 balPackageInfoSymbolsByPackageId.Add(packageId, balPackageInfoSymbol);
360 }
361
362 balPackageInfoSymbol.PrimaryPackageType = BalPrimaryPackageType.Default;
363 defaultPrimaryPackage = balPackageInfoSymbol;
364 nonPermanentNonPrimaryPackages.RemoveAt(0);
365
366 this.VerifyIuibaPrimaryPackage(packageSymbol, balPackageInfoSymbol);
367 }
368 }
369 }
370
371 if (nonPermanentNonPrimaryPackages.Count > 0)
372 {
373 foreach (var packageSymbol in nonPermanentNonPrimaryPackages)
374 {
375 this.Messaging.Write(BalErrors.IuibaNonPermanentNonPrimaryPackage(packageSymbol.SourceLineNumbers));
376 }
377 }
378 else if (defaultPrimaryPackage == null)
379 {
380 this.Messaging.Write(BalErrors.MissingIUIPrimaryPackage(baSourceLineNumbers));
381 }
382 else
383 {
384 var foundPrimaryPackage = false;
385 var chainPackageGroupSymbols = section.Symbols.OfType<WixGroupSymbol>()
386 .Where(x => x.ChildType == ComplexReferenceChildType.Package &&
387 x.ParentType == ComplexReferenceParentType.PackageGroup &&
388 x.ParentId == BurnConstants.BundleChainPackageGroupId);
389 foreach (var chainPackageGroupSymbol in chainPackageGroupSymbols)
390 {
391 var packageId = chainPackageGroupSymbol.ChildId;
392 if (balPackageInfoSymbolsByPackageId.TryGetValue(packageId, out var balPackageInfo) && balPackageInfo.PrimaryPackageType != BalPrimaryPackageType.None)
393 {
394 foundPrimaryPackage = true;
395 }
396 else if (foundPrimaryPackage && mbaPrereqInfoSymbolsByPackageId.TryGetValue(packageId, out var mbaPrereqInformationSymbol))
397 {
398 this.Messaging.Write(BalWarnings.IuibaPrereqPackageAfterPrimaryPackage(chainPackageGroupSymbol.SourceLineNumbers));
399 }
400 }
401 }
402 }
403
404 private void VerifyIuibaPrereqPackage(WixBundlePackageSymbol packageSymbol)
405 {
406 if (packageSymbol.Cache == BundleCacheType.Force)
407 {
408 this.Messaging.Write(BalWarnings.IuibaForceCachePrereq(packageSymbol.SourceLineNumbers));
409 }
410 }
411
412 private void VerifyIuibaPrimaryPackage(WixBundlePackageSymbol packageSymbol, WixBalPackageInfoSymbol balPackageInfoSymbol)
413 {
414 if (packageSymbol.InstallCondition != null)
415 {
416 this.Messaging.Write(BalWarnings.IuibaPrimaryPackageInstallCondition(packageSymbol.SourceLineNumbers));
417 }
418
419 if (balPackageInfoSymbol.DisplayInternalUICondition != null)
420 {
421 this.Messaging.Write(BalWarnings.IuibaPrimaryPackageDisplayInternalUICondition(packageSymbol.SourceLineNumbers));
422 }
423 }
424
425 private void VerifyOverridableVariables(IntermediateSection section)
426 {
427 var commandLineSymbol = section.Symbols.OfType<WixStdbaCommandLineSymbol>().SingleOrDefault();
428 if (commandLineSymbol?.VariableType != WixStdbaCommandLineVariableType.CaseInsensitive)
429 {
430 return;
431 }
432
433 var overridableVariableSymbols = section.Symbols.OfType<WixStdbaOverridableVariableSymbol>().ToList();
434 var overridableVariables = new Dictionary<string, WixStdbaOverridableVariableSymbol>(StringComparer.InvariantCultureIgnoreCase);
435 foreach (var overridableVariableSymbol in overridableVariableSymbols)
436 {
437 if (!overridableVariables.TryGetValue(overridableVariableSymbol.Name, out var collisionVariableSymbol))
438 {
439 overridableVariables.Add(overridableVariableSymbol.Name, overridableVariableSymbol);
440 }
441 else
442 {
443 this.Messaging.Write(BalErrors.OverridableVariableCollision(overridableVariableSymbol.SourceLineNumbers, overridableVariableSymbol.Name, collisionVariableSymbol.Name));
444 this.Messaging.Write(BalErrors.OverridableVariableCollision2(collisionVariableSymbol.SourceLineNumbers));
445 }
446 }
447 }
448
449 private void VerifyPrereqPackages(IntermediateSection section, SourceLineNumber baSourceLineNumbers, bool isIuiBA)
450 {
451 var prereqInfoSymbols = section.Symbols.OfType<WixPrereqInformationSymbol>().ToList();
452 if (!isIuiBA && prereqInfoSymbols.Count == 0)
453 {
454 this.Messaging.Write(BalErrors.MissingPrereq(baSourceLineNumbers));
455 return;
456 }
457
458 var foundLicenseFile = false;
459 var foundLicenseUrl = false;
460
461 foreach (var prereqInfoSymbol in prereqInfoSymbols)
462 {
463 if (null != prereqInfoSymbol.LicenseFile)
464 {
465 if (foundLicenseFile || foundLicenseUrl)
466 {
467 this.Messaging.Write(BalErrors.MultiplePrereqLicenses(prereqInfoSymbol.SourceLineNumbers));
468 return;
469 }
470
471 foundLicenseFile = true;
472 }
473
474 if (null != prereqInfoSymbol.LicenseUrl)
475 {
476 if (foundLicenseFile || foundLicenseUrl)
477 {
478 this.Messaging.Write(BalErrors.MultiplePrereqLicenses(prereqInfoSymbol.SourceLineNumbers));
479 return;
480 }
481
482 foundLicenseUrl = true;
483 }
484 }
485 }
486 }
487 }