internal static string GetLongPathName(string path, GetFileSystemEntries getFileSystemEntries) { string str; if (path.IndexOf("~", StringComparison.Ordinal) == -1) { return(path); } Microsoft.Build.Shared.ErrorUtilities.VerifyThrow(!HasWildcards(path), "GetLongPathName does not handle wildcards and was passed '{0}'.", path); string[] strArray = path.Split(directorySeparatorCharacters); int num = 0; if (path.StartsWith(directorySeparator + directorySeparator, StringComparison.Ordinal)) { str = ((directorySeparator + directorySeparator) + strArray[2] + directorySeparator) + strArray[3] + directorySeparator; num = 4; } else if ((path.Length > 2) && (path[1] == ':')) { str = strArray[0] + directorySeparator; num = 1; } else { str = string.Empty; num = 0; } string[] strArray2 = new string[strArray.Length - num]; string str2 = str; for (int i = num; i < strArray.Length; i++) { if (strArray[i].Length == 0) { strArray2[i - num] = string.Empty; } else if (strArray[i].IndexOf("~", StringComparison.Ordinal) == -1) { strArray2[i - num] = strArray[i]; str2 = Path.Combine(str2, strArray[i]); } else { string[] strArray3 = getFileSystemEntries(FileSystemEntity.FilesAndDirectories, str2, strArray[i], null, false); if (strArray3.Length == 0) { for (int j = i; j < strArray.Length; j++) { strArray2[j - num] = strArray[j]; } break; } Microsoft.Build.Shared.ErrorUtilities.VerifyThrow(strArray3.Length == 1, "Unexpected number of entries ({3}) found when enumerating '{0}' under '{1}'. Original path was '{2}'", strArray[i], str2, path, strArray3.Length); str2 = strArray3[0]; strArray2[i - num] = Path.GetFileName(str2); } } return(str + string.Join(directorySeparator, strArray2)); }
/// <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="baseDirectory">The path to enumerate</param> /// <param name="remainingWildcardDirectory">The remaining, wildcard part of the directory.</param> /// <param name="filespec">The filespec.</param> /// <param name="extensionLengthToEnforce"></param> /// <param name="regexFileMatch">Wild-card matching.</param> /// <param name="needsRecursion">If true, then recursion is required.</param> /// <param name="projectDirectory"></param> /// <param name="stripProjectDirectory"></param> /// <param name="getFileSystemEntries">Delegate.</param> private static void GetFilesRecursive ( System.Collections.IList listOfFiles, string baseDirectory, string remainingWildcardDirectory, string filespec, // can be null int extensionLengthToEnforce, // only relevant when filespec is not null Regex regexFileMatch, // can be null bool needsRecursion, string projectDirectory, bool stripProjectDirectory, GetFileSystemEntries getFileSystemEntries ) { ErrorUtilities.VerifyThrow((filespec == null) || (regexFileMatch == null), "File-spec overrides the regular expression -- pass null for file-spec if you want to use the regular expression."); ErrorUtilities.VerifyThrow((filespec != null) || (regexFileMatch != null), "Need either a file-spec or a regular expression to match files."); ErrorUtilities.VerifyThrow(remainingWildcardDirectory != null, "Expected non-null remaning wildcard directory."); /* * Get the matching files. */ bool considerFiles = false; // Only consider files if... if (remainingWildcardDirectory.Length == 0) { // We've reached the end of the wildcard directory elements. considerFiles = true; } else if (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, baseDirectory, filespec, projectDirectory, stripProjectDirectory); foreach (string file in files) { if ((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 regexFileMatch.IsMatch(file)) { if ((filespec == null) || // if we used a file-spec with a "loosely" defined extension (extensionLengthToEnforce == 0) || // discard all files that do not have extensions of the desired length (Path.GetExtension(file).Length == extensionLengthToEnforce)) { listOfFiles.Add((object)file); } } } } /* * Recurse into subdirectories. */ if (needsRecursion && remainingWildcardDirectory.Length > 0) { // Find the next directory piece. string pattern = null; if (remainingWildcardDirectory != recursiveDirectoryMatch) { int indexOfNextSlash = remainingWildcardDirectory.IndexOfAny(directorySeparatorCharacters); ErrorUtilities.VerifyThrow(indexOfNextSlash != -1, "Slash should be guaranteed."); // 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. pattern = remainingWildcardDirectory.Substring(0, indexOfNextSlash); remainingWildcardDirectory = remainingWildcardDirectory.Substring(indexOfNextSlash + 1); // If pattern turned into **, then there's no choice but to enumerate everything. if (pattern == recursiveDirectoryMatch) { pattern = null; remainingWildcardDirectory = recursiveDirectoryMatch; } } // We never want to strip the project directory from the leaves, because the current // process directory maybe different string[] subdirs = getFileSystemEntries(FileSystemEntity.Directories, baseDirectory, pattern, null, false); foreach (string subdir in subdirs) { GetFilesRecursive(listOfFiles, subdir, remainingWildcardDirectory, filespec, extensionLengthToEnforce, regexFileMatch, true, projectDirectory, stripProjectDirectory, getFileSystemEntries); } } }
/// <summary> /// Given a filespec, split it into left-most 'fixed' dir part, middle 'wildcard' dir part, and filename part. /// The filename part may have wildcard characters in it. /// </summary> /// <param name="filespec">The filespec to be decomposed.</param> /// <param name="fixedDirectoryPart">Receives the fixed directory part.</param> /// <param name="wildcardDirectoryPart">The wildcard directory part.</param> /// <param name="filenamePart">The filename part.</param> /// <param name="getFileSystemEntries">Delegate.</param> internal static void SplitFileSpec ( string filespec, out string fixedDirectoryPart, out string wildcardDirectoryPart, out string filenamePart, GetFileSystemEntries getFileSystemEntries ) { PreprocessFileSpecForSplitting ( filespec, out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart ); /* * Handle the special case in which filenamePart is '**'. * In this case, filenamePart becomes '*.*' and the '**' is appended * to the end of the wildcardDirectory part. * This is so that later regular expression matching can accurately * pull out the different parts (fixed, wildcard, filename) of given * file specs. */ if (recursiveDirectoryMatch == filenamePart) { wildcardDirectoryPart += recursiveDirectoryMatch; wildcardDirectoryPart += s_directorySeparator; filenamePart = "*.*"; } fixedDirectoryPart = FileMatcher.GetLongPathName(fixedDirectoryPart, getFileSystemEntries); }
/// <summary> /// Given a path name, get its long version. /// </summary> /// <param name="path">The short path.</param> /// <param name="getFileSystemEntries">Delegate.</param> /// <returns>The long path.</returns> internal static string GetLongPathName ( string path, GetFileSystemEntries getFileSystemEntries ) { if (path.IndexOf("~", StringComparison.Ordinal) == -1) { // A path with no '~' must not be a short name. return path; } ErrorUtilities.VerifyThrow(!HasWildcards(path), "GetLongPathName does not handle wildcards and was passed '{0}'.", path); string[] parts = path.Split(directorySeparatorCharacters); string pathRoot; int startingElement = 0; bool isUnc = path.StartsWith(s_directorySeparator + s_directorySeparator, StringComparison.Ordinal); if (isUnc) { pathRoot = s_directorySeparator + s_directorySeparator; pathRoot += parts[2]; pathRoot += s_directorySeparator; pathRoot += parts[3]; pathRoot += s_directorySeparator; startingElement = 4; } else { // Is it relative? if (path.Length > 2 && path[1] == ':') { // Not relative pathRoot = parts[0] + s_directorySeparator; startingElement = 1; } else { // Relative pathRoot = String.Empty; startingElement = 0; } } // Build up an array of parts. These elements may be "" if there are // extra slashes. string[] longParts = new string[parts.Length - startingElement]; string longPath = pathRoot; for (int i = startingElement; i < parts.Length; ++i) { // If there is a zero-length part, then that means there was an extra slash. if (parts[i].Length == 0) { longParts[i - startingElement] = String.Empty; } else { if (parts[i].IndexOf("~", StringComparison.Ordinal) == -1) { // If there's no ~, don't hit the disk. longParts[i - startingElement] = parts[i]; longPath = Path.Combine(longPath, parts[i]); } else { // getFileSystemEntries(...) returns an empty array if longPath doesn't exist. string[] entries = getFileSystemEntries(FileSystemEntity.FilesAndDirectories, longPath, parts[i], null, false); if (0 == entries.Length) { // The next part doesn't exist. Therefore, no more of the path will exist. // Just return the rest. for (int j = i; j < parts.Length; ++j) { longParts[j - startingElement] = parts[j]; } break; } // Since we know there are no wild cards, this should be length one. ErrorUtilities.VerifyThrow(entries.Length == 1, "Unexpected number of entries ({3}) found when enumerating '{0}' under '{1}'. Original path was '{2}'", parts[i], longPath, path, entries.Length); // Entries[0] contains the full path. longPath = entries[0]; // We just want the trailing node. longParts[i - startingElement] = Path.GetFileName(longPath); } } } return pathRoot + String.Join(s_directorySeparator, longParts); }
static SearchAction GetFileSearchData(string projectDirectoryUnescaped, string filespecUnescaped, GetFileSystemEntries getFileSystemEntries, DirectoryExists directoryExists, out bool stripProjectDirectory, out RecursionState result) { stripProjectDirectory = false; result = new RecursionState(); string fixedDirectoryPart; string wildcardDirectoryPart; string filenamePart; string matchFileExpression; bool needsRecursion; bool isLegalFileSpec; GetFileSpecInfo ( filespecUnescaped, out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart, out matchFileExpression, out needsRecursion, out isLegalFileSpec, getFileSystemEntries ); /* * If the filespec is invalid, then just return now. */ if (!isLegalFileSpec) { return SearchAction.ReturnFileSpec; } // The projectDirectory is not null only if we are running the evaluation from // inside the engine (i.e. not from a task) if (projectDirectoryUnescaped != null) { if (fixedDirectoryPart != null) { string oldFixedDirectoryPart = fixedDirectoryPart; try { fixedDirectoryPart = Path.Combine(projectDirectoryUnescaped, fixedDirectoryPart); } catch (ArgumentException) { return SearchAction.ReturnEmptyList; } stripProjectDirectory = !String.Equals(fixedDirectoryPart, oldFixedDirectoryPart, StringComparison.OrdinalIgnoreCase); } else { fixedDirectoryPart = projectDirectoryUnescaped; stripProjectDirectory = true; } } /* * If the fixed directory part doesn't exist, then this means no files should be * returned. */ if (fixedDirectoryPart.Length > 0 && !directoryExists(fixedDirectoryPart)) { return SearchAction.ReturnEmptyList; } // determine if we need to use the regular expression to match the files // PERF NOTE: Constructing a Regex object is expensive, so we avoid it whenever possible bool matchWithRegex = // if we have a directory specification that uses wildcards, and (wildcardDirectoryPart.Length > 0) && // the specification is not a simple "**" !IsRecursiveDirectoryMatch(wildcardDirectoryPart); // then we need to use the regular expression // if we're not using the regular expression, get the file pattern extension string extensionPart = matchWithRegex ? null : Path.GetExtension(filenamePart); // check if the file pattern would cause Windows to match more loosely on the extension // NOTE: Windows matches loosely in two cases (in the absence of the * wildcard in the extension): // 1) if the extension ends with the ? wildcard, it matches files with shorter extensions also e.g. "file.tx?" would // match both "file.txt" and "file.tx" // 2) if the extension is three characters, and the filename contains the * wildcard, it matches files with longer // extensions that start with the same three characters e.g. "*.htm" would match both "file.htm" and "file.html" bool needToEnforceExtensionLength = (extensionPart != null) && (extensionPart.IndexOf('*') == -1) && (extensionPart.EndsWith("?", StringComparison.Ordinal) || ((extensionPart.Length == (3 + 1 /* +1 for the period */)) && (filenamePart.IndexOf('*') != -1))); var searchData = new FilesSearchData( // if using the regular expression, ignore the file pattern (matchWithRegex ? null : filenamePart), (needToEnforceExtensionLength ? extensionPart.Length : 0), // if using the file pattern, ignore the regular expression (matchWithRegex ? new Regex(matchFileExpression, RegexOptions.IgnoreCase) : null), needsRecursion); result.SearchData = searchData; result.BaseDirectory = fixedDirectoryPart; result.RemainingWildcardDirectory = wildcardDirectoryPart; return SearchAction.RunSearch; }
private static void GetFileSpecInfo(string filespec, out string fixedDirectoryPart, out string wildcardDirectoryPart, out string filenamePart, out string matchFileExpression, out bool needsRecursion, out bool isLegalFileSpec, GetFileSystemEntries getFileSystemEntries) { isLegalFileSpec = true; needsRecursion = false; fixedDirectoryPart = string.Empty; wildcardDirectoryPart = string.Empty; filenamePart = string.Empty; matchFileExpression = null; if (-1 != filespec.IndexOfAny(Path.GetInvalidPathChars())) { isLegalFileSpec = false; } else if (-1 != filespec.IndexOf("...", StringComparison.Ordinal)) { isLegalFileSpec = false; } else { int num = filespec.LastIndexOf(":", StringComparison.Ordinal); if ((-1 != num) && (1 != num)) { isLegalFileSpec = false; } else { SplitFileSpec(filespec, out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart, getFileSystemEntries); matchFileExpression = RegularExpressionFromFileSpec(fixedDirectoryPart, wildcardDirectoryPart, filenamePart, out isLegalFileSpec); if (isLegalFileSpec) { needsRecursion = wildcardDirectoryPart.Length != 0; } } } }
internal static string[] GetFiles(string projectDirectoryUnescaped, string filespecUnescaped, GetFileSystemEntries getFileSystemEntries, Microsoft.Build.Shared.DirectoryExists directoryExists) { string str; string str2; string str3; string str4; bool flag; bool flag2; if (!HasWildcards(filespecUnescaped)) { return(new string[] { filespecUnescaped }); } ArrayList list = new ArrayList(); IList listOfFiles = list; GetFileSpecInfo(filespecUnescaped, out str, out str2, out str3, out str4, out flag, out flag2, getFileSystemEntries); if (!flag2) { return(new string[] { filespecUnescaped }); } bool stripProjectDirectory = false; if (projectDirectoryUnescaped != null) { if (str != null) { string b = str; try { str = Path.Combine(projectDirectoryUnescaped, str); } catch (ArgumentException) { return(new string[0]); } stripProjectDirectory = !string.Equals(str, b, StringComparison.OrdinalIgnoreCase); } else { str = projectDirectoryUnescaped; stripProjectDirectory = true; } } if ((str.Length > 0) && !directoryExists(str)) { return(new string[0]); } bool flag4 = (str2.Length > 0) && (str2 != ("**" + directorySeparator)); string str6 = flag4 ? null : Path.GetExtension(str3); bool flag5 = ((str6 != null) && (str6.IndexOf('*') == -1)) && (str6.EndsWith("?", StringComparison.Ordinal) || ((str6.Length == 4) && (str3.IndexOf('*') != -1))); GetFilesRecursive(listOfFiles, str, str2, flag4 ? null : str3, flag5 ? str6.Length : 0, flag4 ? new Regex(str4, RegexOptions.IgnoreCase) : null, flag, projectDirectoryUnescaped, stripProjectDirectory, getFileSystemEntries); return((string[])list.ToArray(typeof(string))); }
internal static string[] GetFiles(string projectDirectoryUnescaped, string filespecUnescaped, GetFileSystemEntries getFileSystemEntries, Microsoft.Build.Shared.DirectoryExists directoryExists) { string str; string str2; string str3; string str4; bool flag; bool flag2; if (!HasWildcards(filespecUnescaped)) { return new string[] { filespecUnescaped }; } ArrayList list = new ArrayList(); IList listOfFiles = list; GetFileSpecInfo(filespecUnescaped, out str, out str2, out str3, out str4, out flag, out flag2, getFileSystemEntries); if (!flag2) { return new string[] { filespecUnescaped }; } bool stripProjectDirectory = false; if (projectDirectoryUnescaped != null) { if (str != null) { string b = str; try { str = Path.Combine(projectDirectoryUnescaped, str); } catch (ArgumentException) { return new string[0]; } stripProjectDirectory = !string.Equals(str, b, StringComparison.OrdinalIgnoreCase); } else { str = projectDirectoryUnescaped; stripProjectDirectory = true; } } if ((str.Length > 0) && !directoryExists(str)) { return new string[0]; } bool flag4 = (str2.Length > 0) && (str2 != ("**" + directorySeparator)); string str6 = flag4 ? null : Path.GetExtension(str3); bool flag5 = ((str6 != null) && (str6.IndexOf('*') == -1)) && (str6.EndsWith("?", StringComparison.Ordinal) || ((str6.Length == 4) && (str3.IndexOf('*') != -1))); GetFilesRecursive(listOfFiles, str, str2, flag4 ? null : str3, flag5 ? str6.Length : 0, flag4 ? new Regex(str4, RegexOptions.IgnoreCase) : null, flag, projectDirectoryUnescaped, stripProjectDirectory, getFileSystemEntries); return (string[]) list.ToArray(typeof(string)); }
internal static void GetFileSpecInfo(string filespec, out Regex regexFileMatch, out bool needsRecursion, out bool isLegalFileSpec, GetFileSystemEntries getFileSystemEntries) { string str; string str2; string str3; string str4; GetFileSpecInfo(filespec, out str, out str2, out str3, out str4, out needsRecursion, out isLegalFileSpec, getFileSystemEntries); if (isLegalFileSpec) { regexFileMatch = new Regex(str4, RegexOptions.IgnoreCase); } else { regexFileMatch = null; } }
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); } } }
/// <summary> /// Given a filespec, find the files that match. /// Will never throw IO exceptions: if there is no match, returns the input verbatim. /// </summary> /// <param name="projectDirectoryUnescaped">The project directory.</param> /// <param name="filespecUnescaped">Get files that match the given file spec.</param> /// <param name="getFileSystemEntries">Get files that match the given file spec.</param> /// <param name="directoryExists">Determine whether a directory exists.</param> /// <returns>The array of files.</returns> internal static string[] GetFiles ( string projectDirectoryUnescaped, string filespecUnescaped, IEnumerable<string> excludeSpecsUnescaped, GetFileSystemEntries getFileSystemEntries, DirectoryExists directoryExists ) { // For performance. Short-circuit iff there is no wildcard. // Perf Note: Doing a [Last]IndexOfAny(...) is much faster than compiling a // regular expression that does the same thing, regardless of whether // filespec contains one of the characters. // Choose LastIndexOfAny instead of IndexOfAny because it seems more likely // that wildcards will tend to be towards the right side. if (!HasWildcards(filespecUnescaped)) { return CreateArrayWithSingleItemIfNotExcluded(filespecUnescaped, excludeSpecsUnescaped); } // UNDONE (perf): Short circuit the complex processing when we only have a path and a wildcarded filename /* * Analyze the file spec and get the information we need to do the matching. */ bool stripProjectDirectory; RecursionState state; var action = GetFileSearchData(projectDirectoryUnescaped, filespecUnescaped, getFileSystemEntries, directoryExists, out stripProjectDirectory, out state); if (action == SearchAction.ReturnEmptyList) { return new string[0]; } else if (action == SearchAction.ReturnFileSpec) { return CreateArrayWithSingleItemIfNotExcluded(filespecUnescaped, excludeSpecsUnescaped); } else if (action != SearchAction.RunSearch) { // This means the enum value wasn't valid (or a new one was added without updating code correctly) throw new NotSupportedException(action.ToString()); } List<RecursionState> searchesToExclude = null; // Exclude searches which will become active when the recursive search reaches their BaseDirectory. // The BaseDirectory of the exclude search is the key for this dictionary. Dictionary<string, List<RecursionState>> searchesToExcludeInSubdirs = null; HashSet<string> resultsToExclude = null; if (excludeSpecsUnescaped != null) { searchesToExclude = new List<RecursionState>(); foreach (string excludeSpec in excludeSpecsUnescaped) { // This is ignored, we always use the include pattern's value for stripProjectDirectory bool excludeStripProjectDirectory; RecursionState excludeState; var excludeAction = GetFileSearchData(projectDirectoryUnescaped, excludeSpec, getFileSystemEntries, directoryExists, out excludeStripProjectDirectory, out excludeState); if (excludeAction == SearchAction.ReturnFileSpec) { if (resultsToExclude == null) { resultsToExclude = new HashSet<string>(); } resultsToExclude.Add(excludeSpec); } else if (excludeAction == SearchAction.ReturnEmptyList) { // Nothing to do continue; } else if (excludeAction != SearchAction.RunSearch) { // This means the enum value wasn't valid (or a new one was added without updating code correctly) throw new NotSupportedException(excludeAction.ToString()); } var excludeBaseDirectoryNormalized = excludeState.BaseDirectory.NormalizeForPathComparison(); var includeBaseDirectoryNormalized = state.BaseDirectory.NormalizeForPathComparison(); if (excludeBaseDirectoryNormalized != includeBaseDirectoryNormalized) { // What to do if the BaseDirectory for the exclude search doesn't match the one for inclusion? // - If paths don't match (one isn't a prefix of the other), then ignore the exclude search. Examples: // - c:\Foo\ - c:\Bar\ // - c:\Foo\Bar\ - C:\Foo\Baz\ // - c:\Foo\ - c:\Foo2\ if (excludeBaseDirectoryNormalized.Length == includeBaseDirectoryNormalized.Length) { // Same length, but different paths. Ignore this exclude search continue; } else if (excludeBaseDirectoryNormalized.Length > includeBaseDirectoryNormalized.Length) { if (!excludeBaseDirectoryNormalized.StartsWith(includeBaseDirectoryNormalized)) { // Exclude path is longer, but doesn't start with include path. So ignore it. continue; } // - The exclude BaseDirectory is somewhere under the include BaseDirectory. So // keep the exclude search, but don't do any processing on it while recursing until the baseDirectory // in the recursion matches the exclude BaseDirectory. Examples: // - Include - Exclude // - C:\git\msbuild\ - c:\git\msbuild\obj\ // - C:\git\msbuild\ - c:\git\msbuild\src\Common\ if (searchesToExcludeInSubdirs == null) { searchesToExcludeInSubdirs = new Dictionary<string, List<RecursionState>>(); } List<RecursionState> listForSubdir; if (!searchesToExcludeInSubdirs.TryGetValue(excludeBaseDirectoryNormalized, out listForSubdir)) { listForSubdir = new List<RecursionState>(); // The normalization fixes https://github.com/Microsoft/msbuild/issues/917 // and is a partial fix for https://github.com/Microsoft/msbuild/issues/724 searchesToExcludeInSubdirs[excludeBaseDirectoryNormalized] = listForSubdir; } listForSubdir.Add(excludeState); } else { // Exclude base directory length is less than include base directory length. if (!state.BaseDirectory.StartsWith(excludeState.BaseDirectory)) { // Include path is longer, but doesn't start with the exclude path. So ignore exclude path // (since it won't match anything under the include path) continue; } // Now check the wildcard part if (excludeState.RemainingWildcardDirectory.Length == 0) { // The wildcard part is empty, so ignore the exclude search, as it's looking for files non-recursively // in a folder higher up than the include baseDirectory. // Example: include="c:\git\msbuild\src\Framework\**\*.cs" exclude="c:\git\msbuild\*.cs" continue; } else if (IsRecursiveDirectoryMatch(excludeState.RemainingWildcardDirectory)) { // The wildcard part is exactly "**\", so the exclude pattern will apply to everything in the include // pattern, so simply update the exclude's BaseDirectory to be the same as the include baseDirectory // Example: include="c:\git\msbuild\src\Framework\**\*.*" exclude="c:\git\msbuild\**\*.bak" excludeState.BaseDirectory = state.BaseDirectory; searchesToExclude.Add(excludeState); } else { // The wildcard part is non-empty and not "**\", so we will need to match it with a Regex. Fortunately // these conditions mean that it needs to be matched with a Regex anyway, so here we will update the // BaseDirectory to be the same as the exclude BaseDirectory, and change the wildcard part to be "**\" // because we don't know where the different parts of the exclude wildcard part would be matched. // Example: include="c:\git\msbuild\src\Framework\**\*.*" exclude="c:\git\msbuild\**\bin\**\*.*" Debug.Assert(excludeState.SearchData.RegexFileMatch != null, "Expected Regex to be used for exclude file matching"); excludeState.BaseDirectory = state.BaseDirectory; excludeState.RemainingWildcardDirectory = recursiveDirectoryMatch + s_directorySeparator; searchesToExclude.Add(excludeState); } } } else { searchesToExclude.Add(excludeState); } } } if (searchesToExclude != null && searchesToExclude.Count == 0) { searchesToExclude = null; } /* * Even though we return a string[] we work internally with an IList. * This is because it's cheaper to add items to an IList and this code * might potentially do a lot of that. */ var listOfFiles = new List<string>(); /* * Now get the files that match, starting at the lowest fixed directory. */ try { GetFilesRecursive( listOfFiles, state, projectDirectoryUnescaped, stripProjectDirectory, getFileSystemEntries, searchesToExclude, searchesToExcludeInSubdirs); } catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { // Assume it's not meant to be a path return CreateArrayWithSingleItemIfNotExcluded(filespecUnescaped, excludeSpecsUnescaped); } /* * Build the return array. */ var files = resultsToExclude != null ? listOfFiles.Where(f => !resultsToExclude.Contains(f)).ToArray() : listOfFiles.ToArray(); return files; }
/// <summary> /// Given a filespec, get the information needed for file matching. /// </summary> /// <param name="filespec">The filespec.</param> /// <param name="regexFileMatch">Receives the regular expression.</param> /// <param name="needsRecursion">Receives the flag that is true if recursion is required.</param> /// <param name="isLegalFileSpec">Receives the flag that is true if the filespec is legal.</param> /// <param name="getFileSystemEntries">Delegate.</param> internal static void GetFileSpecInfo ( string filespec, out Regex regexFileMatch, out bool needsRecursion, out bool isLegalFileSpec, GetFileSystemEntries getFileSystemEntries ) { string fixedDirectoryPart; string wildcardDirectoryPart; string filenamePart; string matchFileExpression; GetFileSpecInfo(filespec, out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart, out matchFileExpression, out needsRecursion, out isLegalFileSpec, getFileSystemEntries); if (isLegalFileSpec) { regexFileMatch = new Regex(matchFileExpression, RegexOptions.IgnoreCase); } else { regexFileMatch = null; } }
private static void GetFilesRecursive(IList listOfFiles, string baseDirectory, string remainingWildcardDirectory, string filespec, int extensionLengthToEnforce, Regex regexFileMatch, bool needsRecursion, string projectDirectory, bool stripProjectDirectory, GetFileSystemEntries getFileSystemEntries) { Microsoft.Build.Shared.ErrorUtilities.VerifyThrow((filespec == null) || (regexFileMatch == null), "File-spec overrides the regular expression -- pass null for file-spec if you want to use the regular expression."); Microsoft.Build.Shared.ErrorUtilities.VerifyThrow((filespec != null) || (regexFileMatch != null), "Need either a file-spec or a regular expression to match files."); Microsoft.Build.Shared.ErrorUtilities.VerifyThrow(remainingWildcardDirectory != null, "Expected non-null remaning wildcard directory."); bool flag = false; if (remainingWildcardDirectory.Length == 0) { flag = true; } else if (remainingWildcardDirectory.IndexOf("**", StringComparison.Ordinal) == 0) { flag = true; } if (flag) { foreach (string str in getFileSystemEntries(FileSystemEntity.Files, baseDirectory, filespec, projectDirectory, stripProjectDirectory)) { if (((filespec != null) || regexFileMatch.IsMatch(str)) && (((filespec == null) || (extensionLengthToEnforce == 0)) || (Path.GetExtension(str).Length == extensionLengthToEnforce))) { listOfFiles.Add(str); } } } if (needsRecursion && (remainingWildcardDirectory.Length > 0)) { string pattern = null; if (remainingWildcardDirectory != "**") { int length = remainingWildcardDirectory.IndexOfAny(directorySeparatorCharacters); Microsoft.Build.Shared.ErrorUtilities.VerifyThrow(length != -1, "Slash should be guaranteed."); pattern = remainingWildcardDirectory.Substring(0, length); remainingWildcardDirectory = remainingWildcardDirectory.Substring(length + 1); if (pattern == "**") { pattern = null; remainingWildcardDirectory = "**"; } } foreach (string str3 in getFileSystemEntries(FileSystemEntity.Directories, baseDirectory, pattern, null, false)) { GetFilesRecursive(listOfFiles, str3, remainingWildcardDirectory, filespec, extensionLengthToEnforce, regexFileMatch, true, projectDirectory, stripProjectDirectory, getFileSystemEntries); } } }
/// <summary> /// Given a filespec, get the information needed for file matching. /// </summary> /// <param name="filespec">The filespec.</param> /// <param name="fixedDirectoryPart">Receives the fixed directory part.</param> /// <param name="wildcardDirectoryPart">Receives the wildcard directory part.</param> /// <param name="filenamePart">Receives the filename part.</param> /// <param name="matchFileExpression">Receives the regular expression.</param> /// <param name="needsRecursion">Receives the flag that is true if recursion is required.</param> /// <param name="isLegalFileSpec">Receives the flag that is true if the filespec is legal.</param> /// <param name="getFileSystemEntries">Delegate.</param> private static void GetFileSpecInfo ( string filespec, out string fixedDirectoryPart, out string wildcardDirectoryPart, out string filenamePart, out string matchFileExpression, out bool needsRecursion, out bool isLegalFileSpec, GetFileSystemEntries getFileSystemEntries ) { isLegalFileSpec = true; needsRecursion = false; fixedDirectoryPart = String.Empty; wildcardDirectoryPart = String.Empty; filenamePart = String.Empty; matchFileExpression = null; // bail out if filespec contains illegal characters if (-1 != filespec.IndexOfAny(s_invalidPathChars)) { isLegalFileSpec = false; return; } /* * Check for patterns in the filespec that are explicitly illegal. * * Any path with "..." in it is illegal. */ if (-1 != filespec.IndexOf("...", StringComparison.Ordinal)) { isLegalFileSpec = false; return; } /* * If there is a ':' anywhere but the second character, this is an illegal pattern. * Catches this case among others, * * http://www.website.com * */ int rightmostColon = filespec.LastIndexOf(":", StringComparison.Ordinal); if ( -1 != rightmostColon && 1 != rightmostColon ) { isLegalFileSpec = false; return; } /* * Now break up the filespec into constituent parts--fixed, wildcard and filename. */ SplitFileSpec(filespec, out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart, getFileSystemEntries); /* * Get a regular expression for matching files that will be found. */ matchFileExpression = RegularExpressionFromFileSpec(fixedDirectoryPart, wildcardDirectoryPart, filenamePart, out isLegalFileSpec); /* * Was the filespec valid? If not, then just return now. */ if (!isLegalFileSpec) { return; } /* * Determine whether recursion will be required. */ needsRecursion = (wildcardDirectoryPart.Length != 0); }
internal static string GetLongPathName(string path, GetFileSystemEntries getFileSystemEntries) { string str; if (path.IndexOf("~", StringComparison.Ordinal) == -1) { return path; } Microsoft.Build.Shared.ErrorUtilities.VerifyThrow(!HasWildcards(path), "GetLongPathName does not handle wildcards and was passed '{0}'.", path); string[] strArray = path.Split(directorySeparatorCharacters); int num = 0; if (path.StartsWith(directorySeparator + directorySeparator, StringComparison.Ordinal)) { str = ((directorySeparator + directorySeparator) + strArray[2] + directorySeparator) + strArray[3] + directorySeparator; num = 4; } else if ((path.Length > 2) && (path[1] == ':')) { str = strArray[0] + directorySeparator; num = 1; } else { str = string.Empty; num = 0; } string[] strArray2 = new string[strArray.Length - num]; string str2 = str; for (int i = num; i < strArray.Length; i++) { if (strArray[i].Length == 0) { strArray2[i - num] = string.Empty; } else if (strArray[i].IndexOf("~", StringComparison.Ordinal) == -1) { strArray2[i - num] = strArray[i]; str2 = Path.Combine(str2, strArray[i]); } else { string[] strArray3 = getFileSystemEntries(FileSystemEntity.FilesAndDirectories, str2, strArray[i], null, false); if (strArray3.Length == 0) { for (int j = i; j < strArray.Length; j++) { strArray2[j - num] = strArray[j]; } break; } Microsoft.Build.Shared.ErrorUtilities.VerifyThrow(strArray3.Length == 1, "Unexpected number of entries ({3}) found when enumerating '{0}' under '{1}'. Original path was '{2}'", strArray[i], str2, path, strArray3.Length); str2 = strArray3[0]; strArray2[i - num] = Path.GetFileName(str2); } } return (str + string.Join(directorySeparator, strArray2)); }
internal static void SplitFileSpec(string filespec, out string fixedDirectoryPart, out string wildcardDirectoryPart, out string filenamePart, GetFileSystemEntries getFileSystemEntries) { PreprocessFileSpecForSplitting(filespec, out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart); if ("**" == filenamePart) { wildcardDirectoryPart = wildcardDirectoryPart + "**"; wildcardDirectoryPart = wildcardDirectoryPart + directorySeparator; filenamePart = "*.*"; } fixedDirectoryPart = GetLongPathName(fixedDirectoryPart, getFileSystemEntries); }
/// <summary> /// Given a filespec, find the files that match. /// Will never throw IO exceptions: if there is no match, returns the input verbatim. /// </summary> /// <param name="projectDirectoryUnescaped">The project directory.</param> /// <param name="filespecUnescaped">Get files that match the given file spec.</param> /// <param name="getFileSystemEntries">Get files that match the given file spec.</param> /// <param name="directoryExists">Determine whether a directory exists.</param> /// <returns>The array of files.</returns> internal static string[] GetFiles ( string projectDirectoryUnescaped, string filespecUnescaped, GetFileSystemEntries getFileSystemEntries, DirectoryExists directoryExists ) { // For performance. Short-circuit iff there is no wildcard. // Perf Note: Doing a [Last]IndexOfAny(...) is much faster than compiling a // regular expression that does the same thing, regardless of whether // filespec contains one of the characters. // Choose LastIndexOfAny instead of IndexOfAny because it seems more likely // that wildcards will tend to be towards the right side. if (!HasWildcards(filespecUnescaped)) { return new string[] { filespecUnescaped }; } // UNDONE (perf): Short circuit the complex processing when we only have a path and a wildcarded filename /* * Even though we return a string[] we work internally with an IList. * This is because it's cheaper to add items to an IList and this code * might potentially do a lot of that. */ System.Collections.ArrayList arrayListOfFiles = new System.Collections.ArrayList(); System.Collections.IList listOfFiles = (System.Collections.IList)arrayListOfFiles; /* * Analyze the file spec and get the information we need to do the matching. */ string fixedDirectoryPart; string wildcardDirectoryPart; string filenamePart; string matchFileExpression; bool needsRecursion; bool isLegalFileSpec; GetFileSpecInfo ( filespecUnescaped, out fixedDirectoryPart, out wildcardDirectoryPart, out filenamePart, out matchFileExpression, out needsRecursion, out isLegalFileSpec, getFileSystemEntries ); /* * If the filespec is invalid, then just return now. */ if (!isLegalFileSpec) { return new string[] { filespecUnescaped }; } // The projectDirectory is not null only if we are running the evaluation from // inside the engine (i.e. not from a task) bool stripProjectDirectory = false; if (projectDirectoryUnescaped != null) { if (fixedDirectoryPart != null) { string oldFixedDirectoryPart = fixedDirectoryPart; try { fixedDirectoryPart = Path.Combine(projectDirectoryUnescaped, fixedDirectoryPart); } catch (ArgumentException) { return new string[0]; } stripProjectDirectory = !String.Equals(fixedDirectoryPart, oldFixedDirectoryPart, StringComparison.OrdinalIgnoreCase); } else { fixedDirectoryPart = projectDirectoryUnescaped; stripProjectDirectory = true; } } /* * If the fixed directory part doesn't exist, then this means no files should be * returned. */ if (fixedDirectoryPart.Length > 0 && !directoryExists(fixedDirectoryPart)) { return new string[0]; } // determine if we need to use the regular expression to match the files // PERF NOTE: Constructing a Regex object is expensive, so we avoid it whenever possible bool matchWithRegex = // if we have a directory specification that uses wildcards, and (wildcardDirectoryPart.Length > 0) && // the specification is not a simple "**" (wildcardDirectoryPart != (recursiveDirectoryMatch + s_directorySeparator)); // then we need to use the regular expression // if we're not using the regular expression, get the file pattern extension string extensionPart = matchWithRegex ? null : Path.GetExtension(filenamePart); // check if the file pattern would cause Windows to match more loosely on the extension // NOTE: Windows matches loosely in two cases (in the absence of the * wildcard in the extension): // 1) if the extension ends with the ? wildcard, it matches files with shorter extensions also e.g. "file.tx?" would // match both "file.txt" and "file.tx" // 2) if the extension is three characters, and the filename contains the * wildcard, it matches files with longer // extensions that start with the same three characters e.g. "*.htm" would match both "file.htm" and "file.html" bool needToEnforceExtensionLength = (extensionPart != null) && (extensionPart.IndexOf('*') == -1) && (extensionPart.EndsWith("?", StringComparison.Ordinal) || ((extensionPart.Length == (3 + 1 /* +1 for the period */)) && (filenamePart.IndexOf('*') != -1))); /* * Now get the files that match, starting at the lowest fixed directory. */ try { GetFilesRecursive(listOfFiles, fixedDirectoryPart, wildcardDirectoryPart, // if using the regular expression, ignore the file pattern (matchWithRegex ? null : filenamePart), (needToEnforceExtensionLength ? extensionPart.Length : 0), // if using the file pattern, ignore the regular expression (matchWithRegex ? new Regex(matchFileExpression, RegexOptions.IgnoreCase) : null), needsRecursion, projectDirectoryUnescaped, stripProjectDirectory, getFileSystemEntries); } catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { // Assume it's not meant to be a path return new[] { filespecUnescaped }; } /* * Build the return array. */ string[] files = (string[])arrayListOfFiles.ToArray(typeof(string)); return files; }
/// <summary> /// Given a pattern (filespec) and a candidate filename (fileToMatch) /// return matching information. /// </summary> /// <param name="filespec">The filespec.</param> /// <param name="fileToMatch">The candidate to match against.</param> /// <param name="getFileSystemEntries">Delegate.</param> /// <returns>The result class.</returns> internal static Result FileMatch ( string filespec, string fileToMatch, GetFileSystemEntries getFileSystemEntries ) { Result matchResult = new Result(); fileToMatch = GetLongPathName(fileToMatch, getFileSystemEntries); Regex regexFileMatch; GetFileSpecInfo ( filespec, out regexFileMatch, out matchResult.isFileSpecRecursive, out matchResult.isLegalFileSpec, getFileSystemEntries ); if (matchResult.isLegalFileSpec) { Match match = regexFileMatch.Match(fileToMatch); matchResult.isMatch = match.Success; if (matchResult.isMatch) { matchResult.fixedDirectoryPart = match.Groups["FIXEDDIR"].Value; matchResult.wildcardDirectoryPart = match.Groups["WILDCARDDIR"].Value; matchResult.filenamePart = match.Groups["FILENAME"].Value; } } return matchResult; }