main
cs 181 lines 9.55 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.Core.Burn.Bundles
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Burn;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility.Services;
12
13 internal class OrderPackagesAndRollbackBoundariesCommand
14 {
15 public OrderPackagesAndRollbackBoundariesCommand(IMessaging messaging, IntermediateSection section, PackageFacades packageFacades)
16 {
17 this.Messaging = messaging;
18 this.Section = section;
19 this.PackageFacades = packageFacades;
20 }
21
22 private IMessaging Messaging { get; }
23
24 private IntermediateSection Section { get; }
25
26 private PackageFacades PackageFacades { get; }
27
28 public IEnumerable<WixBundleRollbackBoundarySymbol> UsedRollbackBoundaries { get; private set; }
29
30 public void Execute()
31 {
32 var groupSymbols = this.Section.Symbols.OfType<WixGroupSymbol>().ToList();
33 var boundariesById = this.Section.Symbols.OfType<WixBundleRollbackBoundarySymbol>().ToDictionary(b => b.Id.Id);
34
35 var usedBoundaries = new List<WixBundleRollbackBoundarySymbol>();
36
37 // Process the chain of packages to add them in the correct order
38 // and assign the forward rollback boundaries as appropriate. Remember
39 // rollback boundaries are authored as elements in the chain which
40 // we re-interpret here to add them as attributes on the next available
41 // package in the chain. Essentially we mark some packages as being
42 // the start of a rollback boundary when installing and repairing.
43 // We handle uninstall (aka: backwards) rollback boundaries after
44 // we get these install/repair (aka: forward) rollback boundaries
45 // defined.
46 var pendingRollbackBoundary = new WixBundleRollbackBoundarySymbol(null, new Identifier(AccessModifier.Section, BurnConstants.BundleDefaultBoundaryId)) { Vital = true };
47 var lastRollbackBoundary = pendingRollbackBoundary;
48 PackageFacade msiTransactionX86Package = null;
49 var warnedMsiTransaction = false;
50
51 foreach (var groupSymbol in groupSymbols)
52 {
53 if (ComplexReferenceChildType.Package == groupSymbol.ChildType && ComplexReferenceParentType.PackageGroup == groupSymbol.ParentType && BurnConstants.BundleChainPackageGroupId == groupSymbol.ParentId)
54 {
55 if (this.PackageFacades.TryGetFacadeByPackageId(groupSymbol.ChildId, out var facade))
56 {
57 var insideMsiTransaction = lastRollbackBoundary.Transaction;
58
59 if (null != pendingRollbackBoundary)
60 {
61 // If we used the default boundary, ensure the symbol is added to the section.
62 if (pendingRollbackBoundary.Id.Id == BurnConstants.BundleDefaultBoundaryId)
63 {
64 this.Section.AddSymbol(pendingRollbackBoundary);
65 }
66
67 if (insideMsiTransaction && !warnedMsiTransaction)
68 {
69 warnedMsiTransaction = true;
70 this.Messaging.Write(WarningMessages.MsiTransactionLimitations(pendingRollbackBoundary.SourceLineNumbers));
71 }
72
73 usedBoundaries.Add(pendingRollbackBoundary);
74 facade.PackageSymbol.RollbackBoundaryRef = pendingRollbackBoundary.Id.Id;
75 pendingRollbackBoundary = null;
76 msiTransactionX86Package = null;
77 }
78
79 if (insideMsiTransaction)
80 {
81 if (facade.PackageSymbol.Type != WixBundlePackageType.Msi && facade.PackageSymbol.Type != WixBundlePackageType.Msp)
82 {
83 this.Messaging.Write(ErrorMessages.MsiTransactionInvalidPackage(facade.PackageSymbol.SourceLineNumbers, facade.PackageId, facade.PackageSymbol.Type.ToString()));
84 this.Messaging.Write(ErrorMessages.MsiTransactionInvalidPackage2(lastRollbackBoundary.SourceLineNumbers));
85 }
86 // Not possible to tell the bitness of Msp.
87 else if (facade.PackageSymbol.Type == WixBundlePackageType.Msi)
88 {
89 if (msiTransactionX86Package == null && !facade.PackageSymbol.Win64)
90 {
91 msiTransactionX86Package = facade;
92 }
93 // Error if MSI transaction has x86 package preceding x64 packages
94 else if (msiTransactionX86Package != null && facade.PackageSymbol.Win64)
95 {
96 this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64Package(facade.PackageSymbol.SourceLineNumbers, facade.PackageId, msiTransactionX86Package.PackageId));
97 this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64Package2(msiTransactionX86Package.PackageSymbol.SourceLineNumbers));
98 }
99 }
100 }
101
102 this.PackageFacades.AddOrdered(facade);
103 }
104 else // must be a rollback boundary.
105 {
106 // Discard the next rollback boundary if we have a previously defined boundary.
107 var nextRollbackBoundary = boundariesById[groupSymbol.ChildId];
108 if (null != pendingRollbackBoundary && pendingRollbackBoundary.Id.Id != BurnConstants.BundleDefaultBoundaryId)
109 {
110 this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id.Id));
111 this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary2(lastRollbackBoundary.SourceLineNumbers));
112 }
113 else
114 {
115 lastRollbackBoundary = pendingRollbackBoundary = nextRollbackBoundary;
116 }
117 }
118 }
119 }
120
121 if (null != pendingRollbackBoundary)
122 {
123 this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(pendingRollbackBoundary.SourceLineNumbers, pendingRollbackBoundary.Id.Id));
124 }
125
126 // With the forward rollback boundaries assigned, we can now go
127 // through the packages with rollback boundaries and assign backward
128 // rollback boundaries. Backward rollback boundaries are used when
129 // the chain is going "backwards" which (AFAIK) only happens during
130 // uninstall.
131 //
132 // Consider the scenario with three packages: A, B and C. Packages A
133 // and C are marked as rollback boundary packages and package B is
134 // not. The naive implementation would execute the chain like this
135 // (numbers indicate where rollback boundaries would end up):
136 // install: 1 A B 2 C
137 // uninstall: 2 C B 1 A
138 //
139 // The uninstall chain is wrong, A and B should be grouped together
140 // not C and B. The fix is to label packages with a "backwards"
141 // rollback boundary used during uninstall. The backwards rollback
142 // boundaries are assigned to the package *before* the next rollback
143 // boundary. Using our example from above again, I'll mark the
144 // backwards rollback boundaries prime (aka: with ').
145 // install: 1 A B 1' 2 C 2'
146 // uninstall: 2' C 2 1' B A 1
147 //
148 // If the marked boundaries are ignored during install you get the
149 // same thing as above (good) and if the non-marked boundaries are
150 // ignored during uninstall then A and B are correctly grouped.
151 // Here's what it looks like without all the markers:
152 // install: 1 A B 2 C
153 // uninstall: 2 C 1 B A
154 // Woot!
155 string previousRollbackBoundaryId = null;
156 PackageFacade previousFacade = null;
157
158 foreach (var package in this.PackageFacades.OrderedValues)
159 {
160 if (null != package.PackageSymbol.RollbackBoundaryRef)
161 {
162 if (null != previousFacade)
163 {
164 previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId;
165 }
166
167 previousRollbackBoundaryId = package.PackageSymbol.RollbackBoundaryRef;
168 }
169
170 previousFacade = package;
171 }
172
173 if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade)
174 {
175 previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId;
176 }
177
178 this.UsedRollbackBoundaries = usedBoundaries;
179 }
180 }
181 }