main
cs 119 lines 4.57 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.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.Data;
11 using WixToolset.Extensibility.Services;
12
13 internal class OptimizeFileFacadesOrderCommand
14 {
15 public OptimizeFileFacadesOrderCommand(IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform, List<IFileFacade> fileFacades)
16 {
17 this.BackendHelper = helper;
18 this.PathResolver = pathResolver;
19 this.Section = section;
20 this.Platform = platform;
21 this.FileFacades = fileFacades;
22 }
23
24 public List<IFileFacade> FileFacades { get; private set; }
25
26 private IBackendHelper BackendHelper { get; }
27
28 private IPathResolver PathResolver { get; }
29
30 private IntermediateSection Section { get; }
31
32 private Platform Platform { get; }
33
34 public List<IFileFacade> Execute()
35 {
36 var canonicalComponentTargetPaths = this.ComponentTargetPaths();
37
38 this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths, this.Section.Type == SectionType.Module));
39
40 return this.FileFacades;
41 }
42
43 private Dictionary<string, string> ComponentTargetPaths()
44 {
45 var directories = this.ResolveDirectories();
46
47 var canonicalPathsByDirectoryId = new Dictionary<string, string>();
48 foreach (var component in this.Section.Symbols.OfType<ComponentSymbol>())
49 {
50 var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(directories, null, component.DirectoryRef, this.Platform);
51 canonicalPathsByDirectoryId.Add(component.Id.Id, directoryPath);
52 }
53
54 return canonicalPathsByDirectoryId;
55 }
56
57 private Dictionary<string, IResolvedDirectory> ResolveDirectories()
58 {
59 var targetPathsByDirectoryId = new Dictionary<string, IResolvedDirectory>();
60
61 // Get the target paths for all directories.
62 foreach (var directory in this.Section.Symbols.OfType<DirectorySymbol>())
63 {
64 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name);
65 targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory);
66 }
67
68 return targetPathsByDirectoryId;
69 }
70
71 private class FileFacadeOptimizer : IComparer<IFileFacade>
72 {
73 public FileFacadeOptimizer(Dictionary<string, string> componentTargetPaths, bool optimizingMergeModule)
74 {
75 this.ComponentTargetPaths = componentTargetPaths;
76 this.OptimizingMergeModule = optimizingMergeModule;
77 }
78
79 private Dictionary<string, string> ComponentTargetPaths { get; }
80
81 private bool OptimizingMergeModule { get; }
82
83 public int Compare(IFileFacade x, IFileFacade y)
84 {
85 // First group files by DiskId but ignore if processing a Merge Module
86 // because Merge Modules don't have separate disks.
87 var compare = this.OptimizingMergeModule ? 0 : x.DiskId.CompareTo(y.DiskId);
88
89 if (compare != 0)
90 {
91 return compare;
92 }
93
94 // Next try to group files by target install directory.
95 if (this.ComponentTargetPaths.TryGetValue(x.ComponentRef, out var canonicalX) &&
96 this.ComponentTargetPaths.TryGetValue(y.ComponentRef, out var canonicalY))
97 {
98 compare = String.Compare(canonicalX, canonicalY, StringComparison.Ordinal);
99
100 if (compare != 0)
101 {
102 return compare;
103 }
104 }
105
106 // TODO: Consider sorting these facades even smarter by file size or file extension
107 // or other creative ideas to get optimal install speed out of MSI.
108 compare = String.Compare(x.FileName, y.FileName, StringComparison.Ordinal);
109
110 if (compare != 0)
111 {
112 return compare;
113 }
114
115 return String.Compare(x.Id, y.Id, StringComparison.Ordinal);
116 }
117 }
118 }
119 }