main
cs 258 lines 11.1 KB
Raw
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 inclusions = harvestFile.Inclusions.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
49 var exclusions = harvestFile.Exclusions.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
50
51 var comparer = new WildcardFileComparer();
52
53 var resolvedFiles = Enumerable.Empty<WildcardFile>();
54
55 var included = this.GetWildcardFiles(harvestFile, inclusions);
56 var excluded = this.GetWildcardFiles(harvestFile, exclusions);
57
58 foreach (var excludedFile in excluded)
59 {
60 this.Messaging.Write(OptimizerVerboses.ExcludedFile(harvestFile.SourceLineNumbers, excludedFile.Path));
61 }
62
63 resolvedFiles = included.Except(excluded, comparer).ToList();
64
65 if (!resolvedFiles.Any())
66 {
67 this.Messaging.Write(OptimizerWarnings.ZeroFilesHarvested(harvestFile.SourceLineNumbers));
68 }
69
70 foreach (var fileByRecursiveDir in resolvedFiles.GroupBy(resolvedFile => resolvedFile.RecursiveDir, resolvedFile => resolvedFile.Path))
71 {
72 var directoryId = harvestFile.DirectoryRef;
73
74 var recursiveDir = fileByRecursiveDir.Key;
75
76 if (!String.IsNullOrEmpty(recursiveDir))
77 {
78 directoryId = this.ParseHelper.CreateDirectoryReferenceFromInlineSyntax(section, harvestFile.SourceLineNumbers, attribute: null, directoryId, recursiveDir, unusedSectionCachedInlinedDirectoryIds);
79 }
80
81 foreach (var file in fileByRecursiveDir)
82 {
83 if (harvestedFiles.Add(file))
84 {
85 var name = Path.GetFileName(file);
86
87 var id = this.ParseHelper.CreateIdentifier("fls", directoryId, name);
88
89 this.Messaging.Write(OptimizerVerboses.HarvestedFile(harvestFile.SourceLineNumbers, file));
90
91 section.AddSymbol(new FileSymbol(harvestFile.SourceLineNumbers, id)
92 {
93 ComponentRef = id.Id,
94 Name = name,
95 Attributes = FileSymbolAttributes.None | FileSymbolAttributes.Vital,
96 DirectoryRef = directoryId,
97 Source = new IntermediateFieldPathValue { Path = file },
98 });
99
100 section.AddSymbol(new ComponentSymbol(harvestFile.SourceLineNumbers, id)
101 {
102 ComponentId = "*",
103 DirectoryRef = directoryId,
104 Location = ComponentLocation.LocalOnly,
105 KeyPath = id.Id,
106 KeyPathType = ComponentKeyPathType.File,
107 Win64 = this.Context.Platform == Platform.ARM64 || this.Context.Platform == Platform.X64,
108 });
109
110 // if this is a module, automatically add this component to the references to ensure it gets in the ModuleComponents table
111 if (!String.IsNullOrEmpty(harvestFile.ModuleLanguage))
112 {
113 this.ParseHelper.CreateComplexReference(section, harvestFile.SourceLineNumbers, ComplexReferenceParentType.Module, harvestFile.ParentId, harvestFile.ModuleLanguage, ComplexReferenceChildType.Component, id.Id, false);
114 }
115 else if (Enum.TryParse<ComplexReferenceParentType>(harvestFile.ComplexReferenceParentType, out var parentType)
116 && ComplexReferenceParentType.Unknown != parentType && null != harvestFile.ParentId)
117 {
118 // If the parent was provided, add a complex reference to that, and, if
119 // the Files is under a feature, then mark the complex reference primary.
120 this.ParseHelper.CreateComplexReference(section, harvestFile.SourceLineNumbers, parentType, harvestFile.ParentId, null, ComplexReferenceChildType.Component, id.Id, ComplexReferenceParentType.Feature == parentType);
121 }
122 }
123 else
124 {
125 this.Messaging.Write(OptimizerWarnings.SkippingDuplicateFile(harvestFile.SourceLineNumbers, file));
126 }
127 }
128 }
129 }
130
131 private IEnumerable<WildcardFile> GetWildcardFiles(HarvestFilesSymbol harvestFile, IEnumerable<string> patterns)
132 {
133 var sourceLineNumbers = harvestFile.SourceLineNumbers;
134 var sourcePath = harvestFile.SourcePath?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
135
136 var files = new List<WildcardFile>();
137
138 try
139 {
140 foreach (var pattern in patterns)
141 {
142 // Resolve bind paths, if any, which might result in multiple directories.
143 foreach (var path in this.ResolveBindPaths(sourceLineNumbers, pattern))
144 {
145 var sourceDirectory = String.IsNullOrEmpty(sourcePath) ? Path.GetDirectoryName(sourceLineNumbers.FileName) : sourcePath;
146 var recursive = path.IndexOf("**") >= 0;
147 var filePortion = Path.GetFileName(path);
148 var directoryPortion = Path.GetDirectoryName(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
149
150 if (directoryPortion?.EndsWith(@"\**") == true)
151 {
152 directoryPortion = directoryPortion.Substring(0, directoryPortion.Length - 3);
153 }
154
155 if (directoryPortion is null || directoryPortion.Length == 0 || directoryPortion == "**")
156 {
157 directoryPortion = sourceDirectory;
158
159 }
160 else if (!Path.IsPathRooted(directoryPortion))
161 {
162 directoryPortion = Path.Combine(sourceDirectory, directoryPortion);
163 }
164
165 var recursiveDirOffset = directoryPortion.Length + 1;
166
167 var foundFiles = Directory.EnumerateFiles(directoryPortion, filePortion, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
168
169 foreach (var foundFile in foundFiles)
170 {
171 var recursiveDir = Path.GetDirectoryName(foundFile.Substring(recursiveDirOffset));
172 files.Add(new WildcardFile()
173 {
174 RecursiveDir = recursiveDir,
175 Path = foundFile,
176 });
177 }
178 }
179 }
180 }
181 catch (DirectoryNotFoundException e)
182 {
183 this.Messaging.Write(OptimizerWarnings.ExpectedDirectory(harvestFile.SourceLineNumbers, e.Message));
184 }
185
186 return files;
187 }
188
189 private IEnumerable<string> ResolveBindPaths(SourceLineNumber sourceLineNumbers, string source)
190 {
191 var resultingDirectories = new List<string>();
192
193 var bindName = String.Empty;
194 var path = source;
195
196 if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal))
197 {
198 var closeParen = source.IndexOf(')', BindPathOpenString.Length);
199
200 if (-1 != closeParen)
201 {
202 bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length);
203 path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing paren.
204 path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted.
205 }
206 }
207
208 if (String.IsNullOrEmpty(bindName))
209 {
210 var unnamedBindPath = this.Context.BindPaths.SingleOrDefault(bp => bp.Name == null)?.Path;
211
212 resultingDirectories.Add(unnamedBindPath is null ? path : Path.Combine(unnamedBindPath, path));
213 }
214 else
215 {
216 var foundBindPath = false;
217
218 foreach (var bindPath in this.Context.BindPaths)
219 {
220 if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase))
221 {
222 var resolved = Path.Combine(bindPath.Path, path);
223 resultingDirectories.Add(resolved);
224
225 foundBindPath = true;
226 }
227 }
228
229 if (!foundBindPath)
230 {
231 this.Messaging.Write(OptimizerWarnings.ExpectedDirectory(sourceLineNumbers, source));
232 }
233 }
234
235 return resultingDirectories;
236 }
237
238 private class WildcardFile
239 {
240 public string RecursiveDir { get; set; }
241
242 public string Path { get; set; }
243 }
244
245 private class WildcardFileComparer : IEqualityComparer<WildcardFile>
246 {
247 public bool Equals(WildcardFile x, WildcardFile y)
248 {
249 return x?.Path == y?.Path;
250 }
251
252 public int GetHashCode(WildcardFile obj)
253 {
254 return obj?.Path?.GetHashCode() ?? 0;
255 }
256 }
257 }
258 }