aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-01-06 15:15:35 -0800
committerRob Mensching <rob@firegiant.com>2021-01-06 15:24:56 -0800
commit093e1dd144b260b58a0ae46d722d1dbc4019d9d5 (patch)
tree6fd63241e2b196b040d26b3b1749d4c3ecbc531c
parentbf30546113c5f1ffc4cf917b560c5a0451eda37d (diff)
downloadwix-093e1dd144b260b58a0ae46d722d1dbc4019d9d5.tar.gz
wix-093e1dd144b260b58a0ae46d722d1dbc4019d9d5.tar.bz2
wix-093e1dd144b260b58a0ae46d722d1dbc4019d9d5.zip
Implement improved file sequence optimization
First ensures files are grouped by DiskId. Then files are sequenced by target directory order to optimize MSI installation behavior. Finally, files are alphabetized in the directory. Additional optimizations could be considered in the future from here. Fixes wixtoolset/issues#4409 Fixes wixtoolset/issues#4708
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs92
-rw-r--r--src/WixToolset.Core/Linker.cs6
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/CabFixture.cs2
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs62
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs28
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt1
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt1
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt1
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt1
10 files changed, 185 insertions, 11 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index 93c617d9..4b3d554a 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -364,7 +364,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
364 Dictionary<MediaSymbol, IEnumerable<FileFacade>> filesByCabinetMedia; 364 Dictionary<MediaSymbol, IEnumerable<FileFacade>> filesByCabinetMedia;
365 IEnumerable<FileFacade> uncompressedFiles; 365 IEnumerable<FileFacade> uncompressedFiles;
366 { 366 {
367 var order = new OptimizeFileFacadesOrderCommand(fileFacades); 367 var order = new OptimizeFileFacadesOrderCommand(this.BackendHelper, this.PathResolver, section, platform, fileFacades);
368 order.Execute(); 368 order.Execute();
369 369
370 fileFacades = order.FileFacades; 370 fileFacades = order.FileFacades;
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs
index 6943d345..e96dfd91 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs
@@ -4,34 +4,112 @@ namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Linq;
7 using WixToolset.Core.Bind; 8 using WixToolset.Core.Bind;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
8 13
9 internal class OptimizeFileFacadesOrderCommand 14 internal class OptimizeFileFacadesOrderCommand
10 { 15 {
11 public OptimizeFileFacadesOrderCommand(List<FileFacade> fileFacades) 16 public OptimizeFileFacadesOrderCommand(IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform, List<FileFacade> fileFacades)
12 { 17 {
18 this.BackendHelper = helper;
19 this.PathResolver = pathResolver;
20 this.Section = section;
21 this.Platform = platform;
13 this.FileFacades = fileFacades; 22 this.FileFacades = fileFacades;
14 } 23 }
15 24
16 public List<FileFacade> FileFacades { get; private set; } 25 public List<FileFacade> FileFacades { get; private set; }
17 26
27 private IBackendHelper BackendHelper { get; }
28
29 private IPathResolver PathResolver { get; }
30
31 private IntermediateSection Section { get; }
32
33 private Platform Platform { get; }
34
18 public List<FileFacade> Execute() 35 public List<FileFacade> Execute()
19 { 36 {
20 this.FileFacades.Sort(FileFacadeOptimizer.Instance); 37 var canonicalComponentTargetPaths = this.ComponentTargetPaths();
38
39 this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths));
21 40
22 return this.FileFacades; 41 return this.FileFacades;
23 } 42 }
24 43
44 private Dictionary<string, string> ComponentTargetPaths()
45 {
46 var directories = this.ResolveDirectories();
47
48 var canonicalPathsByDirectoryId = new Dictionary<string, string>();
49 foreach (var component in this.Section.Symbols.OfType<ComponentSymbol>())
50 {
51 var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(directories, null, component.DirectoryRef, this.Platform);
52 canonicalPathsByDirectoryId.Add(component.Id.Id, directoryPath);
53 }
54
55 return canonicalPathsByDirectoryId;
56 }
57
58 private Dictionary<string, IResolvedDirectory> ResolveDirectories()
59 {
60 var targetPathsByDirectoryId = new Dictionary<string, IResolvedDirectory>();
61
62 // Get the target paths for all directories.
63 foreach (var directory in this.Section.Symbols.OfType<DirectorySymbol>())
64 {
65 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name);
66 targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory);
67 }
68
69 return targetPathsByDirectoryId;
70 }
71
25 private class FileFacadeOptimizer : IComparer<FileFacade> 72 private class FileFacadeOptimizer : IComparer<FileFacade>
26 { 73 {
27 public static readonly FileFacadeOptimizer Instance = new FileFacadeOptimizer(); 74 public FileFacadeOptimizer(Dictionary<string, string> componentTargetPaths)
75 {
76 this.ComponentTargetPaths = componentTargetPaths;
77 }
78
79 private Dictionary<string, string> ComponentTargetPaths { get; }
28 80
29 public int Compare(FileFacade x, FileFacade y) 81 public int Compare(FileFacade x, FileFacade y)
30 { 82 {
31 // TODO: Sort these facades even smarter by directory path and component id 83 // First group files by DiskId.
32 // and maybe file size or file extension and other creative ideas to 84 var compare = x.DiskId.CompareTo(y.DiskId);
33 // get optimal install speed out of MSI. 85
34 return String.Compare(x.ComponentRef, y.ComponentRef, StringComparison.Ordinal); 86 if (compare != 0)
87 {
88 return compare;
89 }
90
91 // Next try to group files by target install directory.
92 if (this.ComponentTargetPaths.TryGetValue(x.ComponentRef, out var canonicalX) &&
93 this.ComponentTargetPaths.TryGetValue(y.ComponentRef, out var canonicalY))
94 {
95 compare = String.Compare(canonicalX, canonicalY, StringComparison.Ordinal);
96
97 if (compare != 0)
98 {
99 return compare;
100 }
101 }
102
103 // TODO: Consider sorting these facades even smarter by file size or file extension
104 // or other creative ideas to get optimal install speed out of MSI.
105 compare = String.Compare(x.FileName, y.FileName, StringComparison.Ordinal);
106
107 if (compare != 0)
108 {
109 return compare;
110 }
111
112 return String.Compare(x.Id, y.Id, StringComparison.Ordinal);
35 } 113 }
36 } 114 }
37 } 115 }
diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs
index 431ba4c7..e0af89ba 100644
--- a/src/WixToolset.Core/Linker.cs
+++ b/src/WixToolset.Core/Linker.cs
@@ -198,8 +198,10 @@ namespace WixToolset.Core
198 } 198 }
199 199
200 // Report duplicates that would ultimately end up being primary key collisions. 200 // Report duplicates that would ultimately end up being primary key collisions.
201 var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections); 201 {
202 reportDupes.Execute(); 202 var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections);
203 reportDupes.Execute();
204 }
203 205
204 if (this.Messaging.EncounteredError) 206 if (this.Messaging.EncounteredError)
205 { 207 {
diff --git a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs
index 5aef148e..ad62dea6 100644
--- a/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/CabFixture.cs
@@ -42,7 +42,7 @@ namespace WixToolsetTest.CoreIntegration
42 var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList(); 42 var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList();
43 43
44 Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray()); 44 Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray());
45 Assert.Equal(new[] { "test.txt", "Notepad.exe" }, fileRows.Select(f => f.Name).ToArray()); 45 Assert.Equal(new[] { "Notepad.exe", "test.txt" }, fileRows.Select(f => f.Name).ToArray());
46 46
47 var files = Query.GetCabinetFiles(cabPath); 47 var files = Query.GetCabinetFiles(cabPath);
48 Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray()); 48 Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray());
diff --git a/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs b/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs
new file mode 100644
index 00000000..de18e30c
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/MediaFixture.cs
@@ -0,0 +1,62 @@
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 WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using WixToolset.Data;
10 using Xunit;
11
12 public class MediaFixture
13 {
14 [Fact]
15 public void CanBuildMultiMedia()
16 {
17 var folder = TestData.Get(@"TestData");
18
19 using (var fs = new DisposableFileSystem())
20 {
21 var baseFolder = fs.GetFolder();
22 var intermediateFolder = Path.Combine(baseFolder, "obj");
23 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
24
25 var result = WixRunner.Execute(new[]
26 {
27 "build",
28 Path.Combine(folder, "Media", "MultiMedia.wxs"),
29 "-bindpath", Path.Combine(folder, "Media", "data"),
30 "-intermediateFolder", intermediateFolder,
31 "-o", msiPath
32 });
33
34 result.AssertSuccess();
35
36 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
37 var section = intermediate.Sections.Single();
38
39 var mediaSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.MediaSymbol>().OrderBy(m => m.DiskId).ToList();
40 var fileSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.FileSymbol>().OrderBy(f => f.Sequence).ToList();
41 Assert.Equal(1, mediaSymbols[0].DiskId);
42 Assert.Equal(2, mediaSymbols[0].LastSequence);
43 Assert.Equal(2, mediaSymbols[1].DiskId);
44 Assert.Equal(4, mediaSymbols[1].LastSequence);
45 Assert.Equal(new[]
46 {
47 "a1.txt",
48 "a2.txt",
49 "b1.txt",
50 "b2.txt",
51 }, fileSymbols.Select(f => f.Name).ToArray());
52 Assert.Equal(new[]
53 {
54 1,
55 2,
56 3,
57 4,
58 }, fileSymbols.Select(f => f.Sequence).ToArray());
59 }
60 }
61 }
62}
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs
new file mode 100644
index 00000000..8a555bda
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs
@@ -0,0 +1,28 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="~MultiMedia" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation"
3 UpgradeCode="12E4699F-E774-4D05-8A01-5BDD41BBA127">
4
5 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
6
7 <Media Id="1" Cabinet="cab1.cab" />
8 <Media Id="2" Cabinet="cab2.cab" />
9
10 <Feature Id="ProductFeature" Title="MsiPackageTitle">
11 <Component Directory="ProgramFilesFolder:\~MultiMedia" DiskId="1">
12 <File Source="a1.txt" />
13 </Component>
14
15 <Component Directory="ProgramFilesFolder:\~MultiMedia" DiskId="1">
16 <File Source="a2.txt" />
17 </Component>
18
19 <Component Directory="ProgramFilesFolder:\~MultiMedia" DiskId="2">
20 <File Source="b2.txt" />
21 </Component>
22
23 <Component Directory="ProgramFilesFolder:\~MultiMedia" DiskId="2">
24 <File Source="b1.txt" />
25 </Component>
26 </Feature>
27 </Package>
28</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt
new file mode 100644
index 00000000..ad9cdcb5
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt
@@ -0,0 +1 @@
This is a1.txt \ No newline at end of file
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt
new file mode 100644
index 00000000..d5de23de
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt
@@ -0,0 +1 @@
This is a2.txt \ No newline at end of file
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt
new file mode 100644
index 00000000..88bc4a56
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt
@@ -0,0 +1 @@
This is b1.txt \ No newline at end of file
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt
new file mode 100644
index 00000000..38525276
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt
@@ -0,0 +1 @@
This is b2.txt \ No newline at end of file