Implement cabinet spanning
Completes 6368
Rob Mensching committed
Aug 1, 2022 at 19:35 UTC
16ed873f1bd7f69a61fd2effdab9e57203b896e6
21 files changed
+688
-439
src/api/wix/WixToolset.Data/WarningMessages.cs
+6
-6
@@ -267,14 +267,14 @@ namespace WixToolset.Data
267
return Message(sourceLineNumbers, Ids.EmptyAttributeValue, "The {0}/@{1} attribute's value cannot be an empty string. If you want the value to be null or empty, simply remove the entire attribute.", elementName, attributeName);
268
}
269
270
- public static Message EmptyCabinet(SourceLineNumber sourceLineNumbers, string cabinetName)
271
- {
272
- return Message(sourceLineNumbers, Ids.EmptyCabinet, "The cabinet '{0}' does not contain any files. If this installation contains no files, this warning can likely be safely ignored. Otherwise, please add files to the cabinet or remove it.", cabinetName);
273
- }
274
-
270
public static Message EmptyCabinet(SourceLineNumber sourceLineNumbers, string cabinetName, bool isPatch)
271
{
277
- return Message(sourceLineNumbers, Ids.EmptyCabinet, "The cabinet '{0}' does not contain any files. If this patch contains no files, this warning can likely be safely ignored. Otherwise, try passing -p to torch.exe when first building the transforms, or add a ComponentRef to your PatchFamily authoring to pull changed files into the cabinet.", cabinetName, isPatch);
272
+ if (isPatch)
273
+ {
274
+ return Message(sourceLineNumbers, Ids.EmptyCabinet, "The cabinet '{0}' does not contain any files. If this patch contains no files, this warning can likely be safely ignored. Otherwise, try passing -p to torch.exe when first building the transforms, or add a ComponentRef to your PatchFamily authoring to pull changed files into the cabinet.", cabinetName, isPatch);
275
+ }
276
+
277
+ return Message(sourceLineNumbers, Ids.EmptyCabinet, "The cabinet '{0}' does not contain any files. If this installation contains no files, this warning can likely be safely ignored. Otherwise, please add files to the cabinet or remove it.", cabinetName);
278
}
279
280
public static Message ExpectedForeignRow(SourceLineNumber sourceLineNumbers, string tableName, string primaryKey, string columnName, string columnValue, string foreignTableName)
src/libs/dutil/WixToolset.DUtil/inc/cabcutil.h
+1
-1
@@ -10,7 +10,7 @@
10
// First argument is the name of splitting cabinet without extension e.g. "cab1"
11
// Second argument is name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"
12
// Third argument is the file token of the first file present in the splitting cabinet
13
-typedef void (__stdcall * FileSplitCabNamesCallback)(LPWSTR, LPWSTR, LPWSTR);
13
+typedef void (__stdcall * FileSplitCabNamesCallback)(LPCWSTR, LPCWSTR, LPCWSTR);
14
15
#define CAB_MAX_SIZE 0x7FFFFFFF // (see KB: Q174866)
16
src/wix/WixToolset.Core.Native/Cabinet.cs
+24
-2
@@ -36,7 +36,8 @@ namespace WixToolset.Core.Native
36
/// <param name="compressionLevel">Level of compression to apply.</param>
37
/// <param name="maxSize">Maximum size of cabinet.</param>
38
/// <param name="maxThresh">Maximum threshold for each cabinet.</param>
39
- public void Compress(IEnumerable<CabinetCompressFile> files, CompressionLevel compressionLevel, int maxSize = 0, int maxThresh = 0)
39
+ /// <returns>>List of CabinetCreated.</returns>
40
+ public IReadOnlyCollection<CabinetCreated> Compress(IEnumerable<CabinetCompressFile> files, CompressionLevel compressionLevel, int maxSize = 0, int maxThresh = 0)
41
{
42
var compressionLevelVariable = Environment.GetEnvironmentVariable(CompressionLevelVariable);
43
@@ -56,7 +57,9 @@ namespace WixToolset.Core.Native
57
wixnative.AddStdinLine(file.ToWixNativeStdinLine());
58
}
59
59
- wixnative.Run();
60
+ var cabinetsCreated = wixnative.Run();
61
+
62
+ return ParseCreatedCabinets(cabinetsCreated);
63
}
64
65
/// <summary>
@@ -103,5 +106,24 @@ namespace WixToolset.Core.Native
106
var wixnative = new WixNativeExe("extractcab", this.Path, outputFolder);
107
return wixnative.Run().Where(output => !String.IsNullOrWhiteSpace(output));
108
}
109
+
110
+ private static IReadOnlyCollection<CabinetCreated> ParseCreatedCabinets(IReadOnlyCollection<string> cabinetsCreated)
111
+ {
112
+ var created = new List<CabinetCreated>();
113
+
114
+ foreach (var cabinetCreated in cabinetsCreated)
115
+ {
116
+ var data = cabinetCreated.Split(TextLineSplitter, StringSplitOptions.None);
117
+
118
+ if (data.Length != 3)
119
+ {
120
+ continue;
121
+ }
122
+
123
+ created.Add(new CabinetCreated(data[1], data[2]));
124
+ }
125
+
126
+ return created;
127
+ }
128
}
129
}
src/wix/WixToolset.Core.Native/CabinetCreated.cs
new
+31
@@ -0,0 +1,31 @@
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.Native
4
+{
5
+ /// <summary>
6
+ /// Cabinet created by compressing files.
7
+ /// </summary>
8
+ public sealed class CabinetCreated
9
+ {
10
+ /// <summary>
11
+ /// Constructs CabinetCreated.
12
+ /// </summary>
13
+ /// <param name="cabinetName">Name of cabinet.</param>
14
+ /// <param name="firstFileToken">Token of first file compressed in cabinet.</param>
15
+ public CabinetCreated(string cabinetName, string firstFileToken)
16
+ {
17
+ this.CabinetName = cabinetName;
18
+ this.FirstFileToken = firstFileToken;
19
+ }
20
+
21
+ /// <summary>
22
+ /// Gets the name of the cabinet.
23
+ /// </summary>
24
+ public string CabinetName { get; }
25
+
26
+ /// <summary>
27
+ /// Gets the token of the first file in the cabinet.
28
+ /// </summary>
29
+ public string FirstFileToken { get; }
30
+ }
31
+}
src/wix/WixToolset.Core.Native/WixNativeExe.cs
+2
-2
@@ -49,8 +49,8 @@ namespace WixToolset.Core.Native
49
50
using (var process = Process.Start(wixNativeInfo))
51
{
52
- process.OutputDataReceived += (s, a) => outputLines.Add(a.Data);
53
- process.ErrorDataReceived += (s, a) => outputLines.Add(a.Data);
52
+ process.OutputDataReceived += (s, a) => { if (a.Data != null) { outputLines.Add(a.Data); } };
53
+ process.ErrorDataReceived += (s, a) => { if (a.Data != null) { outputLines.Add(a.Data); } };
54
process.BeginOutputReadLine();
55
process.BeginErrorReadLine();
56
src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+4
-26
@@ -410,7 +410,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
410
var modularize = new ModularizeCommand(this.WindowsInstallerBackendHelper, data, modularizationSuffix, section.Symbols.OfType<WixSuppressModularizationSymbol>());
411
modularize.Execute();
412
413
- // Ensure all sequence tables in place because, mergemod.dll requires them.
413
+ // Ensure all sequence tables in place because mergemod.dll requires them.
414
var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions);
415
suppressedTableNames = unsuppress.Execute();
416
}
@@ -438,28 +438,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
438
command.Execute();
439
}
440
441
- // create cabinet files and process uncompressed files
442
- var layoutDirectory = Path.GetDirectoryName(this.OutputPath);
441
+ // Create cabinet files.
442
if (!this.SuppressLayout || OutputType.Module == data.Type)
443
{
444
this.Messaging.Write(VerboseMessages.CreatingCabinetFiles());
445
447
- var mediaTemplate = section.Symbols.OfType<WixMediaTemplateSymbol>().FirstOrDefault();
448
-
449
- var command = new CreateCabinetsCommand(this.ServiceProvider, this.WindowsInstallerBackendHelper, mediaTemplate);
450
- command.CabbingThreadCount = this.CabbingThreadCount;
451
- command.CabCachePath = this.CabCachePath;
452
- command.DefaultCompressionLevel = this.DefaultCompressionLevel;
453
- command.Data = data;
454
- command.Messaging = this.Messaging;
455
- command.BackendExtensions = this.BackendExtensions;
456
- command.LayoutDirectory = layoutDirectory;
457
- command.Compressed = compressed;
458
- command.ModularizationSuffix = modularizationSuffix;
459
- command.FileFacadesByCabinet = filesByCabinetMedia;
460
- command.ResolveMedia = this.ResolveMedia;
461
- command.TableDefinitions = tableDefinitions;
462
- command.IntermediateFolder = this.IntermediateFolder;
446
+ var command = new CreateCabinetsCommand(this.ServiceProvider, this.Messaging, this.WindowsInstallerBackendHelper, this.BackendExtensions, section, this.CabCachePath, this.CabbingThreadCount, this.OutputPath, this.IntermediateFolder, this.DefaultCompressionLevel, compressed, modularizationSuffix, filesByCabinetMedia, data, tableDefinitions, this.ResolveMedia);
447
command.Execute();
448
449
fileTransfers.AddRange(command.FileTransfers);
@@ -523,13 +507,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
507
// Process uncompressed files.
508
if (!this.SuppressLayout && uncompressedFiles.Any())
509
{
526
- var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver);
527
- command.Compressed = compressed;
528
- command.FileFacades = uncompressedFiles;
529
- command.LayoutDirectory = layoutDirectory;
530
- command.LongNamesInImage = longNames;
531
- command.ResolveMedia = this.ResolveMedia;
532
- command.DatabasePath = this.OutputPath;
510
+ var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver, uncompressedFiles, this.OutputPath, compressed, longNames, this.ResolveMedia);
511
command.Execute();
512
513
fileTransfers.AddRange(command.FileTransfers);
src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
+42
-23
@@ -18,18 +18,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
18
internal sealed class CabinetBuilder
19
{
20
private readonly Queue<CabinetWorkItem> cabinetWorkItems;
21
- private int threadCount;
21
+ private readonly List<CompletedCabinetWorkItem> completedCabinets;
22
23
- // Address of Binder's callback function for Cabinet Splitting
24
- private readonly IntPtr newCabNamesCallBackAddress;
25
-
26
- /// <summary>
27
- /// Instantiate a new CabinetBuilder.
28
- /// </summary>
29
- /// <param name="messaging"></param>
30
- /// <param name="threadCount">number of threads to use</param>
31
- /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param>
32
- public CabinetBuilder(IMessaging messaging, int threadCount, IntPtr newCabNamesCallBackAddress)
23
+ public CabinetBuilder(IMessaging messaging, int threadCount, int maximumCabinetSizeForLargeFileSplitting, int maximumUncompressedMediaSize)
24
{
25
if (0 >= threadCount)
26
{
@@ -37,24 +28,32 @@ namespace WixToolset.Core.WindowsInstaller.Bind
28
}
29
30
this.cabinetWorkItems = new Queue<CabinetWorkItem>();
40
- this.Messaging = messaging;
41
- this.threadCount = threadCount;
31
+ this.completedCabinets = new List<CompletedCabinetWorkItem>();
32
43
- // Set Address of Binder's callback function for Cabinet Splitting
44
- this.newCabNamesCallBackAddress = newCabNamesCallBackAddress;
33
+ this.Messaging = messaging;
34
+ this.ThreadCount = threadCount;
35
+ this.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting;
36
+ this.MaximumUncompressedMediaSize = maximumUncompressedMediaSize;
37
}
38
39
private IMessaging Messaging { get; }
40
49
- public int MaximumCabinetSizeForLargeFileSplitting { get; set; }
41
+ private int ThreadCount { get; }
42
51
- public int MaximumUncompressedMediaSize { get; set; }
43
+ private int MaximumCabinetSizeForLargeFileSplitting { get; }
44
+
45
+ private int MaximumUncompressedMediaSize { get; }
46
+
47
+ public IReadOnlyCollection<CompletedCabinetWorkItem> CompletedCabinets => this.completedCabinets;
48
49
/// <summary>
50
/// Enqueues a CabinetWorkItem to the queue.
51
/// </summary>
52
/// <param name="cabinetWorkItem">cabinet work item</param>
57
- public void Enqueue(CabinetWorkItem cabinetWorkItem) => this.cabinetWorkItems.Enqueue(cabinetWorkItem);
53
+ public void Enqueue(CabinetWorkItem cabinetWorkItem)
54
+ {
55
+ this.cabinetWorkItems.Enqueue(cabinetWorkItem);
56
+ }
57
58
/// <summary>
59
/// Create the queued cabinets.
@@ -62,8 +61,20 @@ namespace WixToolset.Core.WindowsInstaller.Bind
61
/// <returns>error message number (zero if no error)</returns>
62
public void CreateQueuedCabinets()
63
{
64
+ if (this.cabinetWorkItems.Count == 0)
65
+ {
66
+ return;
67
+ }
68
+
69
+ var cabinetFolders = this.cabinetWorkItems.Select(c => Path.GetDirectoryName(c.CabinetFile)).Distinct(StringComparer.OrdinalIgnoreCase);
70
+
71
+ foreach (var folder in cabinetFolders)
72
+ {
73
+ Directory.CreateDirectory(folder);
74
+ }
75
+
76
// don't create more threads than the number of cabinets to build
66
- var numberOfThreads = Math.Min(this.threadCount, this.cabinetWorkItems.Count);
77
+ var numberOfThreads = Math.Min(this.ThreadCount, this.cabinetWorkItems.Count);
78
79
if (0 < numberOfThreads)
80
{
@@ -107,8 +118,14 @@ namespace WixToolset.Core.WindowsInstaller.Bind
118
cabinetWorkItem = this.cabinetWorkItems.Dequeue();
119
}
120
110
- // create a cabinet
111
- this.CreateCabinet(cabinetWorkItem);
121
+ // Create a cabinet.
122
+ var created = this.CreateCabinet(cabinetWorkItem);
123
+
124
+ // Update the cabinet work item to report back what cabinets were created.
125
+ lock (this.completedCabinets)
126
+ {
127
+ this.completedCabinets.Add(new CompletedCabinetWorkItem(cabinetWorkItem.DiskId, created));
128
+ }
129
}
130
}
131
catch (WixException we)
@@ -125,7 +142,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
142
/// Creates a cabinet using the wixcab.dll interop layer.
143
/// </summary>
144
/// <param name="cabinetWorkItem">CabinetWorkItem containing information about the cabinet to create.</param>
128
- private void CreateCabinet(CabinetWorkItem cabinetWorkItem)
145
+ private IReadOnlyCollection<CabinetCreated> CreateCabinet(CabinetWorkItem cabinetWorkItem)
146
{
147
this.Messaging.Write(VerboseMessages.CreateCabinet(cabinetWorkItem.CabinetFile));
148
@@ -163,7 +180,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
180
.ToList();
181
182
var cab = new Cabinet(cabinetPath);
166
- cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold);
183
+ var created = cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold);
184
185
// Best effort check to see if the cabinet is too large for the Windows Installer.
186
try
@@ -177,6 +194,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
194
catch
195
{
196
}
197
+
198
+ return created;
199
}
200
}
201
}
src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs
+9
-2
@@ -15,14 +15,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
15
/// Instantiate a new CabinetWorkItem.
16
/// </summary>
17
/// <param name="sourceLineNumber">Source line number that requires the cabinet creation.</param>
18
+ /// <param name="diskId"></param>
19
/// <param name="fileFacades">The collection of files in this cabinet.</param>
20
/// <param name="cabinetFile">The cabinet file.</param>
21
/// <param name="maxThreshold">Maximum threshold for each cabinet.</param>
22
/// <param name="compressionLevel">The compression level of the cabinet.</param>
23
/// <param name="modularizationSuffix">Modularization suffix used when building a Merge Module.</param>
23
- public CabinetWorkItem(SourceLineNumber sourceLineNumber, string cabinetFile, IEnumerable<IFileFacade> fileFacades, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix)
24
+ public CabinetWorkItem(SourceLineNumber sourceLineNumber, int diskId, string cabinetFile, IEnumerable<IFileFacade> fileFacades, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix)
25
{
26
this.SourceLineNumber = sourceLineNumber;
27
+ this.DiskId = diskId;
28
this.CabinetFile = cabinetFile;
29
this.CompressionLevel = compressionLevel;
30
this.ModularizationSuffix = modularizationSuffix;
@@ -35,6 +37,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
37
/// </summary>
38
public SourceLineNumber SourceLineNumber { get; }
39
40
+ /// <summary>
41
+ /// Gets the Media symbol's DiskId that requires the cabinet.
42
+ /// </summary>
43
+ public int DiskId { get; }
44
+
45
/// <summary>
46
/// Gets the cabinet file.
47
/// </summary>
@@ -56,7 +63,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
63
/// Gets the collection of files in this cabinet.
64
/// </summary>
65
/// <value>The collection of files in this cabinet.</value>
59
- public IEnumerable<IFileFacade> FileFacades { get; }
66
+ public IEnumerable<IFileFacade> FileFacades { get; }
67
68
/// <summary>
69
/// Gets the max threshold.
src/wix/WixToolset.Core.WindowsInstaller/Bind/CompletedCabinetWorkItem.cs
new
+20
@@ -0,0 +1,20 @@
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.Collections.Generic;
6
+ using WixToolset.Core.Native;
7
+
8
+ internal class CompletedCabinetWorkItem
9
+ {
10
+ public CompletedCabinetWorkItem(int diskId, IReadOnlyCollection<CabinetCreated> created)
11
+ {
12
+ this.DiskId = diskId;
13
+ this.CreatedCabinets = created;
14
+ }
15
+
16
+ public int DiskId { get; }
17
+
18
+ public IReadOnlyCollection<CabinetCreated> CreatedCabinets { get; }
19
+ }
20
+}
src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
+169
-231
@@ -4,13 +4,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
4
{
5
using System;
6
using System.Collections.Generic;
7
- using System.Globalization;
7
using System.IO;
8
using System.Linq;
10
- using System.Runtime.InteropServices;
9
using WixToolset.Data;
10
using WixToolset.Data.Symbols;
11
using WixToolset.Data.WindowsInstaller;
12
+ using WixToolset.Data.WindowsInstaller.Rows;
13
using WixToolset.Extensibility;
14
using WixToolset.Extensibility.Data;
15
using WixToolset.Extensibility.Services;
@@ -20,70 +19,63 @@ namespace WixToolset.Core.WindowsInstaller.Bind
19
/// </summary>
20
internal class CreateCabinetsCommand
21
{
23
- public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
24
- public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
22
+ public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
23
+ public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
24
25
+ private readonly CabinetResolver cabinetResolver;
26
private readonly List<IFileTransfer> fileTransfers;
27
-
27
private readonly List<ITrackedFile> trackedFiles;
28
30
- private readonly FileSplitCabNamesCallback newCabNamesCallBack;
31
-
32
- private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence
33
-
34
- public CreateCabinetsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, WixMediaTemplateSymbol mediaTemplate)
29
+ public CreateCabinetsCommand(IServiceProvider serviceProvider, IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions, IntermediateSection section, string cabCachePath, int cabbingThreadCount, string outputPath, string intermediateFolder, CompressionLevel? defaultCompressionLevel, bool compressed, string modularizationSuffix, Dictionary<MediaSymbol, IEnumerable<IFileFacade>> filesByCabinetMedia, WindowsInstallerData data, TableDefinitionCollection tableDefinitions, Func<MediaSymbol, string, string, string> resolveMedia)
30
{
36
- this.fileTransfers = new List<IFileTransfer>();
31
+ this.Messaging = messaging;
32
38
- this.trackedFiles = new List<ITrackedFile>();
39
-
40
- this.newCabNamesCallBack = this.NewCabNamesCallBack;
33
+ this.BackendHelper = backendHelper;
34
42
- this.ServiceProvider = serviceProvider;
35
+ this.Section = section;
36
44
- this.BackendHelper = backendHelper;
37
+ this.CabbingThreadCount = cabbingThreadCount;
38
46
- this.MediaTemplate = mediaTemplate;
47
- }
39
+ this.IntermediateFolder = intermediateFolder;
40
+ this.LayoutDirectory = Path.GetDirectoryName(outputPath);
41
49
- private IServiceProvider ServiceProvider { get; }
42
+ this.DefaultCompressionLevel = defaultCompressionLevel;
43
+ this.ModularizationSuffix = modularizationSuffix;
44
+ this.FileFacadesByCabinet = filesByCabinetMedia;
45
51
- private IBackendHelper BackendHelper { get; }
46
+ this.Data = data;
47
+ this.TableDefinitions = tableDefinitions;
48
53
- private WixMediaTemplateSymbol MediaTemplate { get; }
49
+ this.ResolveMedia = resolveMedia;
50
55
- /// <summary>
56
- /// Sets the number of threads to use for cabinet creation.
57
- /// </summary>
58
- public int CabbingThreadCount { private get; set; }
51
+ this.cabinetResolver = new CabinetResolver(serviceProvider, cabCachePath, backendExtensions);
52
+ this.fileTransfers = new List<IFileTransfer>();
53
+ this.trackedFiles = new List<ITrackedFile>();
54
+ }
55
60
- public string CabCachePath { private get; set; }
56
+ private IMessaging Messaging { get; }
57
62
- public IMessaging Messaging { private get; set; }
58
+ private IBackendHelper BackendHelper { get; }
59
64
- public string IntermediateFolder { private get; set; }
60
+ private IntermediateSection Section { get; }
61
66
- /// <summary>
67
- /// Sets the default compression level to use for cabinets
68
- /// that don't have their compression level explicitly set.
69
- /// </summary>
70
- public CompressionLevel? DefaultCompressionLevel { private get; set; }
62
+ private int CabbingThreadCount { get; set; }
63
72
- public IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { private get; set; }
64
+ private string IntermediateFolder { get; }
65
74
- public WindowsInstallerData Data { private get; set; }
66
+ private string LayoutDirectory { get; }
67
76
- public string LayoutDirectory { private get; set; }
68
+ private CompressionLevel? DefaultCompressionLevel { get; }
69
78
- public bool Compressed { private get; set; }
70
+ private string ModularizationSuffix { get; }
71
80
- public string ModularizationSuffix { private get; set; }
72
+ private Dictionary<MediaSymbol, IEnumerable<IFileFacade>> FileFacadesByCabinet { get; }
73
82
- public Dictionary<MediaSymbol, IEnumerable<IFileFacade>> FileFacadesByCabinet { private get; set; }
74
+ private WindowsInstallerData Data { get; }
75
84
- public Func<MediaSymbol, string, string, string> ResolveMedia { private get; set; }
76
+ private TableDefinitionCollection TableDefinitions { get; }
77
86
- public TableDefinitionCollection TableDefinitions { private get; set; }
78
+ private Func<MediaSymbol, string, string, string> ResolveMedia { get; }
79
80
public IEnumerable<IFileTransfer> FileTransfers => this.fileTransfers;
81
@@ -91,8 +83,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
83
84
public void Execute()
85
{
94
- this.lastCabinetAddedToMediaTable = new Dictionary<string, string>();
95
-
86
// If the cabbing thread count wasn't provided, default the number of cabbing threads to the number of processors.
87
if (this.CabbingThreadCount <= 0)
88
{
@@ -101,13 +91,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
91
this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString()));
92
}
93
104
- // Send Binder object to Facilitate NewCabNamesCallBack Callback
105
- var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack));
106
-
107
- // Supply Compile MediaTemplate Attributes to Cabinet Builder
94
this.GetMediaTemplateAttributes(out var maximumCabinetSizeForLargeFileSplitting, out var maximumUncompressedMediaSize);
109
- cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting;
110
- cabinetBuilder.MaximumUncompressedMediaSize = maximumUncompressedMediaSize;
95
+
96
+ var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, maximumCabinetSizeForLargeFileSplitting, maximumUncompressedMediaSize);
97
98
foreach (var entry in this.FileFacadesByCabinet)
99
{
@@ -129,12 +115,15 @@ namespace WixToolset.Core.WindowsInstaller.Bind
115
return;
116
}
117
132
- // create queued cabinets with multiple threads
118
+ // Create queued cabinets with multiple threads.
119
cabinetBuilder.CreateQueuedCabinets();
120
+
121
if (this.Messaging.EncounteredError)
122
{
123
return;
124
}
125
+
126
+ this.UpdateMediaWithSpannedCabinets(cabinetBuilder.CompletedCabinets);
127
}
128
129
private int CalculateCabbingThreadCount()
@@ -163,7 +152,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
152
private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable<IFileFacade> fileFacades)
153
{
154
CabinetWorkItem cabinetWorkItem = null;
166
- var tempCabinetFileX = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet);
155
+
156
+ var intermediateCabinetPath = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet);
157
158
// check for an empty cabinet
159
if (!fileFacades.Any())
@@ -172,25 +162,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
162
var cabinetName = mediaSymbol.Cabinet.TrimStart('#');
163
164
// If building a patch, remind them to run -p for torch.
175
- if (OutputType.Patch == data.Type)
176
- {
177
- this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, true));
178
- }
179
- else
180
- {
181
- this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName));
182
- }
165
+ this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, OutputType.Patch == data.Type));
166
}
167
185
- var cabinetResolver = new CabinetResolver(this.ServiceProvider, this.CabCachePath, this.BackendExtensions);
186
-
187
- var resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades);
168
+ var resolvedCabinet = this.cabinetResolver.ResolveCabinet(intermediateCabinetPath, fileFacades);
169
189
- // create a cabinet work item if it's not being skipped
170
+ // Create a cabinet work item if it's not being skipped.
171
if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption)
172
{
173
// Default to the threshold for best smartcabbing (makes smallest cabinet).
193
- cabinetWorkItem = new CabinetWorkItem(mediaSymbol.SourceLineNumbers, resolvedCabinet.Path, fileFacades, maxThreshold: 0, compressionLevel: compressionLevel, modularizationSuffix: this.ModularizationSuffix);
174
+ cabinetWorkItem = new CabinetWorkItem(mediaSymbol.SourceLineNumbers, mediaSymbol.DiskId, resolvedCabinet.Path, fileFacades, maxThreshold: 0, compressionLevel: compressionLevel, modularizationSuffix: this.ModularizationSuffix);
175
}
176
else // reuse the cabinet from the cabinet cache.
177
{
@@ -235,185 +216,26 @@ namespace WixToolset.Core.WindowsInstaller.Bind
216
return cabinetWorkItem;
217
}
218
238
- //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades)
239
- //{
240
- // ResolvedCabinet resolved = null;
241
-
242
- // List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList();
243
-
244
- // foreach (var extension in this.BackendExtensions)
245
- // {
246
- // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath);
247
- // if (null != resolved)
248
- // {
249
- // break;
250
- // }
251
- // }
252
-
253
- // return resolved;
254
- //}
255
-
256
- /// <summary>
257
- /// Delegate for Cabinet Split Callback
258
- /// </summary>
259
- [UnmanagedFunctionPointer(CallingConvention.StdCall)]
260
- internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken);
261
-
262
- /// <summary>
263
- /// Call back to Add File Transfer for new Cab and add new Cab to Media table
264
- /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe
265
- /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored
266
- /// </summary>
267
- /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param>
268
- /// <param name="newCabinetName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param>
269
- /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param>
270
- internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabinetName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken)
271
- {
272
- throw new NotImplementedException();
273
-#if TODO_CAB_SPANNING
274
- // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads
275
- var mutex = new Mutex(false, "WixCabinetSplitBinderCallback");
276
- try
277
- {
278
- if (!mutex.WaitOne(0, false)) // Check if you can get the lock
279
- {
280
- // Cound not get the Lock
281
- this.Messaging.Write(VerboseMessages.CabinetsSplitInParallel());
282
- mutex.WaitOne(); // Wait on other thread
283
- }
284
-
285
- var firstCabinetName = firstCabName + ".cab";
286
- var transferAdded = false; // Used for Error Handling
287
-
288
- // Create File Transfer for new Cabinet using transfer of Base Cabinet
289
- foreach (var transfer in this.FileTransfers)
290
- {
291
- if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase))
292
- {
293
- var newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName);
294
- var newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName);
295
-
296
- var trackSource = this.BackendHelper.TrackFile(newCabSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers);
297
- this.trackedFiles.Add(trackSource);
298
-
299
- var trackTarget = this.BackendHelper.TrackFile(newCabTargetPath, TrackedFileType.Final, transfer.SourceLineNumbers);
300
- this.trackedFiles.Add(trackTarget);
301
-
302
- var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers);
303
- this.fileTransfers.Add(newTransfer);
304
-
305
- transferAdded = true;
306
- break;
307
- }
308
- }
309
-
310
- // Check if File Transfer was added
311
- if (!transferAdded)
312
- {
313
- throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName));
314
- }
315
-
316
- // Add the new Cabinets to media table using LastSequence of Base Cabinet
317
- var mediaTable = this.Output.Tables["Media"];
318
- var wixFileTable = this.Output.Tables["WixFile"];
319
- var diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain
320
- var lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain
321
- var lastSplitCabinetFound = false; // Used for Error Handling
322
-
323
- var lastCabinetOfThisSequence = String.Empty;
324
- // Get the Value of Last Cabinet Added in this split Sequence from Dictionary
325
- if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence))
326
- {
327
- // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence
328
- lastCabinetOfThisSequence = firstCabinetName;
329
- }
330
-
331
- foreach (MediaRow mediaRow in mediaTable.Rows)
332
- {
333
- // Get details for the Last Cabinet Added in this Split Sequence
334
- if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
335
- {
336
- lastSequenceForLastSplitCabAdded = mediaRow.LastSequence;
337
- diskIDForLastSplitCabAdded = mediaRow.DiskId;
338
- lastSplitCabinetFound = true;
339
- }
340
-
341
- // Check for Name Collision for the new Cabinet added
342
- if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
343
- {
344
- // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row
345
- throw new WixException(ErrorMessages.SplitCabinetNameCollision(newCabinetName, firstCabinetName));
346
- }
347
- }
348
-
349
- // Check if the last Split Cabinet was found in the Media Table
350
- if (!lastSplitCabinetFound)
351
- {
352
- throw new WixException(ErrorMessages.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence));
353
- }
354
-
355
- // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort
356
- // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with
357
- // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction
358
- MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null);
359
- newMediaRow.Cabinet = newCabinetName;
360
- newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion
361
- newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded;
362
-
363
- // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique
364
- foreach (MediaRow mediaRow in mediaTable.Rows)
365
- {
366
- // Check if this row comes after inserted row and it is not the new cabinet inserted row
367
- if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
368
- {
369
- mediaRow.DiskId++; // Increment DiskID
370
- }
371
- }
372
-
373
- // Now Increment DiskID for All files Rows so that they refer to the right Media Row
374
- foreach (WixFileRow wixFileRow in wixFileTable.Rows)
375
- {
376
- // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet
377
- // This check will work as we have only one large file in every splitting cabinet
378
- // If we want to support splitting cabinet with more large files we need to update this code
379
- if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase))
380
- {
381
- wixFileRow.DiskId++; // Increment DiskID
382
- }
383
- }
384
-
385
- // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback
386
- this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName;
387
-
388
- mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails
389
- }
390
- finally
391
- {
392
- // Releasing the Mutex here
393
- mutex.ReleaseMutex();
394
- }
395
-#endif
396
- }
397
-
398
-
219
/// <summary>
220
/// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides
221
/// </summary>
222
private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize)
223
{
404
- // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size
405
- var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS");
406
- var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
224
+ var mediaTemplate = this.Section.Symbols.OfType<WixMediaTemplateSymbol>().FirstOrDefault();
225
226
// Supply Compile MediaTemplate Attributes to Cabinet Builder
409
- if (this.MediaTemplate != null)
227
+ if (mediaTemplate != null)
228
{
229
+ // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size
230
+ var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS");
231
+ var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
232
+
233
// Get the Value for Max Cab Size for File Splitting
234
var maxCabSizeForLargeFileInMB = 0;
235
try
236
{
237
// Override authored mcslfs value if environment variable is authored.
416
- maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : this.MediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting;
238
+ maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : mediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting;
239
240
var testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024;
241
maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB;
@@ -431,7 +253,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
253
try
254
{
255
// Override authored mums value if environment variable is authored.
434
- maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : this.MediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize;
256
+ maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : mediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize;
257
258
var testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024;
259
maxUncompressedMediaSize = maxPreCompressedSizeInMB;
@@ -451,5 +273,121 @@ namespace WixToolset.Core.WindowsInstaller.Bind
273
maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize;
274
}
275
}
276
+
277
+ private void UpdateMediaWithSpannedCabinets(IReadOnlyCollection<CompletedCabinetWorkItem> completedCabinetWorkItems)
278
+ {
279
+ var completedCabinetsSpanned = completedCabinetWorkItems.Where(c => c.CreatedCabinets.Count > 1).OrderBy(c => c.DiskId).ToList();
280
+
281
+ if (completedCabinetsSpanned.Count == 0)
282
+ {
283
+ return;
284
+ }
285
+
286
+ var fileTransfersByName = this.fileTransfers.ToDictionary(t => Path.GetFileName(t.Source), StringComparer.OrdinalIgnoreCase);
287
+ var mediaTable = this.Data.Tables["Media"];
288
+ var fileTable = this.Data.Tables["File"];
289
+ var mediaRows = mediaTable.Rows.Cast<MediaRow>().OrderBy(m => m.DiskId).ToList();
290
+ var fileRows = fileTable.Rows.Cast<FileRow>().OrderBy(f => f.Sequence).ToList();
291
+
292
+ var mediaRowsByOriginalDiskId = mediaRows.ToDictionary(m => m.DiskId);
293
+ var addedMediaRows = new List<MediaRow>();
294
+
295
+ foreach (var completedCabinetSpanned in completedCabinetsSpanned)
296
+ {
297
+ var cabinet = completedCabinetSpanned.CreatedCabinets.First();
298
+ var spannedCabinets = completedCabinetSpanned.CreatedCabinets.Skip(1);
299
+
300
+ if (!fileTransfersByName.TryGetValue(cabinet.CabinetName, out var transfer) ||
301
+ !mediaRowsByOriginalDiskId.TryGetValue(completedCabinetSpanned.DiskId, out var mediaRow))
302
+ {
303
+ throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(spannedCabinets.First().CabinetName, cabinet.CabinetName));
304
+ }
305
+
306
+ var lastDiskId = mediaRow.DiskId;
307
+ var mediaRowsThatWillNeedDiskIdUpdated = mediaRows.OrderBy(m => m.DiskId).Where(m => m.DiskId > mediaRow.DiskId).ToList();
308
+
309
+ foreach (var spannedCabinet in spannedCabinets)
310
+ {
311
+ var spannedCabinetSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), spannedCabinet.CabinetName);
312
+ var spannedCabinetTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), spannedCabinet.CabinetName);
313
+
314
+ var trackSource = this.BackendHelper.TrackFile(spannedCabinetSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers);
315
+ this.trackedFiles.Add(trackSource);
316
+
317
+ var trackTarget = this.BackendHelper.TrackFile(spannedCabinetTargetPath, TrackedFileType.BuiltOutput, transfer.SourceLineNumbers);
318
+ this.trackedFiles.Add(trackTarget);
319
+
320
+ var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers);
321
+ this.fileTransfers.Add(newTransfer);
322
+
323
+ // FDI Extract requires DiskID of Split Cabinets to be continuous. So a new Media row must inserted just
324
+ // after the previous spanned cabinet according to DiskID sort order, otherwise Windows Installer will
325
+ // encounter Error 2350 (FDI Server Error).
326
+ var newMediaRow = (MediaRow)mediaTable.CreateRow(mediaRow.SourceLineNumbers);
327
+ newMediaRow.Cabinet = spannedCabinet.CabinetName;
328
+ newMediaRow.DiskId = ++lastDiskId;
329
+ newMediaRow.LastSequence = mediaRow.LastSequence;
330
+
331
+ addedMediaRows.Add(newMediaRow);
332
+ }
333
+
334
+ // Increment the DiskId for all Media rows that come after the newly inserted row to ensure that the DiskId is unique
335
+ // and the Media rows stay in order based on last sequence.
336
+ foreach (var updateMediaRow in mediaRowsThatWillNeedDiskIdUpdated)
337
+ {
338
+ updateMediaRow.DiskId = ++lastDiskId;
339
+ }
340
+ }
341
+
342
+ mediaTable.ValidateRows();
343
+
344
+ var oldDiskIdToNewDiskId = mediaRowsByOriginalDiskId.Where(originalDiskIdWithMediaRow => originalDiskIdWithMediaRow.Value.DiskId != originalDiskIdWithMediaRow.Key)
345
+ .ToDictionary(originalDiskIdWithMediaRow => originalDiskIdWithMediaRow.Key, originalDiskIdWithMediaRow => originalDiskIdWithMediaRow.Value.DiskId);
346
+
347
+ // Update the File row and FileSymbols so the DiskIds are correct in the WixOutput, even if this
348
+ // data doesn't show up in the Windows Installer database.
349
+ foreach (var fileRow in fileRows)
350
+ {
351
+ if (oldDiskIdToNewDiskId.TryGetValue(fileRow.DiskId, out var newDiskId))
352
+ {
353
+ fileRow.DiskId = newDiskId;
354
+ }
355
+ }
356
+
357
+ foreach (var fileSymbol in this.Section.Symbols.OfType<FileSymbol>())
358
+ {
359
+ if (fileSymbol.DiskId.HasValue && oldDiskIdToNewDiskId.TryGetValue(fileSymbol.DiskId.Value, out var newDiskId))
360
+ {
361
+ fileSymbol.DiskId = newDiskId;
362
+ }
363
+ }
364
+
365
+ // Update the MediaSymbol DiskIds to the correct DiskId. Note that the MediaSymbol Id
366
+ // is not changed because symbol ids are not allowed to change after they are created.
367
+ foreach (var mediaSymbol in this.Section.Symbols.OfType<MediaSymbol>())
368
+ {
369
+ if (oldDiskIdToNewDiskId.TryGetValue(mediaSymbol.DiskId, out var newDiskId))
370
+ {
371
+ mediaSymbol.DiskId = newDiskId;
372
+ }
373
+ }
374
+
375
+ // Now that the existing MediaSymbol DiskIds are updated, add the newly created Media rows
376
+ // as symbols. Notice that the new MediaSymbols do not have an Id because they very likely
377
+ // would conflict with MediaSymbols that had their DiskIds updated but Ids could not be updated.
378
+ // The newly created MediaSymbols will rename anonymous.
379
+ foreach (var mediaRow in addedMediaRows)
380
+ {
381
+ this.Section.AddSymbol(new MediaSymbol(mediaRow.SourceLineNumbers)
382
+ {
383
+ Cabinet = mediaRow.Cabinet,
384
+ DiskId = mediaRow.DiskId,
385
+ DiskPrompt = mediaRow.DiskPrompt,
386
+ LastSequence = mediaRow.LastSequence,
387
+ Source = mediaRow.Source,
388
+ VolumeLabel = mediaRow.VolumeLabel
389
+ });
390
+ }
391
+ }
392
}
393
}
src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
+17
-9
@@ -17,30 +17,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind
17
/// </summary>
18
internal class ProcessUncompressedFilesCommand
19
{
20
- public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver)
20
+ public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver, IEnumerable<IFileFacade> fileFacades, string outputPath, bool compressed, bool longNamesInImage, Func<MediaSymbol, string, string, string> resolveMedia)
21
{
22
this.Section = section;
23
this.BackendHelper = backendHelper;
24
this.PathResolver = pathResolver;
25
+
26
+ this.DatabasePath = outputPath;
27
+ this.LayoutDirectory = Path.GetDirectoryName(outputPath);
28
+ this.Compressed = compressed;
29
+ this.LongNamesInImage = longNamesInImage;
30
+
31
+ this.FileFacades = fileFacades;
32
+ this.ResolveMedia = resolveMedia;
33
}
34
35
private IntermediateSection Section { get; }
36
29
- public IBackendHelper BackendHelper { get; }
37
+ private IBackendHelper BackendHelper { get; }
38
31
- public IPathResolver PathResolver { get; }
39
+ private IPathResolver PathResolver { get; }
40
33
- public string DatabasePath { private get; set; }
41
+ private string DatabasePath { get; }
42
35
- public IEnumerable<IFileFacade> FileFacades { private get; set; }
43
+ private string LayoutDirectory { get; }
44
37
- public string LayoutDirectory { private get; set; }
45
+ private bool Compressed { get; }
46
39
- public bool Compressed { private get; set; }
47
+ private bool LongNamesInImage { get; }
48
41
- public bool LongNamesInImage { private get; set; }
49
+ private IEnumerable<IFileFacade> FileFacades { get; }
50
43
- public Func<MediaSymbol, string, string, string> ResolveMedia { private get; set; }
51
+ private Func<MediaSymbol, string, string, string> ResolveMedia { get; }
52
53
public IEnumerable<IFileTransfer> FileTransfers { get; private set; }
54
src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs
+4
-1
@@ -167,6 +167,9 @@ namespace WixToolset.Core.ExtensibilityServices
167
/// <summary>
168
/// Allows direct access to the underlying FileRow as requried for patching.
169
/// </summary>
170
- public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException();
170
+ public FileRow GetFileRow()
171
+ {
172
+ return this.FileRow ?? throw new NotImplementedException();
173
+ }
174
}
175
}
src/wix/test/WixToolsetTest.Core.Native/CabinetFixture.cs
+38
-4
@@ -2,6 +2,7 @@
2
3
namespace WixToolsetTest.CoreNative
4
{
5
+ using System;
6
using System.IO;
7
using System.Linq;
8
using WixBuildTools.TestSupport;
@@ -19,12 +20,45 @@ namespace WixToolsetTest.CoreNative
20
var intermediateFolder = fs.GetFolder(true);
21
var cabPath = Path.Combine(intermediateFolder, "testout.cab");
22
22
- var files = new[] { new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test.txt") };
23
+ var files = new[] { new CabinetCompressFile(TestData.Get(@"TestData", "test.txt"), "test.txt") };
24
25
var cabinet = new Cabinet(cabPath);
25
- cabinet.Compress(files, CompressionLevel.Low);
26
+ var created = cabinet.Compress(files, CompressionLevel.Low);
27
28
Assert.True(File.Exists(cabPath));
29
+ Assert.Equal(new[]
30
+ {
31
+ "testout.cab, test.txt"
32
+ }, created.Select(c => String.Join(", ", c.CabinetName, c.FirstFileToken)).ToArray());
33
+ }
34
+ }
35
+
36
+ [Fact]
37
+ public void CanCreateSpannedFileCabinet()
38
+ {
39
+ using (var fs = new DisposableFileSystem())
40
+ {
41
+ var intermediateFolder = fs.GetFolder(true);
42
+
43
+ // Put more than non-zero bytes in a file sized just under 3MB since there is
44
+ // some overhead in cabinets that prevent perfect packing on the megabyte boundary.
45
+ var threeMBPath = Path.Combine(intermediateFolder, "_3mb.dat");
46
+ TestData.CreateFile(threeMBPath, (long)(2.9 * 1024 * 1024), fill: true);
47
+
48
+ var cabPath = Path.Combine(intermediateFolder, "test.cab");
49
+
50
+ var files = new[] { new CabinetCompressFile(threeMBPath, Path.GetFileNameWithoutExtension(threeMBPath)) };
51
+
52
+ var cabinet = new Cabinet(cabPath);
53
+ var created = cabinet.Compress(files, CompressionLevel.None, maxSize: 1);
54
+
55
+ Assert.True(File.Exists(cabPath));
56
+ Assert.Equal(new[]
57
+ {
58
+ "test.cab, _3mb",
59
+ "testa.cab, _3mb",
60
+ "testb.cab, _3mb"
61
+ }, created.Select(c => String.Join(", ", c.CabinetName, c.FirstFileToken)).ToArray());
62
}
63
}
64
@@ -58,8 +92,8 @@ namespace WixToolsetTest.CoreNative
92
// Compress.
93
{
94
var files = new[] {
61
- new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test1.txt"),
62
- new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test2.txt"),
95
+ new CabinetCompressFile(TestData.Get(@"TestData", "test.txt"), "test1.txt"),
96
+ new CabinetCompressFile(TestData.Get(@"TestData", "test.txt"), "test2.txt"),
97
};
98
99
var cabinet = new Cabinet(cabinetPath);
src/wix/test/WixToolsetTest.CoreIntegration/MsiCabinetFixture.cs
new
+256
@@ -0,0 +1,256 @@
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 WixToolsetTest.CoreIntegration
4
+{
5
+ using System;
6
+ using System.IO;
7
+ using System.Linq;
8
+ using WixBuildTools.TestSupport;
9
+ using WixToolset.Core.TestPackage;
10
+ using WixToolset.Data;
11
+ using WixToolset.Data.Symbols;
12
+ using WixToolset.Data.WindowsInstaller;
13
+ using WixToolset.Data.WindowsInstaller.Rows;
14
+ using Xunit;
15
+
16
+ public class MsiCabinetFixture
17
+ {
18
+ [Fact]
19
+ public void CanBuildSingleFileCompressed()
20
+ {
21
+ var folder = TestData.Get(@"TestData\SingleFileCompressed");
22
+
23
+ using (var fs = new DisposableFileSystem())
24
+ {
25
+ var intermediateFolder = fs.GetFolder();
26
+
27
+ var result = WixRunner.Execute(new[]
28
+ {
29
+ "build",
30
+ Path.Combine(folder, "Package.wxs"),
31
+ Path.Combine(folder, "PackageComponents.wxs"),
32
+ "-loc", Path.Combine(folder, "Package.en-us.wxl"),
33
+ "-bindpath", Path.Combine(folder, "data"),
34
+ "-intermediateFolder", intermediateFolder,
35
+ "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
36
+ });
37
+
38
+ result.AssertSuccess();
39
+
40
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
41
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab")));
42
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
43
+
44
+ var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
45
+ var section = intermediate.Sections.Single();
46
+
47
+ var fileSymbol = section.Symbols.OfType<FileSymbol>().Single();
48
+ WixAssert.StringEqual(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
49
+ WixAssert.StringEqual(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
50
+ }
51
+ }
52
+
53
+ [Fact]
54
+ public void CanBuildSingleFileCompressedWithMediaTemplate()
55
+ {
56
+ var folder = TestData.Get(@"TestData\SingleFileCompressed");
57
+
58
+ using (var fs = new DisposableFileSystem())
59
+ {
60
+ var intermediateFolder = fs.GetFolder();
61
+
62
+ var result = WixRunner.Execute(new[]
63
+ {
64
+ "build",
65
+ Path.Combine(folder, "Package.wxs"),
66
+ Path.Combine(folder, "PackageComponents.wxs"),
67
+ "-d", "MediaTemplateCompressionLevel",
68
+ "-loc", Path.Combine(folder, "Package.en-us.wxl"),
69
+ "-bindpath", Path.Combine(folder, "data"),
70
+ "-intermediateFolder", intermediateFolder,
71
+ "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
72
+ });
73
+
74
+ result.AssertSuccess();
75
+
76
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
77
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab")));
78
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
79
+ }
80
+ }
81
+
82
+ [Fact]
83
+ public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression()
84
+ {
85
+ var folder = TestData.Get(@"TestData\SingleFileCompressed");
86
+
87
+ using (var fs = new DisposableFileSystem())
88
+ {
89
+ var intermediateFolder = fs.GetFolder();
90
+
91
+ var result = WixRunner.Execute(new[]
92
+ {
93
+ "build",
94
+ Path.Combine(folder, "Package.wxs"),
95
+ Path.Combine(folder, "PackageComponents.wxs"),
96
+ "-d", "MediaTemplateCompressionLevel=low",
97
+ "-loc", Path.Combine(folder, "Package.en-us.wxl"),
98
+ "-bindpath", Path.Combine(folder, "data"),
99
+ "-intermediateFolder", intermediateFolder,
100
+ "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
101
+ });
102
+
103
+ result.AssertSuccess();
104
+
105
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
106
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab")));
107
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
108
+ }
109
+ }
110
+
111
+ [Fact]
112
+ public void CanBuildMultipleFilesCompressed()
113
+ {
114
+ var folder = TestData.Get(@"TestData\MultiFileCompressed");
115
+
116
+ using (var fs = new DisposableFileSystem())
117
+ {
118
+ var intermediateFolder = fs.GetFolder();
119
+
120
+ var result = WixRunner.Execute(new[]
121
+ {
122
+ "build",
123
+ "-sw1079", // TODO: why does this test need to create a second cab which is empty?
124
+ Path.Combine(folder, "Package.wxs"),
125
+ Path.Combine(folder, "PackageComponents.wxs"),
126
+ "-loc", Path.Combine(folder, "Package.en-us.wxl"),
127
+ "-bindpath", Path.Combine(folder, "data"),
128
+ "-intermediateFolder", intermediateFolder,
129
+ "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
130
+ });
131
+
132
+ result.AssertSuccess();
133
+
134
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, "bin", "test.msi")));
135
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, "bin", "example1.cab")));
136
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, "bin", "example2.cab")));
137
+ Assert.True(File.Exists(Path.Combine(intermediateFolder, "bin", "test.wixpdb")));
138
+ }
139
+ }
140
+
141
+ [Fact]
142
+ public void CanBuildMultipleFilesSpanningCabinets()
143
+ {
144
+ var folder = TestData.Get(@"TestData", "MsiCabinet");
145
+
146
+ using (var fs = new DisposableFileSystem())
147
+ {
148
+ var baseFolder = fs.GetFolder();
149
+ var dataFolder = Path.Combine(folder, "data");
150
+ var gendataFolder = Path.Combine(baseFolder, "generated-data");
151
+ var cabFolder = Path.Combine(baseFolder, "cab");
152
+ var intermediateFolder = Path.Combine(baseFolder, "obj");
153
+ var binFolder = Path.Combine(baseFolder, "bin");
154
+ var msiPath = Path.Combine(binFolder, "test.msi");
155
+ var wixpdbPath = Path.ChangeExtension(msiPath, "wixpdb");
156
+
157
+ TestData.CreateFile(Path.Combine(gendataFolder, "abc.gen"), (long)(25 * 1024 * 1024), fill: true);
158
+ TestData.CreateFile(Path.Combine(gendataFolder, "mno.gen"), (long)(45 * 1024 * 1024), fill: true);
159
+ TestData.CreateFile(Path.Combine(gendataFolder, "xyz.gen"), (long)(25 * 1024 * 1024), fill: true);
160
+
161
+ var result = WixRunner.Execute(new[]
162
+ {
163
+ "build",
164
+ Path.Combine(folder, "MultiFileSpanningCabinets.wxs"),
165
+ "-bindpath", dataFolder,
166
+ "-bindpath", gendataFolder,
167
+ "-intermediateFolder", intermediateFolder,
168
+ "-cc", cabFolder,
169
+ "-o", msiPath
170
+ });
171
+
172
+ result.AssertSuccess();
173
+
174
+ var files = Directory.GetFiles(binFolder, "*.*", SearchOption.AllDirectories).Select(s => s.Substring(binFolder.Length + 1)).OrderBy(s => s).ToArray();
175
+ WixAssert.CompareLineByLine(new[]
176
+ {
177
+ "cab1.cab",
178
+ "cab1a.cab",
179
+ "cab2.cab",
180
+ "cab3.cab",
181
+ "cab3a.cab",
182
+ "cab3b.cab",
183
+ "cab4.cab",
184
+ "cab5.cab",
185
+ "cab5a.cab",
186
+ "test.msi",
187
+ "test.wixpdb"
188
+ }, files);
189
+
190
+ var query = Query.QueryDatabase(msiPath, new[] { "Media", "File" });
191
+ WixAssert.CompareLineByLine(new[]
192
+ {
193
+ "File:fil2WOk5jeIBsvmL0db5z96JfeEZoU\tfil2WOk5jeIBsvmL0db5z96JfeEZoU\thij.txt\t18\t\t\t512\t3",
194
+ "File:filgErUV04C8ZBKWWWA0Zg5Fu_6NyM\tfilgErUV04C8ZBKWWWA0Zg5Fu_6NyM\tmno.gen\t47185920\t\t\t512\t4",
195
+ "File:filGqbzmUDXsQhijNpBL2rNX3dtCoo\tfilGqbzmUDXsQhijNpBL2rNX3dtCoo\tced.txt\t18\t\t\t512\t2",
196
+ "File:filj2uZN0Q5bCgR2YY6RRg1c9FqylQ\tfilj2uZN0Q5bCgR2YY6RRg1c9FqylQ\ttuv.txt\t18\t\t\t512\t6",
197
+ "File:filjEKfcIaBHXFyDRtZciPv4j105jQ\tfiljEKfcIaBHXFyDRtZciPv4j105jQ\tqrs.txt\t18\t\t\t512\t5",
198
+ "File:filKv93aSvdbL6M6UutiKdGim1UzcA\tfilKv93aSvdbL6M6UutiKdGim1UzcA\tabc.gen\t26214400\t\t\t512\t1",
199
+ "File:fillf1kS2G7fDKwbyrQIIw1OXPi.eY\tfillf1kS2G7fDKwbyrQIIw1OXPi.eY\txyz.gen\t26214400\t\t\t512\t7",
200
+ "Media:1\t1\t\tcab1.cab\t\t",
201
+ "Media:2\t1\t\tcab1a.cab\t\t",
202
+ "Media:3\t3\t\tcab2.cab\t\t",
203
+ "Media:4\t4\t\tcab3.cab\t\t",
204
+ "Media:5\t4\t\tcab3a.cab\t\t",
205
+ "Media:6\t4\t\tcab3b.cab\t\t",
206
+ "Media:7\t6\t\tcab4.cab\t\t",
207
+ "Media:8\t7\t\tcab5.cab\t\t",
208
+ "Media:9\t7\t\tcab5a.cab\t\t",
209
+ }, query);
210
+
211
+ var wixpdb = WixOutput.Read(wixpdbPath);
212
+
213
+ var data = WindowsInstallerData.Load(wixpdb);
214
+ var fileRows = data.Tables["File"].Rows.Cast<FileRow>().OrderBy(f => f.Sequence);
215
+ var mediaRows = data.Tables["Media"].Rows.Cast<MediaRow>().OrderBy(f => f.DiskId);
216
+
217
+ var intermediate = Intermediate.Load(wixpdb);
218
+ var section = intermediate.Sections.Single();
219
+ var fileSymbols = section.Symbols.OfType<FileSymbol>().OrderBy(f => f.Sequence);
220
+ var mediaSymbols = section.Symbols.OfType<MediaSymbol>().OrderBy(f => f.DiskId);
221
+
222
+ var expectedFiles = new[]
223
+ {
224
+ "filKv93aSvdbL6M6UutiKdGim1UzcA abc.gen 1 1",
225
+ "filGqbzmUDXsQhijNpBL2rNX3dtCoo ced.txt 2 3",
226
+ "fil2WOk5jeIBsvmL0db5z96JfeEZoU hij.txt 3 3",
227
+ "filgErUV04C8ZBKWWWA0Zg5Fu_6NyM mno.gen 4 4",
228
+ "filjEKfcIaBHXFyDRtZciPv4j105jQ qrs.txt 5 7",
229
+ "filj2uZN0Q5bCgR2YY6RRg1c9FqylQ tuv.txt 6 7",
230
+ "fillf1kS2G7fDKwbyrQIIw1OXPi.eY xyz.gen 7 8",
231
+ };
232
+
233
+ var expectedMedia = new[]
234
+ {
235
+ "1 cab1.cab 1",
236
+ "2 cab1a.cab 1",
237
+ "3 cab2.cab 3",
238
+ "4 cab3.cab 4",
239
+ "5 cab3a.cab 4",
240
+ "6 cab3b.cab 4",
241
+ "7 cab4.cab 6",
242
+ "8 cab5.cab 7",
243
+ "9 cab5a.cab 7",
244
+ };
245
+
246
+ WixAssert.CompareLineByLine(expectedFiles, fileRows.Select(r => String.Join(" ", r.File, r.FileName, r.Sequence, r.DiskId)).ToArray());
247
+
248
+ WixAssert.CompareLineByLine(expectedFiles, fileSymbols.Select(s => String.Join(" ", s.Id.Id, s.Name, s.Sequence, s.DiskId)).ToArray());
249
+
250
+ WixAssert.CompareLineByLine(expectedMedia, mediaRows.Select(r => String.Join(" ", r.DiskId, r.Cabinet, r.LastSequence)).ToArray());
251
+
252
+ WixAssert.CompareLineByLine(expectedMedia, mediaSymbols.Select(s => String.Join(" ", s.DiskId, s.Cabinet, s.LastSequence)).ToArray());
253
+ }
254
+ }
255
+ }
256
+}
src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs
+1
-124
@@ -58,130 +58,7 @@ namespace WixToolsetTest.CoreIntegration
58
}
59
60
[Fact]
61
- public void CanBuildSingleFileCompressed()
62
- {
63
- var folder = TestData.Get(@"TestData\SingleFileCompressed");
64
-
65
- using (var fs = new DisposableFileSystem())
66
- {
67
- var intermediateFolder = fs.GetFolder();
68
-
69
- var result = WixRunner.Execute(new[]
70
- {
71
- "build",
72
- Path.Combine(folder, "Package.wxs"),
73
- Path.Combine(folder, "PackageComponents.wxs"),
74
- "-loc", Path.Combine(folder, "Package.en-us.wxl"),
75
- "-bindpath", Path.Combine(folder, "data"),
76
- "-intermediateFolder", intermediateFolder,
77
- "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
78
- });
79
-
80
- result.AssertSuccess();
81
-
82
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
83
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab")));
84
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
85
-
86
- var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
87
- var section = intermediate.Sections.Single();
88
-
89
- var fileSymbol = section.Symbols.OfType<FileSymbol>().Single();
90
- WixAssert.StringEqual(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
91
- WixAssert.StringEqual(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
92
- }
93
- }
94
-
95
- [Fact]
96
- public void CanBuildSingleFileCompressedWithMediaTemplate()
97
- {
98
- var folder = TestData.Get(@"TestData\SingleFileCompressed");
99
-
100
- using (var fs = new DisposableFileSystem())
101
- {
102
- var intermediateFolder = fs.GetFolder();
103
-
104
- var result = WixRunner.Execute(new[]
105
- {
106
- "build",
107
- Path.Combine(folder, "Package.wxs"),
108
- Path.Combine(folder, "PackageComponents.wxs"),
109
- "-d", "MediaTemplateCompressionLevel",
110
- "-loc", Path.Combine(folder, "Package.en-us.wxl"),
111
- "-bindpath", Path.Combine(folder, "data"),
112
- "-intermediateFolder", intermediateFolder,
113
- "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
114
- });
115
-
116
- result.AssertSuccess();
117
-
118
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
119
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab")));
120
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
121
- }
122
- }
123
-
124
- [Fact]
125
- public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression()
126
- {
127
- var folder = TestData.Get(@"TestData\SingleFileCompressed");
128
-
129
- using (var fs = new DisposableFileSystem())
130
- {
131
- var intermediateFolder = fs.GetFolder();
132
-
133
- var result = WixRunner.Execute(new[]
134
- {
135
- "build",
136
- Path.Combine(folder, "Package.wxs"),
137
- Path.Combine(folder, "PackageComponents.wxs"),
138
- "-d", "MediaTemplateCompressionLevel=low",
139
- "-loc", Path.Combine(folder, "Package.en-us.wxl"),
140
- "-bindpath", Path.Combine(folder, "data"),
141
- "-intermediateFolder", intermediateFolder,
142
- "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
143
- });
144
-
145
- result.AssertSuccess();
146
-
147
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
148
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab")));
149
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
150
- }
151
- }
152
-
153
- [Fact]
154
- public void CanBuildMultipleFilesCompressed()
155
- {
156
- var folder = TestData.Get(@"TestData\MultiFileCompressed");
157
-
158
- using (var fs = new DisposableFileSystem())
159
- {
160
- var intermediateFolder = fs.GetFolder();
161
-
162
- var result = WixRunner.Execute(new[]
163
- {
164
- "build",
165
- "-sw1079", // TODO: why does this test need to create a second cab which is empty?
166
- Path.Combine(folder, "Package.wxs"),
167
- Path.Combine(folder, "PackageComponents.wxs"),
168
- "-loc", Path.Combine(folder, "Package.en-us.wxl"),
169
- "-bindpath", Path.Combine(folder, "data"),
170
- "-intermediateFolder", intermediateFolder,
171
- "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
172
- });
173
-
174
- result.AssertSuccess();
175
-
176
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
177
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example1.cab")));
178
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example2.cab")));
179
- Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
180
- }
181
- }
182
-
183
- [Fact]
184
- public void CanFailBuildMissingFile()
61
+ public void CannotBuildMissingFile()
62
{
63
var folder = TestData.Get(@"TestData\SingleFile");
64
src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/MultiFileSpanningCabinets.wxs
new
+38
@@ -0,0 +1,38 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="yes" InstallerVersion="200" Scope="perMachine">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade not allowed" />
4
+
5
+ <MediaTemplate CabinetTemplate="cab{0}.cab" CompressionLevel="none" MaximumUncompressedMediaSize="20" MaximumCabinetSizeForLargeFileSplitting="20" />
6
+
7
+ <Feature Id="ProductFeature">
8
+ <!-- The name of the files here are important to control order of the generated media and cabinets. -->
9
+ <Component Directory="INSTALLFOLDER">
10
+ <File Source="abc.gen" />
11
+ </Component>
12
+ <Component Directory="INSTALLFOLDER">
13
+ <File Source="ced.txt" />
14
+ </Component>
15
+ <Component Directory="INSTALLFOLDER">
16
+ <File Source="hij.txt" />
17
+ </Component>
18
+ <Component Directory="INSTALLFOLDER">
19
+ <File Source="mno.gen" />
20
+ </Component>
21
+ <Component Directory="INSTALLFOLDER">
22
+ <File Source="qrs.txt" />
23
+ </Component>
24
+ <Component Directory="INSTALLFOLDER">
25
+ <File Source="tuv.txt" />
26
+ </Component>
27
+ <Component Directory="INSTALLFOLDER">
28
+ <File Source="xyz.gen" />
29
+ </Component>
30
+ </Feature>
31
+ </Package>
32
+
33
+ <Fragment>
34
+ <StandardDirectory Id="ProgramFilesFolder">
35
+ <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
36
+ </StandardDirectory>
37
+ </Fragment>
38
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/ced.txt
new
+1
@@ -0,0 +1 @@
1
+This is ced.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/hij.txt
new
+1
@@ -0,0 +1 @@
1
+This is hij.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/qrs.txt
new
+1
@@ -0,0 +1 @@
1
+This is qrs.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiCabinet/data/tuv.txt
new
+1
@@ -0,0 +1 @@
1
+This is tuv.txt.
src/wix/wixnative/smartcab.cpp
+22
-8
@@ -2,8 +2,8 @@
2
3
#include "precomp.h"
4
5
-static HRESULT CompressFiles(__in HANDLE hCab);
6
-static void __stdcall CabNamesCallback(__in_z LPWSTR wzFirstCabName, __in_z LPWSTR wzNewCabName, __in_z LPWSTR wzFileToken);
5
+static HRESULT CompressFiles(__in HANDLE hCab, __inout_z LPWSTR* psczFirstFileToken);
6
+static void __stdcall CabNamesCallback(__in_z LPCWSTR wzFirstCabName, __in_z LPCWSTR wzNewCabName, __in_z LPCWSTR wzFileToken);
7
8
9
HRESULT SmartCabCommand(
@@ -20,6 +20,7 @@ HRESULT SmartCabCommand(
20
UINT uiMaxThresh = 0;
21
COMPRESSION_TYPE ct = COMPRESSION_TYPE_NONE;
22
HANDLE hCab = NULL;
23
+ LPWSTR sczFirstFileToken = NULL;
24
25
if (argc < 1)
26
{
@@ -71,16 +72,22 @@ HRESULT SmartCabCommand(
72
hr = WixNativeReadStdinPreamble();
73
ExitOnFailure(hr, "failed to read stdin preamble before smartcabbing");
74
74
- hr = CompressFiles(hCab);
75
+ hr = CompressFiles(hCab, &sczFirstFileToken);
76
ExitOnFailure(hr, "failed to compress files into cabinet: %ls", sczCabPath);
77
+
78
+ CabNamesCallback(wzCabName, wzCabName, sczFirstFileToken);
79
+ }
80
+ else
81
+ {
82
+ CabNamesCallback(wzCabName, wzCabName, L"");
83
}
84
85
hr = CabCFinish(hCab, CabNamesCallback);
86
hCab = NULL; // once finish is called, the handle is invalid.
87
ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to compress cabinet: %ls", sczCabPath);
88
82
-
89
LExit:
90
+ ReleaseStr(sczFirstFileToken);
91
if (hCab)
92
{
93
CabCCancel(hCab);
@@ -93,7 +100,8 @@ LExit:
100
101
102
static HRESULT CompressFiles(
96
- __in HANDLE hCab
103
+ __in HANDLE hCab,
104
+ __inout_z LPWSTR* psczFirstFileToken
105
)
106
{
107
HRESULT hr = S_OK;
@@ -138,6 +146,12 @@ static HRESULT CompressFiles(
146
pHashInfo = &hashInfo;
147
}
148
149
+ if (psczFirstFileToken && !*psczFirstFileToken)
150
+ {
151
+ hr = StrAllocString(psczFirstFileToken, wzToken, 0);
152
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to allocate first file token: %ls", wzToken);
153
+ }
154
+
155
hr = CabCAddFile(wzFilePath, wzToken, pHashInfo, hCab);
156
ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to add file: %ls", wzFilePath);
157
@@ -157,9 +171,9 @@ LExit:
171
// Second argument is name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"
172
// Third argument is the file token of the first file present in the splitting cabinet
173
static void __stdcall CabNamesCallback(
160
- __in_z LPWSTR wzFirstCabName,
161
- __in_z LPWSTR wzNewCabName,
162
- __in_z LPWSTR wzFileToken
174
+ __in_z LPCWSTR wzFirstCabName,
175
+ __in_z LPCWSTR wzNewCabName,
176
+ __in_z LPCWSTR wzFileToken
177
)
178
{
179
ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls\t%ls\t%ls", wzFirstCabName, wzNewCabName, wzFileToken);