Skip to content

Commit

Permalink
Optimize FileSpecMatcherTester to match common patterns without regex'es
Browse files Browse the repository at this point in the history
  • Loading branch information
ladipro committed Feb 12, 2021
1 parent f3cf715 commit 3987dc5
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 13 deletions.
56 changes: 44 additions & 12 deletions src/Build/Utilities/FileSpecMatchTester.cs
Expand Up @@ -12,54 +12,85 @@ namespace Microsoft.Build.Internal
{
private readonly string _currentDirectory;
private readonly string _unescapedFileSpec;
private readonly string _filenamePattern;
private readonly Regex _regex;

private FileSpecMatcherTester(string currentDirectory, string unescapedFileSpec, Regex regex)
private FileSpecMatcherTester(string currentDirectory, string unescapedFileSpec, string filenamePattern, Regex regex)
{
Debug.Assert(!string.IsNullOrEmpty(unescapedFileSpec));

_currentDirectory = currentDirectory;
_unescapedFileSpec = unescapedFileSpec;
_filenamePattern = filenamePattern;
_regex = regex;
}

public static FileSpecMatcherTester Parse(string currentDirectory, string fileSpec)
{
string unescapedFileSpec = EscapingUtilities.UnescapeAll(fileSpec);
Regex regex = EngineFileUtilities.FilespecHasWildcards(fileSpec) ? CreateRegex(unescapedFileSpec, currentDirectory) : null;
string filenamePattern = null;
Regex regex = null;

return new FileSpecMatcherTester(currentDirectory, unescapedFileSpec, regex);
if (EngineFileUtilities.FilespecHasWildcards(fileSpec))
{
CreateRegexOrFilenamePattern(unescapedFileSpec, currentDirectory, out filenamePattern, out regex);
}

return new FileSpecMatcherTester(currentDirectory, unescapedFileSpec, filenamePattern, regex);
}

public bool IsMatch(string fileToMatch)
{
Debug.Assert(!string.IsNullOrEmpty(fileToMatch));

// check if there is a regex matching the file
// We do the matching using one of three code paths, depending on the value of _filenamePattern and _regex.
if (_regex != null)
{
var normalizedFileToMatch = FileUtilities.GetFullPathNoThrow(Path.Combine(_currentDirectory, fileToMatch));
return _regex.IsMatch(normalizedFileToMatch);
}

if (_filenamePattern != null)
{
// Check file name first as it's more likely to not match.
string filename = Path.GetFileName(fileToMatch);
if (!FileMatcher.IsMatch(filename, _filenamePattern))
{
return false;
}

var normalizedFileToMatch = FileUtilities.GetFullPathNoThrow(Path.Combine(_currentDirectory, fileToMatch));
return normalizedFileToMatch.StartsWith(_currentDirectory, System.StringComparison.OrdinalIgnoreCase);
}

return FileUtilities.ComparePathsNoThrow(_unescapedFileSpec, fileToMatch, _currentDirectory, alwaysIgnoreCase: true);
}

// this method parses the glob and extracts the fixed directory part in order to normalize it and make it absolute
// without this normalization step, strings pointing outside the globbing cone would still match when they shouldn't
// for example, we dont want "**/*.cs" to match "../Shared/Foo.cs"
// todo: glob rooting knowledge partially duplicated with MSBuildGlob.Parse and FileMatcher.ComputeFileEnumerationCacheKey
private static Regex CreateRegex(string unescapedFileSpec, string currentDirectory)
private static void CreateRegexOrFilenamePattern(string unescapedFileSpec, string currentDirectory, out string filenamePattern, out Regex regex)
{
FileMatcher.Default.SplitFileSpec(
unescapedFileSpec,
out string fixedDirPart,
out string wildcardDirectoryPart,
out string filenamePart);
unescapedFileSpec,
out string fixedDirPart,
out string wildcardDirectoryPart,
out string filenamePart);

if (FileUtilities.PathIsInvalid(fixedDirPart))
{
return null;
filenamePattern = null;
regex = null;
return;
}

// Most file specs have "**" as their directory specification so we special case these and make matching faster.
if (string.IsNullOrEmpty(fixedDirPart) && FileMatcher.IsRecursiveDirectoryMatch(wildcardDirectoryPart))
{
filenamePattern = filenamePart;
regex = null;
return;
}

var absoluteFixedDirPart = Path.Combine(currentDirectory, fixedDirPart);
Expand All @@ -74,11 +105,12 @@ private static Regex CreateRegex(string unescapedFileSpec, string currentDirecto

FileMatcher.Default.GetFileSpecInfoWithRegexObject(
recombinedFileSpec,
out Regex regex,
out Regex regexObject,
out bool _,
out bool isLegal);

return isLegal ? regex : null;
filenamePattern = null;
regex = isLegal ? regexObject : null;
}
}
}
2 changes: 1 addition & 1 deletion src/Shared/FileMatcher.cs
Expand Up @@ -2535,6 +2535,6 @@ private static bool DirectoryEndsWithPattern(string directoryPath, string patter
return (index != -1 && IsMatch(directoryPath.Substring(index + 1), pattern));
}

private static bool IsRecursiveDirectoryMatch(string path) => path.TrimTrailingSlashes() == recursiveDirectoryMatch;
internal static bool IsRecursiveDirectoryMatch(string path) => path.TrimTrailingSlashes() == recursiveDirectoryMatch;
}
}

0 comments on commit 3987dc5

Please sign in to comment.