@joebigelow / wix / commits / 16ed873f

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);