Reorganize media assignment to correctly place facade order optimization
Rob Mensching committed
Jun 15, 2020 at 16:07 UTC
678c92c50c6fb7aa9a093f0d74d4f92742abd5e8
8 files changed
+208
-126
src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
+74
-85
@@ -35,18 +35,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
35
36
private bool FilesCompressed { get; }
37
38
- public string CabinetNameTemplate { private get; set; }
38
+ private string CabinetNameTemplate { get; set; }
39
40
/// <summary>
41
/// Gets cabinets with their file rows.
42
/// </summary>
43
public Dictionary<MediaTuple, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; }
44
45
- /// <summary>
46
- /// Get media rows.
47
- /// </summary>
48
- public Dictionary<int, MediaTuple> MediaRows { get; private set; }
49
-
45
/// <summary>
46
/// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no.
47
/// This contains all the files when Package element is marked with compression=no
@@ -55,17 +50,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
50
51
public void Execute()
52
{
58
- var filesByCabinetMedia = new Dictionary<MediaTuple, List<FileFacade>>();
59
-
60
- var mediaRows = new Dictionary<int, MediaTuple>();
61
-
62
- var uncompressedFiles = new List<FileFacade>();
63
-
64
- var mediaTable = this.Section.Tuples.OfType<MediaTuple>().ToList();
65
- var mediaTemplateTable = this.Section.Tuples.OfType<WixMediaTemplateTuple>().ToList();
53
+ var mediaTuples = this.Section.Tuples.OfType<MediaTuple>().ToList();
54
+ var mediaTemplateTuples = this.Section.Tuples.OfType<WixMediaTemplateTuple>().ToList();
55
67
- // If both tables are authored, it is an error.
68
- if (mediaTemplateTable.Count > 0 && mediaTable.Count > 1)
56
+ // If both tuples are authored, it is an error.
57
+ if (mediaTemplateTuples.Count > 0 && mediaTuples.Count > 1)
58
{
59
throw new WixException(ErrorMessages.MediaTableCollision(null));
60
}
@@ -78,34 +67,44 @@ namespace WixToolset.Core.WindowsInstaller.Bind
67
Cabinet = "#MergeModule.CABinet",
68
});
69
81
- filesByCabinetMedia.Add(mergeModuleMediaTuple, new List<FileFacade>(this.FileFacades));
70
+ this.FileFacadesByCabinetMedia = new Dictionary<MediaTuple, IEnumerable<FileFacade>>
71
+ {
72
+ { mergeModuleMediaTuple, this.FileFacades }
73
+ };
74
+
75
+ this.UncompressedFileFacades = Array.Empty<FileFacade>();
76
}
83
- else if (mediaTemplateTable.Count == 0)
77
+ else if (mediaTemplateTuples.Count == 0)
78
{
85
- this.ManuallyAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
79
+ var filesByCabinetMedia = new Dictionary<MediaTuple, List<FileFacade>>();
80
+
81
+ var uncompressedFiles = new List<FileFacade>();
82
+
83
+ this.ManuallyAssignFiles(mediaTuples, filesByCabinetMedia, uncompressedFiles);
84
+
85
+ this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable<FileFacade>)kvp.Value);
86
+
87
+ this.UncompressedFileFacades = uncompressedFiles;
88
}
89
else
90
{
89
- this.AutoAssignFiles(mediaTable, filesByCabinetMedia, mediaRows, uncompressedFiles);
90
- }
91
+ var filesByCabinetMedia = new Dictionary<MediaTuple, List<FileFacade>>();
92
92
- this.FileFacadesByCabinetMedia = new Dictionary<MediaTuple, IEnumerable<FileFacade>>();
93
+ var uncompressedFiles = new List<FileFacade>();
94
94
- foreach (var mediaRowWithFiles in filesByCabinetMedia)
95
- {
96
- this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value);
97
- }
95
+ this.AutoAssignFiles(mediaTuples, filesByCabinetMedia, uncompressedFiles);
96
99
- this.MediaRows = mediaRows;
97
+ this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable<FileFacade>)kvp.Value);
98
101
- this.UncompressedFileFacades = uncompressedFiles;
99
+ this.UncompressedFileFacades = uncompressedFiles;
100
+ }
101
}
102
103
/// <summary>
104
/// Assign files to cabinets based on MediaTemplate authoring.
105
/// </summary>
106
/// <param name="fileFacades">FileRowCollection</param>
108
- private void AutoAssignFiles(List<MediaTuple> mediaTable, Dictionary<MediaTuple, List<FileFacade>> filesByCabinetMedia, Dictionary<int, MediaTuple> mediaRows, List<FileFacade> uncompressedFiles)
107
+ private void AutoAssignFiles(List<MediaTuple> mediaTable, Dictionary<MediaTuple, List<FileFacade>> filesByCabinetMedia, List<FileFacade> uncompressedFiles)
108
{
109
const int MaxCabIndex = 999;
110
@@ -158,6 +157,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
157
throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB));
158
}
159
160
+ var mediaTuplesByDiskId = new Dictionary<int, MediaTuple>();
161
+
162
foreach (var facade in this.FileFacades)
163
{
164
// When building a product, if the current file is not to be compressed or if
@@ -171,44 +172,38 @@ namespace WixToolset.Core.WindowsInstaller.Bind
172
if (currentCabIndex == MaxCabIndex)
173
{
174
// Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore.
174
- var cabinetFiles = filesByCabinetMedia[currentMediaRow];
175
- facade.DiskId = currentCabIndex;
176
- cabinetFiles.Add(facade);
177
- continue;
178
- }
179
-
180
- // Update current cab size.
181
- currentPreCabSize += (ulong)facade.FileSize;
182
-
183
- if (currentPreCabSize > maxPreCabSizeInBytes)
184
- {
185
- // Overflow due to current file
186
- currentMediaRow = this.AddMediaRow(mediaTemplateRow, ++currentCabIndex);
187
- mediaRows.Add(currentMediaRow.DiskId, currentMediaRow);
188
- filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
189
-
190
- var cabinetFileRows = filesByCabinetMedia[currentMediaRow];
191
- facade.DiskId = currentCabIndex;
192
- cabinetFileRows.Add(facade);
193
- // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
194
- currentPreCabSize = (ulong)facade.FileSize;
175
}
176
else
177
{
198
- // File fits in the current cab.
199
- if (currentMediaRow == null)
178
+ // Update current cab size.
179
+ currentPreCabSize += (ulong)facade.FileSize;
180
+
181
+ // Overflow due to current file
182
+ if (currentPreCabSize > maxPreCabSizeInBytes)
183
{
201
- // Create new cab and MediaRow
202
- currentMediaRow = this.AddMediaRow(mediaTemplateRow, ++currentCabIndex);
203
- mediaRows.Add(currentMediaRow.DiskId, currentMediaRow);
184
+ currentMediaRow = this.AddMediaTuple(mediaTemplateRow, ++currentCabIndex);
185
+ mediaTuplesByDiskId.Add(currentMediaRow.DiskId, currentMediaRow);
186
filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
205
- }
187
207
- // Associate current file with current cab.
208
- var cabinetFiles = filesByCabinetMedia[currentMediaRow];
209
- facade.DiskId = currentCabIndex;
210
- cabinetFiles.Add(facade);
188
+ // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
189
+ currentPreCabSize = (ulong)facade.FileSize;
190
+ }
191
+ else // file fits in the current cab.
192
+ {
193
+ if (currentMediaRow == null)
194
+ {
195
+ // Create new cab and MediaRow
196
+ currentMediaRow = this.AddMediaTuple(mediaTemplateRow, ++currentCabIndex);
197
+ mediaTuplesByDiskId.Add(currentMediaRow.DiskId, currentMediaRow);
198
+ filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
199
+ }
200
+ }
201
}
202
+
203
+ // Associate current file with current cab.
204
+ var cabinetFiles = filesByCabinetMedia[currentMediaRow];
205
+ facade.DiskId = currentCabIndex;
206
+ cabinetFiles.Add(facade);
207
}
208
209
// If there are uncompressed files and no MediaRow, create a default one.
@@ -219,51 +214,45 @@ namespace WixToolset.Core.WindowsInstaller.Bind
214
DiskId = 1,
215
});
216
222
- mediaRows.Add(1, defaultMediaRow);
217
+ mediaTuplesByDiskId.Add(1, defaultMediaRow);
218
}
219
}
220
221
/// <summary>
222
/// Assign files to cabinets based on Media authoring.
223
/// </summary>
229
- /// <param name="mediaTable"></param>
230
- /// <param name="fileFacades"></param>
231
- private void ManuallyAssignFiles(List<MediaTuple> mediaTable, IEnumerable<FileFacade> fileFacades, Dictionary<MediaTuple, List<FileFacade>> filesByCabinetMedia, Dictionary<int, MediaTuple> mediaRows, List<FileFacade> uncompressedFiles)
224
+ private void ManuallyAssignFiles(List<MediaTuple> mediaTuples, Dictionary<MediaTuple, List<FileFacade>> filesByCabinetMedia, List<FileFacade> uncompressedFiles)
225
{
233
- if (mediaTable.Any())
226
+ var mediaTuplesByDiskId = new Dictionary<int, MediaTuple>();
227
+
228
+ if (mediaTuples.Any())
229
{
235
- var cabinetMediaRows = new Dictionary<string, MediaTuple>(StringComparer.OrdinalIgnoreCase);
236
- foreach (var mediaRow in mediaTable)
230
+ var cabinetMediaTuples = new Dictionary<string, MediaTuple>(StringComparer.OrdinalIgnoreCase);
231
+ foreach (var mediaTuple in mediaTuples)
232
{
233
// If the Media row has a cabinet, make sure it is unique across all Media rows.
239
- if (!String.IsNullOrEmpty(mediaRow.Cabinet))
234
+ if (!String.IsNullOrEmpty(mediaTuple.Cabinet))
235
{
241
- if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out var existingRow))
236
+ if (cabinetMediaTuples.TryGetValue(mediaTuple.Cabinet, out var existingRow))
237
{
243
- this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet));
238
+ this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaTuple.SourceLineNumbers, mediaTuple.Cabinet));
239
this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet));
240
}
241
else
242
{
248
- cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow);
243
+ cabinetMediaTuples.Add(mediaTuple.Cabinet, mediaTuple);
244
}
250
- }
245
252
- mediaRows.Add(mediaRow.DiskId, mediaRow);
253
- }
254
- }
246
+ filesByCabinetMedia.Add(mediaTuple, new List<FileFacade>());
247
+ }
248
256
- foreach (var mediaRow in mediaRows.Values)
257
- {
258
- if (null != mediaRow.Cabinet)
259
- {
260
- filesByCabinetMedia.Add(mediaRow, new List<FileFacade>());
249
+ mediaTuplesByDiskId.Add(mediaTuple.DiskId, mediaTuple);
250
}
251
}
252
264
- foreach (var facade in fileFacades)
253
+ foreach (var facade in this.FileFacades)
254
{
266
- if (!mediaRows.TryGetValue(facade.DiskId, out var mediaRow))
255
+ if (!mediaTuplesByDiskId.TryGetValue(facade.DiskId, out var mediaTuple))
256
{
257
this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId));
258
continue;
@@ -279,7 +268,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
268
}
269
else // file is marked compressed.
270
{
282
- if (filesByCabinetMedia.TryGetValue(mediaRow, out var cabinetFiles))
271
+ if (filesByCabinetMedia.TryGetValue(mediaTuple, out var cabinetFiles))
272
{
273
cabinetFiles.Add(facade);
274
}
@@ -292,12 +281,12 @@ namespace WixToolset.Core.WindowsInstaller.Bind
281
}
282
283
/// <summary>
295
- /// Adds a row to the media table with cab name template filled in.
284
+ /// Adds a tuple to the section with cab name template filled in.
285
/// </summary>
286
/// <param name="mediaTable"></param>
287
/// <param name="cabIndex"></param>
288
/// <returns></returns>
300
- private MediaTuple AddMediaRow(WixMediaTemplateTuple mediaTemplateTuple, int cabIndex)
289
+ private MediaTuple AddMediaTuple(WixMediaTemplateTuple mediaTemplateTuple, int cabIndex)
290
{
291
return this.Section.AddTuple(new MediaTuple(mediaTemplateTuple.SourceLineNumbers, new Identifier(AccessModifier.Private, cabIndex))
292
{
src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+16
-16
@@ -281,19 +281,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
281
command.Execute();
282
}
283
284
- // Assign files to media.
285
- Dictionary<int, MediaTuple> assignedMediaRows;
286
- Dictionary<MediaTuple, IEnumerable<FileFacade>> filesByCabinetMedia;
287
- IEnumerable<FileFacade> uncompressedFiles;
288
- {
289
- var command = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed);
290
- command.Execute();
291
-
292
- assignedMediaRows = command.MediaRows;
293
- filesByCabinetMedia = command.FileFacadesByCabinetMedia;
294
- uncompressedFiles = command.UncompressedFileFacades;
295
- }
296
-
284
// stop processing if an error previously occurred
285
if (this.Messaging.EncounteredError)
286
{
@@ -366,10 +353,23 @@ namespace WixToolset.Core.WindowsInstaller.Bind
353
command.Execute();
354
}
355
369
- // Update file sequence.
356
+ // Assign files to media and update file sequences.
357
+ Dictionary<MediaTuple, IEnumerable<FileFacade>> filesByCabinetMedia;
358
+ IEnumerable<FileFacade> uncompressedFiles;
359
{
371
- var command = new UpdateMediaSequencesCommand(section, fileFacades);
372
- command.Execute();
360
+ var order = new OptimizeFileFacadesOrderCommand(fileFacades);
361
+ order.Execute();
362
+
363
+ fileFacades = order.FileFacades;
364
+
365
+ var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed);
366
+ assign.Execute();
367
+
368
+ filesByCabinetMedia = assign.FileFacadesByCabinetMedia;
369
+ uncompressedFiles = assign.UncompressedFileFacades;
370
+
371
+ var update = new UpdateMediaSequencesCommand(section, fileFacades);
372
+ update.Execute();
373
}
374
375
// stop processing if an error previously occurred
src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
+5
-9
@@ -7,7 +7,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
7
using System.IO;
8
using System.Linq;
9
using System.Threading;
10
- using WixToolset.Core.Bind;
10
using WixToolset.Core.Native;
11
using WixToolset.Data;
12
using WixToolset.Extensibility.Services;
@@ -18,12 +17,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
17
/// </summary>
18
internal sealed class CabinetBuilder
19
{
21
- private Queue cabinetWorkItems;
22
- private object lockObject;
20
+ private readonly object lockObject = new object();
21
+
22
+ private readonly Queue cabinetWorkItems;
23
private int threadCount;
24
25
// Address of Binder's callback function for Cabinet Splitting
26
- private IntPtr newCabNamesCallBackAddress;
26
+ private readonly IntPtr newCabNamesCallBackAddress;
27
28
/// <summary>
29
/// Instantiate a new CabinetBuilder.
@@ -38,7 +38,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
38
}
39
40
this.cabinetWorkItems = new Queue();
41
- this.lockObject = new object();
41
this.Messaging = messaging;
42
this.threadCount = threadCount;
43
@@ -56,10 +55,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
55
/// Enqueues a CabinetWorkItem to the queue.
56
/// </summary>
57
/// <param name="cabinetWorkItem">cabinet work item</param>
59
- public void Enqueue(CabinetWorkItem cabinetWorkItem)
60
- {
61
- this.cabinetWorkItems.Enqueue(cabinetWorkItem);
62
- }
58
+ public void Enqueue(CabinetWorkItem cabinetWorkItem) => this.cabinetWorkItems.Enqueue(cabinetWorkItem);
59
60
/// <summary>
61
/// Create the queued cabinets.
src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
-1
@@ -171,7 +171,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
171
return cabbingThreadCount;
172
}
173
174
-
174
/// <summary>
175
/// Creates a work item to create a cabinet.
176
/// </summary>
src/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs
new
+38
@@ -0,0 +1,38 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.Core.WindowsInstaller.Bind
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using WixToolset.Core.Bind;
8
+
9
+ internal class OptimizeFileFacadesOrderCommand
10
+ {
11
+ public OptimizeFileFacadesOrderCommand(List<FileFacade> fileFacades)
12
+ {
13
+ this.FileFacades = fileFacades;
14
+ }
15
+
16
+ public List<FileFacade> FileFacades { get; private set; }
17
+
18
+ public List<FileFacade> Execute()
19
+ {
20
+ this.FileFacades.Sort(FileFacadeOptimizer.Instance);
21
+
22
+ return this.FileFacades;
23
+ }
24
+
25
+ private class FileFacadeOptimizer : IComparer<FileFacade>
26
+ {
27
+ public static readonly FileFacadeOptimizer Instance = new FileFacadeOptimizer();
28
+
29
+ public int Compare(FileFacade x, FileFacade y)
30
+ {
31
+ // TODO: Sort these facades even smarter by directory path and component id
32
+ // and maybe file size or file extension and other creative ideas to
33
+ // get optimal install speed out of MSI.
34
+ return String.Compare(x.ComponentRef, y.ComponentRef, StringComparison.Ordinal);
35
+ }
36
+ }
37
+ }
38
+}
src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
+2
-13
@@ -29,9 +29,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
29
{
30
var lastSequence = 0;
31
32
- // Order by Component to group the files by directory.
33
- var optimized = this.OptimizedFileFacades();
34
- foreach (var facade in optimized)
32
+ foreach (var facade in this.FileFacades)
33
{
34
facade.Sequence = ++lastSequence;
35
}
@@ -43,8 +41,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
41
var patchGroups = new Dictionary<int, List<FileFacade>>();
42
43
// sequence the non-patch-added files
46
- var optimized = this.OptimizedFileFacades();
47
- foreach (var facade in optimized)
44
+ foreach (var facade in this.FileFacades)
45
{
46
if (null == mediaTuple)
47
{
@@ -108,13 +105,5 @@ namespace WixToolset.Core.WindowsInstaller.Bind
105
}
106
}
107
}
111
-
112
- private IEnumerable<FileFacade> OptimizedFileFacades()
113
- {
114
- // TODO: Sort these facades even smarter by directory path and component id
115
- // and maybe file size or file extension and other creative ideas to
116
- // get optimal install speed out of MSI.
117
- return this.FileFacades.OrderBy(f => f.ComponentRef);
118
- }
108
}
109
}
src/test/WixToolsetTest.CoreIntegration/CabFixture.cs
new
+71
@@ -0,0 +1,71 @@
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 Xunit;
11
+
12
+ public class CabFixture
13
+ {
14
+ [Fact]
15
+ public void CabinetFilesSequencedCorrectly()
16
+ {
17
+ var folder = TestData.Get(@"TestData\MultiFileCompressed");
18
+
19
+ using (var fs = new DisposableFileSystem())
20
+ {
21
+ var baseFolder = fs.GetFolder();
22
+ var intermediateFolder = Path.Combine(baseFolder, "obj");
23
+ var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
24
+ var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab");
25
+
26
+ var result = WixRunner.Execute(new[]
27
+ {
28
+ "build",
29
+ Path.Combine(folder, "Package.wxs"),
30
+ Path.Combine(folder, "PackageComponents.wxs"),
31
+ "-d", "MediaTemplateCompressionLevel",
32
+ "-loc", Path.Combine(folder, "Package.en-us.wxl"),
33
+ "-bindpath", Path.Combine(folder, "data"),
34
+ "-intermediateFolder", intermediateFolder,
35
+ "-o", msiPath
36
+ });
37
+
38
+ result.AssertSuccess();
39
+ Assert.True(File.Exists(cabPath));
40
+
41
+ var fileTable = Query.QueryDatabase(msiPath, new[] { "File" });
42
+ var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList();
43
+
44
+ Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray());
45
+ Assert.Equal(new[] { "test.txt", "Notepad.exe" }, fileRows.Select(f => f.Name).ToArray());
46
+
47
+ var files = Query.GetCabinetFiles(cabPath);
48
+ Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray());
49
+ }
50
+ }
51
+
52
+ private class FileRow
53
+ {
54
+ public FileRow(string row)
55
+ {
56
+ row = row.Substring("File:".Length);
57
+
58
+ var split = row.Split('\t');
59
+ this.Id = split[0];
60
+ this.Name = split[2];
61
+ this.Sequence = Convert.ToInt32(split[7]);
62
+ }
63
+
64
+ public string Id { get; set; }
65
+
66
+ public string Name { get; set; }
67
+
68
+ public int Sequence { get; set; }
69
+ }
70
+ }
71
+}
src/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs
+2
-2
@@ -3,10 +3,10 @@
3
<Fragment>
4
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5
<Component>
6
- <File Source="test.txt" />
6
+ <File Source="$(env.WINDIR)\Notepad.exe" />
7
</Component>
8
<Component>
9
- <File Source="$(env.WINDIR)\Notepad.exe" />
9
+ <File Source="test.txt" />
10
</Component>
11
</ComponentGroup>
12
</Fragment>