main
cs 434 lines 19.6 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.IO;
8 using System.Linq;
9 using System.Xml;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12 using WixToolset.Extensibility;
13 using WixToolset.Extensibility.Data;
14 using WixToolset.Extensibility.Services;
15
16 internal class HarvestBundlePackageCommand
17 {
18 public HarvestBundlePackageCommand(IServiceProvider serviceProvider, IEnumerable<IBurnBackendBinderExtension> backendExtensions, string intermediateFolder, WixBundlePayloadSymbol payloadSymbol, WixBundleBundlePackagePayloadSymbol packagePayloadSymbol, Dictionary<string, WixBundlePayloadSymbol> packagePayloadsById)
19 {
20 this.Messaging = serviceProvider.GetService<IMessaging>();
21 this.FileSystem = serviceProvider.GetService<IFileSystem>();
22 this.BackendHelper = serviceProvider.GetService<IBackendHelper>();
23 this.BackendExtensions = backendExtensions;
24 this.IntermediateFolder = intermediateFolder;
25
26 this.PackagePayload = payloadSymbol;
27 this.BundlePackagePayload = packagePayloadSymbol;
28 this.PackagePayloadsById = packagePayloadsById;
29 }
30
31 private IMessaging Messaging { get; }
32
33 private IFileSystem FileSystem { get; }
34
35 private IBackendHelper BackendHelper { get; }
36
37 private IEnumerable<IBurnBackendBinderExtension> BackendExtensions { get; }
38
39 private string IntermediateFolder { get; }
40
41 private WixBundlePayloadSymbol PackagePayload { get; }
42
43 private WixBundleBundlePackagePayloadSymbol BundlePackagePayload { get; }
44
45 private Dictionary<string, WixBundlePayloadSymbol> PackagePayloadsById { get; }
46
47 public WixBundleHarvestedBundlePackageSymbol HarvestedBundlePackage { get; private set; }
48
49 public WixBundleHarvestedDependencyProviderSymbol HarvestedDependencyProvider { get; private set; }
50
51 public List<WixBundlePayloadSymbol> Payloads { get; } = new List<WixBundlePayloadSymbol>();
52
53 public List<WixBundlePackageRelatedBundleSymbol> RelatedBundles { get; } = new List<WixBundlePackageRelatedBundleSymbol>();
54
55 public List<ITrackedFile> TrackedFiles { get; } = new List<ITrackedFile>();
56
57 public void Execute()
58 {
59 bool win64;
60 string bundleId;
61 string engineVersion;
62 int protocolVersion;
63 string manifestNamespace;
64 bool perMachine;
65 string version;
66 string displayName;
67 long installSize;
68
69 var sourcePath = this.PackagePayload.SourceFile.Path;
70 var sourceLineNumbers = this.PackagePayload.SourceLineNumbers;
71
72 using (var burnReader = BurnReader.Open(this.Messaging, this.FileSystem, sourcePath))
73 {
74 if (burnReader.Invalid)
75 {
76 return;
77 }
78
79 var baFolderPath = Path.Combine(this.IntermediateFolder, burnReader.BundleId.ToString());
80
81 if (!burnReader.ExtractUXContainer(baFolderPath, baFolderPath))
82 {
83 this.Messaging.Write(BurnBackendErrors.BundleMissingBootstrapperApplicationContainer(sourceLineNumbers, sourcePath));
84 return;
85 }
86
87 foreach (var filePath in Directory.EnumerateFiles(baFolderPath, "*.*", SearchOption.AllDirectories))
88 {
89 this.TrackedFiles.Add(this.BackendHelper.TrackFile(filePath, TrackedFileType.Temporary, sourceLineNumbers));
90 }
91
92 bundleId = burnReader.BundleId.ToString("B").ToUpperInvariant();
93
94 try
95 {
96 var document = new XmlDocument();
97 document.Load(Path.Combine(baFolderPath, "manifest.xml"));
98 var namespaceManager = new XmlNamespaceManager(document.NameTable);
99
100 if (document.DocumentElement.LocalName != "BurnManifest")
101 {
102 this.Messaging.Write(BurnBackendErrors.InvalidBundleManifest(sourceLineNumbers, sourcePath, $"Expected root element to be 'BurnManifest' but was '{document.DocumentElement.LocalName}'."));
103 return;
104 }
105
106 engineVersion = document.DocumentElement.GetAttribute("EngineVersion");
107 protocolVersion = this.ProcessProtocolVersion(burnReader, document);
108 win64 = this.ProcessWin64(burnReader, document, sourceLineNumbers, sourcePath);
109
110 manifestNamespace = document.DocumentElement.NamespaceURI;
111
112 namespaceManager.AddNamespace("burn", document.DocumentElement.NamespaceURI);
113 var registrationElement = document.SelectSingleNode("/burn:BurnManifest/burn:Registration", namespaceManager) as XmlElement;
114 var arpElement = document.SelectSingleNode("/burn:BurnManifest/burn:Registration/burn:Arp", namespaceManager) as XmlElement;
115
116 perMachine = registrationElement.GetAttribute("PerMachine") == "yes";
117
118 version = registrationElement.GetAttribute("Version");
119
120 var providerKey = registrationElement.GetAttribute("ProviderKey");
121 var depId = new Identifier(AccessModifier.Section, this.BackendHelper.GenerateIdentifier("dep", this.PackagePayload.Id.Id, providerKey));
122 this.HarvestedDependencyProvider = new WixBundleHarvestedDependencyProviderSymbol(sourceLineNumbers, depId)
123 {
124 PackagePayloadRef = this.PackagePayload.Id.Id,
125 ProviderKey = providerKey,
126 Version = version,
127 };
128
129 displayName = arpElement.GetAttribute("DisplayName");
130
131 installSize = this.ProcessPackages(document, namespaceManager);
132
133 this.ProcessPayloads(document, namespaceManager, this.BundlePackagePayload.PayloadGeneration);
134
135 this.ProcessRelatedBundles(document, namespaceManager, sourcePath);
136 }
137 catch (Exception e)
138 {
139 this.Messaging.Write(BurnBackendErrors.InvalidBundleManifest(sourceLineNumbers, sourcePath, e.ToString()));
140 return;
141 }
142 }
143
144 this.HarvestedBundlePackage = new WixBundleHarvestedBundlePackageSymbol(this.PackagePayload.SourceLineNumbers, this.PackagePayload.Id)
145 {
146 Win64 = win64,
147 BundleId = bundleId,
148 EngineVersion = engineVersion,
149 ManifestNamespace = manifestNamespace,
150 ProtocolVersion = protocolVersion,
151 PerMachine = perMachine,
152 Version = version,
153 DisplayName = displayName,
154 InstallSize = installSize,
155 };
156 }
157
158 private int ProcessProtocolVersion(BurnReader burnReader, XmlDocument document)
159 {
160 var protocolVersionValue = document.DocumentElement.GetAttribute("ProtocolVersion");
161
162 if (Int32.TryParse(protocolVersionValue, out var protocolVersion))
163 {
164 return protocolVersion;
165 }
166
167 // Assume that the .wixburn section version will change when the Burn protocol changes.
168 // This should be a safe assumption since only old bundles should be missing the ProtocolVersion from the manifest.
169 return burnReader.Version == 2 ? 1 : 0;
170 }
171
172 private bool ProcessWin64(BurnReader burnReader, XmlDocument document, SourceLineNumber sourceLineNumbers, string sourcePath)
173 {
174 var win64Value = document.DocumentElement.GetAttribute("Win64");
175
176 switch (win64Value)
177 {
178 case "yes":
179 return true;
180 case "no":
181 return false;
182 }
183
184 switch (burnReader.MachineType)
185 {
186 case BurnCommon.IMAGE_FILE_MACHINE_ARM:
187 case BurnCommon.IMAGE_FILE_MACHINE_ARMNT:
188 case BurnCommon.IMAGE_FILE_MACHINE_I386:
189 case BurnCommon.IMAGE_FILE_MACHINE_LOONGARCH32:
190 return false;
191 case BurnCommon.IMAGE_FILE_MACHINE_AMD64:
192 case BurnCommon.IMAGE_FILE_MACHINE_ARM64:
193 case BurnCommon.IMAGE_FILE_MACHINE_IA64:
194 case BurnCommon.IMAGE_FILE_MACHINE_LOONGARCH64:
195 return true;
196 case BurnCommon.IMAGE_FILE_MACHINE_AM33:
197 case BurnCommon.IMAGE_FILE_MACHINE_EBC:
198 case BurnCommon.IMAGE_FILE_MACHINE_M32R:
199 case BurnCommon.IMAGE_FILE_MACHINE_MIPS16:
200 case BurnCommon.IMAGE_FILE_MACHINE_MIPSFPU:
201 case BurnCommon.IMAGE_FILE_MACHINE_MIPSFPU16:
202 case BurnCommon.IMAGE_FILE_MACHINE_POWERPC:
203 case BurnCommon.IMAGE_FILE_MACHINE_POWERPCFP:
204 case BurnCommon.IMAGE_FILE_MACHINE_R4000:
205 case BurnCommon.IMAGE_FILE_MACHINE_RISCV32:
206 case BurnCommon.IMAGE_FILE_MACHINE_RISCV64:
207 case BurnCommon.IMAGE_FILE_MACHINE_RISCV128:
208 case BurnCommon.IMAGE_FILE_MACHINE_SH3:
209 case BurnCommon.IMAGE_FILE_MACHINE_SH3DSP:
210 case BurnCommon.IMAGE_FILE_MACHINE_SH4:
211 case BurnCommon.IMAGE_FILE_MACHINE_SH5:
212 case BurnCommon.IMAGE_FILE_MACHINE_THUMB:
213 case BurnCommon.IMAGE_FILE_MACHINE_WCEMIPSV2:
214 default:
215 this.Messaging.Write(BurnBackendWarnings.UnknownCoffMachineType(sourceLineNumbers, sourcePath, burnReader.MachineType));
216 return false;
217 }
218 }
219
220 private long ProcessPackages(XmlDocument document, XmlNamespaceManager namespaceManager)
221 {
222 long packageInstallSize = 0;
223
224 foreach (XmlElement packageElement in document.SelectNodes("/burn:BurnManifest/burn:Chain/*", namespaceManager))
225 {
226 if (!packageElement.Name.EndsWith("Package"))
227 {
228 continue;
229 }
230
231 if (Int64.TryParse(packageElement.GetAttribute("InstallSize"), out var installSize))
232 {
233 packageInstallSize += installSize;
234 }
235 }
236
237 return packageInstallSize;
238 }
239
240 private void ProcessPayloads(XmlDocument document, XmlNamespaceManager namespaceManager, BundlePackagePayloadGenerationType payloadGenerationType)
241 {
242 if (payloadGenerationType == BundlePackagePayloadGenerationType.None)
243 {
244 return;
245 }
246
247 var payloadNames = new HashSet<string>(this.PackagePayloadsById.Values.Select(p => p.Name), StringComparer.OrdinalIgnoreCase);
248
249 var containersById = new Dictionary<string, ManifestContainer>();
250
251 foreach (XmlElement containerElement in document.SelectNodes("/burn:BurnManifest/burn:Container", namespaceManager))
252 {
253 var container = new ManifestContainer();
254 container.Attached = containerElement.GetAttribute("Attached") == "yes";
255 container.DownloadUrl = containerElement.GetAttribute("DownloadUrl");
256 container.FilePath = containerElement.GetAttribute("FilePath");
257 container.Id = containerElement.GetAttribute("Id");
258 containersById.Add(container.Id, container);
259
260 if (container.Attached)
261 {
262 continue;
263 }
264
265 switch (payloadGenerationType)
266 {
267 case BundlePackagePayloadGenerationType.ExternalWithoutDownloadUrl:
268 if (!String.IsNullOrEmpty(container.DownloadUrl))
269 {
270 continue;
271 }
272 break;
273 }
274
275 // If we didn't find the Payload as an existing child of the package, we need to
276 // add it. We expect the file to exist on-disk in the same relative location as
277 // the bundle expects to find it...
278 container.IncludedAsPayload = true;
279 var containerName = container.FilePath;
280 var containerFullName = Path.Combine(Path.GetDirectoryName(this.PackagePayload.Name), containerName);
281
282 if (!payloadNames.Contains(containerFullName))
283 {
284 var generatedId = this.BackendHelper.GenerateIdentifier("hcp", this.PackagePayload.Id.Id, containerName);
285 var payloadSourceFile = this.ResolveRelatedFile(this.PackagePayload.SourceFile.Path, this.PackagePayload.UnresolvedSourceFile, containerName, "container", this.PackagePayload.SourceLineNumbers);
286
287 this.Payloads.Add(new WixBundlePayloadSymbol(this.PackagePayload.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId))
288 {
289 Name = containerFullName,
290 SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile },
291 Compressed = this.PackagePayload.Compressed,
292 UnresolvedSourceFile = containerFullName,
293 ContainerRef = this.PackagePayload.ContainerRef,
294 DownloadUrl = this.PackagePayload.DownloadUrl,
295 Packaging = this.PackagePayload.Packaging,
296 ParentPackagePayloadRef = this.PackagePayload.Id.Id,
297 });
298 }
299 }
300
301 foreach (XmlElement payloadElement in document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager))
302 {
303 var payload = new ManifestPayload();
304 payload.Container = payloadElement.GetAttribute("Container");
305 payload.DownloadUrl = payloadElement.GetAttribute("DownloadUrl");
306 payload.FilePath = payloadElement.GetAttribute("FilePath");
307 payload.Id = payloadElement.GetAttribute("Id");
308
309 if (payload.Container == null || !containersById.TryGetValue(payload.Container, out var container))
310 {
311 container = null;
312 }
313
314 if (container != null && container.IncludedAsPayload)
315 {
316 // Don't include payload if it's in a container that's already included.
317 continue;
318 }
319
320 switch (payloadGenerationType)
321 {
322 case BundlePackagePayloadGenerationType.ExternalWithoutDownloadUrl:
323 if (container != null || !String.IsNullOrEmpty(payload.DownloadUrl))
324 {
325 continue;
326 }
327 break;
328 case BundlePackagePayloadGenerationType.External:
329 if (container != null)
330 {
331 continue;
332 }
333 break;
334 }
335
336 // If we didn't find the Payload as an existing child of the package, we need to
337 // add it. We expect the file to exist on-disk in the same relative location as
338 // the bundle expects to find it...
339 var payloadName = payload.FilePath;
340 var payloadFullName = Path.Combine(Path.GetDirectoryName(this.PackagePayload.Name), payloadName);
341
342 if (!payloadNames.Contains(payloadFullName))
343 {
344 var generatedId = this.BackendHelper.GenerateIdentifier("hpp", this.PackagePayload.Id.Id, payloadName);
345 var payloadSourceFile = this.ResolveRelatedFile(this.PackagePayload.SourceFile.Path, this.PackagePayload.UnresolvedSourceFile, payloadName, "payload", this.PackagePayload.SourceLineNumbers);
346
347 this.Payloads.Add(new WixBundlePayloadSymbol(this.PackagePayload.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId))
348 {
349 Name = payloadFullName,
350 SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile },
351 Compressed = this.PackagePayload.Compressed,
352 UnresolvedSourceFile = payloadFullName,
353 ContainerRef = this.PackagePayload.ContainerRef,
354 DownloadUrl = this.PackagePayload.DownloadUrl,
355 Packaging = this.PackagePayload.Packaging,
356 ParentPackagePayloadRef = this.PackagePayload.Id.Id,
357 });
358 }
359 }
360 }
361
362 private string ResolveRelatedFile(string resolvedSource, string unresolvedSource, string relatedSource, string type, SourceLineNumber sourceLineNumbers)
363 {
364 var checkedPaths = new List<string>();
365
366 foreach (var extension in this.BackendExtensions)
367 {
368 var resolved = extension.ResolveRelatedFile(unresolvedSource, relatedSource, type, sourceLineNumbers);
369
370 if (resolved?.CheckedPaths != null)
371 {
372 checkedPaths.AddRange(resolved.CheckedPaths);
373 }
374
375 if (!String.IsNullOrEmpty(resolved?.Path))
376 {
377 return resolved?.Path;
378 }
379 }
380
381 var resolvedPath = Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource);
382
383 if (!File.Exists(resolvedPath))
384 {
385 checkedPaths.Add(resolvedPath);
386 this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, resolvedPath, type, checkedPaths));
387 }
388
389 return resolvedPath;
390 }
391
392 private void ProcessRelatedBundles(XmlDocument document, XmlNamespaceManager namespaceManager, string sourcePath)
393 {
394 var sourceLineNumbers = this.PackagePayload.SourceLineNumbers;
395
396 foreach (XmlElement relatedBundleElement in document.SelectNodes("/burn:BurnManifest/burn:RelatedBundle", namespaceManager))
397 {
398 var id = relatedBundleElement.GetAttribute("Id");
399 var actionValue = relatedBundleElement.GetAttribute("Action");
400
401 if (!Enum.TryParse(actionValue, out RelatedBundleActionType action))
402 {
403 this.Messaging.Write(BurnBackendWarnings.UnknownBundleRelationAction(sourceLineNumbers, sourcePath, actionValue));
404 continue;
405 }
406
407 this.RelatedBundles.Add(new WixBundlePackageRelatedBundleSymbol(sourceLineNumbers)
408 {
409 PackagePayloadRef = this.PackagePayload.Id.Id,
410 BundleId = id,
411 Action = action,
412 });
413 }
414 }
415
416 private class ManifestContainer
417 {
418 public bool Attached { get; set; }
419 public string DownloadUrl { get; set; }
420 public string FilePath { get; set; }
421 public string Id { get; set; }
422
423 public bool IncludedAsPayload { get; set; }
424 }
425
426 private class ManifestPayload
427 {
428 public string Container { get; set; }
429 public string DownloadUrl { get; set; }
430 public string FilePath { get; set; }
431 public string Id { get; set; }
432 }
433 }
434 }