private static RecursiveStepResult GetFilesRecursiveStep ( RecursionState recursionState, string projectDirectory, bool stripProjectDirectory, GetFileSystemEntries getFileSystemEntries ) { RecursiveStepResult ret = new RecursiveStepResult(); /* * Get the matching files. */ bool considerFiles = false; // Only consider files if... if (recursionState.RemainingWildcardDirectory.Length == 0) { // We've reached the end of the wildcard directory elements. considerFiles = true; } else if (recursionState.RemainingWildcardDirectory.IndexOf(recursiveDirectoryMatch, StringComparison.Ordinal) == 0) { // or, we've reached a "**" so everything else is matched recursively. considerFiles = true; } if (considerFiles) { string[] files = getFileSystemEntries(FileSystemEntity.Files, recursionState.BaseDirectory, recursionState.SearchData.Filespec, projectDirectory, stripProjectDirectory); bool needToProcessEachFile = recursionState.SearchData.Filespec == null || recursionState.SearchData.ExtensionLengthToEnforce != 0; if (needToProcessEachFile) { List<string> listOfFiles = new List<string>(); foreach (string file in files) { if ((recursionState.SearchData.Filespec != null) || // if no file-spec provided, match the file to the regular expression // PERF NOTE: Regex.IsMatch() is an expensive operation, so we avoid it whenever possible recursionState.SearchData.RegexFileMatch.IsMatch(file)) { if ((recursionState.SearchData.Filespec == null) || // if we used a file-spec with a "loosely" defined extension (recursionState.SearchData.ExtensionLengthToEnforce == 0) || // discard all files that do not have extensions of the desired length (Path.GetExtension(file).Length == recursionState.SearchData.ExtensionLengthToEnforce)) { listOfFiles.Add(file); } } } ret.Files = listOfFiles.ToArray(); } else { ret.Files = files; } } /* * Recurse into subdirectories. */ if (recursionState.SearchData.NeedsRecursion && recursionState.RemainingWildcardDirectory.Length > 0) { // Find the next directory piece. string pattern = null; if (!IsRecursiveDirectoryMatch(recursionState.RemainingWildcardDirectory)) { int indexOfNextSlash = recursionState.RemainingWildcardDirectory.IndexOfAny(directorySeparatorCharacters); ErrorUtilities.VerifyThrow(indexOfNextSlash != -1, "Slash should be guaranteed."); pattern = recursionState.RemainingWildcardDirectory.Substring(0, indexOfNextSlash); if (pattern == recursiveDirectoryMatch) { // If pattern turned into **, then there's no choice but to enumerate everything. pattern = null; recursionState.RemainingWildcardDirectory = recursiveDirectoryMatch; } else { // Peel off the leftmost directory piece. So for example, if remainingWildcardDirectory // contains: // // ?emp\foo\**\bar // // then put '?emp' into pattern. Then put the remaining part, // // foo\**\bar // // back into remainingWildcardDirectory. // This is a performance optimization. We don't want to enumerate everything if we // don't have to. recursionState.RemainingWildcardDirectory = recursionState.RemainingWildcardDirectory.Substring(indexOfNextSlash + 1); } } ret.RemainingWildcardDirectory = recursionState.RemainingWildcardDirectory; ret.Subdirs = getFileSystemEntries(FileSystemEntity.Directories, recursionState.BaseDirectory, pattern, null, false); } return ret; }
/// <summary> /// Get all files that match either the file-spec or the regular expression. /// </summary> /// <param name="listOfFiles">List of files that gets populated.</param> /// <param name="recursionState">Information about the search</param> /// <param name="projectDirectory"></param> /// <param name="stripProjectDirectory"></param> /// <param name="getFileSystemEntries">Delegate.</param> /// <param name="searchesToExclude">Patterns to exclude from the results</param> /// <param name="searchesToExcludeInSubdirs">exclude patterns that might activate farther down the directory tree. Keys assume paths are normalized with forward slashes and no trailing slashes</param> private static void GetFilesRecursive ( IList<string> listOfFiles, RecursionState recursionState, string projectDirectory, bool stripProjectDirectory, GetFileSystemEntries getFileSystemEntries, IList<RecursionState> searchesToExclude, Dictionary<string, List<RecursionState>> searchesToExcludeInSubdirs ) { ErrorUtilities.VerifyThrow((recursionState.SearchData.Filespec== null) || (recursionState.SearchData.RegexFileMatch == null), "File-spec overrides the regular expression -- pass null for file-spec if you want to use the regular expression."); ErrorUtilities.VerifyThrow((recursionState.SearchData.Filespec != null) || (recursionState.SearchData.RegexFileMatch != null), "Need either a file-spec or a regular expression to match files."); ErrorUtilities.VerifyThrow(recursionState.RemainingWildcardDirectory != null, "Expected non-null remaning wildcard directory."); // Determine if any of searchesToExclude is necessarily a superset of the results that will be returned. // This means all results will be excluded and we should bail out now. if (searchesToExclude != null) { foreach (var searchToExclude in searchesToExclude) { // The BaseDirectory of all the exclude searches should be the same as the include one Debug.Assert(FileUtilities.PathsEqual(searchToExclude.BaseDirectory, recursionState.BaseDirectory), "Expected exclude search base directory to match include search base directory"); // We can exclude all results in this folder if: if ( // We are matching files based on a filespec and not a regular expression searchToExclude.SearchData.Filespec != null && // The wildcard path portion of the excluded search matches the include search FileUtilities.PathsEqual(searchToExclude.RemainingWildcardDirectory, recursionState.RemainingWildcardDirectory) && // The exclude search will match ALL filenames OR (searchToExclude.SearchData.Filespec == "*" || searchToExclude.SearchData.Filespec == "*.*" || // The exclude search filename pattern matches the include search's pattern (searchToExclude.SearchData.Filespec == recursionState.SearchData.Filespec && searchToExclude.SearchData.ExtensionLengthToEnforce == recursionState.SearchData.ExtensionLengthToEnforce))) { // We won't get any results from this search that we would end up keeping return; } } } RecursiveStepResult nextStep = GetFilesRecursiveStep( recursionState, projectDirectory, stripProjectDirectory, getFileSystemEntries); RecursiveStepResult[] excludeNextSteps = null; if (searchesToExclude != null) { excludeNextSteps = new RecursiveStepResult[searchesToExclude.Count]; for (int i = 0; i < searchesToExclude.Count; i++) { excludeNextSteps[i] = GetFilesRecursiveStep( searchesToExclude[i], projectDirectory, stripProjectDirectory, getFileSystemEntries); } } if (nextStep.Files != null) { HashSet<string> filesToExclude = null; if (excludeNextSteps != null) { filesToExclude = new HashSet<string>(); foreach (var excludeStep in excludeNextSteps) { foreach (var file in excludeStep.Files) { filesToExclude.Add(file); } } } foreach (var file in nextStep.Files) { if (filesToExclude == null || !filesToExclude.Contains(file)) { listOfFiles.Add(file); } } } if (nextStep.Subdirs != null) { foreach (string subdir in nextStep.Subdirs) { // RecursionState is a struct so this copies it var newRecursionState = recursionState; newRecursionState.BaseDirectory = subdir; newRecursionState.RemainingWildcardDirectory = nextStep.RemainingWildcardDirectory; List<RecursionState> newSearchesToExclude = null; if (excludeNextSteps != null) { newSearchesToExclude = new List<RecursionState>(); for (int i = 0; i < excludeNextSteps.Length; i++) { if (excludeNextSteps[i].Subdirs != null && excludeNextSteps[i].Subdirs.Any(excludedDir => FileUtilities.PathsEqual(excludedDir, subdir))) { RecursionState thisExcludeStep = searchesToExclude[i]; thisExcludeStep.BaseDirectory = subdir; thisExcludeStep.RemainingWildcardDirectory = excludeNextSteps[i].RemainingWildcardDirectory; newSearchesToExclude.Add(thisExcludeStep); } } } if (searchesToExcludeInSubdirs != null) { List<RecursionState> searchesForSubdir; // The normalization fixes https://github.com/Microsoft/msbuild/issues/917 // and is a partial fix for https://github.com/Microsoft/msbuild/issues/724 if (searchesToExcludeInSubdirs.TryGetValue(subdir.NormalizeForPathComparison(), out searchesForSubdir)) { // We've found the base directory that these exclusions apply to. So now add them as normal searches if (newSearchesToExclude == null) { newSearchesToExclude = new List<RecursionState>(); } newSearchesToExclude.AddRange(searchesForSubdir); } } // We never want to strip the project directory from the leaves, because the current // process directory maybe different GetFilesRecursive( listOfFiles, newRecursionState, projectDirectory, stripProjectDirectory, getFileSystemEntries, newSearchesToExclude, searchesToExcludeInSubdirs); } } }