@joebigelow / wix / commits / 6067839b

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.