Add `Files` file harvesting.
Implements https://github.com/wixtoolset/issues/issues/7857. Like [naked files](https://github.com/wixtoolset/issues/issues/7696), `Files` elements can appear where `Component` elements do in WiX v4. The optimizer enumerates files and directories, generating single-file components as it goes. MSBuild-like wildcards (including `**`) are supported. `Excludes` child elements lets you exclude files that would otherwise be captured by wildcards.
Bob Arnson committed
Oct 13, 2023 at 18:16 UTC
6067839ba180f2f4bcd5eac67f839f68f513ccd2
40 files changed
+1146
-9
src/api/wix/WixToolset.Data/ErrorMessages.cs
+5
@@ -368,6 +368,11 @@ namespace WixToolset.Data
368
return Message(sourceLineNumbers, Ids.ExpectedAttribute, "The {0}/@{1} attribute was not found; it is required unless the attribute {2} has a value of '{3}'.", elementName, attributeName, otherAttributeName, otherAttributeValue, otherAttributeValueUnless);
369
}
370
371
+ public static Message ExpectedAttributeInElementOrParent(SourceLineNumber sourceLineNumbers, string elementName, string attributeName)
372
+ {
373
+ return Message(sourceLineNumbers, Ids.ExpectedAttributeInElementOrParent, "The {0}/@{1} attribute was not found or empty; it is required unless it is specified in the parent element.", elementName, attributeName);
374
+ }
375
+
376
public static Message ExpectedAttributeInElementOrParent(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string parentElementName)
377
{
378
return Message(sourceLineNumbers, Ids.ExpectedAttributeInElementOrParent, "The {0}/@{1} attribute was not found or empty; it is required, or it can be specified in the parent {2} element.", elementName, attributeName, parentElementName);
src/api/wix/WixToolset.Data/Symbols/HarvestFilesSymbol.cs
new
+84
@@ -0,0 +1,84 @@
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.Data
4
+{
5
+ using WixToolset.Data.Symbols;
6
+
7
+ public static partial class SymbolDefinitions
8
+ {
9
+ public static readonly IntermediateSymbolDefinition HarvestFiles = new IntermediateSymbolDefinition(
10
+ SymbolDefinitionType.HarvestFiles,
11
+ new[]
12
+ {
13
+ new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.DirectoryRef), IntermediateFieldType.String),
14
+ new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.Inclusions), IntermediateFieldType.String),
15
+ new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.Exclusions), IntermediateFieldType.String),
16
+ new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.ComplexReferenceParentType), IntermediateFieldType.String),
17
+ new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.ParentId), IntermediateFieldType.String),
18
+ new IntermediateFieldDefinition(nameof(HarvestFilesSymbolFields.SourcePath), IntermediateFieldType.String),
19
+ },
20
+ typeof(HarvestFilesSymbol));
21
+ }
22
+}
23
+
24
+namespace WixToolset.Data.Symbols
25
+{
26
+ public enum HarvestFilesSymbolFields
27
+ {
28
+ DirectoryRef,
29
+ Inclusions,
30
+ Exclusions,
31
+ ComplexReferenceParentType,
32
+ ParentId,
33
+ SourcePath,
34
+ }
35
+
36
+ public class HarvestFilesSymbol : IntermediateSymbol
37
+ {
38
+ public HarvestFilesSymbol() : base(SymbolDefinitions.HarvestFiles, null, null)
39
+ {
40
+ }
41
+
42
+ public HarvestFilesSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(SymbolDefinitions.HarvestFiles, sourceLineNumber, id)
43
+ {
44
+ }
45
+
46
+ public IntermediateField this[HarvestFilesSymbolFields index] => this.Fields[(int)index];
47
+
48
+ public string DirectoryRef
49
+ {
50
+ get => (string)this.Fields[(int)HarvestFilesSymbolFields.DirectoryRef];
51
+ set => this.Set((int)HarvestFilesSymbolFields.DirectoryRef, value);
52
+ }
53
+
54
+ public string Inclusions
55
+ {
56
+ get => (string)this.Fields[(int)HarvestFilesSymbolFields.Inclusions];
57
+ set => this.Set((int)HarvestFilesSymbolFields.Inclusions, value);
58
+ }
59
+
60
+ public string Exclusions
61
+ {
62
+ get => (string)this.Fields[(int)HarvestFilesSymbolFields.Exclusions];
63
+ set => this.Set((int)HarvestFilesSymbolFields.Exclusions, value);
64
+ }
65
+
66
+ public string ComplexReferenceParentType
67
+ {
68
+ get => (string)this.Fields[(int)HarvestFilesSymbolFields.ComplexReferenceParentType];
69
+ set => this.Set((int)HarvestFilesSymbolFields.ComplexReferenceParentType, value);
70
+ }
71
+
72
+ public string ParentId
73
+ {
74
+ get => (string)this.Fields[(int)HarvestFilesSymbolFields.ParentId];
75
+ set => this.Set((int)HarvestFilesSymbolFields.ParentId, value);
76
+ }
77
+
78
+ public string SourcePath
79
+ {
80
+ get => (string)this.Fields[(int)HarvestFilesSymbolFields.SourcePath];
81
+ set => this.Set((int)HarvestFilesSymbolFields.SourcePath, value);
82
+ }
83
+ }
84
+}
src/api/wix/WixToolset.Data/Symbols/SymbolDefinitions.cs
+1
@@ -40,6 +40,7 @@ namespace WixToolset.Data
40
FeatureComponents,
41
File,
42
FileSFPCatalog,
43
+ HarvestFiles,
44
Icon,
45
ImageFamilies,
46
IniFile,
src/api/wix/WixToolset.Extensibility/Services/IParseHelper.cs
+1
-1
@@ -11,7 +11,7 @@ namespace WixToolset.Extensibility.Services
11
using WixToolset.Extensibility.Data;
12
13
/// <summary>
14
- /// Interface provided to help compiler extensions parse.
14
+ /// Interface provided to help compiler and optimizer extensions parse.
15
/// </summary>
16
public interface IParseHelper
17
{
src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs
+1
@@ -234,6 +234,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
234
break;
235
236
// Symbols used internally and are not added to the output.
237
+ case SymbolDefinitionType.HarvestFiles:
238
case SymbolDefinitionType.WixBuildInfo:
239
case SymbolDefinitionType.WixBindUpdatedFiles:
240
case SymbolDefinitionType.WixComponentGroup:
src/wix/WixToolset.Core/CommandLine/BuildCommand.cs
+2
-6
@@ -125,12 +125,8 @@ namespace WixToolset.Core.CommandLine
125
{
126
using (new IntermediateFieldContext("wix.link"))
127
{
128
- var wixipl = inputsOutputs.Wixipls.SingleOrDefault();
129
-
130
- if (wixipl == null)
131
- {
132
- wixipl = this.LinkPhase(wixobjs, inputsOutputs, creator, cancellationToken);
133
- }
128
+ var wixipl = inputsOutputs.Wixipls.SingleOrDefault()
129
+ ?? this.LinkPhase(wixobjs, inputsOutputs, creator, cancellationToken);
130
131
if (!this.Messaging.EncounteredError)
132
{
src/wix/WixToolset.Core/Compiler.cs
+148
-1
@@ -2655,6 +2655,9 @@ namespace WixToolset.Core
2655
case "File":
2656
this.ParseNakedFileElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, directoryId, source);
2657
break;
2658
+ case "Files":
2659
+ this.ParseFilesElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, directoryId, source);
2660
+ break;
2661
default:
2662
this.Core.UnexpectedElement(node, child);
2663
break;
@@ -3106,7 +3109,7 @@ namespace WixToolset.Core
3109
this.Core.AddSymbol(new MoveFileSymbol(sourceLineNumbers, id)
3110
{
3111
ComponentRef = componentId,
3109
- SourceName = sourceName,
3112
+ SourceName = sourceName,
3113
DestinationName = destinationName,
3114
DestinationShortName = destinationShortName,
3115
SourceFolder = sourceDirectory ?? sourceProperty,
@@ -3881,6 +3884,9 @@ namespace WixToolset.Core
3884
case "File":
3885
this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id.Id, fileSource);
3886
break;
3887
+ case "Files":
3888
+ this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, id.Id, fileSource);
3889
+ break;
3890
case "Merge":
3891
this.ParseMergeElement(child, id.Id, diskId);
3892
break;
@@ -3996,6 +4002,9 @@ namespace WixToolset.Core
4002
case "File":
4003
this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id, fileSource);
4004
break;
4005
+ case "Files":
4006
+ this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, id, fileSource);
4007
+ break;
4008
case "Merge":
4009
this.ParseMergeElement(child, id, diskId);
4010
break;
@@ -4436,6 +4445,9 @@ namespace WixToolset.Core
4445
case "File":
4446
this.ParseNakedFileElement(child, ComplexReferenceParentType.Feature, id.Id, null, null);
4447
break;
4448
+ case "Files":
4449
+ this.ParseFilesElement(child, ComplexReferenceParentType.Feature, id.Id, null, null);
4450
+ break;
4451
case "Level":
4452
this.ParseLevelElement(child, id.Id);
4453
break;
@@ -4579,6 +4591,9 @@ namespace WixToolset.Core
4591
case "File":
4592
this.ParseNakedFileElement(child, ComplexReferenceParentType.Feature, id, null, null);
4593
break;
4594
+ case "Files":
4595
+ this.ParseFilesElement(child, ComplexReferenceParentType.Feature, id, null, null);
4596
+ break;
4597
case "MergeRef":
4598
this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id);
4599
break;
@@ -4667,6 +4682,9 @@ namespace WixToolset.Core
4682
case "File":
4683
this.ParseNakedFileElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, null);
4684
break;
4685
+ case "Files":
4686
+ this.ParseFilesElement(child, ComplexReferenceParentType.Feature, id.Id, null, null);
4687
+ break;
4688
case "MergeRef":
4689
this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
4690
break;
@@ -5677,6 +5695,129 @@ namespace WixToolset.Core
5695
}
5696
}
5697
5698
+ /// <summary>
5699
+ /// Parses a `Files` element.
5700
+ /// </summary>
5701
+ /// <param name="node">Files element to parse.</param>
5702
+ /// <param name="parentType">Type of complex reference parent. Will be Unknown if there is no parent.</param>
5703
+ /// <param name="parentId">Optional identifier for primary parent.</param>
5704
+ /// <param name="directoryId">Ancestor's directory id.</param>
5705
+ /// <param name="sourcePath">Default source path of parent directory.</param>
5706
+ private void ParseFilesElement(XElement node, ComplexReferenceParentType parentType, string parentId, string directoryId, string sourcePath)
5707
+ {
5708
+ var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5709
+ var win64 = this.Context.IsCurrentPlatform64Bit;
5710
+ string subdirectory = null;
5711
+ var inclusions = new List<string>();
5712
+ var exclusions = new List<string>();
5713
+
5714
+ foreach (var attrib in node.Attributes())
5715
+ {
5716
+ if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5717
+ {
5718
+ switch (attrib.Name.LocalName)
5719
+ {
5720
+ case "Directory":
5721
+ directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
5722
+ this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
5723
+ break;
5724
+ case "Subdirectory":
5725
+ subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
5726
+ break;
5727
+ case "Include":
5728
+ inclusions.AddRange(this.Core.GetAttributeValue(sourceLineNumbers, attrib).Split(';'));
5729
+ break;
5730
+ default:
5731
+ this.Core.UnexpectedAttribute(node, attrib);
5732
+ break;
5733
+ }
5734
+ }
5735
+ else
5736
+ {
5737
+ var context = new Dictionary<string, string>() { { "Win64", win64.ToString() } };
5738
+ this.Core.ParseExtensionAttribute(node, attrib, context);
5739
+ }
5740
+ }
5741
+
5742
+ foreach (var child in node.Elements())
5743
+ {
5744
+ if (CompilerCore.WixNamespace == child.Name.Namespace)
5745
+ {
5746
+ switch (child.Name.LocalName)
5747
+ {
5748
+ case "Exclude":
5749
+ this.ParseFilesExcludeElement(child, exclusions);
5750
+ break;
5751
+ default:
5752
+ this.Core.UnexpectedElement(node, child);
5753
+ break;
5754
+ }
5755
+ }
5756
+ else
5757
+ {
5758
+ var context = new Dictionary<string, string>() { { "Win64", win64.ToString() } };
5759
+ this.Core.ParseExtensionElement(node, child, context);
5760
+ }
5761
+ }
5762
+
5763
+ if (String.IsNullOrEmpty(directoryId))
5764
+ {
5765
+ directoryId = "INSTALLFOLDER";
5766
+ this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
5767
+ }
5768
+ else if (!String.IsNullOrEmpty(subdirectory))
5769
+ {
5770
+ directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory");
5771
+ }
5772
+
5773
+ if (!inclusions.Any())
5774
+ {
5775
+ this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Include"));
5776
+ }
5777
+
5778
+ var inclusionsAsString = String.Join(";", inclusions);
5779
+ var exclusionsAsString = String.Join(";", exclusions);
5780
+
5781
+ var id = this.Core.CreateIdentifier("hvf", directoryId, inclusionsAsString, exclusionsAsString);
5782
+
5783
+ this.Core.AddSymbol(new HarvestFilesSymbol(sourceLineNumbers, id)
5784
+ {
5785
+ DirectoryRef = directoryId,
5786
+ Inclusions = inclusionsAsString,
5787
+ Exclusions = exclusionsAsString,
5788
+ ComplexReferenceParentType = parentType.ToString(),
5789
+ ParentId = parentId,
5790
+ SourcePath = sourcePath,
5791
+ });
5792
+ }
5793
+
5794
+ private void ParseFilesExcludeElement(XElement node, IList<string> paths)
5795
+ {
5796
+ var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5797
+
5798
+ foreach (var attrib in node.Attributes())
5799
+ {
5800
+ if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5801
+ {
5802
+ switch (attrib.Name.LocalName)
5803
+ {
5804
+ case "Files":
5805
+ paths.Add(this.Core.GetAttributeValue(sourceLineNumbers, attrib));
5806
+ break;
5807
+ default:
5808
+ this.Core.UnexpectedAttribute(node, attrib);
5809
+ break;
5810
+ }
5811
+ }
5812
+ else
5813
+ {
5814
+ this.Core.ParseExtensionAttribute(node, attrib);
5815
+ }
5816
+ }
5817
+
5818
+ this.Core.ParseForExtensionElements(node);
5819
+ }
5820
+
5821
/// <summary>
5822
/// Parses a file search element.
5823
/// </summary>
@@ -5997,6 +6138,9 @@ namespace WixToolset.Core
6138
case "File":
6139
this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, null, null);
6140
break;
6141
+ case "Files":
6142
+ this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, null, null);
6143
+ break;
6144
case "Icon":
6145
this.ParseIconElement(child);
6146
break;
@@ -7347,6 +7491,9 @@ namespace WixToolset.Core
7491
case "File":
7492
this.ParseNakedFileElement(child, ComplexReferenceParentType.Unknown, null, id, null);
7493
break;
7494
+ case "Files":
7495
+ this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, id, null);
7496
+ break;
7497
case "Merge":
7498
this.ParseMergeElement(child, id, diskId: CompilerConstants.IntegerNotSet);
7499
break;
src/wix/WixToolset.Core/Compiler_Module.cs
+3
@@ -178,6 +178,9 @@ namespace WixToolset.Core
178
case "File":
179
this.ParseNakedFileElement(child, ComplexReferenceParentType.Module, this.activeName, null, null);
180
break;
181
+ case "Files":
182
+ this.ParseFilesElement(child, ComplexReferenceParentType.Module, this.activeName, null, null);
183
+ break;
184
case "Icon":
185
this.ParseIconElement(child);
186
break;
src/wix/WixToolset.Core/Compiler_Package.cs
+3
@@ -306,6 +306,9 @@ namespace WixToolset.Core
306
case "File":
307
this.ParseNakedFileElement(child, ComplexReferenceParentType.Product, productCode, null, null);
308
break;
309
+ case "Files":
310
+ this.ParseFilesElement(child, ComplexReferenceParentType.Unknown, null, null, null);
311
+ break;
312
case "Icon":
313
this.ParseIconElement(child);
314
break;
src/wix/WixToolset.Core/HarvestFilesCommand.cs
new
+248
@@ -0,0 +1,248 @@
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
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.IO;
8
+ using System.Linq;
9
+ using WixToolset.Data;
10
+ using WixToolset.Data.Symbols;
11
+ using WixToolset.Extensibility.Data;
12
+ using WixToolset.Extensibility.Services;
13
+
14
+ internal class HarvestFilesCommand
15
+ {
16
+ private const string BindPathOpenString = "!(bindpath.";
17
+
18
+ public HarvestFilesCommand(IOptimizeContext context)
19
+ {
20
+ this.Context = context;
21
+ this.Messaging = this.Context.ServiceProvider.GetService<IMessaging>();
22
+ this.ParseHelper = this.Context.ServiceProvider.GetService<IParseHelper>();
23
+ }
24
+
25
+ public IOptimizeContext Context { get; }
26
+
27
+ public IMessaging Messaging { get; }
28
+
29
+ public IParseHelper ParseHelper { get; }
30
+
31
+ internal void Execute()
32
+ {
33
+ var harvestedFiles = new HashSet<string>();
34
+
35
+ foreach (var section in this.Context.Intermediates.SelectMany(i => i.Sections))
36
+ {
37
+ foreach (var harvestFiles in section.Symbols.OfType<HarvestFilesSymbol>().ToList())
38
+ {
39
+ this.HarvestFiles(harvestFiles, section, harvestedFiles);
40
+ }
41
+ }
42
+ }
43
+
44
+ private void HarvestFiles(HarvestFilesSymbol harvestFile, IntermediateSection section, ISet<string> harvestedFiles)
45
+ {
46
+ var unusedSectionCachedInlinedDirectoryIds = new Dictionary<string, string>();
47
+
48
+ var directoryId = harvestFile.DirectoryRef;
49
+
50
+ var inclusions = harvestFile.Inclusions.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
51
+ var exclusions = harvestFile.Exclusions.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
52
+
53
+ var comparer = new WildcardFileComparer();
54
+
55
+ var resolvedFiles = Enumerable.Empty<WildcardFile>();
56
+
57
+ try
58
+ {
59
+ var included = this.GetWildcardFiles(harvestFile, inclusions);
60
+ var excluded = this.GetWildcardFiles(harvestFile, exclusions);
61
+
62
+ resolvedFiles = included.Except(excluded, comparer).ToList();
63
+
64
+ if (!resolvedFiles.Any())
65
+ {
66
+ this.Messaging.Write(OptimizerWarnings.ZeroFilesHarvested(harvestFile.SourceLineNumbers));
67
+ }
68
+ }
69
+ catch (DirectoryNotFoundException e)
70
+ {
71
+ this.Messaging.Write(OptimizerWarnings.ExpectedDirectory(harvestFile.SourceLineNumbers, e.Message));
72
+
73
+ return;
74
+ }
75
+
76
+ foreach (var fileByRecursiveDir in resolvedFiles.GroupBy(resolvedFile => resolvedFile.RecursiveDir, resolvedFile => resolvedFile.Path))
77
+ {
78
+ var recursiveDir = fileByRecursiveDir.Key;
79
+
80
+ if (!String.IsNullOrEmpty(recursiveDir))
81
+ {
82
+ directoryId = this.ParseHelper.CreateDirectoryReferenceFromInlineSyntax(section, harvestFile.SourceLineNumbers, attribute: null, directoryId, recursiveDir, unusedSectionCachedInlinedDirectoryIds);
83
+ }
84
+
85
+ foreach (var file in fileByRecursiveDir)
86
+ {
87
+ if (harvestedFiles.Add(file))
88
+ {
89
+ var name = Path.GetFileName(file);
90
+
91
+ var id = this.ParseHelper.CreateIdentifier("fls", directoryId, name);
92
+
93
+ section.AddSymbol(new FileSymbol(harvestFile.SourceLineNumbers, id)
94
+ {
95
+ ComponentRef = id.Id,
96
+ Name = name,
97
+ Attributes = FileSymbolAttributes.None | FileSymbolAttributes.Vital,
98
+ DirectoryRef = directoryId,
99
+ Source = new IntermediateFieldPathValue { Path = file },
100
+ });
101
+
102
+ section.AddSymbol(new ComponentSymbol(harvestFile.SourceLineNumbers, id)
103
+ {
104
+ ComponentId = "*",
105
+ DirectoryRef = directoryId,
106
+ Location = ComponentLocation.LocalOnly,
107
+ KeyPath = id.Id,
108
+ KeyPathType = ComponentKeyPathType.File,
109
+ Win64 = this.Context.Platform == Platform.ARM64 || this.Context.Platform == Platform.X64,
110
+ });
111
+
112
+ if (Enum.TryParse<ComplexReferenceParentType>(harvestFile.ComplexReferenceParentType, out var parentType)
113
+ && ComplexReferenceParentType.Unknown != parentType && null != harvestFile.ParentId)
114
+ {
115
+ // If the parent was provided, add a complex reference to that, and, if
116
+ // the Files is under a feature, then mark the complex reference primary.
117
+ this.ParseHelper.CreateComplexReference(section, harvestFile.SourceLineNumbers, parentType, harvestFile.ParentId, null, ComplexReferenceChildType.Component, id.Id, ComplexReferenceParentType.Feature == parentType);
118
+ }
119
+ }
120
+ else
121
+ {
122
+ this.Messaging.Write(OptimizerWarnings.SkippingDuplicateFile(harvestFile.SourceLineNumbers, file));
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ private IEnumerable<WildcardFile> GetWildcardFiles(HarvestFilesSymbol harvestFile, IEnumerable<string> patterns)
129
+ {
130
+ var sourceLineNumbers = harvestFile.SourceLineNumbers;
131
+ var sourcePath = harvestFile.SourcePath;
132
+
133
+ var files = new List<WildcardFile>();
134
+
135
+ foreach (var pattern in patterns)
136
+ {
137
+ // Resolve bind paths, if any, which might result in multiple directories.
138
+ foreach (var path in this.ResolveBindPaths(sourceLineNumbers, pattern))
139
+ {
140
+ var sourceDirectory = String.IsNullOrEmpty(sourcePath) ? Path.GetDirectoryName(sourceLineNumbers.FileName) : sourcePath;
141
+ var recursive = path.IndexOf("**") >= 0;
142
+ var filePortion = Path.GetFileName(path);
143
+ var directoryPortion = Path.GetDirectoryName(path);
144
+
145
+ if (directoryPortion?.EndsWith(@"\**") == true)
146
+ {
147
+ directoryPortion = directoryPortion.Substring(0, directoryPortion.Length - 3);
148
+ }
149
+
150
+ var recursiveDirOffset = directoryPortion.Length + 1;
151
+
152
+ if (directoryPortion is null || directoryPortion.Length == 0 || directoryPortion == "**")
153
+ {
154
+ directoryPortion = sourceDirectory;
155
+ recursiveDirOffset = sourceDirectory.Length + 1;
156
+
157
+ }
158
+ else if (!Path.IsPathRooted(directoryPortion))
159
+ {
160
+ directoryPortion = Path.Combine(sourceDirectory, directoryPortion);
161
+ recursiveDirOffset = sourceDirectory.Length + 1;
162
+ }
163
+
164
+ var foundFiles = Directory.EnumerateFiles(directoryPortion, filePortion, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
165
+
166
+ foreach (var foundFile in foundFiles)
167
+ {
168
+ var recursiveDir = Path.GetDirectoryName(foundFile.Substring(recursiveDirOffset));
169
+ files.Add(new WildcardFile()
170
+ {
171
+ RecursiveDir = recursiveDir,
172
+ Path = foundFile,
173
+ });
174
+ }
175
+ }
176
+ }
177
+
178
+ return files;
179
+ }
180
+
181
+ private IEnumerable<string> ResolveBindPaths(SourceLineNumber sourceLineNumbers, string source)
182
+ {
183
+ var resultingDirectories = new List<string>();
184
+
185
+ var bindName = String.Empty;
186
+ var path = source;
187
+
188
+ if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal))
189
+ {
190
+ var closeParen = source.IndexOf(')', BindPathOpenString.Length);
191
+
192
+ if (-1 != closeParen)
193
+ {
194
+ bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length);
195
+ path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace.
196
+ path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted.
197
+ }
198
+ }
199
+
200
+ if (String.IsNullOrEmpty(bindName))
201
+ {
202
+ resultingDirectories.Add(path);
203
+ }
204
+ else
205
+ {
206
+ var foundBindPath = false;
207
+
208
+ foreach (var bindPath in this.Context.BindPaths)
209
+ {
210
+ if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase))
211
+ {
212
+ var resolved = Path.Combine(bindPath.Path, path);
213
+ resultingDirectories.Add(resolved);
214
+
215
+ foundBindPath = true;
216
+ }
217
+ }
218
+
219
+ if (!foundBindPath)
220
+ {
221
+ this.Messaging.Write(OptimizerWarnings.ExpectedDirectory(sourceLineNumbers, source));
222
+ }
223
+ }
224
+
225
+ return resultingDirectories;
226
+ }
227
+
228
+ private class WildcardFile
229
+ {
230
+ public string RecursiveDir { get; set; }
231
+
232
+ public string Path { get; set; }
233
+ }
234
+
235
+ private class WildcardFileComparer : IEqualityComparer<WildcardFile>
236
+ {
237
+ public bool Equals(WildcardFile x, WildcardFile y)
238
+ {
239
+ return x?.Path == y?.Path;
240
+ }
241
+
242
+ public int GetHashCode(WildcardFile obj)
243
+ {
244
+ return obj?.Path?.GetHashCode() ?? 0;
245
+ }
246
+ }
247
+ }
248
+}
src/wix/WixToolset.Core/Optimizer.cs
+4
-1
@@ -25,7 +25,10 @@ namespace WixToolset.Core
25
extension.PreOptimize(context);
26
}
27
28
- // TODO: Fill with useful optimization features.
28
+ {
29
+ var command = new HarvestFilesCommand(context);
30
+ command.Execute();
31
+ }
32
33
foreach (var extension in context.Extensions)
34
{
src/wix/WixToolset.Core/OptimizerWarnings.cs
new
+36
@@ -0,0 +1,36 @@
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
4
+{
5
+ using WixToolset.Data;
6
+
7
+ internal static class OptimizerWarnings
8
+ {
9
+ public static Message ZeroFilesHarvested(SourceLineNumber sourceLineNumbers)
10
+ {
11
+ return Message(sourceLineNumbers, Ids.ZeroFilesHarvested, "Files inclusions and exclusions resulted in zero files harvested. Unless that is expected, you should verify your Files paths, inclusions, and exclusions for accuracy.");
12
+ }
13
+
14
+ public static Message ExpectedDirectory(SourceLineNumber sourceLineNumbers, string harvestDirectory)
15
+ {
16
+ return Message(sourceLineNumbers, Ids.ExpectedDirectory, "Missing directory for harvesting files: {0}", harvestDirectory);
17
+ }
18
+
19
+ public static Message SkippingDuplicateFile(SourceLineNumber sourceLineNumbers, string duplicateFile)
20
+ {
21
+ return Message(sourceLineNumbers, Ids.SkippingDuplicateFile, "Skipping file that has already been harvested: {0}", duplicateFile);
22
+ }
23
+
24
+ private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
25
+ {
26
+ return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args);
27
+ }
28
+
29
+ public enum Ids
30
+ {
31
+ ZeroFilesHarvested = 8600,
32
+ ExpectedDirectory = 8601,
33
+ SkippingDuplicateFile = 8602,
34
+ }
35
+ }
36
+}
src/wix/test/WixToolsetTest.CoreIntegration/HarvestFilesFixture.cs
new
+320
@@ -0,0 +1,320 @@
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.Data;
6
+ using System.IO;
7
+ using System.Linq;
8
+ using WixInternal.Core.TestPackage;
9
+ using WixInternal.TestSupport;
10
+ using Xunit;
11
+
12
+ public class HarvestFilesFixture
13
+ {
14
+ [Fact]
15
+ public void MustIncludeSomeFiles()
16
+ {
17
+ var messages = BuildAndQueryComponentAndFileTables("BadAuthoring.wxs", isPackage: true, 10);
18
+ Assert.Equal(new[]
19
+ {
20
+ "10",
21
+ }, messages);
22
+ }
23
+
24
+ [Fact]
25
+ public void ZeroFilesHarvestedIsAWarning()
26
+ {
27
+ var messages = BuildAndQueryComponentAndFileTables("ZeroFiles.wxs", isPackage: true, 8600);
28
+ Assert.Equal(new[]
29
+ {
30
+ "8600",
31
+ }, messages);
32
+ }
33
+
34
+ [Fact]
35
+ public void MissingHarvestDirectoryIsAWarning()
36
+ {
37
+ var messages = BuildAndQueryComponentAndFileTables("BadDirectory.wxs", isPackage: true, 8601);
38
+ Assert.Equal(new[]
39
+ {
40
+ "8601",
41
+ "8601",
42
+ }, messages);
43
+ }
44
+
45
+ [Fact]
46
+ public void DuplicateFilesSomethingSomething()
47
+ {
48
+ var messages = BuildAndQueryComponentAndFileTables("DuplicateFiles.wxs", isPackage: true, 8602);
49
+ Assert.Equal(new[]
50
+ {
51
+ "8602",
52
+ "8602",
53
+ "8602",
54
+ "8602",
55
+ }, messages);
56
+ }
57
+
58
+ [Fact]
59
+ public void CanHarvestFilesInComponentGroup()
60
+ {
61
+ BuildQueryAssertFiles("ComponentGroup.wxs", new[]
62
+ {
63
+ "FileName.Extension",
64
+ "test20.txt",
65
+ "test21.txt",
66
+ "test3.txt",
67
+ "test4.txt",
68
+ });
69
+ }
70
+
71
+ [Fact]
72
+ public void CanHarvestFilesInDirectory()
73
+ {
74
+ BuildQueryAssertFiles("Directory.wxs", new[]
75
+ {
76
+ "test10.txt",
77
+ "test120.txt",
78
+ "test2.txt",
79
+ "test3.txt",
80
+ "test4.txt",
81
+ });
82
+ }
83
+
84
+ [Fact]
85
+ public void CanHarvestFilesInDirectoryRef()
86
+ {
87
+ BuildQueryAssertFiles("DirectoryRef.wxs", new[]
88
+ {
89
+ "notatest.txt",
90
+ "pleasedontincludeme.dat",
91
+ "test1.txt",
92
+ "test120.txt",
93
+ "test2.txt",
94
+ "test20.txt",
95
+ "test21.txt",
96
+ "test3.txt",
97
+ "test4.txt",
98
+ });
99
+ }
100
+
101
+ [Fact]
102
+ public void CanHarvestFilesInFeature()
103
+ {
104
+ var rows = BuildAndQueryComponentAndFileTables("Feature.wxs");
105
+
106
+ AssertFileComponentIds(3, rows);
107
+ }
108
+
109
+ [Fact]
110
+ public void CanHarvestFilesInFeatureGroup()
111
+ {
112
+ BuildQueryAssertFiles("FeatureGroup.wxs", new[]
113
+ {
114
+ "FileName.Extension",
115
+ "notatest.txt",
116
+ "pleasedontincludeme.dat",
117
+ "test1.txt",
118
+ "test2.txt",
119
+ "test20.txt",
120
+ "test21.txt",
121
+ "test3.txt",
122
+ "test4.txt",
123
+ });
124
+ }
125
+
126
+ [Fact]
127
+ public void CanHarvestFilesInFeatureRef()
128
+ {
129
+ BuildQueryAssertFiles("FeatureRef.wxs", new[]
130
+ {
131
+ "FileName.Extension",
132
+ "notatest.txt",
133
+ "pleasedontincludeme.dat",
134
+ "test1.txt",
135
+ "test2.txt",
136
+ "test20.txt",
137
+ "test21.txt",
138
+ "test3.txt",
139
+ "test4.txt",
140
+ });
141
+ }
142
+
143
+ [Fact]
144
+ public void CanHarvestFilesInFragments()
145
+ {
146
+ BuildQueryAssertFiles("Fragment.wxs", new[]
147
+ {
148
+ "notatest.txt",
149
+ "test1.txt",
150
+ "test2.txt",
151
+ "test3.txt",
152
+ "test4.txt",
153
+ });
154
+ }
155
+
156
+ [Fact]
157
+ public void CanHarvestFilesInModules()
158
+ {
159
+ BuildQueryAssertFiles("Module.wxs", new[]
160
+ {
161
+ "notatest.txt",
162
+ "test1.txt",
163
+ "test2.txt",
164
+ "test3.txt",
165
+ "test4.txt",
166
+ }, isPackage: false);
167
+ }
168
+
169
+ [Fact]
170
+ public void CanHarvestFilesWithBindPaths()
171
+ {
172
+ BuildQueryAssertFiles("BindPaths.wxs", new[]
173
+ {
174
+ "FileName.Extension",
175
+ "test1.txt",
176
+ "test10.txt",
177
+ "test120.txt",
178
+ "test2.txt",
179
+ "test20.txt",
180
+ "test21.txt",
181
+ "test3.txt",
182
+ "test4.txt",
183
+ });
184
+ }
185
+
186
+ [Fact]
187
+ public void HarvestedFilesUnderPackageWithAuthoredFeatureAreOrphaned()
188
+ {
189
+ var messages = BuildAndQueryComponentAndFileTables("PackageWithoutDefaultFeature.wxs", isPackage: true, 267);
190
+ Assert.Equal(new[]
191
+ {
192
+ "267",
193
+ "267",
194
+ "267",
195
+ "267",
196
+ }, messages);
197
+ }
198
+
199
+ [Fact]
200
+ public void CanHarvestFilesInStandardDirectory()
201
+ {
202
+ BuildQueryAssertFiles("StandardDirectory.wxs", new[]
203
+ {
204
+ "FileName.Extension",
205
+ "notatest.txt",
206
+ "pleasedontincludeme.dat",
207
+ "test1.txt",
208
+ "test10.txt",
209
+ "test120.txt",
210
+ "test2.txt",
211
+ "test20.txt",
212
+ "test21.txt",
213
+ "test3.txt",
214
+ "test4.txt",
215
+ });
216
+ }
217
+
218
+ [Fact]
219
+ public void CanHarvestFilesInFiveLines()
220
+ {
221
+ BuildQueryAssertFiles("PackageFiveLiner.wxs", new[]
222
+ {
223
+ "FileName.Extension",
224
+ "notatest.txt",
225
+ "pleasedontincludeme.dat",
226
+ "test20.txt",
227
+ "test21.txt",
228
+ "test3.txt",
229
+ "test4.txt",
230
+ });
231
+ }
232
+
233
+ private static void BuildQueryAssertFiles(string file, string[] expectedFileNames, bool isPackage = true, int? exitCode = null)
234
+ {
235
+ var rows = BuildAndQueryComponentAndFileTables(file, isPackage, exitCode);
236
+
237
+ var fileNames = AssertFileComponentIds(expectedFileNames.Length, rows);
238
+
239
+ Assert.Equal(expectedFileNames, fileNames);
240
+ }
241
+
242
+ private static string[] BuildAndQueryComponentAndFileTables(string file, bool isPackage = true, int? exitCode = null)
243
+ {
244
+ var folder = TestData.Get("TestData", "HarvestFiles");
245
+
246
+ using (var fs = new DisposableFileSystem())
247
+ {
248
+ var baseFolder = fs.GetFolder();
249
+ var intermediateFolder = Path.Combine(baseFolder, "obj");
250
+ var binFolder = Path.Combine(baseFolder, "bin");
251
+ var msiPath = Path.Combine(binFolder, isPackage ? "test.msi" : "test.msm");
252
+
253
+ var arguments = new[]
254
+ {
255
+ "build",
256
+ Path.Combine(folder, file),
257
+ "-intermediateFolder", intermediateFolder,
258
+ "-bindpath", folder,
259
+ "-bindpath", @$"ToBeHarvested={folder}\files1",
260
+ "-bindpath", @$"ToBeHarvested={folder}\files2",
261
+ "-o", msiPath,
262
+ };
263
+
264
+ var result = WixRunner.Execute(arguments);
265
+
266
+ if (exitCode.HasValue)
267
+ {
268
+ Assert.Equal(exitCode.Value, result.ExitCode);
269
+
270
+ return result.Messages.Select(m => m.Id.ToString()).ToArray();
271
+ }
272
+ else
273
+ {
274
+ result.AssertSuccess();
275
+
276
+ return Query.QueryDatabase(msiPath, new[] { "Component", "File" })
277
+ .OrderBy(s => s)
278
+ .ToArray();
279
+ }
280
+ }
281
+ }
282
+
283
+ private static string[] AssertFileComponentIds(int fileCount, string[] rows)
284
+ {
285
+ var componentRows = rows.Where(row => row.StartsWith("Component:")).ToArray();
286
+ var fileRows = rows.Where(row => row.StartsWith("File:")).ToArray();
287
+
288
+ Assert.Equal(componentRows.Length, fileRows.Length);
289
+
290
+ // Component id == Component keypath == File id
291
+ foreach (var componentRow in componentRows)
292
+ {
293
+ var columns = componentRow.Split(':', '\t');
294
+ Assert.Equal(columns[1], columns[6]);
295
+ }
296
+
297
+ foreach (var fileRow in fileRows)
298
+ {
299
+ var columns = fileRow.Split(':', '\t');
300
+ Assert.Equal(columns[1], columns[2]);
301
+ }
302
+
303
+ Assert.Equal(fileCount, componentRows.Length);
304
+
305
+ var files = fileRows.Select(row => row.Split('\t')[2]);
306
+ var lfns = files.Select(name => name.Split('|'));
307
+
308
+ return fileRows
309
+ .Select(row => row.Split('\t')[2])
310
+ .Select(GetLFN)
311
+ .OrderBy(name => name).ToArray();
312
+
313
+ static string GetLFN(string possibleSfnLfnPair)
314
+ {
315
+ var parts = possibleSfnLfnPair.Split('|');
316
+ return parts[parts.Length - 1];
317
+ }
318
+ }
319
+ }
320
+}
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/BadAuthoring.wxs
new
+7
@@ -0,0 +1,7 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <ComponentGroup Id="Files">
4
+ <Files />
5
+ </ComponentGroup>
6
+ </Package>
7
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/BadDirectory.wxs
new
+14
@@ -0,0 +1,14 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <Feature Id="ProductFeature">
6
+ <ComponentGroupRef Id="Files" />
7
+ </Feature>
8
+
9
+ <ComponentGroup Id="Files" Directory="ProgramFilesFolder" Subdirectory="MsiPackage" Source="$(sys.SOURCEFILEDIR)files2">
10
+ <Files Include="MissingDirectory\**" />
11
+ <Files Include="ThisDirectoryIsAlsoMissing\**" />
12
+ </ComponentGroup>
13
+ </Package>
14
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/BindPaths.wxs
new
+16
@@ -0,0 +1,16 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="HarvestedFiles" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <Feature Id="ProductFeature">
6
+ <ComponentGroupRef Id="Files" />
7
+ </Feature>
8
+
9
+ <ComponentGroup Id="Files" Directory="ProgramFilesFolder" Subdirectory="HarvestedFiles">
10
+ <Files Include="!(bindpath.ToBeHarvested)\**">
11
+ <Exclude Files="!(bindpath.ToBeHarvested)\notatest.txt" />
12
+ <Exclude Files="**\pleasedontincludeme.dat" />
13
+ </Files>
14
+ </ComponentGroup>
15
+ </Package>
16
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/ComponentGroup.wxs
new
+16
@@ -0,0 +1,16 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <Feature Id="ProductFeature">
6
+ <ComponentGroupRef Id="Files" />
7
+ </Feature>
8
+
9
+ <ComponentGroup Id="Files" Directory="ProgramFilesFolder" Subdirectory="MsiPackage" Source="$(sys.SOURCEFILEDIR)files2">
10
+ <Files Include="**">
11
+ <Exclude Files="notatest.txt" />
12
+ <Exclude Files="files2_sub2\pleasedontincludeme.dat" />
13
+ </Files>
14
+ </ComponentGroup>
15
+ </Package>
16
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/Directory.wxs
new
+20
@@ -0,0 +1,20 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <StandardDirectory Id="ProgramFilesFolder">
6
+ <Directory Id="INSTALLFOLDER" Name="MsiPackage" FileSource="$(sys.SOURCEFILEDIR)">
7
+ <!-- Relies on default-feature feature to include naked files in package. -->
8
+ <Files Include="files1\**">
9
+ <Exclude Files="files1\test1.txt" />
10
+ </Files>
11
+
12
+ <Files Include="files2\**">
13
+ <Exclude Files="**\*.Extension" />
14
+ <Exclude Files="files2\notatest.txt" />
15
+ <Exclude Files="files2\files2_sub2\**" />
16
+ </Files>
17
+ </Directory>
18
+ </StandardDirectory>
19
+ </Package>
20
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/DirectoryRef.wxs
new
+21
@@ -0,0 +1,21 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <DirectoryRef Id="INSTALLFOLDER">
6
+ <!-- Relies on default-feature feature to include naked files in package. -->
7
+ <Files Include="files1\**">
8
+ <Exclude Files="files1\files1_sub1\*" />
9
+ </Files>
10
+ <Files Include="files2\**">
11
+ <Exclude Files="$(sys.SOURCEFILEDIR)\files2\**.extension" />
12
+ </Files>
13
+ </DirectoryRef>
14
+ </Package>
15
+
16
+ <Fragment>
17
+ <StandardDirectory Id="ProgramFilesFolder">
18
+ <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
19
+ </StandardDirectory>
20
+ </Fragment>
21
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/DuplicateFiles.wxs
new
+23
@@ -0,0 +1,23 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <Feature Id="ProductFeature">
6
+ <ComponentGroupRef Id="FilesA" />
7
+ <ComponentGroupRef Id="FilesB" />
8
+ </Feature>
9
+
10
+ </Package>
11
+
12
+ <Fragment>
13
+ <ComponentGroup Id="FilesA" Directory="ProgramFilesFolder" Subdirectory="MsiPackage">
14
+ <Files Include="files1\**" />
15
+ </ComponentGroup>
16
+ </Fragment>
17
+
18
+ <Fragment>
19
+ <ComponentGroup Id="FilesB" Directory="ProgramFilesFolder" Subdirectory="MsiPackage">
20
+ <Files Include="files1\**" />
21
+ </ComponentGroup>
22
+ </Fragment>
23
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/Feature.wxs
new
+25
@@ -0,0 +1,25 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <Feature Id="ProductFeature">
6
+ <Files
7
+ Directory="ProgramFiles6432Folder"
8
+ Subdirectory="Example Product"
9
+ Include="files1\*">
10
+ <Exclude Files="files1\test1.txt" />
11
+ </Files>
12
+
13
+ <!--
14
+ `$(sys.SOURCEFILEDIR)` is equivalent to the above (i.e., the default),
15
+ but this validates that preprocessor variables are happy here.
16
+ -->
17
+ <Files
18
+ Directory="ProgramFiles6432Folder"
19
+ Subdirectory="Example Product\Assets"
20
+ Include="$(sys.SOURCEFILEDIR)\files2\*">
21
+ <Exclude Files="$(sys.SOURCEFILEDIR)\files2\notatest.txt" />
22
+ </Files>
23
+ </Feature>
24
+ </Package>
25
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/FeatureGroup.wxs
new
+25
@@ -0,0 +1,25 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <Feature Id="ProductFeature">
6
+ <FeatureGroupRef Id="ProductFeatureGroup" />
7
+ </Feature>
8
+ </Package>
9
+
10
+ <Fragment>
11
+ <StandardDirectory Id="ProgramFilesFolder">
12
+ <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
13
+ </StandardDirectory>
14
+ </Fragment>
15
+
16
+ <Fragment>
17
+ <FeatureGroup Id="ProductFeatureGroup">
18
+ <Files Directory="INSTALLFOLDER" Include="files1\**">
19
+ <Exclude Files="files1\files1_sub1\**" />
20
+ </Files>
21
+
22
+ <Files Directory="INSTALLFOLDER" Subdirectory="assets" Include="files2\**" />
23
+ </FeatureGroup>
24
+ </Fragment>
25
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/FeatureRef.wxs
new
+23
@@ -0,0 +1,23 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <FeatureRef Id="ProductFeature">
6
+ <Files Directory="INSTALLFOLDER" Include="files1\**">
7
+ <Exclude Files="files1\files1_sub1\**" />
8
+ </Files>
9
+
10
+ <Files Directory="INSTALLFOLDER" Subdirectory="assets" Include="files2\**" />
11
+ </FeatureRef>
12
+ </Package>
13
+
14
+ <Fragment>
15
+ <StandardDirectory Id="ProgramFilesFolder">
16
+ <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
17
+ </StandardDirectory>
18
+ </Fragment>
19
+
20
+ <Fragment>
21
+ <Feature Id="ProductFeature" />
22
+ </Fragment>
23
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/Fragment.wxs
new
+24
@@ -0,0 +1,24 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <FeatureGroupRef Id="FeatureGroup1" />
6
+ <FeatureGroupRef Id="FeatureGroup2" />
7
+ </Package>
8
+
9
+ <Fragment>
10
+ <StandardDirectory Id="ProgramFilesFolder">
11
+ <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
12
+ </StandardDirectory>
13
+ </Fragment>
14
+
15
+ <Fragment>
16
+ <FeatureGroup Id="FeatureGroup1" />
17
+ <Files Directory="ProgramFilesFolder" Subdirectory="MsiPackage" Include="files1\*" />
18
+ </Fragment>
19
+
20
+ <Fragment>
21
+ <FeatureGroup Id="FeatureGroup2" />
22
+ <Files Directory="ProgramFilesFolder" Subdirectory="MsiPackage" Include="files2\*" />
23
+ </Fragment>
24
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/Module.wxs
new
+6
@@ -0,0 +1,6 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Module Id="MergeModule" Guid="e535b765-1019-4a4f-b3ea-ae28870e6d73" Language="1033" Version="1.0.0.0">
3
+ <Files Directory="ProgramFilesFolder" Subdirectory="MergeModule" Include="files1\*" />
4
+ <Files Directory="ProgramFilesFolder" Subdirectory="MergeModule" Include="files2\*" />
5
+ </Module>
6
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/PackageFiveLiner.wxs
new
+5
@@ -0,0 +1,5 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <Files Include="files2\**" />
4
+ </Package>
5
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/PackageWithoutDefaultFeature.wxs
new
+15
@@ -0,0 +1,15 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <Files Directory="ProgramFilesFolder" Subdirectory="MsiPackage" Include="files1\**" />
6
+
7
+ <Feature Id="ProductFeature">
8
+ <ComponentGroupRef Id="Files" />
9
+ </Feature>
10
+
11
+ <ComponentGroup Id="Files" Directory="ProgramFilesFolder" Subdirectory="MsiPackage">
12
+ <Files Directory="ProgramFilesFolder" Subdirectory="MsiPackage" Include="files2\*" />
13
+ </ComponentGroup>
14
+ </Package>
15
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/StandardDirectory.wxs
new
+11
@@ -0,0 +1,11 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <StandardDirectory Id="ProgramFiles6432Folder">
6
+ <!-- Relies on default-feature feature to include naked files in package. -->
7
+ <Files Subdirectory="MsiPackage" Include="files1\**" />
8
+ <Files Subdirectory="MsiPackage" Include="files2\**" />
9
+ </StandardDirectory>
10
+ </Package>
11
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/ZeroFiles.wxs
new
+15
@@ -0,0 +1,15 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="MsiPackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
+ <MajorUpgrade DowngradeErrorMessage="Downgrade error message." />
4
+
5
+ <Feature Id="ProductFeature">
6
+ <ComponentGroupRef Id="Files" />
7
+ </Feature>
8
+
9
+ <ComponentGroup Id="Files" Directory="ProgramFilesFolder" Subdirectory="MsiPackage" Source="$(sys.SOURCEFILEDIR)files2">
10
+ <Files Include="**">
11
+ <Exclude Files="**" />
12
+ </Files>
13
+ </ComponentGroup>
14
+ </Package>
15
+</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files1/files1_sub1/files1_sub2/test120.txt
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files1/files1_sub1/test10.txt
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files1/test1.txt
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files1/test2.txt
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files2/files2_sub2/pleasedontincludeme.dat
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files2/files2_sub2/test20.txt
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files2/files2_sub2/test21.txt
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files2/files2_sub3/FileName.Extension
new
+14
@@ -0,0 +1,14 @@
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.TestData.HarvestFiles.files2.files2_sub3
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.Linq;
8
+ using System.Text;
9
+ using System.Threading.Tasks;
10
+
11
+ internal class FileName
12
+ {
13
+ }
14
+}
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files2/notatest.txt
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files2/test3.txt
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.
src/wix/test/WixToolsetTest.CoreIntegration/TestData/HarvestFiles/files2/test4.txt
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.