From 17526c3c8c6a50211ac356c629fea3cbf19393f4 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 17 Jul 2026 02:05:50 +0000 Subject: [PATCH 1/4] Fix undesired regressions that happened due to incremental codeflows at commit `463e0cf2f2b2fb9b136f6440341ff1e7b0a93acf` --- eng/_._ | 0 src/Build/Evaluation/ToolsetElement.cs | 749 +---- src/Framework.UnitTests/FileMatcher_Tests.cs | 2699 +---------------- .../BackEnd/InterningBinaryReader.cs | 10 +- src/Framework/BackEnd/TranslatorHelpers.cs | 430 +-- src/Framework/BuildEnvironmentHelper.cs | 10 +- .../Collections/CopyOnWriteDictionary.cs | 410 +-- .../Collections/ReadOnlyEmptyDictionary.cs | 10 +- src/Framework/Loader/LoadedType.cs | 10 +- .../MultiThreadedTaskEnvironmentDriver.cs | 10 +- src/Framework/NativeMethods.cs | 13 +- src/Framework/SolutionConfiguration.cs | 151 +- src/Framework/Utilities/AssemblyLoadInfo.cs | 10 +- src/Framework/Utilities/CollectionHelpers.cs | 84 +- src/Framework/Utilities/FileMatcher.cs | 10 +- .../Utilities/FrameworkLocationHelper.cs | 10 +- .../Tasks/InstallDotNetCoreTask.cs | 23 +- src/Shared/BinaryReaderExtensions.cs | 6 + src/Shared/BinaryWriterExtensions.cs | 6 + src/Shared/Constants.cs | 6 + src/Shared/EnvironmentUtilities.cs | 6 + src/Shared/EscapingUtilities.cs | 6 + src/Shared/ExceptionHandling.cs | 6 + .../FileSystem/CachingFileSystemWrapper.cs | 6 + src/Shared/FileSystem/FileSystems.cs | 6 + src/Shared/FileSystem/IFileSystem.cs | 6 + .../FileSystem/MSBuildOnWindowsFileSystem.cs | 6 + src/Shared/FileSystem/ManagedFileSystem.cs | 6 + src/Shared/FileSystem/NativeWin32Exception.cs | 6 + src/Shared/FileSystem/SafeFileHandle.cs | 6 + src/Shared/FileSystem/WindowsFileSystem.cs | 6 + src/Shared/FileSystem/WindowsNative.cs | 6 + src/Shared/FileSystemSources.proj | 6 + src/Shared/FileUtilities.cs | 6 + src/Shared/FileUtilitiesRegex.cs | 6 + src/Shared/IMSBuildElementLocation.cs | 6 + src/Shared/Modifiers.cs | 6 + .../Resources/xlf/Strings.shared.cs.xlf | 2 +- .../Resources/xlf/Strings.shared.de.xlf | 2 +- .../Resources/xlf/Strings.shared.es.xlf | 2 +- .../Resources/xlf/Strings.shared.fr.xlf | 2 +- .../Resources/xlf/Strings.shared.it.xlf | 2 +- .../Resources/xlf/Strings.shared.ja.xlf | 2 +- .../Resources/xlf/Strings.shared.ko.xlf | 2 +- .../Resources/xlf/Strings.shared.pl.xlf | 2 +- .../Resources/xlf/Strings.shared.pt-BR.xlf | 2 +- .../Resources/xlf/Strings.shared.ru.xlf | 2 +- .../Resources/xlf/Strings.shared.tr.xlf | 2 +- .../Resources/xlf/Strings.shared.zh-Hans.xlf | 2 +- .../Resources/xlf/Strings.shared.zh-Hant.xlf | 2 +- src/Shared/TempFileUtilities.cs | 6 + .../UnitTests/EscapingUtilities_Tests.cs | 6 + src/Shared/UnitTests/FileUtilities_Tests.cs | 6 + 53 files changed, 274 insertions(+), 4529 deletions(-) mode change 100644 => 100755 eng/_._ create mode 100644 src/Shared/BinaryReaderExtensions.cs create mode 100644 src/Shared/BinaryWriterExtensions.cs create mode 100644 src/Shared/Constants.cs create mode 100644 src/Shared/EnvironmentUtilities.cs create mode 100644 src/Shared/EscapingUtilities.cs create mode 100644 src/Shared/ExceptionHandling.cs create mode 100644 src/Shared/FileSystem/CachingFileSystemWrapper.cs create mode 100644 src/Shared/FileSystem/FileSystems.cs create mode 100644 src/Shared/FileSystem/IFileSystem.cs create mode 100644 src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs create mode 100644 src/Shared/FileSystem/ManagedFileSystem.cs create mode 100644 src/Shared/FileSystem/NativeWin32Exception.cs create mode 100644 src/Shared/FileSystem/SafeFileHandle.cs create mode 100644 src/Shared/FileSystem/WindowsFileSystem.cs create mode 100644 src/Shared/FileSystem/WindowsNative.cs create mode 100644 src/Shared/FileSystemSources.proj create mode 100644 src/Shared/FileUtilities.cs create mode 100644 src/Shared/FileUtilitiesRegex.cs create mode 100644 src/Shared/IMSBuildElementLocation.cs create mode 100644 src/Shared/Modifiers.cs create mode 100644 src/Shared/TempFileUtilities.cs create mode 100644 src/Shared/UnitTests/EscapingUtilities_Tests.cs create mode 100644 src/Shared/UnitTests/FileUtilities_Tests.cs diff --git a/eng/_._ b/eng/_._ old mode 100644 new mode 100755 diff --git a/src/Build/Evaluation/ToolsetElement.cs b/src/Build/Evaluation/ToolsetElement.cs index 9b6945eaf86..3ed04d2188b 100644 --- a/src/Build/Evaluation/ToolsetElement.cs +++ b/src/Build/Evaluation/ToolsetElement.cs @@ -1,745 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.IO; -using Microsoft.Build.Collections; -using Microsoft.Build.Framework; -using Microsoft.Build.Shared; - -#nullable disable - -namespace Microsoft.Build.Evaluation -{ - /// - /// Helper class for reading toolsets out of the configuration file. - /// - internal static class ToolsetConfigurationReaderHelpers - { - /// - /// Lock for process wide ToolsetConfigurationSection section cache - /// - private static readonly LockType s_syncLock = new LockType(); - - /// - /// Process wide ToolsetConfigurationSection section cache - /// - private static ToolsetConfigurationSection s_toolsetConfigurationSectionCache; - private static Configuration s_configurationOfCachedSection; - - internal static ToolsetConfigurationSection ReadToolsetConfigurationSection(Configuration configuration) - { - if (configuration == null) - { - return null; - } - - lock (s_syncLock) - { - // Cache 1st requested configuration section. In unit tests, different Configuration is provided for particular test cases. - // During runtime, however, only MSBuild exe configuration file is provided to read toolset configuration from, - // and modifying MSBuild exe configuration during lifetime of msbuild nodes is neither expected nor supported. - if (s_toolsetConfigurationSectionCache == null) - { - s_toolsetConfigurationSectionCache = GetToolsetConfigurationSection(configuration); - s_configurationOfCachedSection = configuration; - } - - return s_configurationOfCachedSection == configuration ? - s_toolsetConfigurationSectionCache : - GetToolsetConfigurationSection(configuration); - } - } - - private static ToolsetConfigurationSection GetToolsetConfigurationSection(Configuration configuration) - { - ToolsetConfigurationSection configurationSection = null; - - // This will be null if the application config file does not have the following section - // definition for the msbuildToolsets section as the first child element. - // - //
- // "; - // Note that the application config file may or may not contain an msbuildToolsets element. - // For example: - // If section definition is present and section is not present, this value is not null - // If section definition is not present and section is also not present, this value is null - // If the section definition is not present and section is present, then this value is null - if (configuration != null) - { - ConfigurationSection msbuildSection = configuration.GetSection("msbuildToolsets"); - configurationSection = msbuildSection as ToolsetConfigurationSection; - - if (configurationSection == null && msbuildSection != null) // we found msbuildToolsets but the wrong type of handler - { - if (String.IsNullOrEmpty(msbuildSection.SectionInformation.Type) || - msbuildSection.SectionInformation.Type.IndexOf("Microsoft.Build", StringComparison.OrdinalIgnoreCase) >= 0) - { - // Set the configuration type handler to the current ToolsetConfigurationSection type - msbuildSection.SectionInformation.Type = typeof(ToolsetConfigurationSection).AssemblyQualifiedName; - - try - { - // fabricate a temporary config file with the correct section handler type in it - string tempFileName = FileUtilities.GetTemporaryFile(); - - // Save the modified config - configuration.SaveAs(tempFileName + ".config"); - - // Open the configuration again, the new type for the section handler will do its stuff - // Note that the OpenExeConfiguration call uses the config filename *without* the .config - // extension - configuration = ConfigurationManager.OpenExeConfiguration(tempFileName); - - // Get the toolset information from the section using our real handler - configurationSection = configuration.GetSection("msbuildToolsets") as ToolsetConfigurationSection; - - File.Delete(tempFileName + ".config"); - File.Delete(tempFileName); - } - catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) - { - } - } - } - } - - return configurationSection; - } - } - - /// - /// Class representing the Toolset element - /// - /// - /// Internal for unit testing only - /// - internal sealed class ToolsetElement : ConfigurationElement - { - /// - /// ToolsVersion attribute of the element - /// - [ConfigurationProperty("toolsVersion", IsKey = true, IsRequired = true)] - public string toolsVersion - { - get - { - return (string)base[nameof(toolsVersion)]; - } - - set - { - base[nameof(toolsVersion)] = value; - } - } - - /// - /// Property element collection - /// - [ConfigurationProperty("", IsDefaultCollection = true)] - public PropertyElementCollection PropertyElements - { - get - { - return (PropertyElementCollection)base[""]; - } - } - - /// - /// Collection of all the search paths for project imports, per OS - /// - [ConfigurationProperty("projectImportSearchPaths")] - public ExtensionsPathsElementCollection AllProjectImportSearchPaths - { - get - { - return (ExtensionsPathsElementCollection)base["projectImportSearchPaths"]; - } - } - - /// - /// Class representing all the per-OS search paths for MSBuildExtensionsPath* - /// - internal sealed class ExtensionsPathsElementCollection : ConfigurationElementCollection - { - /// - /// We use this dictionary to track whether or not we've seen a given - /// searchPaths definition before, since the .NET configuration classes - /// won't perform this check without respect for case. - /// - private Dictionary _previouslySeenOS = new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Type of the collection - /// This has to be public as cannot change access modifier when overriding - /// - public override ConfigurationElementCollectionType CollectionType - { - get - { - return ConfigurationElementCollectionType.BasicMap; - } - } - - /// - /// Throw exception if an element with a duplicate key is added to the collection - /// - protected override bool ThrowOnDuplicate - { - get - { - return false; - } - } - - /// - /// Name of the element - /// - protected override string ElementName - { - get - { - return "searchPaths"; - } - } - - /// - /// Gets an element with the specified name - /// - /// OS of the element - /// element - public ExtensionsPathElement GetElement(string os) - { - return (ExtensionsPathElement)this.BaseGet(os); - } - - /// - /// Gets an element based at the specified position - /// - /// position - /// element - public ExtensionsPathElement GetElement(int index) - { - return (ExtensionsPathElement)this.BaseGet(index); - } - - /// - /// Returns the key value for the given element - /// - /// element whose key is returned - /// key - protected override object GetElementKey(ConfigurationElement element) - { - return ((ExtensionsPathElement)element).OS; - } - - /// - /// Creates a new element of the collection - /// - /// Created element - protected override ConfigurationElement CreateNewElement() - { - return new ExtensionsPathElement(); - } - - /// - /// overridden so we can track previously seen elements - /// - protected override void BaseAdd(int index, ConfigurationElement element) - { - UpdateOSMap(element); - - base.BaseAdd(index, element); - } - - /// - /// overridden so we can track previously seen elements - /// - protected override void BaseAdd(ConfigurationElement element) - { - UpdateOSMap(element); - - base.BaseAdd(element); - } - - /// - /// Stores the name of the OS in a case-insensitive map - /// so we can detect if it is specified more than once but with - /// different case - /// - private void UpdateOSMap(ConfigurationElement element) - { - string os = GetElementKey(element).ToString(); - - if (_previouslySeenOS.ContainsKey(os)) - { - string locationString = String.Empty; - if (!String.IsNullOrEmpty(element.ElementInformation.Source)) - { - if (element.ElementInformation.LineNumber != 0) - { - locationString = $"{element.ElementInformation.Source} ({element.ElementInformation.LineNumber})"; - } - else - { - locationString = element.ElementInformation.Source; - } - } - - string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("MultipleDefinitionsForSameExtensionsPathOS", os, locationString); - - throw new ConfigurationErrorsException(message, element.ElementInformation.Source, element.ElementInformation.LineNumber); - } - - _previouslySeenOS.Add(os, string.Empty); - } - } - - /// - /// Class representing searchPaths element for a single OS - /// - internal sealed class ExtensionsPathElement : ConfigurationElement - { - /// - /// OS attribute of the element - /// - [ConfigurationProperty("os", IsKey = true, IsRequired = true)] - public string OS - { - get - { - return (string)base["os"]; - } - - set - { - base["os"] = value; - } - } - - /// - /// Property element collection - /// - [ConfigurationProperty("", IsDefaultCollection = true)] - public PropertyElementCollection PropertyElements - { - get - { - return (PropertyElementCollection)base[""]; - } - } - } - - /// - /// Class representing collection of property elements - /// - internal sealed class PropertyElementCollection : ConfigurationElementCollection - { - #region Private Fields - - /// - /// We use this dictionary to track whether or not we've seen a given - /// property definition before, since the .NET configuration classes - /// won't perform this check without respect for case. - /// - private Dictionary _previouslySeenPropertyNames = new Dictionary(MSBuildNameIgnoreCaseComparer.Default); - - #endregion - - #region Properties - - /// - /// Collection type - /// This has to be public as cannot change access modifier when overriding - /// - public override ConfigurationElementCollectionType CollectionType - { - get - { - return ConfigurationElementCollectionType.BasicMap; - } - } - - /// - /// Throw exception if an element with a duplicate is added - /// - protected override bool ThrowOnDuplicate - { - get - { - return false; - } - } - - /// - /// name of the element - /// - protected override string ElementName - { - get - { - return "property"; - } - } - - #endregion - - #region Methods - - /// - /// Gets an element with the specified name - /// - /// name of the element - /// element - public PropertyElement GetElement(string name) - { - return (PropertyElement)this.BaseGet(name); - } - - /// - /// Gets an element at the specified position - /// - /// position - /// element - public PropertyElement GetElement(int index) - { - return (PropertyElement)this.BaseGet(index); - } - - /// - /// Creates a new element - /// - /// element - protected override ConfigurationElement CreateNewElement() - { - return new PropertyElement(); - } - - /// - /// overridden so we can track previously seen property names - /// - protected override void BaseAdd(int index, ConfigurationElement element) - { - UpdatePropertyNameMap(element); - - base.BaseAdd(index, element); - } - - /// - /// overridden so we can track previously seen property names - /// - protected override void BaseAdd(ConfigurationElement element) - { - UpdatePropertyNameMap(element); - - base.BaseAdd(element); - } - - /// - /// Gets the key for the element - /// - /// element - /// key - protected override object GetElementKey(ConfigurationElement element) - { - return ((PropertyElement)element).Name; - } - - /// - /// Stores the name of the tools version in a case-insensitive map - /// so we can detect if it is specified more than once but with - /// different case - /// - private void UpdatePropertyNameMap(ConfigurationElement element) - { - string propertyName = GetElementKey(element).ToString(); - - if (_previouslySeenPropertyNames.ContainsKey(propertyName)) - { - string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("MultipleDefinitionsForSameProperty", propertyName); - - throw new ConfigurationErrorsException(message, element.ElementInformation.Source, element.ElementInformation.LineNumber); - } - - _previouslySeenPropertyNames.Add(propertyName, string.Empty); - } - - #endregion - } - - /// - /// This class represents property element - /// - internal sealed class PropertyElement : ConfigurationElement - { - /// - /// name attribute - /// - [ConfigurationProperty("name", IsKey = true, IsRequired = true)] - public string Name - { - get - { - return (string)base["name"]; - } - - set - { - base["name"] = value; - } - } - - /// - /// value attribute - /// - [ConfigurationProperty("value", IsRequired = true)] - public string Value - { - get - { - return (string)base["value"]; - } - - set - { - base["value"] = value; - } - } - } - } - - /// - /// Class representing the collection of toolset elements - /// - /// - /// Internal for unit testing only - /// - internal sealed class ToolsetElementCollection : ConfigurationElementCollection - { - /// - /// We use this dictionary to track whether or not we've seen a given - /// toolset definition before, since the .NET configuration classes - /// won't perform this check without respect for case. - /// - private Dictionary _previouslySeenToolsVersions = new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Type of the collection - /// This has to be public as cannot change access modifier when overriding - /// - public override ConfigurationElementCollectionType CollectionType - { - get - { - return ConfigurationElementCollectionType.BasicMap; - } - } - - /// - /// Throw exception if an element with a duplicate key is added to the collection - /// - protected override bool ThrowOnDuplicate - { - get - { - return false; - } - } - - /// - /// Name of the element - /// - protected override string ElementName - { - get - { - return "toolset"; - } - } - - /// - /// Gets an element with the specified name - /// - /// toolsVersion of the element - /// element - public ToolsetElement GetElement(string toolsVersion) - { - return (ToolsetElement)this.BaseGet(toolsVersion); - } - - /// - /// Gets an element based at the specified position - /// - /// position - /// element - public ToolsetElement GetElement(int index) - { - return (ToolsetElement)this.BaseGet(index); - } - - /// - /// Returns the key value for the given element - /// - /// element whose key is returned - /// key - protected override object GetElementKey(ConfigurationElement element) - { - return ((ToolsetElement)element).toolsVersion; - } - - /// - /// Creates a new element of the collection - /// - /// Created element - protected override ConfigurationElement CreateNewElement() - { - return new ToolsetElement(); - } - - /// - /// overridden so we can track previously seen tools versions - /// - protected override void BaseAdd(int index, ConfigurationElement element) - { - UpdateToolsVersionMap(element); - - base.BaseAdd(index, element); - } - - /// - /// overridden so we can track previously seen tools versions - /// - protected override void BaseAdd(ConfigurationElement element) - { - UpdateToolsVersionMap(element); - - base.BaseAdd(element); - } - - /// - /// Stores the name of the tools version in a case-insensitive map - /// so we can detect if it is specified more than once but with - /// different case - /// - private void UpdateToolsVersionMap(ConfigurationElement element) - { - string toolsVersion = GetElementKey(element).ToString(); - - if (_previouslySeenToolsVersions.ContainsKey(toolsVersion)) - { - string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("MultipleDefinitionsForSameToolset", toolsVersion); - - throw new ConfigurationErrorsException(message, element.ElementInformation.Source, element.ElementInformation.LineNumber); - } - - _previouslySeenToolsVersions.Add(toolsVersion, string.Empty); - } - } - - /// - /// This class is used to programmatically read msbuildToolsets section - /// in from the configuration file. An example of application config file: - /// - /// <configuration> - /// <msbuildToolsets default="2.0"> - /// <toolset toolsVersion="2.0"> - /// <property name="MSBuildBinPath" value="D:\windows\Microsoft.NET\Framework\v2.0.x86ret\"/> - /// <property name="SomeOtherProperty" value="SomeOtherPropertyValue"/> - /// </toolset> - /// <toolset toolsVersion="3.5"> - /// <property name="MSBuildBinPath" value="D:\windows\Microsoft.NET\Framework\v3.5.x86ret\"/> - /// </toolset> - /// </msbuildToolsets> - /// </configuration> - /// - /// - /// - /// Internal for unit testing only - /// - internal sealed class ToolsetConfigurationSection : ConfigurationSection - { - /// - /// toolsVersion element collection - /// - [ConfigurationProperty("", IsDefaultCollection = true)] - public ToolsetElementCollection Toolsets - { - get - { - return (ToolsetElementCollection)base[""]; - } - } - - /// - /// default attribute on msbuildToolsets element, specifying the default ToolsVersion - /// - [ConfigurationProperty("default")] - public string Default - { - get - { - // The ConfigurationPropertyAttribute constructor accepts a named parameter "DefaultValue" - // that doesn't seem to work if null is the desired default value. So here we return null - // whenever the base class gives us an empty string. - // Note this means we can't distinguish between the attribute being present but containing - // an empty string for its value and the attribute not being present at all. - string defaultValue = (string)base["default"]; - return String.IsNullOrEmpty(defaultValue) ? null : defaultValue; - } - - set - { - base["default"] = value; - } - } - - /// - /// MsBuildOverrideTasksPath attribute on msbuildToolsets element, specifying the path to find msbuildOverrideTasks files - /// - [ConfigurationProperty("msbuildOverrideTasksPath")] // This string is case sensitive, can't change it - public string MSBuildOverrideTasksPath - { - get - { - // The ConfigurationPropertyAttribute constructor accepts a named parameter "DefaultValue" - // that doesn't seem to work if null is the desired default value. So here we return null - // whenever the base class gives us an empty string. - // Note this means we can't distinguish between the attribute being present but containing - // an empty string for its value and the attribute not being present at all. - string defaultValue = (string)base["msbuildOverrideTasksPath"]; - return String.IsNullOrEmpty(defaultValue) ? null : defaultValue; - } - - set - { - base["msbuildOverrideTasksPath"] = value; - } - } - - /// - /// DefaultOverrideToolsVersion attribute on msbuildToolsets element, specifying the toolsversion that should be used by - /// default to build projects with this version of MSBuild. - /// - [ConfigurationProperty("DefaultOverrideToolsVersion")] - public string DefaultOverrideToolsVersion - { - get - { - // The ConfigurationPropertyAttribute constructor accepts a named parameter "DefaultValue" - // that doesn't seem to work if null is the desired default value. So here we return null - // whenever the base class gives us an empty string. - // Note this means we can't distinguish between the attribute being present but containing - // an empty string for its value and the attribute not being present at all. - string defaultValue = (string)base[nameof(DefaultOverrideToolsVersion)]; - return String.IsNullOrEmpty(defaultValue) ? null : defaultValue; - } - - set - { - base[nameof(DefaultOverrideToolsVersion)] = value; - } - } - } -} +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Framework.UnitTests/FileMatcher_Tests.cs b/src/Framework.UnitTests/FileMatcher_Tests.cs index c0bea972965..3ed04d2188b 100644 --- a/src/Framework.UnitTests/FileMatcher_Tests.cs +++ b/src/Framework.UnitTests/FileMatcher_Tests.cs @@ -1,2695 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using Microsoft.Build.Framework; -using Microsoft.Build.Shared; -using Microsoft.Build.Shared.FileSystem; -using Microsoft.Build.UnitTests.Shared; -using Shouldly; -using Xunit; -using Xunit.Abstractions; - -#nullable disable - -namespace Microsoft.Build.UnitTests -{ - public class FileMatcherTest : IDisposable - { - private readonly TestEnvironment _env; - private Lazy _mappedDrive = DummyMappedDriveUtils.GetLazyDummyMappedDrive(); - - public FileMatcherTest(ITestOutputHelper output) - { - _env = TestEnvironment.Create(output); - } - - public void Dispose() - { - _env.Dispose(); - _mappedDrive.Value?.Dispose(); - } - - [Theory] - [InlineData("*.txt", 5)] - [InlineData("???.cs", 1)] - [InlineData("????.cs", 1)] - [InlineData("file?.txt", 1)] - [InlineData("fi?e?.txt", 2)] - [InlineData("???.*", 1)] - [InlineData("????.*", 4)] - [InlineData("*.???", 5)] - [InlineData("f??e1.txt", 2)] - [InlineData("file.*.txt", 1)] - public void GetFilesPatternMatching(string pattern, int expectedMatchCount) - { - TransientTestFolder testFolder = _env.CreateFolder(); - - foreach (var file in new[] - { - "Foo.cs", - "Foo2.cs", - "file.txt", - "file1.txt", - "file1.txtother", - "fie1.txt", - "fire1.txt", - "file.bak.txt" - }) - { - File.WriteAllBytes(Path.Combine(testFolder.Path, file), new byte[1]); - } - - string[] fileMatches = FileMatcher.Default.GetFiles(testFolder.Path, pattern).FileList; - - fileMatches.Length.ShouldBe(expectedMatchCount, $"Matches: '{String.Join("', '", fileMatches)}'"); - } - -#if FEATURE_SYMLINK_TARGET - [RequiresSymbolicLinksFact] - public void DoNotFollowRecursiveSymlinks() - { - TransientTestFolder testFolder = _env.CreateFolder(); - TransientTestFile file = _env.CreateFile(testFolder, "Foo.cs"); - TransientTestFolder tf2 = _env.CreateFolder(Path.Combine(testFolder.Path, "subfolder")); - string symlinkPath = Path.Combine(tf2.Path, "mySymlink"); - try - { - Directory.CreateSymbolicLink(symlinkPath, testFolder.Path); - string[] fileMatches = FileMatcher.Default.GetFiles(testFolder.Path, "**").FileList; - fileMatches.Length.ShouldBe(1); - } - finally - { - if (Directory.Exists(symlinkPath)) - { - Directory.Delete(symlinkPath); - } - } - } -#endif - - [Theory] - [MemberData(nameof(GetFilesComplexGlobbingMatchingInfo.GetTestData), MemberType = typeof(GetFilesComplexGlobbingMatchingInfo), DisableDiscoveryEnumeration = true)] - public void GetFilesComplexGlobbingMatching(GetFilesComplexGlobbingMatchingInfo info) - { - TransientTestFolder testFolder = _env.CreateFolder(); - - // Create directories and files - foreach (string fullPath in GetFilesComplexGlobbingMatchingInfo.FilesToCreate.Select(i => Path.Combine(testFolder.Path, i.ToPlatformSlash()))) - { - Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); - - File.WriteAllBytes(fullPath, new byte[1]); - } - - void VerifyImpl(FileMatcher fileMatcher, string include, string[] excludes, bool shouldHaveNoMatches = false, string customMessage = null) - { - string[] matchedFiles = fileMatcher.GetFiles(testFolder.Path, include, excludes?.ToList()).FileList; - - if (shouldHaveNoMatches) - { - matchedFiles.ShouldBeEmpty(customMessage); - } - else - { - // The matches are: - // 1. Normalized ("\" regardless of OS and lowercase) - // 2. Sorted - // Are the same as the expected matches sorted - matchedFiles - .Select(i => i.Replace(Path.DirectorySeparatorChar, '\\')) - .OrderBy(i => i) - .ToArray() - .ShouldBe(info.ExpectedMatches.OrderBy(i => i), caseSensitivity: Case.Insensitive, customMessage: customMessage); - } - } - - var fileMatcherWithCache = new FileMatcher(FileSystems.Default, new ConcurrentDictionary>()); - - void Verify(string include, string[] excludes, bool shouldHaveNoMatches = false, string customMessage = null) - { - // Verify using the default non-caching FileMatcher. - VerifyImpl(FileMatcher.Default, include, excludes, shouldHaveNoMatches, customMessage); - - // Verify using a caching FileMatcher and do it twice to exercise the cache. - VerifyImpl(fileMatcherWithCache, include, excludes, shouldHaveNoMatches, customMessage); - VerifyImpl(fileMatcherWithCache, include, excludes, shouldHaveNoMatches, customMessage); - } - - // Normal matching - Verify(info.Include, info.Excludes); - - // Include forward slash - Verify(info.Include.Replace('\\', '/'), info.Excludes, customMessage: "Include directory separator was changed to forward slash"); - - // Excludes forward slash - Verify(info.Include, info.Excludes?.Select(o => o.Replace('\\', '/')).ToArray(), customMessage: "Excludes directory separator was changed to forward slash"); - - // Uppercase includes - Verify(info.Include.ToUpperInvariant(), info.Excludes, info.ExpectNoMatches, "Include was changed to uppercase"); - - // Changing the case of the exclude break Linux - if (!NativeMethodsShared.IsLinux) - { - // Uppercase excludes - Verify(info.Include, info.Excludes?.Select(o => o.ToUpperInvariant()).ToArray(), false, "Excludes were changed to uppercase"); - } - - // Backward compatibilities: - // 1. When an include or exclude starts with a fixed directory part e.g. "src/foo/**", - // then matching should be case-sensitive on Linux, as the directory was checked for its existance - // by using Directory.Exists, which is case-sensitive on Linux (on OSX is not). - // 2. On Unix, when an include uses a simple ** wildcard e.g. "**\*.cs", the file pattern e.g. "*.cs", - // should be matched case-sensitive, as files were retrieved by using the searchPattern parameter - // of Directory.GetFiles, which is case-sensitive on Unix. - } - - /// - /// A test data class for providing data to the test. - /// - public class GetFilesComplexGlobbingMatchingInfo - { - /// - /// The list of known files to create. - /// - public static string[] FilesToCreate = - { - @"src\foo.cs", - @"src\bar.cs", - @"src\baz.cs", - @"src\foo\foo.cs", - @"src\foo\licence", - @"src\bar\bar.cs", - @"src\baz\baz.cs", - @"src\foo\inner\foo.cs", - @"src\foo\inner\foo\foo.cs", - @"src\foo\inner\bar\bar.cs", - @"src\bar\inner\baz.cs", - @"src\bar\inner\baz\baz.cs", - @"src\bar\inner\foo\foo.cs", - @"subd\sub.cs", - @"subdirectory\subdirectory.cs", - @"build\baz\foo.cs", - @"readme.txt", - @"licence" - }; - - /// - /// Gets or sets the include pattern. - /// - public string Include { get; set; } - - /// - /// Gets or sets a list of exclude patterns. - /// - public string[] Excludes { get; set; } - - /// - /// Gets or sets the list of expected matches. - /// - public string[] ExpectedMatches { get; set; } - - /// - /// Get or sets a value indicating to expect no matches if the include pattern is mutated to uppercase. - /// - public bool ExpectNoMatches { get; set; } - - public override string ToString() - { - IEnumerable GetParts() - { - yield return $"Include = {Include}"; - - if (Excludes != null) - { - yield return $"Excludes = {String.Join(";", Excludes)}"; - } - - if (ExpectNoMatches) - { - yield return "ExpectNoMatches"; - } - } - - return String.Join(", ", GetParts()); - } - - /// - /// Gets the test data - /// - public static IEnumerable GetTestData() - { - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"src\**\inner\**\*.cs", - ExpectedMatches = new[] - { - @"src\foo\inner\foo.cs", - @"src\foo\inner\foo\foo.cs", - @"src\foo\inner\bar\bar.cs", - @"src\bar\inner\baz.cs", - @"src\bar\inner\baz\baz.cs", - @"src\bar\inner\foo\foo.cs" - }, - ExpectNoMatches = NativeMethodsShared.IsLinux - } - }; - - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"src\**\inner\**\*.cs", - Excludes = new[] - { - @"src\foo\inner\foo.*.cs" - }, - ExpectedMatches = new[] - { - @"src\foo\inner\foo.cs", - @"src\foo\inner\foo\foo.cs", - @"src\foo\inner\bar\bar.cs", - @"src\bar\inner\baz.cs", - @"src\bar\inner\baz\baz.cs", - @"src\bar\inner\foo\foo.cs" - }, - ExpectNoMatches = NativeMethodsShared.IsLinux, - } - }; - - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"src\**\inner\**\*.cs", - Excludes = new[] - { - @"**\foo\**" - }, - ExpectedMatches = new[] - { - @"src\bar\inner\baz.cs", - @"src\bar\inner\baz\baz.cs" - }, - ExpectNoMatches = NativeMethodsShared.IsLinux, - } - }; - - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"src\**\inner\**\*.cs", - Excludes = new[] - { - @"src\bar\inner\baz\**" - }, - ExpectedMatches = new[] - { - @"src\foo\inner\foo.cs", - @"src\foo\inner\foo\foo.cs", - @"src\foo\inner\bar\bar.cs", - @"src\bar\inner\baz.cs", - @"src\bar\inner\foo\foo.cs" - }, - ExpectNoMatches = NativeMethodsShared.IsLinux, - } - }; - - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"src\foo\**\*.cs", - Excludes = new[] - { - @"src\foo\**\foo\**" - }, - ExpectedMatches = new[] - { - @"src\foo\foo.cs", - @"src\foo\inner\foo.cs", - @"src\foo\inner\bar\bar.cs" - }, - ExpectNoMatches = NativeMethodsShared.IsLinux, - } - }; - - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"src\foo\inner\**\*.cs", - Excludes = new[] - { - @"src\foo\**\???\**" - }, - ExpectedMatches = new[] - { - @"src\foo\inner\foo.cs" - }, - ExpectNoMatches = NativeMethodsShared.IsLinux, - } - }; - - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"**\???\**\*.cs", - ExpectedMatches = new[] - { - @"src\foo.cs", - @"src\bar.cs", - @"src\baz.cs", - @"src\foo\foo.cs", - @"src\bar\bar.cs", - @"src\baz\baz.cs", - @"src\foo\inner\foo.cs", - @"src\foo\inner\foo\foo.cs", - @"src\foo\inner\bar\bar.cs", - @"src\bar\inner\baz.cs", - @"src\bar\inner\baz\baz.cs", - @"src\bar\inner\foo\foo.cs", - @"build\baz\foo.cs" - } - } - }; - - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"**\*.*", - Excludes = new[] - { - @"**\???\**\*.cs", - @"subd*\*", - }, - ExpectedMatches = new[] - { - @"readme.txt", - @"licence", - @"src\foo\licence", - } - } - }; - - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"**\?a?\**\?a?\*.c?", - ExpectedMatches = new[] - { - @"src\bar\inner\baz\baz.cs" - } - } - }; - - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"**\?a?\**\?a?.c?", - Excludes = new[] - { - @"**\?a?\**\?a?\*.c?" - }, - ExpectedMatches = new[] - { - @"src\bar\bar.cs", - @"src\baz\baz.cs", - @"src\foo\inner\bar\bar.cs", - @"src\bar\inner\baz.cs" - } - } - }; - - // Regression test for https://github.com/dotnet/msbuild/issues/4175 - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"subdirectory\**", - Excludes = new[] - { - @"sub\**" - }, - ExpectedMatches = new[] - { - @"subdirectory\subdirectory.cs", - }, - ExpectNoMatches = NativeMethodsShared.IsLinux, - } - }; - - // Regression test for https://github.com/dotnet/msbuild/issues/6502 - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"src\**", - Excludes = new[] - { - @"**\foo\**", - }, - ExpectedMatches = new[] - { - @"src\foo.cs", - @"src\bar.cs", - @"src\baz.cs", - @"src\bar\bar.cs", - @"src\baz\baz.cs", - @"src\bar\inner\baz.cs", - @"src\bar\inner\baz\baz.cs", - }, - ExpectNoMatches = NativeMethodsShared.IsLinux, - } - }; - - // Hits the early elimination of exclude file patterns that do not intersect with the include. - // The exclude is redundant and can be eliminated before starting the file system walk. - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"src\foo\**\*.cs", - Excludes = new[] - { - @"src\foo\**\foo\**", - @"src\foo\**\*.vb" // redundant exclude - }, - ExpectedMatches = new[] - { - @"src\foo\foo.cs", - @"src\foo\inner\foo.cs", - @"src\foo\inner\bar\bar.cs" - }, - ExpectNoMatches = NativeMethodsShared.IsLinux, - } - }; - - // Hits the early elimination of exclude file patterns that do not intersect with the include. - // The exclude is not redundant and must not be eliminated. - yield return new object[] - { - new GetFilesComplexGlobbingMatchingInfo - { - Include = @"src\foo\**\*.cs", - Excludes = new[] - { - @"src\foo\**\*.*" // effective exclude - }, - ExpectedMatches = Array.Empty(), - ExpectNoMatches = NativeMethodsShared.IsLinux, - } - }; - } - } - - [Fact] - public void WildcardMatching() - { - var inputs = new List> - { - // No wildcards - new Tuple("a", "a", true), - new Tuple("a", "", false), - new Tuple("", "a", false), - - // Non ASCII characters - new Tuple("šđčćž", "šđčćž", true), - - // * wildcard - new Tuple("abc", "*bc", true), - new Tuple("abc", "a*c", true), - new Tuple("abc", "ab*", true), - new Tuple("ab", "*ab", true), - new Tuple("ab", "a*b", true), - new Tuple("ab", "ab*", true), - new Tuple("aba", "ab*ba", false), - new Tuple("", "*", true), - - // ? wildcard - new Tuple("abc", "?bc", true), - new Tuple("abc", "a?c", true), - new Tuple("abc", "ab?", true), - new Tuple("ab", "?ab", false), - new Tuple("ab", "a?b", false), - new Tuple("ab", "ab?", false), - new Tuple("", "?", false), - - // Mixed wildcards - new Tuple("a", "*?", true), - new Tuple("a", "?*", true), - new Tuple("ab", "*?", true), - new Tuple("ab", "?*", true), - new Tuple("abc", "*?", true), - new Tuple("abc", "?*", true), - - // Multiple mixed wildcards - new Tuple("a", "??", false), - new Tuple("ab", "?*?", true), - new Tuple("ab", "*?*?*", true), - new Tuple("abc", "?**?*?", true), - new Tuple("abc", "?**?*c?", false), - new Tuple("abcd", "?b*??", true), - new Tuple("abcd", "?a*??", false), - new Tuple("abcd", "?**?c?", true), - new Tuple("abcd", "?**?d?", false), - new Tuple("abcde", "?*b*?*d*?", true), - - // ? wildcard in the input string - new Tuple("?", "?", true), - new Tuple("?a", "?a", true), - new Tuple("a?", "a?", true), - new Tuple("a?b", "a?", false), - new Tuple("a?ab", "a?aab", false), - new Tuple("aa?bbbc?d", "aa?bbc?dd", false), - - // * wildcard in the input string - new Tuple("*", "*", true), - new Tuple("*a", "*a", true), - new Tuple("a*", "a*", true), - new Tuple("a*b", "a*", true), - new Tuple("a*ab", "a*aab", false), - new Tuple("a*abab", "a*b", true), - new Tuple("aa*bbbc*d", "aa*bbc*dd", false), - new Tuple("aa*bbbc*d", "a*bbc*d", true) - }; - foreach (var input in inputs) - { - try - { - Assert.Equal(input.Item3, FileMatcher.IsMatch(input.Item1, input.Item2)); - Assert.Equal(input.Item3, FileMatcher.IsMatch(input.Item1.ToUpperInvariant(), input.Item2)); - Assert.Equal(input.Item3, FileMatcher.IsMatch(input.Item1, input.Item2.ToUpperInvariant())); - } - catch (Exception) - { - Console.WriteLine($"Input {input.Item1} with pattern {input.Item2} failed"); - throw; - } - } - } - - /* - * Method: GetFileSystemEntries - * - * Simulate Directories.GetFileSystemEntries where file names are short. - * - */ - private static IReadOnlyList GetFileSystemEntries(FileMatcher.FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory) - { - if - ( - pattern == @"LONGDI~1" - && (@"D:\" == path || @"\\server\share\" == path || path.Length == 0)) - { - return new string[] { Path.Combine(path, "LongDirectoryName") }; - } - else if - ( - pattern == @"LONGSU~1" - && (@"D:\LongDirectoryName" == path || @"\\server\share\LongDirectoryName" == path || @"LongDirectoryName" == path)) - { - return new string[] { Path.Combine(path, "LongSubDirectory") }; - } - else if - ( - pattern == @"LONGFI~1.TXT" - && (@"D:\LongDirectoryName\LongSubDirectory" == path || @"\\server\share\LongDirectoryName\LongSubDirectory" == path || @"LongDirectoryName\LongSubDirectory" == path)) - { - return new string[] { Path.Combine(path, "LongFileName.txt") }; - } - else if - ( - pattern == @"pomegr~1" - && @"c:\apple\banana\tomato" == path) - { - return new string[] { Path.Combine(path, "pomegranate") }; - } - else if - ( - @"c:\apple\banana\tomato\pomegranate\orange" == path) - { - // No files exist here. This is an empty directory. - return Array.Empty(); - } - else - { - Console.WriteLine("GetFileSystemEntries('{0}', '{1}')", path, pattern); - Assert.Fail("Unexpected input into GetFileSystemEntries"); - } - return new string[] { "" }; - } - - private static readonly char S = Path.DirectorySeparatorChar; - - public static IEnumerable NormalizeTestData() - { - yield return new object[] - { - null, - null - }; - yield return new object[] - { - "", - "" - }; - yield return new object[] - { - " ", - " " - }; - - yield return new object[] - { - @"\\", - @"\\" - }; - yield return new object[] - { - @"\\/\//", - @"\\" - }; - yield return new object[] - { - @"\\a/\b/\", - $@"\\a{S}b" - }; - - yield return new object[] - { - @"\", - @"\" - }; - yield return new object[] - { - @"\/\/\/", - @"\" - }; - yield return new object[] - { - @"\a/\b/\", - $@"\a{S}b" - }; - - yield return new object[] - { - "/", - "/" - }; - yield return new object[] - { - @"/\/\", - "/" - }; - yield return new object[] - { - @"/a\/b/\\", - $@"/a{S}b" - }; - - yield return new object[] - { - @"c:\", - @"c:\" - }; - yield return new object[] - { - @"c:/", - @"c:\" - }; - yield return new object[] - { - @"c:/\/\/", - @"c:\" - }; - yield return new object[] - { - @"c:/ab", - @"c:\ab" - }; - yield return new object[] - { - @"c:\/\a//b", - $@"c:\a{S}b" - }; - yield return new object[] - { - @"c:\/\a//b\/", - $@"c:\a{S}b" - }; - - yield return new object[] - { - @"..\/a\../.\b\/", - $@"..{S}a{S}..{S}.{S}b" - }; - yield return new object[] - { - @"**/\foo\/**\/", - $@"**{S}foo{S}**" - }; - - yield return new object[] - { - "AbCd", - "AbCd" - }; - } - - [Theory] - [MemberData(nameof(NormalizeTestData))] - public void NormalizeTest(string inputString, string expectedString) - { - FileMatcher.Normalize(inputString).ShouldBe(expectedString); - } - - /// - /// Simple test of the MatchDriver code. - /// - [Fact] - public void BasicMatchDriver() - { - MatchDriver( - "Source" + Path.DirectorySeparatorChar + "**", - new string[] // Files that exist and should match. - { - "Source" + Path.DirectorySeparatorChar + "Bart.txt", - "Source" + Path.DirectorySeparatorChar + "Sub" + Path.DirectorySeparatorChar + "Homer.txt", - }, - new string[] // Files that exist and should not match. - { - "Destination" + Path.DirectorySeparatorChar + "Bart.txt", - "Destination" + Path.DirectorySeparatorChar + "Sub" + Path.DirectorySeparatorChar + "Homer.txt", - }, - null); - } - - /// - /// This pattern should *not* recurse indefinitely since there is no '**' in the pattern: - /// - /// c:\?emp\foo - /// - /// - [Fact] - public void Regress162390() - { - MatchDriver( - @"c:\?emp\foo.txt", - new string[] { @"c:\temp\foo.txt" }, // Should match - new string[] { @"c:\timp\foo.txt" }, // Shouldn't match - new string[] // Should not even consider. - { - @"c:\temp\sub\foo.txt" - }); - } - - /* - * Method: GetLongFileNameForShortLocalPath - * - * Convert a short local path to a long path. - * - */ - [WindowsOnlyFact("Short names are for Windows only.")] - public void GetLongFileNameForShortLocalPath() - { - string longPath = FileMatcher.GetLongPathName( - @"D:\LONGDI~1\LONGSU~1\LONGFI~1.TXT", - new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); - - Assert.Equal(@"D:\LongDirectoryName\LongSubDirectory\LongFileName.txt", longPath); - } - - /* - * Method: GetLongFileNameForLongLocalPath - * - * Convert a long local path to a long path (nop). - * - */ - [Fact] - public void GetLongFileNameForLongLocalPath() - { - string longPath = FileMatcher.GetLongPathName( - @"D:\LongDirectoryName\LongSubDirectory\LongFileName.txt", - new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); - - Assert.Equal(@"D:\LongDirectoryName\LongSubDirectory\LongFileName.txt", longPath); - } - - /* - * Method: GetLongFileNameForShortUncPath - * - * Convert a short UNC path to a long path. - * - */ - [WindowsOnlyFact("Short names are for Windows only.")] - public void GetLongFileNameForShortUncPath() - { - string longPath = FileMatcher.GetLongPathName( - @"\\server\share\LONGDI~1\LONGSU~1\LONGFI~1.TXT", - new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); - - Assert.Equal(@"\\server\share\LongDirectoryName\LongSubDirectory\LongFileName.txt", longPath); - } - - /* - * Method: GetLongFileNameForLongUncPath - * - * Convert a long UNC path to a long path (nop) - * - */ - [Fact] - public void GetLongFileNameForLongUncPath() - { - string longPath = FileMatcher.GetLongPathName( - @"\\server\share\LongDirectoryName\LongSubDirectory\LongFileName.txt", - new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); - - Assert.Equal(@"\\server\share\LongDirectoryName\LongSubDirectory\LongFileName.txt", longPath); - } - - /* - * Method: GetLongFileNameForRelativePath - * - * Convert a short relative path to a long path - * - */ - [WindowsOnlyFact("Short names are for Windows only.")] - public void GetLongFileNameForRelativePath() - { - string longPath = FileMatcher.GetLongPathName( - @"LONGDI~1\LONGSU~1\LONGFI~1.TXT", - new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); - - Assert.Equal(@"LongDirectoryName\LongSubDirectory\LongFileName.txt", longPath); - } - - /* - * Method: GetLongFileNameForRelativePathPreservesTrailingSlash - * - * Convert a short relative path with a trailing backslash to a long path - * - */ - [WindowsOnlyFact("Short names are for Windows only.")] - public void GetLongFileNameForRelativePathPreservesTrailingSlash() - { - string longPath = FileMatcher.GetLongPathName( - @"LONGDI~1\LONGSU~1\", - new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); - - Assert.Equal(@"LongDirectoryName\LongSubDirectory\", longPath); - } - - /* - * Method: GetLongFileNameForRelativePathPreservesExtraSlashes - * - * Convert a short relative path with doubled embedded backslashes to a long path - * - */ - [WindowsOnlyFact("Short names are for Windows only.")] - public void GetLongFileNameForRelativePathPreservesExtraSlashes() - { - string longPath = FileMatcher.GetLongPathName( - @"LONGDI~1\\LONGSU~1\\", - new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); - - Assert.Equal(@"LongDirectoryName\\LongSubDirectory\\", longPath); - } - - /* - * Method: GetLongFileNameForMixedLongAndShort - * - * Only part of the path might be short. - * - */ - [WindowsOnlyFact("Short names are for Windows only.")] - public void GetLongFileNameForMixedLongAndShort() - { - string longPath = FileMatcher.GetLongPathName( - @"c:\apple\banana\tomato\pomegr~1\orange\", - new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); - - Assert.Equal(@"c:\apple\banana\tomato\pomegranate\orange\", longPath); - } - - /* - * Method: GetLongFileNameWherePartOfThePathDoesntExist - * - * Part of the path may not exist. In this case, we treat the non-existent parts - * as if they were already a long file name. - * - */ - [WindowsOnlyFact("Short names are for Windows only.")] - public void GetLongFileNameWherePartOfThePathDoesntExist() - { - string longPath = FileMatcher.GetLongPathName( - @"c:\apple\banana\tomato\pomegr~1\orange\chocol~1\vanila~1", - new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); - - Assert.Equal(@"c:\apple\banana\tomato\pomegranate\orange\chocol~1\vanila~1", longPath); - } - - [Fact] - public void BasicMatch() - { - ValidateFileMatch("file.txt", "File.txt", false); - ValidateNoFileMatch("file.txt", "File.bin", false); - } - - [Fact] - public void MatchSingleCharacter() - { - ValidateFileMatch("file.?xt", "File.txt", false); - ValidateNoFileMatch("file.?xt", "File.bin", false); - } - - [Fact] - public void MatchMultipleCharacters() - { - ValidateFileMatch("*.txt", "*.txt", false); - ValidateNoFileMatch("*.txt", "*.bin", false); - } - - [Fact] - public void SimpleRecursive() - { - ValidateFileMatch("**", ".\\File.txt", true); - } - - [Fact] - public void DotForCurrentDirectory() - { - ValidateFileMatch(Path.Combine(".", "File.txt"), Path.Combine(".", "File.txt"), false); - ValidateNoFileMatch(Path.Combine(".", "File.txt"), Path.Combine(".", "File.bin"), false); - } - - [Fact] - public void DotDotForParentDirectory() - { - ValidateFileMatch(Path.Combine("..", "..", "*.*"), Path.Combine("..", "..", "File.txt"), false); - if (NativeMethodsShared.IsWindows) - { - // On Linux *. * does not pick up files with no extension - ValidateFileMatch(Path.Combine("..", "..", "*.*"), Path.Combine("..", "..", "File"), false); - } - ValidateNoFileMatch(Path.Combine("..", "..", "*.*"), Path.Combine(new[] { "..", "..", "dir1", "dir2", "File.txt" }), false); - ValidateNoFileMatch(Path.Combine("..", "..", "*.*"), Path.Combine(new[] { "..", "..", "dir1", "dir2", "File" }), false); - } - - [Fact] - public void ReduceDoubleSlashesBaseline() - { - // Baseline - ValidateFileMatch( - NativeMethodsShared.IsWindows ? "f:\\dir1\\dir2\\file.txt" : "/dir1/dir2/file.txt", - NativeMethodsShared.IsWindows ? "f:\\dir1\\dir2\\file.txt" : "/dir1/dir2/file.txt", - false); - ValidateFileMatch(Path.Combine("**", "*.cs"), Path.Combine("dir1", "dir2", "file.cs"), true); - ValidateFileMatch(Path.Combine("**", "*.cs"), "file.cs", true); - } - - [Fact] - public void ReduceDoubleSlashes() - { - ValidateFileMatch("f:\\\\dir1\\dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); - ValidateFileMatch("f:\\\\dir1\\\\\\dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); - ValidateFileMatch("f:\\\\dir1\\\\\\dir2\\\\\\\\\\file.txt", "f:\\dir1\\dir2\\file.txt", false); - ValidateFileMatch("..\\**/\\*.cs", "..\\dir1\\dir2\\file.cs", true); - ValidateFileMatch("..\\**/.\\*.cs", "..\\dir1\\dir2\\file.cs", true); - ValidateFileMatch("..\\**\\./.\\*.cs", "..\\dir1\\dir2\\file.cs", true); - } - - [Fact] - public void DoubleSlashesOnBothSidesOfComparison() - { - ValidateFileMatch("f:\\\\dir1\\dir2\\file.txt", "f:\\\\dir1\\dir2\\file.txt", false, false); - ValidateFileMatch("f:\\\\dir1\\\\\\dir2\\file.txt", "f:\\\\dir1\\\\\\dir2\\file.txt", false, false); - ValidateFileMatch("f:\\\\dir1\\\\\\dir2\\\\\\\\\\file.txt", "f:\\\\dir1\\\\\\dir2\\\\\\\\\\file.txt", false, false); - ValidateFileMatch("..\\**/\\*.cs", "..\\dir1\\dir2\\\\file.cs", true, false); - ValidateFileMatch("..\\**/.\\*.cs", "..\\dir1\\dir2//\\file.cs", true, false); - ValidateFileMatch("..\\**\\./.\\*.cs", "..\\dir1/\\/\\/dir2\\file.cs", true, false); - } - - [Fact] - public void DecomposeDotSlash() - { - ValidateFileMatch("f:\\.\\dir1\\dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); - ValidateFileMatch("f:\\dir1\\.\\dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); - ValidateFileMatch("f:\\dir1\\dir2\\.\\file.txt", "f:\\dir1\\dir2\\file.txt", false); - ValidateFileMatch("f:\\.//dir1\\dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); - ValidateFileMatch("f:\\dir1\\.//dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); - ValidateFileMatch("f:\\dir1\\dir2\\.//file.txt", "f:\\dir1\\dir2\\file.txt", false); - - ValidateFileMatch(".\\dir1\\dir2\\file.txt", ".\\dir1\\dir2\\file.txt", false); - ValidateFileMatch(".\\.\\dir1\\dir2\\file.txt", ".\\dir1\\dir2\\file.txt", false); - ValidateFileMatch(".//dir1\\dir2\\file.txt", ".\\dir1\\dir2\\file.txt", false); - ValidateFileMatch(".//.//dir1\\dir2\\file.txt", ".\\dir1\\dir2\\file.txt", false); - } - - [Fact] - public void RecursiveDirRecursive() - { - // Check that a wildcardpath of **\x\**\ matches correctly since, \**\ is a - // separate code path. - ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\x\file.txt", true); - ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\y\x\file.txt", true); - ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\x\y\file.txt", true); - ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\y\x\y\file.txt", true); - ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\x\x\file.txt", true); - ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\x\x\file.txt", true); - ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\x\x\x\file.txt", true); - } - - [Fact] - public void Regress155731() - { - ValidateFileMatch(@"a\b\**\**\**\**\**\e\*", @"a\b\c\d\e\f.txt", true); - ValidateFileMatch(@"a\b\**\e\*", @"a\b\c\d\e\f.txt", true); - ValidateFileMatch(@"a\b\**\**\e\*", @"a\b\c\d\e\f.txt", true); - ValidateFileMatch(@"a\b\**\**\**\e\*", @"a\b\c\d\e\f.txt", true); - ValidateFileMatch(@"a\b\**\**\**\**\e\*", @"a\b\c\d\e\f.txt", true); - } - - [Fact] - public void ParentWithoutSlash() - { - // However, we don't wtool this to match, - ValidateNoFileMatch(@"C:\foo\**", @"C:\foo", true); - // because we don't know whether foo is a file or folder. - - // Same for UNC - ValidateNoFileMatch( - "\\\\server\\c$\\Documents and Settings\\User\\**", - "\\\\server\\c$\\Documents and Settings\\User", - true); - } - - [Fact] - public void Unc() - { - using (var env = TestEnvironment.Create()) - { - try - { - // Set env var to log on drive enumerating wildcard detection - Helpers.ResetStateForDriveEnumeratingWildcardTests(env, "0"); - - // Check UNC functionality - ValidateFileMatch( - "\\\\server\\c$\\**\\*.cs", - "\\\\server\\c$\\Documents and Settings\\User\\Source.cs", - true); - - ValidateNoFileMatch( - "\\\\server\\c$\\**\\*.cs", - "\\\\server\\c$\\Documents and Settings\\User\\Source.txt", - true); - ValidateFileMatch( - "\\\\**", - "\\\\server\\c$\\Documents and Settings\\User\\Source.cs", - true); - ValidateFileMatch( - "\\\\**\\*.*", - "\\\\server\\c$\\Documents and Settings\\User\\Source.cs", - true); - - ValidateFileMatch( - "**", - "\\\\server\\c$\\Documents and Settings\\User\\Source.cs", - true); - } - finally - { - ChangeWaves.ResetStateForTests(); - } - } - } - - [Fact] - public void ExplicitToolCompatibility() - { - // Explicit ANT compatibility. These patterns taken from the ANT documentation. - ValidateFileMatch("**/SourceSafe/*", "./SourceSafe/Repository", true); - ValidateFileMatch("**\\SourceSafe/*", "./SourceSafe/Repository", true); - ValidateFileMatch("**/SourceSafe/*", ".\\SourceSafe\\Repository", true); - ValidateFileMatch("**/SourceSafe/*", "./org/IIS/SourceSafe/Entries", true); - ValidateFileMatch("**/SourceSafe/*", "./org/IIS/pluggin/tools/tool/SourceSafe/Entries", true); - ValidateNoFileMatch("**/SourceSafe/*", "./org/IIS/SourceSafe/foo/bar/Entries", true); - ValidateNoFileMatch("**/SourceSafe/*", "./SourceSafeRepository", true); - ValidateNoFileMatch("**/SourceSafe/*", "./aSourceSafe/Repository", true); - - ValidateFileMatch("org/IIS/pluggin/**", "org/IIS/pluggin/tools/tool/docs/index.html", true); - ValidateFileMatch("org/IIS/pluggin/**", "org/IIS/pluggin/test.xml", true); - ValidateFileMatch("org/IIS/pluggin/**", "org/IIS/pluggin\\test.xml", true); - ValidateNoFileMatch("org/IIS/pluggin/**", "org/IIS/abc.cs", true); - - ValidateFileMatch("org/IIS/**/SourceSafe/*", "org/IIS/SourceSafe/Entries", true); - ValidateFileMatch("org/IIS/**/SourceSafe/*", "org\\IIS/SourceSafe/Entries", true); - ValidateFileMatch("org/IIS/**/SourceSafe/*", "org/IIS\\SourceSafe/Entries", true); - ValidateFileMatch("org/IIS/**/SourceSafe/*", "org/IIS/pluggin/tools/tool/SourceSafe/Entries", true); - ValidateNoFileMatch("org/IIS/**/SourceSafe/*", "org/IIS/SourceSafe/foo/bar/Entries", true); - ValidateNoFileMatch("org/IIS/**/SourceSafe/*", "org/IISSourceSage/Entries", true); - } - - [Fact] - public void ExplicitToolIncompatibility() - { - // NOTE: Weirdly, ANT syntax is to match a file here. - // We don't because MSBuild philosophy is that a trailing slash indicates a directory - ValidateNoFileMatch("**/test/**", ".\\test", true); - - // NOTE: We deviate from ANT format here. ANT would append a ** to any path - // that ends with '/' or '\'. We think this is the wrong thing because 'folder\' - // is a valid folder name. - ValidateNoFileMatch("org/", "org/IISSourceSage/Entries", false); - ValidateNoFileMatch("org\\", "org/IISSourceSage/Entries", false); - } - - [Fact] - public void MultipleStarStar() - { - using (var env = TestEnvironment.Create()) - { - try - { - // Set env var to log on drive enumerating wildcard detection - Helpers.ResetStateForDriveEnumeratingWildcardTests(env, "0"); - - // Multiple-** matches - ValidateFileMatch("c:\\**\\user\\**\\*.*", "c:\\Documents and Settings\\user\\NTUSER.DAT", true); - ValidateNoFileMatch("c:\\**\\user1\\**\\*.*", "c:\\Documents and Settings\\user\\NTUSER.DAT", true); - ValidateFileMatch("c:\\**\\user\\**\\*.*", "c://Documents and Settings\\user\\NTUSER.DAT", true); - ValidateNoFileMatch("c:\\**\\user1\\**\\*.*", "c:\\Documents and Settings//user\\NTUSER.DAT", true); - } - finally - { - ChangeWaves.ResetStateForTests(); - } - } - } - - [Fact] - public void RegressItemRecursionWorksAsExpected() - { - // Regress bug#54411: Item recursion doesn't work as expected on "c:\foo\**" - ValidateFileMatch("c:\\foo\\**", "c:\\foo\\two\\subfile.txt", true); - } - - [Fact] - public void IllegalPaths() - { - // Certain patterns are illegal. - ValidateIllegal("**.cs"); - ValidateIllegal("***"); - ValidateIllegal("****"); - ValidateIllegal("*.cs**"); - ValidateIllegal("*.cs**"); - ValidateIllegal("...\\*.cs"); - ValidateIllegal("http://www.website.com"); - } - - [Fact] - public void SplitFileSpec() - { - /************************************************************************************* - * Call ValidateSplitFileSpec with various supported combinations. - *************************************************************************************/ - ValidateSplitFileSpec("foo.cs", "", "", "foo.cs"); - ValidateSplitFileSpec("**\\foo.cs", "", "**\\", "foo.cs"); - ValidateSplitFileSpec("f:\\dir1\\**\\foo.cs", "f:\\dir1\\", "**\\", "foo.cs"); - ValidateSplitFileSpec("..\\**\\foo.cs", "..\\", "**\\", "foo.cs"); - ValidateSplitFileSpec("f:\\dir1\\foo.cs", "f:\\dir1\\", "", "foo.cs"); - ValidateSplitFileSpec("f:\\dir?\\foo.cs", "f:\\", "dir?\\", "foo.cs"); - ValidateSplitFileSpec("dir?\\foo.cs", "", "dir?\\", "foo.cs"); - ValidateSplitFileSpec(@"**\test\**", "", @"**\test\**\", "*.*"); - ValidateSplitFileSpec("bin\\**\\*.cs", "bin\\", "**\\", "*.cs"); - ValidateSplitFileSpec("bin\\**\\*.*", "bin\\", "**\\", "*.*"); - ValidateSplitFileSpec("bin\\**", "bin\\", "**\\", "*.*"); - ValidateSplitFileSpec("bin\\**\\", "bin\\", "**\\", ""); - ValidateSplitFileSpec("bin\\**\\*", "bin\\", "**\\", "*"); - ValidateSplitFileSpec("**", "", "**\\", "*.*"); - } - - [Fact] - public void Regress367780_CrashOnStarDotDot() - { - string workingPath = _env.CreateFolder().Path; - string workingPathSubfolder = Path.Combine(workingPath, "SubDir"); - string offendingPattern = Path.Combine(workingPath, @"*\..\bar"); - string[] files = Array.Empty(); - - Directory.CreateDirectory(workingPath); - Directory.CreateDirectory(workingPathSubfolder); - - files = FileMatcher.Default.GetFiles(workingPath, offendingPattern).FileList; - } - - [Fact] - public void Regress141071_StarStarSlashStarStarIsLiteral() - { - string workingPath = _env.CreateFolder().Path; - string fileName = Path.Combine(workingPath, "MyFile.txt"); - string offendingPattern = Path.Combine(workingPath, @"**\**"); - - Directory.CreateDirectory(workingPath); - File.WriteAllText(fileName, "Hello there."); - var files = FileMatcher.Default.GetFiles(workingPath, offendingPattern).FileList; - - string result = String.Join(", ", files); - Console.WriteLine(result); - Assert.DoesNotContain("**", result); - Assert.Contains("MyFile.txt", result); - } - - [Fact] - public void Regress14090_TrailingDotMatchesNoExtension() - { - string workingPath = _env.CreateFolder().Path; - string workingPathSubdir = Path.Combine(workingPath, "subdir"); - string workingPathSubdirBing = Path.Combine(workingPathSubdir, "bing"); - - string offendingPattern = Path.Combine(workingPath, @"**\sub*\*."); - - Directory.CreateDirectory(workingPath); - Directory.CreateDirectory(workingPathSubdir); - File.AppendAllText(workingPathSubdirBing, "y"); - var files = FileMatcher.Default.GetFiles(workingPath, offendingPattern).FileList; - - string result = String.Join(", ", files); - Console.WriteLine(result); - Assert.Single(files); - } - - [Fact] - public void Regress14090_TrailingDotMatchesNoExtension_Part2() - { - ValidateFileMatch(@"c:\mydir\**\*.", @"c:\mydir\subdir\bing", true, /* simulate filesystem? */ false); - ValidateNoFileMatch(@"c:\mydir\**\*.", @"c:\mydir\subdir\bing.txt", true); - } - - [Fact] - public void FileEnumerationCacheTakesExcludesIntoAccount() - { - try - { - using (var env = TestEnvironment.Create()) - { - env.SetEnvironmentVariable("MsBuildCacheFileEnumerations", "1"); - - var testProject = env.CreateTestProjectWithFiles(string.Empty, new[] { "a.cs", "b.cs", "c.cs" }); - - var files = FileMatcher.Default.GetFiles(testProject.TestRoot, "**/*.cs").FileList; - Array.Sort(files); - Assert.Equal(new[] { "a.cs", "b.cs", "c.cs" }, files); - - files = FileMatcher.Default.GetFiles(testProject.TestRoot, "**/*.cs", new List { "a.cs" }).FileList; - Array.Sort(files); - Assert.Equal(new[] { "b.cs", "c.cs" }, files); - - files = FileMatcher.Default.GetFiles(testProject.TestRoot, "**/*.cs", new List { "a.cs", "c.cs" }).FileList; - Array.Sort(files); - Assert.Equal(new[] { "b.cs" }, files); - } - } - finally - { - FileMatcher.ClearCaches(); - } - } - - [Theory] - [InlineData(@"\", "**")] - [InlineData(@"\\", "**")] - [InlineData(@"\\\\\\\\", "**")] - [InlineData("/", "**/*.cs")] - [InlineData("/", "**")] - [InlineData("//", "**")] - [InlineData("////////", "**")] - public void DriveEnumeratingWildcardIsObservedOnAnyPlatform(string directoryPart, string wildcardPart) => - DriveEnumeratingWildcardIsObserved(directoryPart, wildcardPart); - - [WindowsOnlyTheory] - [InlineData(@"\", "**")] - [InlineData(@"c:\", "**")] - [InlineData(@"c:\\", "**")] - [InlineData(@"c:\\\\\\\\", "**")] - [InlineData(@"c:\", @"**\*.cs")] - public void DriveEnumeratingWildcardIsObservedOnWindows(string directoryPart, string wildcardPart) - { - DriveEnumeratingWildcardIsObserved(directoryPart, wildcardPart); - DriveEnumeratingWildcardFailsAndReturns(directoryPart, wildcardPart); - } - - private void DriveEnumeratingWildcardIsObserved(string directoryPart, string wildcardPart) => - FileMatcher.IsDriveEnumeratingWildcardPattern(directoryPart, wildcardPart).ShouldBeTrue(); - - [UnixOnlyTheory] - [InlineData(@"\", "**")] - [InlineData("/", "**/*.cs")] - [InlineData("/", "**")] - [InlineData("//", "**")] - [InlineData("////////", "**")] - public void DriveEnumeratingWildcardFailsAndReturnsOnUnix(string directoryPart, string wildcardPart) - { - DriveEnumeratingWildcardFailsAndReturns(directoryPart, wildcardPart); - } - - private void DriveEnumeratingWildcardFailsAndReturns(string directoryPart, string wildcardPart) - { - string driveEnumeratingWildcard = string.Concat(directoryPart, wildcardPart); - - using (var env = TestEnvironment.Create()) - { - try - { - // Set env var to fail on drive enumerating wildcard detection - Helpers.ResetStateForDriveEnumeratingWildcardTests(env, "1"); - - (string[] fileList, FileMatcher.SearchAction action, string excludeFileSpec, _) = FileMatcher.Default.GetFiles( - string.Empty, - driveEnumeratingWildcard); - - action.ShouldBe(FileMatcher.SearchAction.FailOnDriveEnumeratingWildcard); - fileList.ShouldBeEmpty(); - excludeFileSpec.ShouldBe(string.Empty); - - // Handle failing with drive enumerating exclude - (fileList, action, excludeFileSpec, _) = FileMatcher.Default.GetFiles( - string.Empty, - @"/*/*.cs", - new List { driveEnumeratingWildcard }); - - action.ShouldBe(FileMatcher.SearchAction.FailOnDriveEnumeratingWildcard); - fileList.ShouldBeEmpty(); - excludeFileSpec.ShouldBe(driveEnumeratingWildcard); - } - finally - { - ChangeWaves.ResetStateForTests(); - } - } - } - - [WindowsOnlyTheory] - [InlineData(@"%DRIVE%:\**")] - [InlineData(@"%DRIVE%:\\**")] - [InlineData(@"%DRIVE%:\\\\\\\\**")] - [InlineData(@"%DRIVE%:\**\*.cs")] - public void DriveEnumeratingWildcardIsLoggedOnWindows(string driveEnumeratingWildcard) - { - using (var env = TestEnvironment.Create()) - { - try - { - driveEnumeratingWildcard = DummyMappedDriveUtils.UpdatePathToMappedDrive(driveEnumeratingWildcard, _mappedDrive.Value.MappedDriveLetter); - - // Set env var to log on drive enumerating wildcard detection - Helpers.ResetStateForDriveEnumeratingWildcardTests(env, "0"); - - (_, FileMatcher.SearchAction action, string excludeFileSpec, _) = FileMatcher.Default.GetFiles( - string.Empty, - driveEnumeratingWildcard); - - action.ShouldBe(FileMatcher.SearchAction.LogDriveEnumeratingWildcard); - excludeFileSpec.ShouldBe(string.Empty); - - // Handle logging with drive enumerating exclude - (_, action, excludeFileSpec, _) = FileMatcher.Default.GetFiles( - string.Empty, - @"/*/*.cs", - new List { driveEnumeratingWildcard }); - - action.ShouldBe(FileMatcher.SearchAction.LogDriveEnumeratingWildcard); - excludeFileSpec.ShouldBe(driveEnumeratingWildcard); - } - finally - { - ChangeWaves.ResetStateForTests(); - } - } - } - - [Theory] - [InlineData(@"\", @"*\*.cs")] - [InlineData(@"\\", @"*\*.cs")] - [InlineData(@"\", @"*\*.*")] - [InlineData(@"/", @"*/*.cs")] - [InlineData(@"//", @"*/*.cs")] - [InlineData(@"/", @"*/*.*")] - public void DriveEnumeratingWildcardIsNotObservedOnAnyPlatform(string directoryPart, string wildcardPart) => - DriveEnumeratingWildcardIsNotObserved(directoryPart, wildcardPart); - - [UnixOnlyTheory] - [InlineData(@"c:\", "**")] - [InlineData(@"c:\\", "**")] - [InlineData(@"c:\\\\\\\\", "**")] - [InlineData(@"c:\", @"**\*.cs")] - public void DriveEnumeratingWildcardIsNotObservedOnUnix(string directoryPart, string wildcardPart) - { - DriveEnumeratingWildcardIsNotObserved(directoryPart, wildcardPart); - } - - private void DriveEnumeratingWildcardIsNotObserved(string directoryPart, string wildcardPart) => - FileMatcher.IsDriveEnumeratingWildcardPattern(directoryPart, wildcardPart).ShouldBeFalse(); - - [Fact] - public void RemoveProjectDirectory() - { - string[] strings = new string[1] { NativeMethodsShared.IsWindows ? "c:\\1.file" : "/1.file" }; - strings = FileMatcher.RemoveProjectDirectory(strings, NativeMethodsShared.IsWindows ? "c:\\" : "/").ToArray(); - Assert.Equal("1.file", strings[0]); - - strings = new string[1] { NativeMethodsShared.IsWindows ? "c:\\directory\\1.file" : "/directory/1.file" }; - strings = FileMatcher.RemoveProjectDirectory(strings, NativeMethodsShared.IsWindows ? "c:\\" : "/").ToArray(); - Assert.Equal(strings[0], NativeMethodsShared.IsWindows ? "directory\\1.file" : "directory/1.file"); - - strings = new string[1] { NativeMethodsShared.IsWindows ? "c:\\directory\\1.file" : "/directory/1.file" }; - strings = FileMatcher.RemoveProjectDirectory(strings, NativeMethodsShared.IsWindows ? "c:\\directory" : "/directory").ToArray(); - Assert.Equal("1.file", strings[0]); - - strings = new string[1] { NativeMethodsShared.IsWindows ? "c:\\1.file" : "/1.file" }; - strings = FileMatcher.RemoveProjectDirectory(strings, NativeMethodsShared.IsWindows ? "c:\\directory" : "/directory").ToArray(); - Assert.Equal(strings[0], NativeMethodsShared.IsWindows ? "c:\\1.file" : "/1.file"); - - strings = new string[1] { NativeMethodsShared.IsWindows ? "c:\\directorymorechars\\1.file" : "/directorymorechars/1.file" }; - strings = FileMatcher.RemoveProjectDirectory(strings, NativeMethodsShared.IsWindows ? "c:\\directory" : "/directory").ToArray(); - Assert.Equal(strings[0], NativeMethodsShared.IsWindows ? "c:\\directorymorechars\\1.file" : "/directorymorechars/1.file"); - - if (NativeMethodsShared.IsWindows) - { - strings = new string[1] { "\\Machine\\1.file" }; - strings = FileMatcher.RemoveProjectDirectory(strings, "\\Machine").ToArray(); - Assert.Equal("1.file", strings[0]); - - strings = new string[1] { "\\Machine\\directory\\1.file" }; - strings = FileMatcher.RemoveProjectDirectory(strings, "\\Machine").ToArray(); - Assert.Equal("directory\\1.file", strings[0]); - - strings = new string[1] { "\\Machine\\directory\\1.file" }; - strings = FileMatcher.RemoveProjectDirectory(strings, "\\Machine\\directory").ToArray(); - Assert.Equal("1.file", strings[0]); - - strings = new string[1] { "\\Machine\\1.file" }; - strings = FileMatcher.RemoveProjectDirectory(strings, "\\Machine\\directory").ToArray(); - Assert.Equal("\\Machine\\1.file", strings[0]); - - strings = new string[1] { "\\Machine\\directorymorechars\\1.file" }; - strings = FileMatcher.RemoveProjectDirectory(strings, "\\Machine\\directory").ToArray(); - Assert.Equal("\\Machine\\directorymorechars\\1.file", strings[0]); - } - } - - [Theory] - [InlineData( - @"src/**/*.cs", // Include Pattern - new string[] // Matching files - { - @"src/a.cs", - @"src/a\b\b.cs", - })] - [InlineData( - @"src/test/**/*.cs", // Include Pattern - new string[] // Matching files - { - @"src/test/a.cs", - @"src/test/a\b\c.cs", - })] - [InlineData( - @"src/test/**/a/b/**/*.cs", // Include Pattern - new string[] // Matching files - { - @"src/test/dir\a\b\a.cs", - @"src/test/dir\a\b\c\a.cs", - })] - public void IncludePatternShouldNotPreserveUserSlashesInFixedDirPart(string include, string[] matching) - { - MatchDriver(include, null, matching, null, null, normalizeAllPaths: false, normalizeExpectedMatchingFiles: true); - } - - [Theory] - [InlineData( - @"**\*.cs", // Include Pattern - new[] // Exclude patterns - { - @"bin\**" - }, - new string[] // Matching files - { - }, - new string[] // Non matching files - { - }, - new[] // Non matching files that shouldn't be touched - { - @"bin\foo.cs", - @"bin\bar\foo.cs", - @"bin\bar\" - })] - [InlineData( - @"**\*.cs", // Include Pattern - new[] // Exclude patterns - { - @"bin\**" - }, - new[] // Matching files - { - "a.cs", - @"b\b.cs", - }, - new[] // Non matching files - { - @"b\b.txt" - }, - new[] // Non matching files that shouldn't be touched - { - @"bin\foo.cs", - @"bin\bar\foo.cs", - @"bin\bar\" - })] - public void ExcludePattern(string include, string[] exclude, string[] matching, string[] nonMatching, string[] untouchable) - { - MatchDriver(include, exclude, matching, nonMatching, untouchable); - } - - [Fact] - public void ExcludeSpecificFiles() - { - MatchDriverWithDifferentSlashes( - @"**\*.cs", // Include Pattern - new[] // Exclude patterns - { - @"Program_old.cs", - @"Properties\AssemblyInfo_old.cs" - }, - new[] // Matching files - { - @"foo.cs", - @"Properties\AssemblyInfo.cs", - @"Foo\Bar\Baz\Buzz.cs" - }, - new[] // Non matching files - { - @"Program_old.cs", - @"Properties\AssemblyInfo_old.cs" - }, - Array.Empty()); // Non matching files that shouldn't be touched - } - - [Fact] - public void ExcludePatternAndSpecificFiles() - { - MatchDriverWithDifferentSlashes( - @"**\*.cs", // Include Pattern - new[] // Exclude patterns - { - @"bin\**", - @"Program_old.cs", - @"Properties\AssemblyInfo_old.cs" - }, - new[] // Matching files - { - @"foo.cs", - @"Properties\AssemblyInfo.cs", - @"Foo\Bar\Baz\Buzz.cs" - }, - new[] // Non matching files - { - @"foo.txt", - @"Foo\foo.txt", - @"Program_old.cs", - @"Properties\AssemblyInfo_old.cs" - }, - new[] // Non matching files that shouldn't be touched - { - @"bin\foo.cs", - @"bin\bar\foo.cs", - @"bin\bar\" - }); - } - - [Theory] - [InlineData( - @"**\*.cs", // Include Pattern - new[] // Exclude patterns - { - @"**\bin\**\*.cs", - @"src\Common\**", - }, - new[] // Matching files - { - @"foo.cs", - @"src\Framework\Properties\AssemblyInfo.cs", - @"src\Framework\Foo\Bar\Baz\Buzz.cs" - }, - new[] // Non matching files - { - @"foo.txt", - @"src\Framework\Readme.md", - @"src\Common\foo.cs", - - // Ideally these would be untouchable - @"src\Framework\bin\foo.cs", - @"src\Framework\bin\Debug", - @"src\Framework\bin\Debug\foo.cs", - }, - new[] // Non matching files that shouldn't be touched - { - @"src\Common\Properties\", - @"src\Common\Properties\AssemblyInfo.cs", - })] - [InlineData( - @"**\*.cs", // Include Pattern - new[] // Exclude patterns - { - @"**\bin\**\*.cs", - @"src\Co??on\**", - }, - new[] // Matching files - { - @"foo.cs", - @"src\Framework\Properties\AssemblyInfo.cs", - @"src\Framework\Foo\Bar\Baz\Buzz.cs" - }, - new[] // Non matching files - { - @"foo.txt", - @"src\Framework\Readme.md", - @"src\Common\foo.cs", - - // Ideally these would be untouchable - @"src\Framework\bin\foo.cs", - @"src\Framework\bin\Debug", - @"src\Framework\bin\Debug\foo.cs", - @"src\Common\Properties\AssemblyInfo.cs" - }, - new[] // Non matching files that shouldn't be touched - { - @"src\Common\Properties\" - })] - [InlineData( - @"src\**\proj\**\*.cs", // Include Pattern - new[] // Exclude patterns - { - @"src\**\proj\**\none\**\*", - }, - new[] // Matching files - { - @"src\proj\m1.cs", - @"src\proj\a\m2.cs", - @"src\b\proj\m3.cs", - @"src\c\proj\d\m4.cs", - }, - new[] // Non matching files - { - @"nm1.cs", - @"a\nm2.cs", - @"src\nm3.cs", - @"src\a\nm4.cs", - - // Ideally these would be untouchable - @"src\proj\none\nm5.cs", - @"src\proj\a\none\nm6.cs", - @"src\b\proj\none\nm7.cs", - @"src\c\proj\d\none\nm8.cs", - @"src\e\proj\f\none\g\nm8.cs", - }, - new string[] // Non matching files that shouldn't be touched - { - })] - // patterns with excludes that ideally would prune entire recursive subtrees (files in pruned tree aren't touched at all) but the exclude pattern is too complex for that to work with the current logic - public void ExcludeComplexPattern(string include, string[] exclude, string[] matching, string[] nonMatching, string[] untouchable) - { - MatchDriverWithDifferentSlashes(include, exclude, matching, nonMatching, untouchable); - } - - [Theory] - // Empty string is valid - [InlineData( - "", - "", - "", - "", - "^(?)(?)$", - false, - true)] - // ... anywhere is invalid - [InlineData( - @"...\foo", - "", - "", - "", - "", - false, - false)] - // : not placed at second index is invalid - [InlineData( - "http://www.website.com", - "", - "", - "", - "", - false, - false)] - // ** not alone in filename part is invalid - [InlineData( - "**foo", - "", - "", - "**foo", - "", - false, - false)] - // ** not alone in filename part is invalid - [InlineData( - "foo**", - "", - "", - "foo**", - "", - false, - false)] - // ** not alone between slashes in wildcard part is invalid - [InlineData( - @"**foo\bar", - "", - @"**foo\", - "bar", - "", - false, - false)] - // .. placed after any ** is invalid - [InlineData( - @"**\..\bar", - "", - @"**\..\", - "bar", - "", - false, - false)] - // Common wildcard characters in wildcard and filename part - [InlineData( - @"*fo?ba?\*fo?ba?", - "", - @"*fo?ba?\", - "*fo?ba?", - @"^(?[^/\\]*fo.ba.[/\\]+)(?[^/\\]*fo.ba.)$", - true, - true)] - // Special case for ? and * when trailing . in filename part - [InlineData( - "?oo*.", - "", - "", - "?oo*.", - @"^(?)(?[^\.].oo[^\.]*)$", - false, - true)] - // Skip the .* portion of any *.* sequence in filename part - [InlineData( - "*.*foo*.*", - "", - "", - "*.*foo*.*", - @"^(?)(?[^/\\]*foo[^/\\]*)$", - false, - true)] - // Collapse successive directory separators - [InlineData( - @"\foo///bar\\\?foo///bar\\\foo", - @"\foo///bar\\\", - @"?foo///bar\\\", - "foo", - @"^[/\\]+foo[/\\]+bar[/\\]+(?.foo[/\\]+bar[/\\]+)(?foo)$", - true, - true)] - // Collapse successive relative separators - [InlineData( - @"\./.\foo/.\./bar\./.\?foo/.\./bar\./.\foo", - @"\./.\foo/.\./bar\./.\", - @"?foo/.\./bar\./.\", - "foo", - @"^[/\\]+foo[/\\]+bar[/\\]+(?.foo[/\\]+bar[/\\]+)(?foo)$", - true, - true)] - // Collapse successive recursive operators - [InlineData( - @"foo\**/**\bar/**\**/foo\**/**\bar", - @"foo\", - @"**/**\bar/**\**/foo\**/**\", - "bar", - @"^foo[/\\]+(?((.*/)|(.*\\)|())bar((/)|(\\)|(/.*/)|(/.*\\)|(\\.*\\)|(\\.*/))foo((/)|(\\)|(/.*/)|(/.*\\)|(\\.*\\)|(\\.*/)))(?bar)$", - true, - true)] - // Collapse all three cases combined - [InlineData( - @"foo\\\.///**\\\.///**\\\.///bar\\\.///**\\\.///**\\\.///foo\\\.///**\\\.///**\\\.///bar", - @"foo\\\.///", - @"**\\\.///**\\\.///bar\\\.///**\\\.///**\\\.///foo\\\.///**\\\.///**\\\.///", - "bar", - @"^foo[/\\]+(?((.*/)|(.*\\)|())bar((/)|(\\)|(/.*/)|(/.*\\)|(\\.*\\)|(\\.*/))foo((/)|(\\)|(/.*/)|(/.*\\)|(\\.*\\)|(\\.*/)))(?bar)$", - true, - true)] - public void GetFileSpecInfoCommon( - string filespec, - string expectedFixedDirectoryPart, - string expectedWildcardDirectoryPart, - string expectedFilenamePart, - string expectedMatchFileExpression, - bool expectedNeedsRecursion, - bool expectedIsLegalFileSpec) - { - if (NativeMethodsShared.IsUnixLike) - { - expectedFixedDirectoryPart = FileUtilities.FixFilePath(expectedFixedDirectoryPart); - expectedWildcardDirectoryPart = FileUtilities.FixFilePath(expectedWildcardDirectoryPart); - } - TestGetFileSpecInfo( - filespec, - expectedFixedDirectoryPart, - expectedWildcardDirectoryPart, - expectedFilenamePart, - expectedMatchFileExpression, - expectedNeedsRecursion, - expectedIsLegalFileSpec); - } - - [WindowsOnlyTheory] - // Escape pecial regex characters valid in Windows paths - [InlineData( - @"$()+.[^{\?$()+.[^{\$()+.[^{", - @"$()+.[^{\", - @"?$()+.[^{\", - "$()+.[^{", - @"^\$\(\)\+\.\[\^\{[/\\]+(?.\$\(\)\+\.\[\^\{[/\\]+)(?\$\(\)\+\.\[\^\{)$", - true, - true)] - // Preserve UNC paths in fixed directory part - [InlineData( - @"\\\.\foo/bar", - @"\\\.\foo/", - "", - "bar", - @"^\\\\foo[/\\]+(?)(?bar)$", - false, - true)] - public void GetFileSpecInfoWindows( - string filespec, - string expectedFixedDirectoryPart, - string expectedWildcardDirectoryPart, - string expectedFilenamePart, - string expectedMatchFileExpression, - bool expectedNeedsRecursion, - bool expectedIsLegalFileSpec) - { - TestGetFileSpecInfo( - filespec, - expectedFixedDirectoryPart, - expectedWildcardDirectoryPart, - expectedFilenamePart, - expectedMatchFileExpression, - expectedNeedsRecursion, - expectedIsLegalFileSpec); - } - - [UnixOnlyTheory] - // Escape regex characters valid in Unix paths - [InlineData( - @"$()+.[^{|\?$()+.[^{|\$()+.[^{|", - @"$()+.[^{|/", - @"?$()+.[^{|/", - "$()+.[^{|", - @"^\$\(\)\+\.\[\^\{\|[/\\]+(?.\$\(\)\+\.\[\^\{\|[/\\]+)(?\$\(\)\+\.\[\^\{\|)$", - true, - true)] - // Collapse leading successive directory separators in fixed directory part - [InlineData( - @"\\\.\foo/bar", - @"///./foo/", - "", - "bar", - @"^[/\\]+foo[/\\]+(?)(?bar)$", - false, - true)] - public void GetFileSpecInfoUnix( - string filespec, - string expectedFixedDirectoryPart, - string expectedWildcardDirectoryPart, - string expectedFilenamePart, - string expectedMatchFileExpression, - bool expectedNeedsRecursion, - bool expectedIsLegalFileSpec) - { - TestGetFileSpecInfo( - filespec, - expectedFixedDirectoryPart, - expectedWildcardDirectoryPart, - expectedFilenamePart, - expectedMatchFileExpression, - expectedNeedsRecursion, - expectedIsLegalFileSpec); - } - - private void TestGetFileSpecInfo( - string filespec, - string expectedFixedDirectoryPart, - string expectedWildcardDirectoryPart, - string expectedFilenamePart, - string expectedMatchFileExpression, - bool expectedNeedsRecursion, - bool expectedIsLegalFileSpec) - { - FileMatcher.Default.GetFileSpecInfo( - filespec, - out string fixedDirectoryPart, - out string wildcardDirectoryPart, - out string filenamePart, - out bool needsRecursion, - out bool isLegalFileSpec); - string matchFileExpression = isLegalFileSpec - ? FileMatcher.RegularExpressionFromFileSpec(fixedDirectoryPart, wildcardDirectoryPart, filenamePart) - : string.Empty; - - fixedDirectoryPart.ShouldBe(expectedFixedDirectoryPart); - wildcardDirectoryPart.ShouldBe(expectedWildcardDirectoryPart); - filenamePart.ShouldBe(expectedFilenamePart); - matchFileExpression.ShouldBe(expectedMatchFileExpression); - needsRecursion.ShouldBe(expectedNeedsRecursion); - isLegalFileSpec.ShouldBe(expectedIsLegalFileSpec); - } - - #region Support functions. - - /// - /// This support class simulates a file system. - /// It accepts multiple sets of files and keeps track of how many files were "hit" - /// In this case, "hit" means that the caller asked for that file directly. - /// - internal sealed class MockFileSystem - { - /// - /// Array of files (set1) - /// - private string[] _fileSet1; - - /// - /// Array of files (set2) - /// - private string[] _fileSet2; - - /// - /// Array of files (set3) - /// - private string[] _fileSet3; - - /// - /// Number of times a file from set 1 was requested. - /// - private int _fileSet1Hits = 0; - - /// - /// Number of times a file from set 2 was requested. - /// - private int _fileSet2Hits = 0; - - /// - /// Number of times a file from set 3 was requested. - /// - private int _fileSet3Hits = 0; - - /// - /// Construct. - /// - /// First set of files. - /// Second set of files. - /// Third set of files. - internal MockFileSystem( - string[] fileSet1, - string[] fileSet2, - string[] fileSet3) - { - _fileSet1 = fileSet1; - _fileSet2 = fileSet2; - _fileSet3 = fileSet3; - } - - /// - /// Number of times a file from set 1 was requested. - /// - internal int FileHits1 - { - get { return _fileSet1Hits; } - } - - /// - /// Number of times a file from set 2 was requested. - /// - internal int FileHits2 - { - get { return _fileSet2Hits; } - } - - /// - /// Number of times a file from set 3 was requested. - /// - internal int FileHits3 - { - get { return _fileSet3Hits; } - } - - /// - /// Return files that match the given files. - /// - /// Candidate files. - /// The path to search within - /// The pattern to search for. - /// Hashtable receives the files. - /// - private int GetMatchingFiles(string[] candidates, string path, string pattern, ISet files) - { - int hits = 0; - - if (candidates != null) - { - foreach (string candidate in candidates) - { - string normalizedCandidate = Normalize(candidate); - - // Get the candidate directory. - string candidateDirectoryName = ""; - if (normalizedCandidate.IndexOfAny(FileMatcher.directorySeparatorCharacters) != -1) - { - candidateDirectoryName = Path.GetDirectoryName(normalizedCandidate); - } - - // Does the candidate directory match the requested path? - if (FileUtilities.PathsEqual(path, candidateDirectoryName)) - { - // Match the basic *.* or null. These both match any file. - if - ( - pattern == null || - String.Equals(pattern, "*.*", StringComparison.OrdinalIgnoreCase)) - { - ++hits; - files.Add(FileMatcher.Normalize(candidate)); - } - else if (pattern.Substring(0, 2) == "*.") // Match patterns like *.cs - { - string tail = pattern.Substring(1); - string candidateTail = candidate.Substring(candidate.Length - tail.Length); - if (String.Equals(tail, candidateTail, StringComparison.OrdinalIgnoreCase)) - { - ++hits; - files.Add(FileMatcher.Normalize(candidate)); - } - } - else if (pattern.Substring(pattern.Length - 4, 2) == ".?") // Match patterns like foo.?xt - { - string leader = pattern.Substring(0, pattern.Length - 4); - string candidateLeader = candidate.Substring(candidate.Length - leader.Length - 4, leader.Length); - if (String.Equals(leader, candidateLeader, StringComparison.OrdinalIgnoreCase)) - { - string tail = pattern.Substring(pattern.Length - 2); - string candidateTail = candidate.Substring(candidate.Length - 2); - if (String.Equals(tail, candidateTail, StringComparison.OrdinalIgnoreCase)) - { - ++hits; - files.Add(FileMatcher.Normalize(candidate)); - } - } - } - else if (!FileMatcher.HasWildcards(pattern)) - { - if (normalizedCandidate == Path.Combine(path, pattern)) - { - ++hits; - files.Add(candidate); - } - } - else - { - Assert.Fail(String.Format("Unhandled case in GetMatchingFiles: {0}", pattern)); - } - } - } - } - - return hits; - } - - /// - /// Given a path and pattern, return all the simulated directories out of candidates. - /// - /// Candidate file to extract directories from. - /// The path to search. - /// The pattern to match. - /// Receives the directories. - private void GetMatchingDirectories(string[] candidates, string path, string pattern, ISet directories) - { - if (candidates != null) - { - foreach (string candidate in candidates) - { - string normalizedCandidate = Normalize(candidate); - - if (IsMatchingDirectory(path, normalizedCandidate)) - { - int nextSlash = normalizedCandidate.IndexOfAny(FileMatcher.directorySeparatorCharacters, path.Length + 1); - if (nextSlash != -1) - { - // UNC paths start with a \\ fragment. Match against \\ when path is empty (i.e., inside the current working directory) - string match = normalizedCandidate.StartsWith(@"\\") && string.IsNullOrEmpty(path) - ? @"\\" - : normalizedCandidate.Substring(0, nextSlash); - - string baseMatch = Path.GetFileName(normalizedCandidate.Substring(0, nextSlash)); - - if - ( - String.Equals(pattern, "*.*", StringComparison.OrdinalIgnoreCase) - || pattern == null) - { - directories.Add(FileMatcher.Normalize(match)); - } - else if // Match patterns like ?emp - ( - pattern.Substring(0, 1) == "?" - && pattern.Length == baseMatch.Length) - { - string tail = pattern.Substring(1); - string baseMatchTail = baseMatch.Substring(1); - if (String.Equals(tail, baseMatchTail, StringComparison.OrdinalIgnoreCase)) - { - directories.Add(FileMatcher.Normalize(match)); - } - } - else - { - Assert.Fail(String.Format("Unhandled case in GetMatchingDirectories: {0}", pattern)); - } - } - } - } - } - } - - /// - /// Method that is delegable for use by FileMatcher. This method simulates a filesystem by returning - /// files and\or folders that match the requested path and pattern. - /// - /// Files, Directories or both - /// The path to search. - /// The pattern to search (may be null) - /// The matched files or folders. - internal IReadOnlyList GetAccessibleFileSystemEntries(FileMatcher.FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory) - { - string normalizedPath = Normalize(path); - - ISet files = new HashSet(); - if (entityType == FileMatcher.FileSystemEntity.Files || entityType == FileMatcher.FileSystemEntity.FilesAndDirectories) - { - _fileSet1Hits += GetMatchingFiles(_fileSet1, normalizedPath, pattern, files); - _fileSet2Hits += GetMatchingFiles(_fileSet2, normalizedPath, pattern, files); - _fileSet3Hits += GetMatchingFiles(_fileSet3, normalizedPath, pattern, files); - } - - if (entityType == FileMatcher.FileSystemEntity.Directories || entityType == FileMatcher.FileSystemEntity.FilesAndDirectories) - { - GetMatchingDirectories(_fileSet1, normalizedPath, pattern, files); - GetMatchingDirectories(_fileSet2, normalizedPath, pattern, files); - GetMatchingDirectories(_fileSet3, normalizedPath, pattern, files); - } - - return files.ToList(); - } - - /// - /// Given a path, fix it up so that it can be compared to another path. - /// - /// The path to fix up. - /// The normalized path. - internal static string Normalize(string path) - { - if (path.Length == 0) - { - return path; - } - - string normalized = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - if (Path.DirectorySeparatorChar != '\\') - { - normalized = path.Replace("\\", Path.DirectorySeparatorChar.ToString()); - } - // Replace leading UNC. - if (normalized.StartsWith(@"\\")) - { - normalized = "<:UNC:>" + normalized.Substring(2); - } - - // Preserve parent-directory markers. - normalized = normalized.Replace(@".." + Path.DirectorySeparatorChar, "<:PARENT:>"); - - // Just get rid of doubles enough to satisfy our test cases. - string doubleSeparator = Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString(); - normalized = normalized.Replace(doubleSeparator, Path.DirectorySeparatorChar.ToString()); - normalized = normalized.Replace(doubleSeparator, Path.DirectorySeparatorChar.ToString()); - normalized = normalized.Replace(doubleSeparator, Path.DirectorySeparatorChar.ToString()); - - // Strip any .\ - normalized = normalized.Replace(@"." + Path.DirectorySeparatorChar, ""); - - // Put back the preserved markers. - normalized = normalized.Replace("<:UNC:>", @"\\"); - normalized = normalized.Replace("<:PARENT:>", @".." + Path.DirectorySeparatorChar); - - return normalized; - } - - /// - /// Determines whether candidate is in a subfolder of path. - /// - /// - /// - /// True if there is a match. - private bool IsMatchingDirectory(string path, string candidate) - { - string normalizedPath = Normalize(path); - string normalizedCandidate = Normalize(candidate); - - // Current directory always matches for non-rooted paths. - if (path.Length == 0 && !Path.IsPathRooted(candidate)) - { - return true; - } - - if (normalizedCandidate.Length > normalizedPath.Length) - { - if (String.Compare(normalizedPath, 0, normalizedCandidate, 0, normalizedPath.Length, StringComparison.OrdinalIgnoreCase) == 0) - { - if (FileUtilities.EndsWithSlash(normalizedPath)) - { - return true; - } - else if (FileUtilities.IsSlash(normalizedCandidate[normalizedPath.Length])) - { - return true; - } - } - } - - return false; - } - - /// - /// Searches the candidates array for one that matches path - /// - /// - /// - /// The index of the first match or negative one. - private int IndexOfFirstMatchingDirectory(string path, string[] candidates) - { - if (candidates != null) - { - int i = 0; - foreach (string candidate in candidates) - { - if (IsMatchingDirectory(path, candidate)) - { - return i; - } - - ++i; - } - } - - return -1; - } - - /// - /// Delegable method that returns true if the given directory exists in this simulated filesystem - /// - /// The path to check. - /// True if the directory exists. - internal bool DirectoryExists(string path) - { - if (IndexOfFirstMatchingDirectory(path, _fileSet1) != -1) - { - return true; - } - - if (IndexOfFirstMatchingDirectory(path, _fileSet2) != -1) - { - return true; - } - - if (IndexOfFirstMatchingDirectory(path, _fileSet3) != -1) - { - return true; - } - - return false; - } - } - - /// - /// A general purpose method used to: - /// - /// (1) Simulate a file system. - /// (2) Check whether all matchingFiles where hit by the filespec pattern. - /// (3) Check whether all nonmatchingFiles were *not* hit by the filespec pattern. - /// (4) Check whether all untouchableFiles were not even requested (usually for perf reasons). - /// - /// These can be used in various combinations to test the filematcher framework. - /// - /// A FileMatcher filespec, possibly with wildcards. - /// Files that exist and should be matched. - /// Files that exists and should not be matched. - /// Files that exist but should not be requested. - private static void MatchDriver( - string filespec, - string[] matchingFiles, - string[] nonmatchingFiles, - string[] untouchableFiles) - { - MatchDriver(filespec, null, matchingFiles, nonmatchingFiles, untouchableFiles); - } - - /// - /// Runs the test 4 times with the include and exclude using either forward or backward slashes. - /// Expects the and to contain only backward slashes - /// - /// To preserve current MSBuild behaviour, it only does so if the path is not rooted. Rooted paths do not support forward slashes (as observed on MSBuild 14.0.25420.1) - /// - private static void MatchDriverWithDifferentSlashes( - string filespec, - string[] excludeFilespecs, - string[] matchingFiles, - string[] nonmatchingFiles, - string[] untouchableFiles) - { - // tests should call this method with backward slashes - Assert.DoesNotContain(filespec, "/"); - foreach (var excludeFilespec in excludeFilespecs) - { - Assert.DoesNotContain(excludeFilespec, "/"); - } - - var forwardSlashFileSpec = Helpers.ToForwardSlash(filespec); - var forwardSlashExcludeSpecs = excludeFilespecs.Select(Helpers.ToForwardSlash).ToArray(); - - MatchDriver(filespec, excludeFilespecs, matchingFiles, nonmatchingFiles, untouchableFiles); - MatchDriver(filespec, forwardSlashExcludeSpecs, matchingFiles, nonmatchingFiles, untouchableFiles); - MatchDriver(forwardSlashFileSpec, excludeFilespecs, matchingFiles, nonmatchingFiles, untouchableFiles); - MatchDriver(forwardSlashFileSpec, forwardSlashExcludeSpecs, matchingFiles, nonmatchingFiles, untouchableFiles); - } - - private static void MatchDriver(string filespec, string[] excludeFilespecs, string[] matchingFiles, string[] nonmatchingFiles, string[] untouchableFiles, bool normalizeAllPaths = true, bool normalizeExpectedMatchingFiles = false) - { - MockFileSystem mockFileSystem = new MockFileSystem(matchingFiles, nonmatchingFiles, untouchableFiles); - - var fileMatcher = new FileMatcher(new FileSystemAdapter(mockFileSystem), mockFileSystem.GetAccessibleFileSystemEntries); - - string[] files = fileMatcher.GetFiles( - String.Empty, /* we don't need project directory as we use mock filesystem */ - filespec, - excludeFilespecs?.ToList()) - .FileList; - - Func normalizeAllFunc = (paths => normalizeAllPaths ? paths.Select(MockFileSystem.Normalize).ToArray() : paths); - Func normalizeMatching = (paths => normalizeExpectedMatchingFiles ? paths.Select(MockFileSystem.Normalize).ToArray() : paths); - - string[] normalizedFiles = normalizeAllFunc(files); - - // Validate the matching files. - if (matchingFiles != null) - { - string[] normalizedMatchingFiles = normalizeAllFunc(normalizeMatching(matchingFiles)); - - foreach (string matchingFile in normalizedMatchingFiles) - { - int timesFound = 0; - foreach (string file in normalizedFiles) - { - if (String.Equals(file, matchingFile, StringComparison.OrdinalIgnoreCase)) - { - ++timesFound; - } - } - Assert.Equal(1, timesFound); - } - } - - // Validate the non-matching files - if (nonmatchingFiles != null) - { - string[] normalizedNonMatchingFiles = normalizeAllFunc(nonmatchingFiles); - - foreach (string nonmatchingFile in normalizedNonMatchingFiles) - { - int timesFound = 0; - foreach (string file in normalizedFiles) - { - if (String.Equals(file, nonmatchingFile, StringComparison.OrdinalIgnoreCase)) - { - ++timesFound; - } - } - Assert.Equal(0, timesFound); - } - } - - // Check untouchable files. - Assert.Equal(0, mockFileSystem.FileHits3); // "At least one file that was marked untouchable was referenced." - } - - /// - /// Simulate GetFileSystemEntries - /// - /// - /// - /// Array of matching file system entries (can be empty). - private static IReadOnlyList GetFileSystemEntriesLoopBack(FileMatcher.FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory) - { - return new string[] { Path.Combine(path, pattern) }; - } - - /************************************************************************************* - * Validate that SplitFileSpec(...) is returning the expected constituent values. - *************************************************************************************/ - - private static FileMatcher loopBackFileMatcher = new FileMatcher(FileSystems.Default, GetFileSystemEntriesLoopBack); - - private static void ValidateSplitFileSpec( - string filespec, - string expectedFixedDirectoryPart, - string expectedWildcardDirectoryPart, - string expectedFilenamePart) - { - string fixedDirectoryPart; - string wildcardDirectoryPart; - string filenamePart; - - loopBackFileMatcher.SplitFileSpec( - filespec, - out fixedDirectoryPart, - out wildcardDirectoryPart, - out filenamePart); - - expectedFixedDirectoryPart = FileUtilities.FixFilePath(expectedFixedDirectoryPart); - expectedWildcardDirectoryPart = FileUtilities.FixFilePath(expectedWildcardDirectoryPart); - expectedFilenamePart = FileUtilities.FixFilePath(expectedFilenamePart); - - if - ( - expectedWildcardDirectoryPart != wildcardDirectoryPart - || expectedFixedDirectoryPart != fixedDirectoryPart - || expectedFilenamePart != filenamePart) - { - Console.WriteLine("Expect Fixed '{0}' got '{1}'", expectedFixedDirectoryPart, fixedDirectoryPart); - Console.WriteLine("Expect Wildcard '{0}' got '{1}'", expectedWildcardDirectoryPart, wildcardDirectoryPart); - Console.WriteLine("Expect Filename '{0}' got '{1}'", expectedFilenamePart, filenamePart); - Assert.Fail("FileMatcher Regression: Failure while validating SplitFileSpec."); - } - } - - /************************************************************************************* - * Given a pattern (filespec) and a candidate filename (fileToMatch). Verify that they - * do indeed match. - *************************************************************************************/ - private static void ValidateFileMatch( - string filespec, - string fileToMatch, - bool shouldBeRecursive) - { - ValidateFileMatch(filespec, fileToMatch, shouldBeRecursive, /* Simulate filesystem? */ true); - } - - /************************************************************************************* - * Given a pattern (filespec) and a candidate filename (fileToMatch). Verify that they - * do indeed match. - *************************************************************************************/ - private static void ValidateFileMatch( - string filespec, - string fileToMatch, - bool shouldBeRecursive, - bool fileSystemSimulation) - { - if (!IsFileMatchAssertIfIllegal(filespec, fileToMatch, shouldBeRecursive)) - { - Assert.Fail("FileMatcher Regression: Failure while validating that files match."); - } - - // Now, simulate a filesystem with only fileToMatch. Make sure the file exists that way. - if (fileSystemSimulation) - { - MatchDriver( - filespec, - new string[] { fileToMatch }, - null, - null); - } - } - - /************************************************************************************* - * Given a pattern (filespec) and a candidate filename (fileToMatch). Verify that they - * DON'T match. - *************************************************************************************/ - private static void ValidateNoFileMatch( - string filespec, - string fileToMatch, - bool shouldBeRecursive) - { - if (IsFileMatchAssertIfIllegal(filespec, fileToMatch, shouldBeRecursive)) - { - Assert.Fail("FileMatcher Regression: Failure while validating that files don't match."); - } - - // Now, simulate a filesystem with only fileToMatch. Make sure the file doesn't exist that way. - MatchDriver( - filespec, - null, - new string[] { fileToMatch }, - null); - } - - /************************************************************************************* - * Verify that the given filespec is illegal. - *************************************************************************************/ - private static void ValidateIllegal( - string filespec) - { - Regex regexFileMatch; - bool needsRecursion; - bool isLegalFileSpec; - loopBackFileMatcher.GetFileSpecInfoWithRegexObject( - filespec, - out regexFileMatch, - out needsRecursion, - out isLegalFileSpec); - - if (isLegalFileSpec) - { - Assert.Fail("FileMatcher Regression: Expected an illegal filespec, but got a legal one."); - } - - // Now, FileMatcher is supposed to take any legal file name and just return it immediately. - // Let's see if it does. - MatchDriver( - filespec, // Not legal. - new string[] { filespec }, // Should match - null, - null); - } - /************************************************************************************* - * Given a pattern (filespec) and a candidate filename (fileToMatch) return true if - * FileMatcher would say that they match. - *************************************************************************************/ - private static bool IsFileMatchAssertIfIllegal( - string filespec, - string fileToMatch, - bool shouldBeRecursive) - { - FileMatcher.Result match = FileMatcher.Default.FileMatch(filespec, fileToMatch); - - if (!match.isLegalFileSpec) - { - Console.WriteLine("Checking FileSpec: '{0}' against '{1}'", filespec, fileToMatch); - Assert.Fail("FileMatcher Regression: Invalid filespec."); - } - if (shouldBeRecursive != match.isFileSpecRecursive) - { - Console.WriteLine("Checking FileSpec: '{0}' against '{1}'", filespec, fileToMatch); - Assert.True(shouldBeRecursive); // "FileMatcher Regression: Match was recursive when it shouldn't be." - Assert.False(shouldBeRecursive); // "FileMatcher Regression: Match was not recursive when it should have been." - } - return match.isMatch; - } - - #endregion - - private sealed class FileSystemAdapter : IFileSystem - { - private readonly MockFileSystem _mockFileSystem; - - public FileSystemAdapter(MockFileSystem mockFileSystem) - { - _mockFileSystem = mockFileSystem; - } - - public TextReader ReadFile(string path) => throw new NotImplementedException(); - - public Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) => throw new NotImplementedException(); - - public string ReadFileAllText(string path) => throw new NotImplementedException(); - - public byte[] ReadFileAllBytes(string path) => throw new NotImplementedException(); - - public IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) - { - return FileSystems.Default.EnumerateFiles(path, searchPattern, searchOption); - } - - public IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) - { - return FileSystems.Default.EnumerateDirectories(path, searchPattern, searchOption); - } - - public IEnumerable EnumerateFileSystemEntries(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) - { - return FileSystems.Default.EnumerateFileSystemEntries(path, searchPattern, searchOption); - } - - public FileAttributes GetAttributes(string path) => throw new NotImplementedException(); - - public DateTime GetLastWriteTimeUtc(string path) => throw new NotImplementedException(); - - public bool DirectoryExists(string path) - { - return _mockFileSystem.DirectoryExists(path); - } - - public bool FileExists(string path) - { - return FileSystems.Default.FileExists(path); - } - - public bool FileOrDirectoryExists(string path) - { - return FileSystems.Default.FileOrDirectoryExists(path); - } - } - } -} +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Framework/BackEnd/InterningBinaryReader.cs b/src/Framework/BackEnd/InterningBinaryReader.cs index ce7f6a4a348..a795156ad35 100644 --- a/src/Framework/BackEnd/InterningBinaryReader.cs +++ b/src/Framework/BackEnd/InterningBinaryReader.cs @@ -1,6 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ +<<<<<<<< HEAD:src/Framework/BackEnd/InterningBinaryReader.cs using System; using System.Buffers; using System.Diagnostics; @@ -298,3 +298,9 @@ public override BinaryReader Create(Stream stream) } } } +======== +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. +>>>>>>>> darc/back/463e0cf-337c123:src/Shared/InterningBinaryReader.cs diff --git a/src/Framework/BackEnd/TranslatorHelpers.cs b/src/Framework/BackEnd/TranslatorHelpers.cs index b195d17daf1..3ed04d2188b 100644 --- a/src/Framework/BackEnd/TranslatorHelpers.cs +++ b/src/Framework/BackEnd/TranslatorHelpers.cs @@ -1,426 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ -using System; -using System.Collections.Frozen; -using System.Collections.Immutable; -using System.Collections.Generic; -using System.Configuration.Assemblies; -using System.Globalization; -using System.Reflection; -using AssemblyHashAlgorithm = System.Configuration.Assemblies.AssemblyHashAlgorithm; - -#nullable disable - -namespace Microsoft.Build.BackEnd -{ - /// - /// This class provides helper methods to adapt from to - /// . - /// - internal static class TranslatorHelpers - { - /// - /// Translates an object implementing which does not expose a - /// public parameterless constructor. - /// - /// The reference type. - /// The translator - /// The value to be translated. - /// The factory method used to instantiate values of type T. - public static void Translate( - this ITranslator translator, - ref T instance, - NodePacketValueFactory valueFactory) where T : ITranslatable - { - if (!translator.TranslateNullable(instance)) - { - return; - } - if (translator.Mode == TranslationDirection.ReadFromStream) - { - instance = valueFactory(translator); - } - else - { - instance.Translate(translator); - } - } - - private static ObjectTranslatorWithValueFactory AdaptFactory(NodePacketValueFactory valueFactory) where T : ITranslatable - { - static void TranslateUsingValueFactory(ITranslator translator, NodePacketValueFactory valueFactory, ref T objectToTranslate) - { - translator.Translate(ref objectToTranslate, valueFactory); - } - - return TranslateUsingValueFactory; - } - - public static void Translate( - this ITranslator translator, - ref List list, - NodePacketValueFactory valueFactory) where T : class, ITranslatable - { - translator.Translate(ref list, AdaptFactory(valueFactory), valueFactory); - } - - public static void Translate( - this ITranslator translator, - ref IList list, - NodePacketValueFactory valueFactory, - NodePacketCollectionCreator collectionFactory) where L : IList where T : ITranslatable - { - translator.Translate(ref list, AdaptFactory(valueFactory), valueFactory, collectionFactory); - } - - public static void TranslateArray( - this ITranslator translator, - ref T[] array, - NodePacketValueFactory valueFactory) where T : class, ITranslatable - { - translator.TranslateArray(ref array, AdaptFactory(valueFactory), valueFactory); - } - - public static void TranslateDictionary( - this ITranslator translator, - ref Dictionary dictionary, - IEqualityComparer comparer, - NodePacketValueFactory valueFactory) where T : class, ITranslatable - { - translator.TranslateDictionary(ref dictionary, comparer, AdaptFactory(valueFactory), valueFactory); - } - - public static void InternDictionary( - this ITranslator translator, - ref Dictionary dictionary, - IEqualityComparer comparer) - { - IDictionary localDict = dictionary; - translator.TranslateDictionary( - ref localDict, - (ITranslator translator, ref string key) => translator.Intern(ref key), - (ITranslator translator, ref string val) => translator.Intern(ref val), - capacity => new Dictionary(capacity, comparer)); - dictionary = (Dictionary)localDict; - } - - public static void InternDictionary( - this ITranslator translator, - ref Dictionary dictionary, - IEqualityComparer stringComparer, - NodePacketValueFactory valueFactory) - where T : ITranslatable - { - IDictionary localDict = dictionary; - translator.TranslateDictionary( - ref localDict, - (ITranslator translator, ref string key) => translator.Intern(ref key), - AdaptFactory(valueFactory), - valueFactory, - capacity => new Dictionary(capacity, stringComparer)); - dictionary = (Dictionary)localDict; - } - - public static void InternPathDictionary( - this ITranslator translator, - ref Dictionary dictionary, - IEqualityComparer comparer) - { - IDictionary localDict = dictionary; - - // For now, assume only the value contains path-like strings (e.g. TaskItem metadata). - translator.TranslateDictionary( - ref localDict, - (ITranslator translator, ref string key) => translator.Intern(ref key), - (ITranslator translator, ref string val) => translator.InternPath(ref val), - capacity => new Dictionary(capacity, comparer)); - dictionary = (Dictionary)localDict; - } - - public static void InternPathDictionary( - this ITranslator translator, - ref Dictionary dictionary, - IEqualityComparer stringComparer, - NodePacketValueFactory valueFactory) - where T : ITranslatable - { - IDictionary localDict = dictionary; - translator.TranslateDictionary( - ref localDict, - (ITranslator translator, ref string key) => translator.InternPath(ref key), - AdaptFactory(valueFactory), - valueFactory, - capacity => new Dictionary(capacity, stringComparer)); - dictionary = (Dictionary)localDict; - } - - public static void TranslateDictionary( - this ITranslator translator, - ref D dictionary, - NodePacketValueFactory valueFactory) - where D : IDictionary, new() - where T : class, ITranslatable - { - translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), valueFactory); - } - - public static void TranslateDictionary( - this ITranslator translator, - ref D dictionary, - NodePacketValueFactory valueFactory, - NodePacketCollectionCreator collectionCreator) - where D : IDictionary - where T : class, ITranslatable - { - translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), valueFactory, collectionCreator); - } - - public static void TranslateDictionary( - this ITranslator translator, - ref FrozenDictionary dictionary, - IEqualityComparer comparer) - { - IDictionary localDict = dictionary; - translator.TranslateDictionary(ref localDict, capacity => new Dictionary(capacity, comparer)); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - dictionary = localDict?.ToFrozenDictionary(comparer); - } - } - - public static void TranslateDictionary( - this ITranslator translator, - ref IReadOnlyDictionary dictionary, - IEqualityComparer comparer) - { - // Defensive copy since immutable dictionaries are expected to be overwritten. - IReadOnlyDictionary localDict = dictionary; - - if (!translator.TranslateNullable(localDict)) - { - return; - } - - if (translator.Mode == TranslationDirection.WriteToStream) - { - int count = localDict.Count; - translator.Translate(ref count); - - foreach (KeyValuePair kvp in localDict) - { - string key = kvp.Key; - string value = kvp.Value; - - translator.Translate(ref key); - translator.Translate(ref value); - } - } - else - { - int count = default; - translator.Translate(ref count); - - ImmutableDictionary.Builder builder = ImmutableDictionary.Create(comparer).ToBuilder(); - - for (int i = 0; i < count; i++) - { - string key = null; - string value = null; - - translator.Translate(ref key); - translator.Translate(ref value); - - builder[key] = value; - } - - dictionary = builder.ToImmutable(); - } - } - - public static void TranslateDictionary( - this ITranslator translator, - ref ImmutableDictionary dictionary, - IEqualityComparer comparer) - { - // Defensive copy since immutable dictionaries are expected to be overwritten. - IReadOnlyDictionary localDict = dictionary; - - TranslateDictionary(translator, ref localDict, comparer); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - dictionary = (ImmutableDictionary)localDict; - } - } - - public static void TranslateHashSet( - this ITranslator translator, - ref HashSet hashSet, - NodePacketValueFactory valueFactory, - NodePacketCollectionCreator> collectionFactory) where T : class, ITranslatable - { - if (!translator.TranslateNullable(hashSet)) - { - return; - } - - int count = default; - if (translator.Mode == TranslationDirection.WriteToStream) - { - count = hashSet.Count; - } - translator.Translate(ref count); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - hashSet = collectionFactory(count); - for (int i = 0; i < count; i++) - { - T value = default; - translator.Translate(ref value, valueFactory); - hashSet.Add(value); - } - } - - if (translator.Mode == TranslationDirection.WriteToStream) - { - foreach (T item in hashSet) - { - T value = item; - translator.Translate(ref value, valueFactory); - } - } - } - - public static void Translate(this ITranslator translator, ref CultureInfo cultureInfo) - { - if (!translator.TranslateNullable(cultureInfo)) - { - return; - } - - int lcid = default; - - if (translator.Mode == TranslationDirection.WriteToStream) - { - lcid = cultureInfo.LCID; - } - - translator.Translate(ref lcid); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - cultureInfo = new CultureInfo(lcid); - } - } - - public static void Translate(this ITranslator translator, ref Version version) - { - if (!translator.TranslateNullable(version)) - { - return; - } - - int major = 0; - int minor = 0; - int build = 0; - int revision = 0; - - if (translator.Mode == TranslationDirection.WriteToStream) - { - major = version.Major; - minor = version.Minor; - build = version.Build; - revision = version.Revision; - } - - translator.Translate(ref major); - translator.Translate(ref minor); - translator.Translate(ref build); - translator.Translate(ref revision); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - if (build < 0) - { - version = new Version(major, minor); - } - else if (revision < 0) - { - version = new Version(major, minor, build); - } - else - { - version = new Version(major, minor, build, revision); - } - } - } - - public static void Translate(this ITranslator translator, ref AssemblyName assemblyName) - { - if (!translator.TranslateNullable(assemblyName)) - { - return; - } - - string name = null; - Version version = null; - AssemblyNameFlags flags = default; - ProcessorArchitecture processorArchitecture = default; - CultureInfo cultureInfo = null; - AssemblyHashAlgorithm hashAlgorithm = default; - AssemblyVersionCompatibility versionCompatibility = default; - string codeBase = null; - - byte[] publicKey = null; - byte[] publicKeyToken = null; - - if (translator.Mode == TranslationDirection.WriteToStream) - { - name = assemblyName.Name; - version = assemblyName.Version; - flags = assemblyName.Flags; - processorArchitecture = assemblyName.ProcessorArchitecture; - cultureInfo = assemblyName.CultureInfo; - hashAlgorithm = assemblyName.HashAlgorithm; - versionCompatibility = assemblyName.VersionCompatibility; - codeBase = assemblyName.CodeBase; - - publicKey = assemblyName.GetPublicKey(); // TODO: no need to serialize, public key is not used anywhere in context of RAR, only public key token - publicKeyToken = assemblyName.GetPublicKeyToken(); - } - - translator.Translate(ref name); - translator.Translate(ref version); - translator.TranslateEnum(ref flags, (int)flags); - translator.TranslateEnum(ref processorArchitecture, (int)processorArchitecture); - translator.Translate(ref cultureInfo); - translator.TranslateEnum(ref hashAlgorithm, (int)hashAlgorithm); - translator.TranslateEnum(ref versionCompatibility, (int)versionCompatibility); - translator.Translate(ref codeBase); - - translator.Translate(ref publicKey); - translator.Translate(ref publicKeyToken); - - if (translator.Mode == TranslationDirection.ReadFromStream) - { - assemblyName = new AssemblyName - { - Name = name, - Version = version, - Flags = flags, - ProcessorArchitecture = processorArchitecture, - CultureInfo = cultureInfo, - HashAlgorithm = hashAlgorithm, - VersionCompatibility = versionCompatibility, - CodeBase = codeBase, - }; - - assemblyName.SetPublicKey(publicKey); - assemblyName.SetPublicKeyToken(publicKeyToken); - } - } - } -} +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Framework/BuildEnvironmentHelper.cs b/src/Framework/BuildEnvironmentHelper.cs index d8a9bfd9e2c..fe4233bb626 100644 --- a/src/Framework/BuildEnvironmentHelper.cs +++ b/src/Framework/BuildEnvironmentHelper.cs @@ -1,6 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ +<<<<<<<< HEAD:src/Framework/BuildEnvironmentHelper.cs using System; using System.Collections.Generic; using System.IO; @@ -730,3 +730,9 @@ internal string MSBuildSDKsPath internal string MSBuildExtensionsPath { get; set; } } } +======== +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. +>>>>>>>> darc/back/463e0cf-337c123:src/Shared/BuildEnvironmentHelper.cs diff --git a/src/Framework/Collections/CopyOnWriteDictionary.cs b/src/Framework/Collections/CopyOnWriteDictionary.cs index f11c87f5b5c..3ed04d2188b 100644 --- a/src/Framework/Collections/CopyOnWriteDictionary.cs +++ b/src/Framework/Collections/CopyOnWriteDictionary.cs @@ -1,406 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.Serialization; - -namespace Microsoft.Build.Collections -{ - /// - /// A dictionary that has copy-on-write semantics. - /// KEYS AND VALUES MUST BE IMMUTABLE OR COPY-ON-WRITE FOR THIS TO WORK. - /// - /// The value type. - /// - /// Thread safety: for all users, this class is as thread safe as the underlying Dictionary implementation, that is, - /// safe for concurrent readers or one writer from EACH user. It achieves this by locking itself and cloning before - /// any write, if it is being shared - i.e., stopping sharing before any writes occur. - /// - /// - /// This class must be serializable as it is used for metadata passed to tasks, which may - /// be run in a separate appdomain. - /// - [Serializable] - internal class CopyOnWriteDictionary : IDictionary, IDictionary, ISerializable - { - /// - /// Empty dictionary with a , - /// used as the basis of new dictionaries with that comparer to avoid - /// allocating new comparers objects. - /// - private static readonly ImmutableDictionary NameComparerDictionaryPrototype = ImmutableDictionary.Create(MSBuildNameIgnoreCaseComparer.Default); - - /// - /// Empty dictionary with , - /// used as the basis of new dictionaries with that comparer to avoid - /// allocating new comparers objects. - /// - private static readonly ImmutableDictionary OrdinalIgnoreCaseComparerDictionaryPrototype = ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase); - - /// - /// The backing dictionary. - /// Lazily created. - /// - private ImmutableDictionary _backing; - - /// - /// Constructor. Consider supplying a comparer instead. - /// - internal CopyOnWriteDictionary() - { - _backing = ImmutableDictionary.Empty; - } - - /// - /// Constructor taking a specified comparer for the keys - /// - internal CopyOnWriteDictionary(IEqualityComparer? keyComparer) - { - _backing = GetInitialDictionary(keyComparer); - } - - /// - /// Serialization constructor, for crossing appdomain boundaries - /// - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "context", Justification = "Not needed")] - protected CopyOnWriteDictionary(SerializationInfo info, StreamingContext context) - { - object v = info.GetValue(nameof(_backing), typeof(KeyValuePair[]))!; - - object comparer = info.GetValue(nameof(Comparer), typeof(IEqualityComparer))!; - - var b = GetInitialDictionary((IEqualityComparer?)comparer); - - _backing = b.AddRange((KeyValuePair[])v); - } - - private static ImmutableDictionary GetInitialDictionary(IEqualityComparer? keyComparer) - { - return keyComparer is MSBuildNameIgnoreCaseComparer - ? NameComparerDictionaryPrototype - : keyComparer == StringComparer.OrdinalIgnoreCase - ? OrdinalIgnoreCaseComparerDictionaryPrototype - : ImmutableDictionary.Create(keyComparer); - } - - /// - /// Cloning constructor. Defers the actual clone. - /// - private CopyOnWriteDictionary(CopyOnWriteDictionary that) - { - _backing = that._backing; - } - - public CopyOnWriteDictionary(IDictionary dictionary) - { - _backing = dictionary.GetType() == typeof(ImmutableDictionary) - ? (ImmutableDictionary)dictionary - : dictionary.ToImmutableDictionary(); - } - - /// - /// Returns the collection of keys in the dictionary. - /// - public ICollection Keys => ((IDictionary)_backing).Keys; - - /// - /// Returns the collection of values in the dictionary. - /// - public ICollection Values => ((IDictionary)_backing).Values; - - /// - /// Returns the number of items in the collection. - /// - public int Count => _backing.Count; - - /// - /// Returns true if the collection is read-only. - /// - public bool IsReadOnly => ((IDictionary)_backing).IsReadOnly; - - /// - /// IDictionary implementation - /// - bool IDictionary.IsFixedSize => false; - - /// - /// IDictionary implementation - /// - bool IDictionary.IsReadOnly => IsReadOnly; - - /// - /// IDictionary implementation - /// - ICollection IDictionary.Keys => (ICollection)Keys; - - /// - /// IDictionary implementation - /// - ICollection IDictionary.Values => (ICollection)Values; - - /// - /// IDictionary implementation - /// - int ICollection.Count => Count; - - /// - /// IDictionary implementation - /// - bool ICollection.IsSynchronized => false; - - /// - /// IDictionary implementation - /// - object ICollection.SyncRoot => this; - - /// - /// Comparer used for keys - /// - internal IEqualityComparer Comparer - { - get => _backing.KeyComparer; - private set => _backing = _backing.WithComparers(keyComparer: value); - } - - /// - /// The backing copy-on-write dictionary, safe to reuse. - /// - internal ImmutableDictionary BackingDictionary => _backing; - - /// - /// Accesses the value for the specified key. - /// - public V this[string key] - { - get => _backing[key]; - - set - { - _backing = _backing.SetItem(key, value); - } - } - - /// - /// IDictionary implementation - /// - object? IDictionary.this[object key] - { - get - { - TryGetValue((string)key, out V? val); - return val; - } -#nullable disable - set => this[(string)key] = (V)value; -#nullable enable - } - - /// - /// Adds a value to the dictionary. - /// - public void Add(string key, V value) - { - _backing = _backing.SetItem(key, value); - } - - /// - /// Adds several value to the dictionary. - /// - public void SetItems(IEnumerable> items) - { - _backing = _backing.SetItems(items); - } - - public IEnumerable> Where(Func, bool> predicate) - { - return _backing.Where(predicate); - } - /// - /// Returns true if the dictionary contains the specified key. - /// - public bool ContainsKey(string key) - { - return _backing.ContainsKey(key); - } - - /// - /// Removes the entry for the specified key from the dictionary. - /// - public bool Remove(string key) - { - ImmutableDictionary initial = _backing; - - _backing = _backing.Remove(key); - - return initial != _backing; // whether the removal occured - } - -#nullable disable - /// - /// Attempts to find the value for the specified key in the dictionary. - /// - public bool TryGetValue(string key, out V value) - { - return _backing.TryGetValue(key, out value); - } -#nullable restore - - /// - /// Adds an item to the collection. - /// - public void Add(KeyValuePair item) - { - _backing = _backing.SetItem(item.Key, item.Value); - } - - /// - /// Clears the collection. - /// - public void Clear() - { - _backing = _backing.Clear(); - } - - /// - /// Returns true ff the collection contains the specified item. - /// - public bool Contains(KeyValuePair item) - { - return _backing.Contains(item); - } - - /// - /// Copies all of the elements of the collection to the specified array. - /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - ((IDictionary)_backing).CopyTo(array, arrayIndex); - } - - /// - /// Remove an item from the dictionary. - /// - public bool Remove(KeyValuePair item) - { - ImmutableDictionary initial = _backing; - - _backing = _backing.Remove(item.Key); - - return initial != _backing; // whether the removal occured - } - -#if NET472_OR_GREATER || NETCOREAPP - /// - /// Implementation of generic IEnumerable.GetEnumerator() - /// - public ImmutableDictionary.Enumerator GetEnumerator() - { - return _backing.GetEnumerator(); - } - - IEnumerator> IEnumerable>.GetEnumerator() - { - ImmutableDictionary.Enumerator enumerator = _backing.GetEnumerator(); - return _backing.GetEnumerator(); - } -#else - public IEnumerator> GetEnumerator() - { - return _backing.GetEnumerator(); - } -#endif - - /// - /// Implementation of IEnumerable.GetEnumerator() - /// - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable>)this).GetEnumerator(); - } - -#nullable disable - /// - /// IDictionary implementation. - /// - void IDictionary.Add(object key, object value) - { - Add((string)key, (V)value); - } -#nullable enable - - /// - /// IDictionary implementation. - /// - void IDictionary.Clear() - { - Clear(); - } - - /// - /// IDictionary implementation. - /// - bool IDictionary.Contains(object key) - { - return ContainsKey((string)key); - } - - /// - /// IDictionary implementation. - /// - IDictionaryEnumerator IDictionary.GetEnumerator() - { - return ((IDictionary)_backing).GetEnumerator(); - } - - /// - /// IDictionary implementation. - /// - void IDictionary.Remove(object key) - { - Remove((string)key); - } - - /// - /// IDictionary implementation. - /// - void ICollection.CopyTo(Array array, int index) - { - int i = 0; - foreach (KeyValuePair entry in this) - { - array.SetValue(new DictionaryEntry(entry.Key, entry.Value), index + i); - i++; - } - } - - /// - /// Clone, with the actual clone deferred - /// - internal CopyOnWriteDictionary Clone() - { - return new CopyOnWriteDictionary(this); - } - - /// - /// Returns true if these dictionaries have the same backing. - /// - internal bool HasSameBacking(CopyOnWriteDictionary other) - { - return ReferenceEquals(other._backing, _backing); - } - - public void GetObjectData(SerializationInfo info, StreamingContext context) - { - ImmutableDictionary snapshot = _backing; - KeyValuePair[] array = snapshot.ToArray(); - - info.AddValue(nameof(_backing), array); - info.AddValue(nameof(Comparer), Comparer); - } - } -} +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Framework/Collections/ReadOnlyEmptyDictionary.cs b/src/Framework/Collections/ReadOnlyEmptyDictionary.cs index 917d6c2faa4..b2b55dec503 100644 --- a/src/Framework/Collections/ReadOnlyEmptyDictionary.cs +++ b/src/Framework/Collections/ReadOnlyEmptyDictionary.cs @@ -1,6 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ +<<<<<<<< HEAD:src/Framework/Collections/ReadOnlyEmptyDictionary.cs using System; using System.Collections; using System.Collections.Generic; @@ -295,3 +295,9 @@ public void CopyTo(System.Array array, int index) } } } +======== +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. +>>>>>>>> darc/back/463e0cf-337c123:src/Shared/ReadOnlyEmptyDictionary.cs diff --git a/src/Framework/Loader/LoadedType.cs b/src/Framework/Loader/LoadedType.cs index fe240c49bb8..d1735caab58 100644 --- a/src/Framework/Loader/LoadedType.cs +++ b/src/Framework/Loader/LoadedType.cs @@ -1,6 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ +<<<<<<<< HEAD:src/Framework/Loader/LoadedType.cs using System; using System.Reflection; using Microsoft.Build.Execution; @@ -244,3 +244,9 @@ private bool CheckForHardcodedSTARequirement() #endregion } } +======== +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. +>>>>>>>> darc/back/463e0cf-337c123:src/Shared/LoadedType.cs diff --git a/src/Framework/MultiThreadedTaskEnvironmentDriver.cs b/src/Framework/MultiThreadedTaskEnvironmentDriver.cs index da8e6bc4430..7908de6da32 100644 --- a/src/Framework/MultiThreadedTaskEnvironmentDriver.cs +++ b/src/Framework/MultiThreadedTaskEnvironmentDriver.cs @@ -1,6 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ +<<<<<<<< HEAD:src/Framework/MultiThreadedTaskEnvironmentDriver.cs using System; using System.Collections; using System.Collections.Generic; @@ -132,3 +132,9 @@ public void Dispose() } } } +======== +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. +>>>>>>>> darc/back/463e0cf-337c123:src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs index 46aad3925dc..1b5ce893994 100644 --- a/src/Framework/NativeMethods.cs +++ b/src/Framework/NativeMethods.cs @@ -1484,15 +1484,18 @@ internal static List> GetChildProcessIds(in /// Internal, optimized GetCurrentDirectory implementation that simply delegates to the native method /// /// - internal static unsafe string GetCurrentDirectory() + internal static string GetCurrentDirectory() { #if FEATURE_LEGACY_GETCURRENTDIRECTORY if (IsWindows) { - int bufferSize = GetCurrentDirectoryWin32(0, null); - char* buffer = stackalloc char[bufferSize]; - int pathLength = GetCurrentDirectoryWin32(bufferSize, buffer); - return new string(buffer, startIndex: 0, length: pathLength); + unsafe + { + int bufferSize = GetCurrentDirectoryWin32(0, null); + char* buffer = stackalloc char[bufferSize]; + int pathLength = GetCurrentDirectoryWin32(bufferSize, buffer); + return new string(buffer, startIndex: 0, length: pathLength); + } } #endif return Directory.GetCurrentDirectory(); diff --git a/src/Framework/SolutionConfiguration.cs b/src/Framework/SolutionConfiguration.cs index 22b086a8c71..3ed04d2188b 100644 --- a/src/Framework/SolutionConfiguration.cs +++ b/src/Framework/SolutionConfiguration.cs @@ -1,147 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Xml; -using Microsoft.Build.Framework; - -namespace Microsoft.Build.Shared -{ - internal sealed class SolutionConfiguration - { - public const string ProjectAttribute = "Project"; - - public const string AbsolutePathAttribute = "AbsolutePath"; - - public const string BuildProjectInSolutionAttribute = "BuildProjectInSolution"; - - public static readonly char[] ConfigPlatformSeparator = { '|' }; - - // This field stores pre-cached project elements for project guids for quicker access by project guid - private readonly Dictionary _cachedProjectElements; - - // This field stores pre-cached project elements for project guids for quicker access by project absolute path - private readonly Dictionary _cachedProjectElementsByAbsolutePath; - - // This field stores the project absolute path for quicker access by project guid - private readonly Dictionary _cachedProjectAbsolutePathsByGuid; - - // This field stores the project guid for quicker access by project absolute path - private readonly Dictionary _cachedProjectGuidsByAbsolutePath; - - // This field stores the list of dependency project guids by depending project guid - private readonly Dictionary> _cachedDependencyProjectGuidsByDependingProjectGuid; - - public SolutionConfiguration(string xmlString) - { - // Example: - // - // - // Debug|AnyCPU - // Debug|AnyCPU - // Debug|AnyCPU - // - // - - XmlNodeList? projectConfigurationElements = GetProjectConfigurations(xmlString); - int projectConfigurationCount = projectConfigurationElements?.Count ?? 0; - - _cachedProjectElements = new Dictionary(projectConfigurationCount, StringComparer.OrdinalIgnoreCase); - _cachedProjectElementsByAbsolutePath = new Dictionary(projectConfigurationCount, StringComparer.OrdinalIgnoreCase); - _cachedProjectAbsolutePathsByGuid = new Dictionary(projectConfigurationCount, StringComparer.OrdinalIgnoreCase); - _cachedProjectGuidsByAbsolutePath = new Dictionary(projectConfigurationCount, StringComparer.OrdinalIgnoreCase); - _cachedDependencyProjectGuidsByDependingProjectGuid = new Dictionary>(projectConfigurationCount, StringComparer.OrdinalIgnoreCase); - - if (projectConfigurationElements != null) - { - foreach (XmlElement xmlElement in projectConfigurationElements) - { - string projectGuid = xmlElement.GetAttribute(ProjectAttribute); - string projectAbsolutePath = xmlElement.GetAttribute(AbsolutePathAttribute); - - // What we really want here is the normalized path, like we'd get with an item's "FullPath" metadata. However, - // if there's some bogus full path in the solution configuration (e.g. a website with a "full path" of c:\solutiondirectory\http://localhost) - // we do NOT want to throw -- chances are extremely high that that's information that will never actually be used. So resolve the full path - // but just swallow any IO-related exceptions that result. If the path is bogus, the method will return null, so we'll just quietly fail - // to cache it below. - projectAbsolutePath = FileUtilities.GetFullPathNoThrow(projectAbsolutePath); - - if (!string.IsNullOrEmpty(projectGuid)) - { - _cachedProjectElements[projectGuid] = xmlElement; - if (!string.IsNullOrEmpty(projectAbsolutePath)) - { - _cachedProjectElementsByAbsolutePath[projectAbsolutePath] = xmlElement; - _cachedProjectAbsolutePathsByGuid[projectGuid] = projectAbsolutePath; - _cachedProjectGuidsByAbsolutePath[projectAbsolutePath] = projectGuid; - } - - foreach (XmlNode dependencyNode in xmlElement.ChildNodes) - { - if (dependencyNode.NodeType != XmlNodeType.Element) - { - continue; - } - - XmlElement dependencyElement = ((XmlElement)dependencyNode); - - if (!String.Equals(dependencyElement.Name, "ProjectDependency", StringComparison.Ordinal)) - { - continue; - } - - string dependencyGuid = dependencyElement.GetAttribute("Project"); - - if (dependencyGuid.Length == 0) - { - continue; - } - - if (!_cachedDependencyProjectGuidsByDependingProjectGuid.TryGetValue(projectGuid, out List? list)) - { - list = new List(); - _cachedDependencyProjectGuidsByDependingProjectGuid.Add(projectGuid, list); - } - - list.Add(dependencyGuid); - } - } - } - } - } - - public static SolutionConfiguration Empty { get; } = new SolutionConfiguration(string.Empty); - - public ICollection ProjectConfigurations => _cachedProjectElements.Values; - - public static XmlNodeList? GetProjectConfigurations(string xmlString) - { - XmlDocument? doc = null; - - if (!string.IsNullOrEmpty(xmlString)) - { - doc = new XmlDocument(); - var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }; - using (XmlReader reader = XmlReader.Create(new StringReader(xmlString), settings)) - { - doc.Load(reader); - } - } - - return doc?.DocumentElement?.ChildNodes; - } - - public bool TryGetProjectByGuid(string projectGuid, [NotNullWhen(true)] out XmlElement? projectElement) => _cachedProjectElements.TryGetValue(projectGuid, out projectElement); - - public bool TryGetProjectByAbsolutePath(string projectFullPath, [NotNullWhen(true)] out XmlElement? projectElement) => _cachedProjectElementsByAbsolutePath.TryGetValue(projectFullPath, out projectElement); - - public bool TryGetProjectGuidByAbsolutePath(string projectFullPath, [NotNullWhen(true)] out string? projectGuid) => _cachedProjectGuidsByAbsolutePath.TryGetValue(projectFullPath, out projectGuid); - - public bool TryGetProjectDependencies(string projectGuid, [NotNullWhen(true)] out List? dependencyProjectGuids) => _cachedDependencyProjectGuidsByDependingProjectGuid.TryGetValue(projectGuid, out dependencyProjectGuids); - - public bool TryGetProjectPathByGuid(string projectGuid, [NotNullWhen(true)] out string? projectPath) => _cachedProjectAbsolutePathsByGuid.TryGetValue(projectGuid, out projectPath); - } -} +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Framework/Utilities/AssemblyLoadInfo.cs b/src/Framework/Utilities/AssemblyLoadInfo.cs index d347119e4b7..e5cf6cbed81 100644 --- a/src/Framework/Utilities/AssemblyLoadInfo.cs +++ b/src/Framework/Utilities/AssemblyLoadInfo.cs @@ -1,6 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ +<<<<<<<< HEAD:src/Framework/Utilities/AssemblyLoadInfo.cs using System; using System.Diagnostics; using System.IO; @@ -234,3 +234,9 @@ internal override bool IsInlineTask } } } +======== +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. +>>>>>>>> darc/back/463e0cf-337c123:src/Shared/AssemblyLoadInfo.cs diff --git a/src/Framework/Utilities/CollectionHelpers.cs b/src/Framework/Utilities/CollectionHelpers.cs index 624502e3196..3ed04d2188b 100644 --- a/src/Framework/Utilities/CollectionHelpers.cs +++ b/src/Framework/Utilities/CollectionHelpers.cs @@ -1,80 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ -using System; -using System.Collections.Generic; -using System.Linq; - -#nullable disable - -namespace Microsoft.Build.Shared -{ - /// - /// Utilities for collections - /// - internal static class CollectionHelpers - { - /// - /// Returns a new list containing the input list - /// contents, except for nulls - /// - /// Type of list elements - internal static List RemoveNulls(List inputs) - { - List inputsWithoutNulls = new List(inputs.Count); - - foreach (T entry in inputs) - { - if (entry != null) - { - inputsWithoutNulls.Add(entry); - } - } - - // Avoid possibly having two identical lists floating around - return (inputsWithoutNulls.Count == inputs.Count) ? inputs : inputsWithoutNulls; - } - - /// - /// Extension method -- combines a TryGet with a check to see that the value is equal. - /// - internal static bool ContainsValueAndIsEqual(this Dictionary dictionary, string key, string value, StringComparison comparer) - { - string valueFromDictionary; - if (dictionary.TryGetValue(key, out valueFromDictionary)) - { - return String.Equals(value, valueFromDictionary, comparer); - } - - return false; - } - - internal static bool SetEquivalent(IEnumerable a, IEnumerable b) - { - return a.ToHashSet().SetEquals(b); - } - - internal static bool DictionaryEquals(IReadOnlyDictionary a, IReadOnlyDictionary b) - { - if (a.Count != b.Count) - { - return false; - } - - foreach (var aKvp in a) - { - if (!b.TryGetValue(aKvp.Key, out var bValue)) - { - return false; - } - - if (!Equals(aKvp.Value, bValue)) - { - return false; - } - } - - return true; - } - } -} +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Framework/Utilities/FileMatcher.cs b/src/Framework/Utilities/FileMatcher.cs index b6411ec342e..e2bea23d34e 100644 --- a/src/Framework/Utilities/FileMatcher.cs +++ b/src/Framework/Utilities/FileMatcher.cs @@ -1,6 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ +<<<<<<<< HEAD:src/Framework/Utilities/FileMatcher.cs using System; using System.Buffers; using System.Collections.Concurrent; @@ -2700,3 +2700,9 @@ private static bool DirectoryEndsWithPattern(string directoryPath, string patter internal static bool IsRecursiveDirectoryMatch(string path) => path.TrimTrailingSlashes() == recursiveDirectoryMatch; } } +======== +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. +>>>>>>>> darc/back/463e0cf-337c123:src/Shared/FileMatcher.cs diff --git a/src/Framework/Utilities/FrameworkLocationHelper.cs b/src/Framework/Utilities/FrameworkLocationHelper.cs index 1f6d047d4c9..c121ae59385 100644 --- a/src/Framework/Utilities/FrameworkLocationHelper.cs +++ b/src/Framework/Utilities/FrameworkLocationHelper.cs @@ -1,6 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. +PLEASE READ +<<<<<<<< HEAD:src/Framework/Utilities/FrameworkLocationHelper.cs using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -1791,3 +1791,9 @@ public override string GetPathToDotNetFrameworkReferenceAssemblies() } } } +======== +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. +>>>>>>>> darc/back/463e0cf-337c123:src/Shared/FrameworkLocationHelper.cs diff --git a/src/MSBuild.Bootstrap.Utils/Tasks/InstallDotNetCoreTask.cs b/src/MSBuild.Bootstrap.Utils/Tasks/InstallDotNetCoreTask.cs index 9606b11002d..15d35446d8a 100644 --- a/src/MSBuild.Bootstrap.Utils/Tasks/InstallDotNetCoreTask.cs +++ b/src/MSBuild.Bootstrap.Utils/Tasks/InstallDotNetCoreTask.cs @@ -154,13 +154,32 @@ private ScriptExecutionSettings SetupScriptsExecutionSettings() { string scriptExtension = IsWindows ? "ps1" : "sh"; string scriptPath = Path.Combine(DotNetInstallScriptRootPath, $"{ScriptName}.{scriptExtension}"); + // On Windows the native command-line parser treats a backslash before the closing quote as + // escaping it (e.g. "C:\dir\" becomes C:\dir"), so trim a trailing separator before quoting. + string installDir = TrimTrailingDirectorySeparators(InstallDir); string scriptArgs = IsWindows - ? $"-NoProfile -ExecutionPolicy Bypass -File {scriptPath} -Version {Version} -InstallDir {InstallDir}" - : $"{scriptPath} --version {Version} --install-dir {InstallDir}"; + ? $"-NoProfile -ExecutionPolicy Bypass -File \"{scriptPath}\" -Version {Version} -InstallDir \"{installDir}\"" + : $"\"{scriptPath}\" --version {Version} --install-dir \"{installDir}\""; return new ScriptExecutionSettings($"{ScriptName}.{scriptExtension}", scriptPath, scriptArgs); } + /// + /// Trims trailing directory separators while preserving a path root (e.g. "C:\"). + /// + private static string TrimTrailingDirectorySeparators(string path) + { + string root = Path.GetPathRoot(path) ?? string.Empty; + while (path.Length > root.Length && + (path[path.Length - 1] == Path.DirectorySeparatorChar || + path[path.Length - 1] == Path.AltDirectorySeparatorChar)) + { + path = path.Substring(0, path.Length - 1); + } + + return path; + } + /// /// A private struct to hold settings for script execution. /// diff --git a/src/Shared/BinaryReaderExtensions.cs b/src/Shared/BinaryReaderExtensions.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/BinaryReaderExtensions.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/BinaryWriterExtensions.cs b/src/Shared/BinaryWriterExtensions.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/BinaryWriterExtensions.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/Constants.cs b/src/Shared/Constants.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/Constants.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/EnvironmentUtilities.cs b/src/Shared/EnvironmentUtilities.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/EnvironmentUtilities.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/EscapingUtilities.cs b/src/Shared/EscapingUtilities.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/EscapingUtilities.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/ExceptionHandling.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/CachingFileSystemWrapper.cs b/src/Shared/FileSystem/CachingFileSystemWrapper.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileSystem/CachingFileSystemWrapper.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/FileSystems.cs b/src/Shared/FileSystem/FileSystems.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileSystem/FileSystems.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/IFileSystem.cs b/src/Shared/FileSystem/IFileSystem.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileSystem/IFileSystem.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs b/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/ManagedFileSystem.cs b/src/Shared/FileSystem/ManagedFileSystem.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileSystem/ManagedFileSystem.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/NativeWin32Exception.cs b/src/Shared/FileSystem/NativeWin32Exception.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileSystem/NativeWin32Exception.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/SafeFileHandle.cs b/src/Shared/FileSystem/SafeFileHandle.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileSystem/SafeFileHandle.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/WindowsFileSystem.cs b/src/Shared/FileSystem/WindowsFileSystem.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileSystem/WindowsFileSystem.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/WindowsNative.cs b/src/Shared/FileSystem/WindowsNative.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileSystem/WindowsNative.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystemSources.proj b/src/Shared/FileSystemSources.proj new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileSystemSources.proj @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileUtilities.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileUtilitiesRegex.cs b/src/Shared/FileUtilitiesRegex.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/FileUtilitiesRegex.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/IMSBuildElementLocation.cs b/src/Shared/IMSBuildElementLocation.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/IMSBuildElementLocation.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/Modifiers.cs b/src/Shared/Modifiers.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/Modifiers.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.cs.xlf b/src/Shared/Resources/xlf/Strings.shared.cs.xlf index 7c27da24ffb..849c8056d00 100644 --- a/src/Shared/Resources/xlf/Strings.shared.cs.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.cs.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.de.xlf b/src/Shared/Resources/xlf/Strings.shared.de.xlf index 96e44a116e6..d2f313d6ad7 100644 --- a/src/Shared/Resources/xlf/Strings.shared.de.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.de.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.es.xlf b/src/Shared/Resources/xlf/Strings.shared.es.xlf index 28992cf81d7..a4bc963bdbc 100644 --- a/src/Shared/Resources/xlf/Strings.shared.es.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.es.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.fr.xlf b/src/Shared/Resources/xlf/Strings.shared.fr.xlf index c33ef71d145..c300e1b5251 100644 --- a/src/Shared/Resources/xlf/Strings.shared.fr.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.fr.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.it.xlf b/src/Shared/Resources/xlf/Strings.shared.it.xlf index 13d9fc42900..11d616a31a1 100644 --- a/src/Shared/Resources/xlf/Strings.shared.it.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.it.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.ja.xlf b/src/Shared/Resources/xlf/Strings.shared.ja.xlf index bf5724129aa..f07f4185a32 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ja.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ja.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.ko.xlf b/src/Shared/Resources/xlf/Strings.shared.ko.xlf index f36fecb0d5d..d8eef7e2479 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ko.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ko.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.pl.xlf b/src/Shared/Resources/xlf/Strings.shared.pl.xlf index e0cf71f3188..61c8747b748 100644 --- a/src/Shared/Resources/xlf/Strings.shared.pl.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.pl.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf b/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf index 01ca48b9f29..e8ea01505ff 100644 --- a/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.ru.xlf b/src/Shared/Resources/xlf/Strings.shared.ru.xlf index d0f2c1e081f..2bfb3efe06c 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ru.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ru.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.tr.xlf b/src/Shared/Resources/xlf/Strings.shared.tr.xlf index 359306dbdc8..510fd93fbe2 100644 --- a/src/Shared/Resources/xlf/Strings.shared.tr.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.tr.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf b/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf index 6b1f3ae581e..d3e0d78a845 100644 --- a/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf b/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf index 518e09fdb4c..70777704d7a 100644 --- a/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf @@ -341,4 +341,4 @@ - \ No newline at end of file + diff --git a/src/Shared/TempFileUtilities.cs b/src/Shared/TempFileUtilities.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/TempFileUtilities.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/UnitTests/EscapingUtilities_Tests.cs b/src/Shared/UnitTests/EscapingUtilities_Tests.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/UnitTests/EscapingUtilities_Tests.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/UnitTests/FileUtilities_Tests.cs b/src/Shared/UnitTests/FileUtilities_Tests.cs new file mode 100644 index 00000000000..3ed04d2188b --- /dev/null +++ b/src/Shared/UnitTests/FileUtilities_Tests.cs @@ -0,0 +1,6 @@ +PLEASE READ + +Please remove this file during conflict resolution in your PR. +This file has been reverted (removed) in the source repository but the PR branch +does not have the file yet as it's based on an older commit. This means the file is +not getting removed in the PR due to the other conflicts. \ No newline at end of file From f848e0c058fa4b8067ad970bef1a3c278af4db4d Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 17 Jul 2026 02:06:01 +0000 Subject: [PATCH 2/4] Update dependencies from build 322439 No dependency updates to commit [[ commit created by automation ]] --- eng/Version.Details.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index feea7e754e7..e84d6b39b6e 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,6 +1,6 @@ - + From b1de25de7172ac76aa194472b6a15e25f50a220e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Mon, 20 Jul 2026 10:04:38 +0200 Subject: [PATCH 3/4] Resolve codeflow conflicts: keep baseline bump + real backflow, drop phantom edits This unsafe codeflow PR had no prior baseline, so darc generated spurious conflicts against content that is byte-identical between vs18.6 and the VMR (release/10.0.3xx): 29 files replaced with "PLEASE READ" placeholders, 8 files with raw conflict markers, and 23 phantom src/Shared/* placeholder adds. Resolved by keeping only the legitimate changes and reverting all phantom edits to the fork point (7cac27b6): - eng/Version.Details.xml: advance baseline to 463e0cf / BarId 322439 - src/Framework/NativeMethods.cs: VMR-side unsafe-block refactor - src/MSBuild.Bootstrap.Utils/Tasks/InstallDotNetCoreTask.cs: install-dir quoting fix Net diff vs fork point is now exactly these 3 files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f81c60b-d12b-42df-bae1-af82864104f5 --- eng/_._ | 0 src/Build/Evaluation/ToolsetElement.cs | 749 ++++- src/Framework.UnitTests/FileMatcher_Tests.cs | 2699 ++++++++++++++++- .../BackEnd/InterningBinaryReader.cs | 10 +- src/Framework/BackEnd/TranslatorHelpers.cs | 430 ++- src/Framework/BuildEnvironmentHelper.cs | 10 +- .../Collections/CopyOnWriteDictionary.cs | 410 ++- .../Collections/ReadOnlyEmptyDictionary.cs | 10 +- src/Framework/Loader/LoadedType.cs | 10 +- .../MultiThreadedTaskEnvironmentDriver.cs | 10 +- src/Framework/SolutionConfiguration.cs | 151 +- src/Framework/Utilities/AssemblyLoadInfo.cs | 10 +- src/Framework/Utilities/CollectionHelpers.cs | 84 +- src/Framework/Utilities/FileMatcher.cs | 10 +- .../Utilities/FrameworkLocationHelper.cs | 10 +- src/Shared/BinaryReaderExtensions.cs | 6 - src/Shared/BinaryWriterExtensions.cs | 6 - src/Shared/Constants.cs | 6 - src/Shared/EnvironmentUtilities.cs | 6 - src/Shared/EscapingUtilities.cs | 6 - src/Shared/ExceptionHandling.cs | 6 - .../FileSystem/CachingFileSystemWrapper.cs | 6 - src/Shared/FileSystem/FileSystems.cs | 6 - src/Shared/FileSystem/IFileSystem.cs | 6 - .../FileSystem/MSBuildOnWindowsFileSystem.cs | 6 - src/Shared/FileSystem/ManagedFileSystem.cs | 6 - src/Shared/FileSystem/NativeWin32Exception.cs | 6 - src/Shared/FileSystem/SafeFileHandle.cs | 6 - src/Shared/FileSystem/WindowsFileSystem.cs | 6 - src/Shared/FileSystem/WindowsNative.cs | 6 - src/Shared/FileSystemSources.proj | 6 - src/Shared/FileUtilities.cs | 6 - src/Shared/FileUtilitiesRegex.cs | 6 - src/Shared/IMSBuildElementLocation.cs | 6 - src/Shared/Modifiers.cs | 6 - .../Resources/xlf/Strings.shared.cs.xlf | 2 +- .../Resources/xlf/Strings.shared.de.xlf | 2 +- .../Resources/xlf/Strings.shared.es.xlf | 2 +- .../Resources/xlf/Strings.shared.fr.xlf | 2 +- .../Resources/xlf/Strings.shared.it.xlf | 2 +- .../Resources/xlf/Strings.shared.ja.xlf | 2 +- .../Resources/xlf/Strings.shared.ko.xlf | 2 +- .../Resources/xlf/Strings.shared.pl.xlf | 2 +- .../Resources/xlf/Strings.shared.pt-BR.xlf | 2 +- .../Resources/xlf/Strings.shared.ru.xlf | 2 +- .../Resources/xlf/Strings.shared.tr.xlf | 2 +- .../Resources/xlf/Strings.shared.zh-Hans.xlf | 2 +- .../Resources/xlf/Strings.shared.zh-Hant.xlf | 2 +- src/Shared/TempFileUtilities.cs | 6 - .../UnitTests/EscapingUtilities_Tests.cs | 6 - src/Shared/UnitTests/FileUtilities_Tests.cs | 6 - 51 files changed, 4522 insertions(+), 245 deletions(-) mode change 100755 => 100644 eng/_._ delete mode 100644 src/Shared/BinaryReaderExtensions.cs delete mode 100644 src/Shared/BinaryWriterExtensions.cs delete mode 100644 src/Shared/Constants.cs delete mode 100644 src/Shared/EnvironmentUtilities.cs delete mode 100644 src/Shared/EscapingUtilities.cs delete mode 100644 src/Shared/ExceptionHandling.cs delete mode 100644 src/Shared/FileSystem/CachingFileSystemWrapper.cs delete mode 100644 src/Shared/FileSystem/FileSystems.cs delete mode 100644 src/Shared/FileSystem/IFileSystem.cs delete mode 100644 src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs delete mode 100644 src/Shared/FileSystem/ManagedFileSystem.cs delete mode 100644 src/Shared/FileSystem/NativeWin32Exception.cs delete mode 100644 src/Shared/FileSystem/SafeFileHandle.cs delete mode 100644 src/Shared/FileSystem/WindowsFileSystem.cs delete mode 100644 src/Shared/FileSystem/WindowsNative.cs delete mode 100644 src/Shared/FileSystemSources.proj delete mode 100644 src/Shared/FileUtilities.cs delete mode 100644 src/Shared/FileUtilitiesRegex.cs delete mode 100644 src/Shared/IMSBuildElementLocation.cs delete mode 100644 src/Shared/Modifiers.cs delete mode 100644 src/Shared/TempFileUtilities.cs delete mode 100644 src/Shared/UnitTests/EscapingUtilities_Tests.cs delete mode 100644 src/Shared/UnitTests/FileUtilities_Tests.cs diff --git a/eng/_._ b/eng/_._ old mode 100755 new mode 100644 diff --git a/src/Build/Evaluation/ToolsetElement.cs b/src/Build/Evaluation/ToolsetElement.cs index 3ed04d2188b..9b6945eaf86 100644 --- a/src/Build/Evaluation/ToolsetElement.cs +++ b/src/Build/Evaluation/ToolsetElement.cs @@ -1,6 +1,745 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using Microsoft.Build.Collections; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; + +#nullable disable + +namespace Microsoft.Build.Evaluation +{ + /// + /// Helper class for reading toolsets out of the configuration file. + /// + internal static class ToolsetConfigurationReaderHelpers + { + /// + /// Lock for process wide ToolsetConfigurationSection section cache + /// + private static readonly LockType s_syncLock = new LockType(); + + /// + /// Process wide ToolsetConfigurationSection section cache + /// + private static ToolsetConfigurationSection s_toolsetConfigurationSectionCache; + private static Configuration s_configurationOfCachedSection; + + internal static ToolsetConfigurationSection ReadToolsetConfigurationSection(Configuration configuration) + { + if (configuration == null) + { + return null; + } + + lock (s_syncLock) + { + // Cache 1st requested configuration section. In unit tests, different Configuration is provided for particular test cases. + // During runtime, however, only MSBuild exe configuration file is provided to read toolset configuration from, + // and modifying MSBuild exe configuration during lifetime of msbuild nodes is neither expected nor supported. + if (s_toolsetConfigurationSectionCache == null) + { + s_toolsetConfigurationSectionCache = GetToolsetConfigurationSection(configuration); + s_configurationOfCachedSection = configuration; + } + + return s_configurationOfCachedSection == configuration ? + s_toolsetConfigurationSectionCache : + GetToolsetConfigurationSection(configuration); + } + } + + private static ToolsetConfigurationSection GetToolsetConfigurationSection(Configuration configuration) + { + ToolsetConfigurationSection configurationSection = null; + + // This will be null if the application config file does not have the following section + // definition for the msbuildToolsets section as the first child element. + // + //
+ // "; + // Note that the application config file may or may not contain an msbuildToolsets element. + // For example: + // If section definition is present and section is not present, this value is not null + // If section definition is not present and section is also not present, this value is null + // If the section definition is not present and section is present, then this value is null + if (configuration != null) + { + ConfigurationSection msbuildSection = configuration.GetSection("msbuildToolsets"); + configurationSection = msbuildSection as ToolsetConfigurationSection; + + if (configurationSection == null && msbuildSection != null) // we found msbuildToolsets but the wrong type of handler + { + if (String.IsNullOrEmpty(msbuildSection.SectionInformation.Type) || + msbuildSection.SectionInformation.Type.IndexOf("Microsoft.Build", StringComparison.OrdinalIgnoreCase) >= 0) + { + // Set the configuration type handler to the current ToolsetConfigurationSection type + msbuildSection.SectionInformation.Type = typeof(ToolsetConfigurationSection).AssemblyQualifiedName; + + try + { + // fabricate a temporary config file with the correct section handler type in it + string tempFileName = FileUtilities.GetTemporaryFile(); + + // Save the modified config + configuration.SaveAs(tempFileName + ".config"); + + // Open the configuration again, the new type for the section handler will do its stuff + // Note that the OpenExeConfiguration call uses the config filename *without* the .config + // extension + configuration = ConfigurationManager.OpenExeConfiguration(tempFileName); + + // Get the toolset information from the section using our real handler + configurationSection = configuration.GetSection("msbuildToolsets") as ToolsetConfigurationSection; + + File.Delete(tempFileName + ".config"); + File.Delete(tempFileName); + } + catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) + { + } + } + } + } + + return configurationSection; + } + } + + /// + /// Class representing the Toolset element + /// + /// + /// Internal for unit testing only + /// + internal sealed class ToolsetElement : ConfigurationElement + { + /// + /// ToolsVersion attribute of the element + /// + [ConfigurationProperty("toolsVersion", IsKey = true, IsRequired = true)] + public string toolsVersion + { + get + { + return (string)base[nameof(toolsVersion)]; + } + + set + { + base[nameof(toolsVersion)] = value; + } + } + + /// + /// Property element collection + /// + [ConfigurationProperty("", IsDefaultCollection = true)] + public PropertyElementCollection PropertyElements + { + get + { + return (PropertyElementCollection)base[""]; + } + } + + /// + /// Collection of all the search paths for project imports, per OS + /// + [ConfigurationProperty("projectImportSearchPaths")] + public ExtensionsPathsElementCollection AllProjectImportSearchPaths + { + get + { + return (ExtensionsPathsElementCollection)base["projectImportSearchPaths"]; + } + } + + /// + /// Class representing all the per-OS search paths for MSBuildExtensionsPath* + /// + internal sealed class ExtensionsPathsElementCollection : ConfigurationElementCollection + { + /// + /// We use this dictionary to track whether or not we've seen a given + /// searchPaths definition before, since the .NET configuration classes + /// won't perform this check without respect for case. + /// + private Dictionary _previouslySeenOS = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Type of the collection + /// This has to be public as cannot change access modifier when overriding + /// + public override ConfigurationElementCollectionType CollectionType + { + get + { + return ConfigurationElementCollectionType.BasicMap; + } + } + + /// + /// Throw exception if an element with a duplicate key is added to the collection + /// + protected override bool ThrowOnDuplicate + { + get + { + return false; + } + } + + /// + /// Name of the element + /// + protected override string ElementName + { + get + { + return "searchPaths"; + } + } + + /// + /// Gets an element with the specified name + /// + /// OS of the element + /// element + public ExtensionsPathElement GetElement(string os) + { + return (ExtensionsPathElement)this.BaseGet(os); + } + + /// + /// Gets an element based at the specified position + /// + /// position + /// element + public ExtensionsPathElement GetElement(int index) + { + return (ExtensionsPathElement)this.BaseGet(index); + } + + /// + /// Returns the key value for the given element + /// + /// element whose key is returned + /// key + protected override object GetElementKey(ConfigurationElement element) + { + return ((ExtensionsPathElement)element).OS; + } + + /// + /// Creates a new element of the collection + /// + /// Created element + protected override ConfigurationElement CreateNewElement() + { + return new ExtensionsPathElement(); + } + + /// + /// overridden so we can track previously seen elements + /// + protected override void BaseAdd(int index, ConfigurationElement element) + { + UpdateOSMap(element); + + base.BaseAdd(index, element); + } + + /// + /// overridden so we can track previously seen elements + /// + protected override void BaseAdd(ConfigurationElement element) + { + UpdateOSMap(element); + + base.BaseAdd(element); + } + + /// + /// Stores the name of the OS in a case-insensitive map + /// so we can detect if it is specified more than once but with + /// different case + /// + private void UpdateOSMap(ConfigurationElement element) + { + string os = GetElementKey(element).ToString(); + + if (_previouslySeenOS.ContainsKey(os)) + { + string locationString = String.Empty; + if (!String.IsNullOrEmpty(element.ElementInformation.Source)) + { + if (element.ElementInformation.LineNumber != 0) + { + locationString = $"{element.ElementInformation.Source} ({element.ElementInformation.LineNumber})"; + } + else + { + locationString = element.ElementInformation.Source; + } + } + + string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("MultipleDefinitionsForSameExtensionsPathOS", os, locationString); + + throw new ConfigurationErrorsException(message, element.ElementInformation.Source, element.ElementInformation.LineNumber); + } + + _previouslySeenOS.Add(os, string.Empty); + } + } + + /// + /// Class representing searchPaths element for a single OS + /// + internal sealed class ExtensionsPathElement : ConfigurationElement + { + /// + /// OS attribute of the element + /// + [ConfigurationProperty("os", IsKey = true, IsRequired = true)] + public string OS + { + get + { + return (string)base["os"]; + } + + set + { + base["os"] = value; + } + } + + /// + /// Property element collection + /// + [ConfigurationProperty("", IsDefaultCollection = true)] + public PropertyElementCollection PropertyElements + { + get + { + return (PropertyElementCollection)base[""]; + } + } + } + + /// + /// Class representing collection of property elements + /// + internal sealed class PropertyElementCollection : ConfigurationElementCollection + { + #region Private Fields + + /// + /// We use this dictionary to track whether or not we've seen a given + /// property definition before, since the .NET configuration classes + /// won't perform this check without respect for case. + /// + private Dictionary _previouslySeenPropertyNames = new Dictionary(MSBuildNameIgnoreCaseComparer.Default); + + #endregion + + #region Properties + + /// + /// Collection type + /// This has to be public as cannot change access modifier when overriding + /// + public override ConfigurationElementCollectionType CollectionType + { + get + { + return ConfigurationElementCollectionType.BasicMap; + } + } + + /// + /// Throw exception if an element with a duplicate is added + /// + protected override bool ThrowOnDuplicate + { + get + { + return false; + } + } + + /// + /// name of the element + /// + protected override string ElementName + { + get + { + return "property"; + } + } + + #endregion + + #region Methods + + /// + /// Gets an element with the specified name + /// + /// name of the element + /// element + public PropertyElement GetElement(string name) + { + return (PropertyElement)this.BaseGet(name); + } + + /// + /// Gets an element at the specified position + /// + /// position + /// element + public PropertyElement GetElement(int index) + { + return (PropertyElement)this.BaseGet(index); + } + + /// + /// Creates a new element + /// + /// element + protected override ConfigurationElement CreateNewElement() + { + return new PropertyElement(); + } + + /// + /// overridden so we can track previously seen property names + /// + protected override void BaseAdd(int index, ConfigurationElement element) + { + UpdatePropertyNameMap(element); + + base.BaseAdd(index, element); + } + + /// + /// overridden so we can track previously seen property names + /// + protected override void BaseAdd(ConfigurationElement element) + { + UpdatePropertyNameMap(element); + + base.BaseAdd(element); + } + + /// + /// Gets the key for the element + /// + /// element + /// key + protected override object GetElementKey(ConfigurationElement element) + { + return ((PropertyElement)element).Name; + } + + /// + /// Stores the name of the tools version in a case-insensitive map + /// so we can detect if it is specified more than once but with + /// different case + /// + private void UpdatePropertyNameMap(ConfigurationElement element) + { + string propertyName = GetElementKey(element).ToString(); + + if (_previouslySeenPropertyNames.ContainsKey(propertyName)) + { + string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("MultipleDefinitionsForSameProperty", propertyName); + + throw new ConfigurationErrorsException(message, element.ElementInformation.Source, element.ElementInformation.LineNumber); + } + + _previouslySeenPropertyNames.Add(propertyName, string.Empty); + } + + #endregion + } + + /// + /// This class represents property element + /// + internal sealed class PropertyElement : ConfigurationElement + { + /// + /// name attribute + /// + [ConfigurationProperty("name", IsKey = true, IsRequired = true)] + public string Name + { + get + { + return (string)base["name"]; + } + + set + { + base["name"] = value; + } + } + + /// + /// value attribute + /// + [ConfigurationProperty("value", IsRequired = true)] + public string Value + { + get + { + return (string)base["value"]; + } + + set + { + base["value"] = value; + } + } + } + } + + /// + /// Class representing the collection of toolset elements + /// + /// + /// Internal for unit testing only + /// + internal sealed class ToolsetElementCollection : ConfigurationElementCollection + { + /// + /// We use this dictionary to track whether or not we've seen a given + /// toolset definition before, since the .NET configuration classes + /// won't perform this check without respect for case. + /// + private Dictionary _previouslySeenToolsVersions = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Type of the collection + /// This has to be public as cannot change access modifier when overriding + /// + public override ConfigurationElementCollectionType CollectionType + { + get + { + return ConfigurationElementCollectionType.BasicMap; + } + } + + /// + /// Throw exception if an element with a duplicate key is added to the collection + /// + protected override bool ThrowOnDuplicate + { + get + { + return false; + } + } + + /// + /// Name of the element + /// + protected override string ElementName + { + get + { + return "toolset"; + } + } + + /// + /// Gets an element with the specified name + /// + /// toolsVersion of the element + /// element + public ToolsetElement GetElement(string toolsVersion) + { + return (ToolsetElement)this.BaseGet(toolsVersion); + } + + /// + /// Gets an element based at the specified position + /// + /// position + /// element + public ToolsetElement GetElement(int index) + { + return (ToolsetElement)this.BaseGet(index); + } + + /// + /// Returns the key value for the given element + /// + /// element whose key is returned + /// key + protected override object GetElementKey(ConfigurationElement element) + { + return ((ToolsetElement)element).toolsVersion; + } + + /// + /// Creates a new element of the collection + /// + /// Created element + protected override ConfigurationElement CreateNewElement() + { + return new ToolsetElement(); + } + + /// + /// overridden so we can track previously seen tools versions + /// + protected override void BaseAdd(int index, ConfigurationElement element) + { + UpdateToolsVersionMap(element); + + base.BaseAdd(index, element); + } + + /// + /// overridden so we can track previously seen tools versions + /// + protected override void BaseAdd(ConfigurationElement element) + { + UpdateToolsVersionMap(element); + + base.BaseAdd(element); + } + + /// + /// Stores the name of the tools version in a case-insensitive map + /// so we can detect if it is specified more than once but with + /// different case + /// + private void UpdateToolsVersionMap(ConfigurationElement element) + { + string toolsVersion = GetElementKey(element).ToString(); + + if (_previouslySeenToolsVersions.ContainsKey(toolsVersion)) + { + string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("MultipleDefinitionsForSameToolset", toolsVersion); + + throw new ConfigurationErrorsException(message, element.ElementInformation.Source, element.ElementInformation.LineNumber); + } + + _previouslySeenToolsVersions.Add(toolsVersion, string.Empty); + } + } + + /// + /// This class is used to programmatically read msbuildToolsets section + /// in from the configuration file. An example of application config file: + /// + /// <configuration> + /// <msbuildToolsets default="2.0"> + /// <toolset toolsVersion="2.0"> + /// <property name="MSBuildBinPath" value="D:\windows\Microsoft.NET\Framework\v2.0.x86ret\"/> + /// <property name="SomeOtherProperty" value="SomeOtherPropertyValue"/> + /// </toolset> + /// <toolset toolsVersion="3.5"> + /// <property name="MSBuildBinPath" value="D:\windows\Microsoft.NET\Framework\v3.5.x86ret\"/> + /// </toolset> + /// </msbuildToolsets> + /// </configuration> + /// + /// + /// + /// Internal for unit testing only + /// + internal sealed class ToolsetConfigurationSection : ConfigurationSection + { + /// + /// toolsVersion element collection + /// + [ConfigurationProperty("", IsDefaultCollection = true)] + public ToolsetElementCollection Toolsets + { + get + { + return (ToolsetElementCollection)base[""]; + } + } + + /// + /// default attribute on msbuildToolsets element, specifying the default ToolsVersion + /// + [ConfigurationProperty("default")] + public string Default + { + get + { + // The ConfigurationPropertyAttribute constructor accepts a named parameter "DefaultValue" + // that doesn't seem to work if null is the desired default value. So here we return null + // whenever the base class gives us an empty string. + // Note this means we can't distinguish between the attribute being present but containing + // an empty string for its value and the attribute not being present at all. + string defaultValue = (string)base["default"]; + return String.IsNullOrEmpty(defaultValue) ? null : defaultValue; + } + + set + { + base["default"] = value; + } + } + + /// + /// MsBuildOverrideTasksPath attribute on msbuildToolsets element, specifying the path to find msbuildOverrideTasks files + /// + [ConfigurationProperty("msbuildOverrideTasksPath")] // This string is case sensitive, can't change it + public string MSBuildOverrideTasksPath + { + get + { + // The ConfigurationPropertyAttribute constructor accepts a named parameter "DefaultValue" + // that doesn't seem to work if null is the desired default value. So here we return null + // whenever the base class gives us an empty string. + // Note this means we can't distinguish between the attribute being present but containing + // an empty string for its value and the attribute not being present at all. + string defaultValue = (string)base["msbuildOverrideTasksPath"]; + return String.IsNullOrEmpty(defaultValue) ? null : defaultValue; + } + + set + { + base["msbuildOverrideTasksPath"] = value; + } + } + + /// + /// DefaultOverrideToolsVersion attribute on msbuildToolsets element, specifying the toolsversion that should be used by + /// default to build projects with this version of MSBuild. + /// + [ConfigurationProperty("DefaultOverrideToolsVersion")] + public string DefaultOverrideToolsVersion + { + get + { + // The ConfigurationPropertyAttribute constructor accepts a named parameter "DefaultValue" + // that doesn't seem to work if null is the desired default value. So here we return null + // whenever the base class gives us an empty string. + // Note this means we can't distinguish between the attribute being present but containing + // an empty string for its value and the attribute not being present at all. + string defaultValue = (string)base[nameof(DefaultOverrideToolsVersion)]; + return String.IsNullOrEmpty(defaultValue) ? null : defaultValue; + } + + set + { + base[nameof(DefaultOverrideToolsVersion)] = value; + } + } + } +} diff --git a/src/Framework.UnitTests/FileMatcher_Tests.cs b/src/Framework.UnitTests/FileMatcher_Tests.cs index 3ed04d2188b..c0bea972965 100644 --- a/src/Framework.UnitTests/FileMatcher_Tests.cs +++ b/src/Framework.UnitTests/FileMatcher_Tests.cs @@ -1,6 +1,2695 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using Microsoft.Build.Framework; +using Microsoft.Build.Shared; +using Microsoft.Build.Shared.FileSystem; +using Microsoft.Build.UnitTests.Shared; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +#nullable disable + +namespace Microsoft.Build.UnitTests +{ + public class FileMatcherTest : IDisposable + { + private readonly TestEnvironment _env; + private Lazy _mappedDrive = DummyMappedDriveUtils.GetLazyDummyMappedDrive(); + + public FileMatcherTest(ITestOutputHelper output) + { + _env = TestEnvironment.Create(output); + } + + public void Dispose() + { + _env.Dispose(); + _mappedDrive.Value?.Dispose(); + } + + [Theory] + [InlineData("*.txt", 5)] + [InlineData("???.cs", 1)] + [InlineData("????.cs", 1)] + [InlineData("file?.txt", 1)] + [InlineData("fi?e?.txt", 2)] + [InlineData("???.*", 1)] + [InlineData("????.*", 4)] + [InlineData("*.???", 5)] + [InlineData("f??e1.txt", 2)] + [InlineData("file.*.txt", 1)] + public void GetFilesPatternMatching(string pattern, int expectedMatchCount) + { + TransientTestFolder testFolder = _env.CreateFolder(); + + foreach (var file in new[] + { + "Foo.cs", + "Foo2.cs", + "file.txt", + "file1.txt", + "file1.txtother", + "fie1.txt", + "fire1.txt", + "file.bak.txt" + }) + { + File.WriteAllBytes(Path.Combine(testFolder.Path, file), new byte[1]); + } + + string[] fileMatches = FileMatcher.Default.GetFiles(testFolder.Path, pattern).FileList; + + fileMatches.Length.ShouldBe(expectedMatchCount, $"Matches: '{String.Join("', '", fileMatches)}'"); + } + +#if FEATURE_SYMLINK_TARGET + [RequiresSymbolicLinksFact] + public void DoNotFollowRecursiveSymlinks() + { + TransientTestFolder testFolder = _env.CreateFolder(); + TransientTestFile file = _env.CreateFile(testFolder, "Foo.cs"); + TransientTestFolder tf2 = _env.CreateFolder(Path.Combine(testFolder.Path, "subfolder")); + string symlinkPath = Path.Combine(tf2.Path, "mySymlink"); + try + { + Directory.CreateSymbolicLink(symlinkPath, testFolder.Path); + string[] fileMatches = FileMatcher.Default.GetFiles(testFolder.Path, "**").FileList; + fileMatches.Length.ShouldBe(1); + } + finally + { + if (Directory.Exists(symlinkPath)) + { + Directory.Delete(symlinkPath); + } + } + } +#endif + + [Theory] + [MemberData(nameof(GetFilesComplexGlobbingMatchingInfo.GetTestData), MemberType = typeof(GetFilesComplexGlobbingMatchingInfo), DisableDiscoveryEnumeration = true)] + public void GetFilesComplexGlobbingMatching(GetFilesComplexGlobbingMatchingInfo info) + { + TransientTestFolder testFolder = _env.CreateFolder(); + + // Create directories and files + foreach (string fullPath in GetFilesComplexGlobbingMatchingInfo.FilesToCreate.Select(i => Path.Combine(testFolder.Path, i.ToPlatformSlash()))) + { + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); + + File.WriteAllBytes(fullPath, new byte[1]); + } + + void VerifyImpl(FileMatcher fileMatcher, string include, string[] excludes, bool shouldHaveNoMatches = false, string customMessage = null) + { + string[] matchedFiles = fileMatcher.GetFiles(testFolder.Path, include, excludes?.ToList()).FileList; + + if (shouldHaveNoMatches) + { + matchedFiles.ShouldBeEmpty(customMessage); + } + else + { + // The matches are: + // 1. Normalized ("\" regardless of OS and lowercase) + // 2. Sorted + // Are the same as the expected matches sorted + matchedFiles + .Select(i => i.Replace(Path.DirectorySeparatorChar, '\\')) + .OrderBy(i => i) + .ToArray() + .ShouldBe(info.ExpectedMatches.OrderBy(i => i), caseSensitivity: Case.Insensitive, customMessage: customMessage); + } + } + + var fileMatcherWithCache = new FileMatcher(FileSystems.Default, new ConcurrentDictionary>()); + + void Verify(string include, string[] excludes, bool shouldHaveNoMatches = false, string customMessage = null) + { + // Verify using the default non-caching FileMatcher. + VerifyImpl(FileMatcher.Default, include, excludes, shouldHaveNoMatches, customMessage); + + // Verify using a caching FileMatcher and do it twice to exercise the cache. + VerifyImpl(fileMatcherWithCache, include, excludes, shouldHaveNoMatches, customMessage); + VerifyImpl(fileMatcherWithCache, include, excludes, shouldHaveNoMatches, customMessage); + } + + // Normal matching + Verify(info.Include, info.Excludes); + + // Include forward slash + Verify(info.Include.Replace('\\', '/'), info.Excludes, customMessage: "Include directory separator was changed to forward slash"); + + // Excludes forward slash + Verify(info.Include, info.Excludes?.Select(o => o.Replace('\\', '/')).ToArray(), customMessage: "Excludes directory separator was changed to forward slash"); + + // Uppercase includes + Verify(info.Include.ToUpperInvariant(), info.Excludes, info.ExpectNoMatches, "Include was changed to uppercase"); + + // Changing the case of the exclude break Linux + if (!NativeMethodsShared.IsLinux) + { + // Uppercase excludes + Verify(info.Include, info.Excludes?.Select(o => o.ToUpperInvariant()).ToArray(), false, "Excludes were changed to uppercase"); + } + + // Backward compatibilities: + // 1. When an include or exclude starts with a fixed directory part e.g. "src/foo/**", + // then matching should be case-sensitive on Linux, as the directory was checked for its existance + // by using Directory.Exists, which is case-sensitive on Linux (on OSX is not). + // 2. On Unix, when an include uses a simple ** wildcard e.g. "**\*.cs", the file pattern e.g. "*.cs", + // should be matched case-sensitive, as files were retrieved by using the searchPattern parameter + // of Directory.GetFiles, which is case-sensitive on Unix. + } + + /// + /// A test data class for providing data to the test. + /// + public class GetFilesComplexGlobbingMatchingInfo + { + /// + /// The list of known files to create. + /// + public static string[] FilesToCreate = + { + @"src\foo.cs", + @"src\bar.cs", + @"src\baz.cs", + @"src\foo\foo.cs", + @"src\foo\licence", + @"src\bar\bar.cs", + @"src\baz\baz.cs", + @"src\foo\inner\foo.cs", + @"src\foo\inner\foo\foo.cs", + @"src\foo\inner\bar\bar.cs", + @"src\bar\inner\baz.cs", + @"src\bar\inner\baz\baz.cs", + @"src\bar\inner\foo\foo.cs", + @"subd\sub.cs", + @"subdirectory\subdirectory.cs", + @"build\baz\foo.cs", + @"readme.txt", + @"licence" + }; + + /// + /// Gets or sets the include pattern. + /// + public string Include { get; set; } + + /// + /// Gets or sets a list of exclude patterns. + /// + public string[] Excludes { get; set; } + + /// + /// Gets or sets the list of expected matches. + /// + public string[] ExpectedMatches { get; set; } + + /// + /// Get or sets a value indicating to expect no matches if the include pattern is mutated to uppercase. + /// + public bool ExpectNoMatches { get; set; } + + public override string ToString() + { + IEnumerable GetParts() + { + yield return $"Include = {Include}"; + + if (Excludes != null) + { + yield return $"Excludes = {String.Join(";", Excludes)}"; + } + + if (ExpectNoMatches) + { + yield return "ExpectNoMatches"; + } + } + + return String.Join(", ", GetParts()); + } + + /// + /// Gets the test data + /// + public static IEnumerable GetTestData() + { + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"src\**\inner\**\*.cs", + ExpectedMatches = new[] + { + @"src\foo\inner\foo.cs", + @"src\foo\inner\foo\foo.cs", + @"src\foo\inner\bar\bar.cs", + @"src\bar\inner\baz.cs", + @"src\bar\inner\baz\baz.cs", + @"src\bar\inner\foo\foo.cs" + }, + ExpectNoMatches = NativeMethodsShared.IsLinux + } + }; + + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"src\**\inner\**\*.cs", + Excludes = new[] + { + @"src\foo\inner\foo.*.cs" + }, + ExpectedMatches = new[] + { + @"src\foo\inner\foo.cs", + @"src\foo\inner\foo\foo.cs", + @"src\foo\inner\bar\bar.cs", + @"src\bar\inner\baz.cs", + @"src\bar\inner\baz\baz.cs", + @"src\bar\inner\foo\foo.cs" + }, + ExpectNoMatches = NativeMethodsShared.IsLinux, + } + }; + + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"src\**\inner\**\*.cs", + Excludes = new[] + { + @"**\foo\**" + }, + ExpectedMatches = new[] + { + @"src\bar\inner\baz.cs", + @"src\bar\inner\baz\baz.cs" + }, + ExpectNoMatches = NativeMethodsShared.IsLinux, + } + }; + + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"src\**\inner\**\*.cs", + Excludes = new[] + { + @"src\bar\inner\baz\**" + }, + ExpectedMatches = new[] + { + @"src\foo\inner\foo.cs", + @"src\foo\inner\foo\foo.cs", + @"src\foo\inner\bar\bar.cs", + @"src\bar\inner\baz.cs", + @"src\bar\inner\foo\foo.cs" + }, + ExpectNoMatches = NativeMethodsShared.IsLinux, + } + }; + + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"src\foo\**\*.cs", + Excludes = new[] + { + @"src\foo\**\foo\**" + }, + ExpectedMatches = new[] + { + @"src\foo\foo.cs", + @"src\foo\inner\foo.cs", + @"src\foo\inner\bar\bar.cs" + }, + ExpectNoMatches = NativeMethodsShared.IsLinux, + } + }; + + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"src\foo\inner\**\*.cs", + Excludes = new[] + { + @"src\foo\**\???\**" + }, + ExpectedMatches = new[] + { + @"src\foo\inner\foo.cs" + }, + ExpectNoMatches = NativeMethodsShared.IsLinux, + } + }; + + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"**\???\**\*.cs", + ExpectedMatches = new[] + { + @"src\foo.cs", + @"src\bar.cs", + @"src\baz.cs", + @"src\foo\foo.cs", + @"src\bar\bar.cs", + @"src\baz\baz.cs", + @"src\foo\inner\foo.cs", + @"src\foo\inner\foo\foo.cs", + @"src\foo\inner\bar\bar.cs", + @"src\bar\inner\baz.cs", + @"src\bar\inner\baz\baz.cs", + @"src\bar\inner\foo\foo.cs", + @"build\baz\foo.cs" + } + } + }; + + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"**\*.*", + Excludes = new[] + { + @"**\???\**\*.cs", + @"subd*\*", + }, + ExpectedMatches = new[] + { + @"readme.txt", + @"licence", + @"src\foo\licence", + } + } + }; + + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"**\?a?\**\?a?\*.c?", + ExpectedMatches = new[] + { + @"src\bar\inner\baz\baz.cs" + } + } + }; + + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"**\?a?\**\?a?.c?", + Excludes = new[] + { + @"**\?a?\**\?a?\*.c?" + }, + ExpectedMatches = new[] + { + @"src\bar\bar.cs", + @"src\baz\baz.cs", + @"src\foo\inner\bar\bar.cs", + @"src\bar\inner\baz.cs" + } + } + }; + + // Regression test for https://github.com/dotnet/msbuild/issues/4175 + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"subdirectory\**", + Excludes = new[] + { + @"sub\**" + }, + ExpectedMatches = new[] + { + @"subdirectory\subdirectory.cs", + }, + ExpectNoMatches = NativeMethodsShared.IsLinux, + } + }; + + // Regression test for https://github.com/dotnet/msbuild/issues/6502 + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"src\**", + Excludes = new[] + { + @"**\foo\**", + }, + ExpectedMatches = new[] + { + @"src\foo.cs", + @"src\bar.cs", + @"src\baz.cs", + @"src\bar\bar.cs", + @"src\baz\baz.cs", + @"src\bar\inner\baz.cs", + @"src\bar\inner\baz\baz.cs", + }, + ExpectNoMatches = NativeMethodsShared.IsLinux, + } + }; + + // Hits the early elimination of exclude file patterns that do not intersect with the include. + // The exclude is redundant and can be eliminated before starting the file system walk. + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"src\foo\**\*.cs", + Excludes = new[] + { + @"src\foo\**\foo\**", + @"src\foo\**\*.vb" // redundant exclude + }, + ExpectedMatches = new[] + { + @"src\foo\foo.cs", + @"src\foo\inner\foo.cs", + @"src\foo\inner\bar\bar.cs" + }, + ExpectNoMatches = NativeMethodsShared.IsLinux, + } + }; + + // Hits the early elimination of exclude file patterns that do not intersect with the include. + // The exclude is not redundant and must not be eliminated. + yield return new object[] + { + new GetFilesComplexGlobbingMatchingInfo + { + Include = @"src\foo\**\*.cs", + Excludes = new[] + { + @"src\foo\**\*.*" // effective exclude + }, + ExpectedMatches = Array.Empty(), + ExpectNoMatches = NativeMethodsShared.IsLinux, + } + }; + } + } + + [Fact] + public void WildcardMatching() + { + var inputs = new List> + { + // No wildcards + new Tuple("a", "a", true), + new Tuple("a", "", false), + new Tuple("", "a", false), + + // Non ASCII characters + new Tuple("šđčćž", "šđčćž", true), + + // * wildcard + new Tuple("abc", "*bc", true), + new Tuple("abc", "a*c", true), + new Tuple("abc", "ab*", true), + new Tuple("ab", "*ab", true), + new Tuple("ab", "a*b", true), + new Tuple("ab", "ab*", true), + new Tuple("aba", "ab*ba", false), + new Tuple("", "*", true), + + // ? wildcard + new Tuple("abc", "?bc", true), + new Tuple("abc", "a?c", true), + new Tuple("abc", "ab?", true), + new Tuple("ab", "?ab", false), + new Tuple("ab", "a?b", false), + new Tuple("ab", "ab?", false), + new Tuple("", "?", false), + + // Mixed wildcards + new Tuple("a", "*?", true), + new Tuple("a", "?*", true), + new Tuple("ab", "*?", true), + new Tuple("ab", "?*", true), + new Tuple("abc", "*?", true), + new Tuple("abc", "?*", true), + + // Multiple mixed wildcards + new Tuple("a", "??", false), + new Tuple("ab", "?*?", true), + new Tuple("ab", "*?*?*", true), + new Tuple("abc", "?**?*?", true), + new Tuple("abc", "?**?*c?", false), + new Tuple("abcd", "?b*??", true), + new Tuple("abcd", "?a*??", false), + new Tuple("abcd", "?**?c?", true), + new Tuple("abcd", "?**?d?", false), + new Tuple("abcde", "?*b*?*d*?", true), + + // ? wildcard in the input string + new Tuple("?", "?", true), + new Tuple("?a", "?a", true), + new Tuple("a?", "a?", true), + new Tuple("a?b", "a?", false), + new Tuple("a?ab", "a?aab", false), + new Tuple("aa?bbbc?d", "aa?bbc?dd", false), + + // * wildcard in the input string + new Tuple("*", "*", true), + new Tuple("*a", "*a", true), + new Tuple("a*", "a*", true), + new Tuple("a*b", "a*", true), + new Tuple("a*ab", "a*aab", false), + new Tuple("a*abab", "a*b", true), + new Tuple("aa*bbbc*d", "aa*bbc*dd", false), + new Tuple("aa*bbbc*d", "a*bbc*d", true) + }; + foreach (var input in inputs) + { + try + { + Assert.Equal(input.Item3, FileMatcher.IsMatch(input.Item1, input.Item2)); + Assert.Equal(input.Item3, FileMatcher.IsMatch(input.Item1.ToUpperInvariant(), input.Item2)); + Assert.Equal(input.Item3, FileMatcher.IsMatch(input.Item1, input.Item2.ToUpperInvariant())); + } + catch (Exception) + { + Console.WriteLine($"Input {input.Item1} with pattern {input.Item2} failed"); + throw; + } + } + } + + /* + * Method: GetFileSystemEntries + * + * Simulate Directories.GetFileSystemEntries where file names are short. + * + */ + private static IReadOnlyList GetFileSystemEntries(FileMatcher.FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory) + { + if + ( + pattern == @"LONGDI~1" + && (@"D:\" == path || @"\\server\share\" == path || path.Length == 0)) + { + return new string[] { Path.Combine(path, "LongDirectoryName") }; + } + else if + ( + pattern == @"LONGSU~1" + && (@"D:\LongDirectoryName" == path || @"\\server\share\LongDirectoryName" == path || @"LongDirectoryName" == path)) + { + return new string[] { Path.Combine(path, "LongSubDirectory") }; + } + else if + ( + pattern == @"LONGFI~1.TXT" + && (@"D:\LongDirectoryName\LongSubDirectory" == path || @"\\server\share\LongDirectoryName\LongSubDirectory" == path || @"LongDirectoryName\LongSubDirectory" == path)) + { + return new string[] { Path.Combine(path, "LongFileName.txt") }; + } + else if + ( + pattern == @"pomegr~1" + && @"c:\apple\banana\tomato" == path) + { + return new string[] { Path.Combine(path, "pomegranate") }; + } + else if + ( + @"c:\apple\banana\tomato\pomegranate\orange" == path) + { + // No files exist here. This is an empty directory. + return Array.Empty(); + } + else + { + Console.WriteLine("GetFileSystemEntries('{0}', '{1}')", path, pattern); + Assert.Fail("Unexpected input into GetFileSystemEntries"); + } + return new string[] { "" }; + } + + private static readonly char S = Path.DirectorySeparatorChar; + + public static IEnumerable NormalizeTestData() + { + yield return new object[] + { + null, + null + }; + yield return new object[] + { + "", + "" + }; + yield return new object[] + { + " ", + " " + }; + + yield return new object[] + { + @"\\", + @"\\" + }; + yield return new object[] + { + @"\\/\//", + @"\\" + }; + yield return new object[] + { + @"\\a/\b/\", + $@"\\a{S}b" + }; + + yield return new object[] + { + @"\", + @"\" + }; + yield return new object[] + { + @"\/\/\/", + @"\" + }; + yield return new object[] + { + @"\a/\b/\", + $@"\a{S}b" + }; + + yield return new object[] + { + "/", + "/" + }; + yield return new object[] + { + @"/\/\", + "/" + }; + yield return new object[] + { + @"/a\/b/\\", + $@"/a{S}b" + }; + + yield return new object[] + { + @"c:\", + @"c:\" + }; + yield return new object[] + { + @"c:/", + @"c:\" + }; + yield return new object[] + { + @"c:/\/\/", + @"c:\" + }; + yield return new object[] + { + @"c:/ab", + @"c:\ab" + }; + yield return new object[] + { + @"c:\/\a//b", + $@"c:\a{S}b" + }; + yield return new object[] + { + @"c:\/\a//b\/", + $@"c:\a{S}b" + }; + + yield return new object[] + { + @"..\/a\../.\b\/", + $@"..{S}a{S}..{S}.{S}b" + }; + yield return new object[] + { + @"**/\foo\/**\/", + $@"**{S}foo{S}**" + }; + + yield return new object[] + { + "AbCd", + "AbCd" + }; + } + + [Theory] + [MemberData(nameof(NormalizeTestData))] + public void NormalizeTest(string inputString, string expectedString) + { + FileMatcher.Normalize(inputString).ShouldBe(expectedString); + } + + /// + /// Simple test of the MatchDriver code. + /// + [Fact] + public void BasicMatchDriver() + { + MatchDriver( + "Source" + Path.DirectorySeparatorChar + "**", + new string[] // Files that exist and should match. + { + "Source" + Path.DirectorySeparatorChar + "Bart.txt", + "Source" + Path.DirectorySeparatorChar + "Sub" + Path.DirectorySeparatorChar + "Homer.txt", + }, + new string[] // Files that exist and should not match. + { + "Destination" + Path.DirectorySeparatorChar + "Bart.txt", + "Destination" + Path.DirectorySeparatorChar + "Sub" + Path.DirectorySeparatorChar + "Homer.txt", + }, + null); + } + + /// + /// This pattern should *not* recurse indefinitely since there is no '**' in the pattern: + /// + /// c:\?emp\foo + /// + /// + [Fact] + public void Regress162390() + { + MatchDriver( + @"c:\?emp\foo.txt", + new string[] { @"c:\temp\foo.txt" }, // Should match + new string[] { @"c:\timp\foo.txt" }, // Shouldn't match + new string[] // Should not even consider. + { + @"c:\temp\sub\foo.txt" + }); + } + + /* + * Method: GetLongFileNameForShortLocalPath + * + * Convert a short local path to a long path. + * + */ + [WindowsOnlyFact("Short names are for Windows only.")] + public void GetLongFileNameForShortLocalPath() + { + string longPath = FileMatcher.GetLongPathName( + @"D:\LONGDI~1\LONGSU~1\LONGFI~1.TXT", + new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); + + Assert.Equal(@"D:\LongDirectoryName\LongSubDirectory\LongFileName.txt", longPath); + } + + /* + * Method: GetLongFileNameForLongLocalPath + * + * Convert a long local path to a long path (nop). + * + */ + [Fact] + public void GetLongFileNameForLongLocalPath() + { + string longPath = FileMatcher.GetLongPathName( + @"D:\LongDirectoryName\LongSubDirectory\LongFileName.txt", + new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); + + Assert.Equal(@"D:\LongDirectoryName\LongSubDirectory\LongFileName.txt", longPath); + } + + /* + * Method: GetLongFileNameForShortUncPath + * + * Convert a short UNC path to a long path. + * + */ + [WindowsOnlyFact("Short names are for Windows only.")] + public void GetLongFileNameForShortUncPath() + { + string longPath = FileMatcher.GetLongPathName( + @"\\server\share\LONGDI~1\LONGSU~1\LONGFI~1.TXT", + new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); + + Assert.Equal(@"\\server\share\LongDirectoryName\LongSubDirectory\LongFileName.txt", longPath); + } + + /* + * Method: GetLongFileNameForLongUncPath + * + * Convert a long UNC path to a long path (nop) + * + */ + [Fact] + public void GetLongFileNameForLongUncPath() + { + string longPath = FileMatcher.GetLongPathName( + @"\\server\share\LongDirectoryName\LongSubDirectory\LongFileName.txt", + new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); + + Assert.Equal(@"\\server\share\LongDirectoryName\LongSubDirectory\LongFileName.txt", longPath); + } + + /* + * Method: GetLongFileNameForRelativePath + * + * Convert a short relative path to a long path + * + */ + [WindowsOnlyFact("Short names are for Windows only.")] + public void GetLongFileNameForRelativePath() + { + string longPath = FileMatcher.GetLongPathName( + @"LONGDI~1\LONGSU~1\LONGFI~1.TXT", + new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); + + Assert.Equal(@"LongDirectoryName\LongSubDirectory\LongFileName.txt", longPath); + } + + /* + * Method: GetLongFileNameForRelativePathPreservesTrailingSlash + * + * Convert a short relative path with a trailing backslash to a long path + * + */ + [WindowsOnlyFact("Short names are for Windows only.")] + public void GetLongFileNameForRelativePathPreservesTrailingSlash() + { + string longPath = FileMatcher.GetLongPathName( + @"LONGDI~1\LONGSU~1\", + new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); + + Assert.Equal(@"LongDirectoryName\LongSubDirectory\", longPath); + } + + /* + * Method: GetLongFileNameForRelativePathPreservesExtraSlashes + * + * Convert a short relative path with doubled embedded backslashes to a long path + * + */ + [WindowsOnlyFact("Short names are for Windows only.")] + public void GetLongFileNameForRelativePathPreservesExtraSlashes() + { + string longPath = FileMatcher.GetLongPathName( + @"LONGDI~1\\LONGSU~1\\", + new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); + + Assert.Equal(@"LongDirectoryName\\LongSubDirectory\\", longPath); + } + + /* + * Method: GetLongFileNameForMixedLongAndShort + * + * Only part of the path might be short. + * + */ + [WindowsOnlyFact("Short names are for Windows only.")] + public void GetLongFileNameForMixedLongAndShort() + { + string longPath = FileMatcher.GetLongPathName( + @"c:\apple\banana\tomato\pomegr~1\orange\", + new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); + + Assert.Equal(@"c:\apple\banana\tomato\pomegranate\orange\", longPath); + } + + /* + * Method: GetLongFileNameWherePartOfThePathDoesntExist + * + * Part of the path may not exist. In this case, we treat the non-existent parts + * as if they were already a long file name. + * + */ + [WindowsOnlyFact("Short names are for Windows only.")] + public void GetLongFileNameWherePartOfThePathDoesntExist() + { + string longPath = FileMatcher.GetLongPathName( + @"c:\apple\banana\tomato\pomegr~1\orange\chocol~1\vanila~1", + new FileMatcher.GetFileSystemEntries(FileMatcherTest.GetFileSystemEntries)); + + Assert.Equal(@"c:\apple\banana\tomato\pomegranate\orange\chocol~1\vanila~1", longPath); + } + + [Fact] + public void BasicMatch() + { + ValidateFileMatch("file.txt", "File.txt", false); + ValidateNoFileMatch("file.txt", "File.bin", false); + } + + [Fact] + public void MatchSingleCharacter() + { + ValidateFileMatch("file.?xt", "File.txt", false); + ValidateNoFileMatch("file.?xt", "File.bin", false); + } + + [Fact] + public void MatchMultipleCharacters() + { + ValidateFileMatch("*.txt", "*.txt", false); + ValidateNoFileMatch("*.txt", "*.bin", false); + } + + [Fact] + public void SimpleRecursive() + { + ValidateFileMatch("**", ".\\File.txt", true); + } + + [Fact] + public void DotForCurrentDirectory() + { + ValidateFileMatch(Path.Combine(".", "File.txt"), Path.Combine(".", "File.txt"), false); + ValidateNoFileMatch(Path.Combine(".", "File.txt"), Path.Combine(".", "File.bin"), false); + } + + [Fact] + public void DotDotForParentDirectory() + { + ValidateFileMatch(Path.Combine("..", "..", "*.*"), Path.Combine("..", "..", "File.txt"), false); + if (NativeMethodsShared.IsWindows) + { + // On Linux *. * does not pick up files with no extension + ValidateFileMatch(Path.Combine("..", "..", "*.*"), Path.Combine("..", "..", "File"), false); + } + ValidateNoFileMatch(Path.Combine("..", "..", "*.*"), Path.Combine(new[] { "..", "..", "dir1", "dir2", "File.txt" }), false); + ValidateNoFileMatch(Path.Combine("..", "..", "*.*"), Path.Combine(new[] { "..", "..", "dir1", "dir2", "File" }), false); + } + + [Fact] + public void ReduceDoubleSlashesBaseline() + { + // Baseline + ValidateFileMatch( + NativeMethodsShared.IsWindows ? "f:\\dir1\\dir2\\file.txt" : "/dir1/dir2/file.txt", + NativeMethodsShared.IsWindows ? "f:\\dir1\\dir2\\file.txt" : "/dir1/dir2/file.txt", + false); + ValidateFileMatch(Path.Combine("**", "*.cs"), Path.Combine("dir1", "dir2", "file.cs"), true); + ValidateFileMatch(Path.Combine("**", "*.cs"), "file.cs", true); + } + + [Fact] + public void ReduceDoubleSlashes() + { + ValidateFileMatch("f:\\\\dir1\\dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); + ValidateFileMatch("f:\\\\dir1\\\\\\dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); + ValidateFileMatch("f:\\\\dir1\\\\\\dir2\\\\\\\\\\file.txt", "f:\\dir1\\dir2\\file.txt", false); + ValidateFileMatch("..\\**/\\*.cs", "..\\dir1\\dir2\\file.cs", true); + ValidateFileMatch("..\\**/.\\*.cs", "..\\dir1\\dir2\\file.cs", true); + ValidateFileMatch("..\\**\\./.\\*.cs", "..\\dir1\\dir2\\file.cs", true); + } + + [Fact] + public void DoubleSlashesOnBothSidesOfComparison() + { + ValidateFileMatch("f:\\\\dir1\\dir2\\file.txt", "f:\\\\dir1\\dir2\\file.txt", false, false); + ValidateFileMatch("f:\\\\dir1\\\\\\dir2\\file.txt", "f:\\\\dir1\\\\\\dir2\\file.txt", false, false); + ValidateFileMatch("f:\\\\dir1\\\\\\dir2\\\\\\\\\\file.txt", "f:\\\\dir1\\\\\\dir2\\\\\\\\\\file.txt", false, false); + ValidateFileMatch("..\\**/\\*.cs", "..\\dir1\\dir2\\\\file.cs", true, false); + ValidateFileMatch("..\\**/.\\*.cs", "..\\dir1\\dir2//\\file.cs", true, false); + ValidateFileMatch("..\\**\\./.\\*.cs", "..\\dir1/\\/\\/dir2\\file.cs", true, false); + } + + [Fact] + public void DecomposeDotSlash() + { + ValidateFileMatch("f:\\.\\dir1\\dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); + ValidateFileMatch("f:\\dir1\\.\\dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); + ValidateFileMatch("f:\\dir1\\dir2\\.\\file.txt", "f:\\dir1\\dir2\\file.txt", false); + ValidateFileMatch("f:\\.//dir1\\dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); + ValidateFileMatch("f:\\dir1\\.//dir2\\file.txt", "f:\\dir1\\dir2\\file.txt", false); + ValidateFileMatch("f:\\dir1\\dir2\\.//file.txt", "f:\\dir1\\dir2\\file.txt", false); + + ValidateFileMatch(".\\dir1\\dir2\\file.txt", ".\\dir1\\dir2\\file.txt", false); + ValidateFileMatch(".\\.\\dir1\\dir2\\file.txt", ".\\dir1\\dir2\\file.txt", false); + ValidateFileMatch(".//dir1\\dir2\\file.txt", ".\\dir1\\dir2\\file.txt", false); + ValidateFileMatch(".//.//dir1\\dir2\\file.txt", ".\\dir1\\dir2\\file.txt", false); + } + + [Fact] + public void RecursiveDirRecursive() + { + // Check that a wildcardpath of **\x\**\ matches correctly since, \**\ is a + // separate code path. + ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\x\file.txt", true); + ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\y\x\file.txt", true); + ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\x\y\file.txt", true); + ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\y\x\y\file.txt", true); + ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\x\x\file.txt", true); + ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\x\x\file.txt", true); + ValidateFileMatch(@"c:\foo\**\x\**\*.*", @"c:\foo\x\x\x\file.txt", true); + } + + [Fact] + public void Regress155731() + { + ValidateFileMatch(@"a\b\**\**\**\**\**\e\*", @"a\b\c\d\e\f.txt", true); + ValidateFileMatch(@"a\b\**\e\*", @"a\b\c\d\e\f.txt", true); + ValidateFileMatch(@"a\b\**\**\e\*", @"a\b\c\d\e\f.txt", true); + ValidateFileMatch(@"a\b\**\**\**\e\*", @"a\b\c\d\e\f.txt", true); + ValidateFileMatch(@"a\b\**\**\**\**\e\*", @"a\b\c\d\e\f.txt", true); + } + + [Fact] + public void ParentWithoutSlash() + { + // However, we don't wtool this to match, + ValidateNoFileMatch(@"C:\foo\**", @"C:\foo", true); + // because we don't know whether foo is a file or folder. + + // Same for UNC + ValidateNoFileMatch( + "\\\\server\\c$\\Documents and Settings\\User\\**", + "\\\\server\\c$\\Documents and Settings\\User", + true); + } + + [Fact] + public void Unc() + { + using (var env = TestEnvironment.Create()) + { + try + { + // Set env var to log on drive enumerating wildcard detection + Helpers.ResetStateForDriveEnumeratingWildcardTests(env, "0"); + + // Check UNC functionality + ValidateFileMatch( + "\\\\server\\c$\\**\\*.cs", + "\\\\server\\c$\\Documents and Settings\\User\\Source.cs", + true); + + ValidateNoFileMatch( + "\\\\server\\c$\\**\\*.cs", + "\\\\server\\c$\\Documents and Settings\\User\\Source.txt", + true); + ValidateFileMatch( + "\\\\**", + "\\\\server\\c$\\Documents and Settings\\User\\Source.cs", + true); + ValidateFileMatch( + "\\\\**\\*.*", + "\\\\server\\c$\\Documents and Settings\\User\\Source.cs", + true); + + ValidateFileMatch( + "**", + "\\\\server\\c$\\Documents and Settings\\User\\Source.cs", + true); + } + finally + { + ChangeWaves.ResetStateForTests(); + } + } + } + + [Fact] + public void ExplicitToolCompatibility() + { + // Explicit ANT compatibility. These patterns taken from the ANT documentation. + ValidateFileMatch("**/SourceSafe/*", "./SourceSafe/Repository", true); + ValidateFileMatch("**\\SourceSafe/*", "./SourceSafe/Repository", true); + ValidateFileMatch("**/SourceSafe/*", ".\\SourceSafe\\Repository", true); + ValidateFileMatch("**/SourceSafe/*", "./org/IIS/SourceSafe/Entries", true); + ValidateFileMatch("**/SourceSafe/*", "./org/IIS/pluggin/tools/tool/SourceSafe/Entries", true); + ValidateNoFileMatch("**/SourceSafe/*", "./org/IIS/SourceSafe/foo/bar/Entries", true); + ValidateNoFileMatch("**/SourceSafe/*", "./SourceSafeRepository", true); + ValidateNoFileMatch("**/SourceSafe/*", "./aSourceSafe/Repository", true); + + ValidateFileMatch("org/IIS/pluggin/**", "org/IIS/pluggin/tools/tool/docs/index.html", true); + ValidateFileMatch("org/IIS/pluggin/**", "org/IIS/pluggin/test.xml", true); + ValidateFileMatch("org/IIS/pluggin/**", "org/IIS/pluggin\\test.xml", true); + ValidateNoFileMatch("org/IIS/pluggin/**", "org/IIS/abc.cs", true); + + ValidateFileMatch("org/IIS/**/SourceSafe/*", "org/IIS/SourceSafe/Entries", true); + ValidateFileMatch("org/IIS/**/SourceSafe/*", "org\\IIS/SourceSafe/Entries", true); + ValidateFileMatch("org/IIS/**/SourceSafe/*", "org/IIS\\SourceSafe/Entries", true); + ValidateFileMatch("org/IIS/**/SourceSafe/*", "org/IIS/pluggin/tools/tool/SourceSafe/Entries", true); + ValidateNoFileMatch("org/IIS/**/SourceSafe/*", "org/IIS/SourceSafe/foo/bar/Entries", true); + ValidateNoFileMatch("org/IIS/**/SourceSafe/*", "org/IISSourceSage/Entries", true); + } + + [Fact] + public void ExplicitToolIncompatibility() + { + // NOTE: Weirdly, ANT syntax is to match a file here. + // We don't because MSBuild philosophy is that a trailing slash indicates a directory + ValidateNoFileMatch("**/test/**", ".\\test", true); + + // NOTE: We deviate from ANT format here. ANT would append a ** to any path + // that ends with '/' or '\'. We think this is the wrong thing because 'folder\' + // is a valid folder name. + ValidateNoFileMatch("org/", "org/IISSourceSage/Entries", false); + ValidateNoFileMatch("org\\", "org/IISSourceSage/Entries", false); + } + + [Fact] + public void MultipleStarStar() + { + using (var env = TestEnvironment.Create()) + { + try + { + // Set env var to log on drive enumerating wildcard detection + Helpers.ResetStateForDriveEnumeratingWildcardTests(env, "0"); + + // Multiple-** matches + ValidateFileMatch("c:\\**\\user\\**\\*.*", "c:\\Documents and Settings\\user\\NTUSER.DAT", true); + ValidateNoFileMatch("c:\\**\\user1\\**\\*.*", "c:\\Documents and Settings\\user\\NTUSER.DAT", true); + ValidateFileMatch("c:\\**\\user\\**\\*.*", "c://Documents and Settings\\user\\NTUSER.DAT", true); + ValidateNoFileMatch("c:\\**\\user1\\**\\*.*", "c:\\Documents and Settings//user\\NTUSER.DAT", true); + } + finally + { + ChangeWaves.ResetStateForTests(); + } + } + } + + [Fact] + public void RegressItemRecursionWorksAsExpected() + { + // Regress bug#54411: Item recursion doesn't work as expected on "c:\foo\**" + ValidateFileMatch("c:\\foo\\**", "c:\\foo\\two\\subfile.txt", true); + } + + [Fact] + public void IllegalPaths() + { + // Certain patterns are illegal. + ValidateIllegal("**.cs"); + ValidateIllegal("***"); + ValidateIllegal("****"); + ValidateIllegal("*.cs**"); + ValidateIllegal("*.cs**"); + ValidateIllegal("...\\*.cs"); + ValidateIllegal("http://www.website.com"); + } + + [Fact] + public void SplitFileSpec() + { + /************************************************************************************* + * Call ValidateSplitFileSpec with various supported combinations. + *************************************************************************************/ + ValidateSplitFileSpec("foo.cs", "", "", "foo.cs"); + ValidateSplitFileSpec("**\\foo.cs", "", "**\\", "foo.cs"); + ValidateSplitFileSpec("f:\\dir1\\**\\foo.cs", "f:\\dir1\\", "**\\", "foo.cs"); + ValidateSplitFileSpec("..\\**\\foo.cs", "..\\", "**\\", "foo.cs"); + ValidateSplitFileSpec("f:\\dir1\\foo.cs", "f:\\dir1\\", "", "foo.cs"); + ValidateSplitFileSpec("f:\\dir?\\foo.cs", "f:\\", "dir?\\", "foo.cs"); + ValidateSplitFileSpec("dir?\\foo.cs", "", "dir?\\", "foo.cs"); + ValidateSplitFileSpec(@"**\test\**", "", @"**\test\**\", "*.*"); + ValidateSplitFileSpec("bin\\**\\*.cs", "bin\\", "**\\", "*.cs"); + ValidateSplitFileSpec("bin\\**\\*.*", "bin\\", "**\\", "*.*"); + ValidateSplitFileSpec("bin\\**", "bin\\", "**\\", "*.*"); + ValidateSplitFileSpec("bin\\**\\", "bin\\", "**\\", ""); + ValidateSplitFileSpec("bin\\**\\*", "bin\\", "**\\", "*"); + ValidateSplitFileSpec("**", "", "**\\", "*.*"); + } + + [Fact] + public void Regress367780_CrashOnStarDotDot() + { + string workingPath = _env.CreateFolder().Path; + string workingPathSubfolder = Path.Combine(workingPath, "SubDir"); + string offendingPattern = Path.Combine(workingPath, @"*\..\bar"); + string[] files = Array.Empty(); + + Directory.CreateDirectory(workingPath); + Directory.CreateDirectory(workingPathSubfolder); + + files = FileMatcher.Default.GetFiles(workingPath, offendingPattern).FileList; + } + + [Fact] + public void Regress141071_StarStarSlashStarStarIsLiteral() + { + string workingPath = _env.CreateFolder().Path; + string fileName = Path.Combine(workingPath, "MyFile.txt"); + string offendingPattern = Path.Combine(workingPath, @"**\**"); + + Directory.CreateDirectory(workingPath); + File.WriteAllText(fileName, "Hello there."); + var files = FileMatcher.Default.GetFiles(workingPath, offendingPattern).FileList; + + string result = String.Join(", ", files); + Console.WriteLine(result); + Assert.DoesNotContain("**", result); + Assert.Contains("MyFile.txt", result); + } + + [Fact] + public void Regress14090_TrailingDotMatchesNoExtension() + { + string workingPath = _env.CreateFolder().Path; + string workingPathSubdir = Path.Combine(workingPath, "subdir"); + string workingPathSubdirBing = Path.Combine(workingPathSubdir, "bing"); + + string offendingPattern = Path.Combine(workingPath, @"**\sub*\*."); + + Directory.CreateDirectory(workingPath); + Directory.CreateDirectory(workingPathSubdir); + File.AppendAllText(workingPathSubdirBing, "y"); + var files = FileMatcher.Default.GetFiles(workingPath, offendingPattern).FileList; + + string result = String.Join(", ", files); + Console.WriteLine(result); + Assert.Single(files); + } + + [Fact] + public void Regress14090_TrailingDotMatchesNoExtension_Part2() + { + ValidateFileMatch(@"c:\mydir\**\*.", @"c:\mydir\subdir\bing", true, /* simulate filesystem? */ false); + ValidateNoFileMatch(@"c:\mydir\**\*.", @"c:\mydir\subdir\bing.txt", true); + } + + [Fact] + public void FileEnumerationCacheTakesExcludesIntoAccount() + { + try + { + using (var env = TestEnvironment.Create()) + { + env.SetEnvironmentVariable("MsBuildCacheFileEnumerations", "1"); + + var testProject = env.CreateTestProjectWithFiles(string.Empty, new[] { "a.cs", "b.cs", "c.cs" }); + + var files = FileMatcher.Default.GetFiles(testProject.TestRoot, "**/*.cs").FileList; + Array.Sort(files); + Assert.Equal(new[] { "a.cs", "b.cs", "c.cs" }, files); + + files = FileMatcher.Default.GetFiles(testProject.TestRoot, "**/*.cs", new List { "a.cs" }).FileList; + Array.Sort(files); + Assert.Equal(new[] { "b.cs", "c.cs" }, files); + + files = FileMatcher.Default.GetFiles(testProject.TestRoot, "**/*.cs", new List { "a.cs", "c.cs" }).FileList; + Array.Sort(files); + Assert.Equal(new[] { "b.cs" }, files); + } + } + finally + { + FileMatcher.ClearCaches(); + } + } + + [Theory] + [InlineData(@"\", "**")] + [InlineData(@"\\", "**")] + [InlineData(@"\\\\\\\\", "**")] + [InlineData("/", "**/*.cs")] + [InlineData("/", "**")] + [InlineData("//", "**")] + [InlineData("////////", "**")] + public void DriveEnumeratingWildcardIsObservedOnAnyPlatform(string directoryPart, string wildcardPart) => + DriveEnumeratingWildcardIsObserved(directoryPart, wildcardPart); + + [WindowsOnlyTheory] + [InlineData(@"\", "**")] + [InlineData(@"c:\", "**")] + [InlineData(@"c:\\", "**")] + [InlineData(@"c:\\\\\\\\", "**")] + [InlineData(@"c:\", @"**\*.cs")] + public void DriveEnumeratingWildcardIsObservedOnWindows(string directoryPart, string wildcardPart) + { + DriveEnumeratingWildcardIsObserved(directoryPart, wildcardPart); + DriveEnumeratingWildcardFailsAndReturns(directoryPart, wildcardPart); + } + + private void DriveEnumeratingWildcardIsObserved(string directoryPart, string wildcardPart) => + FileMatcher.IsDriveEnumeratingWildcardPattern(directoryPart, wildcardPart).ShouldBeTrue(); + + [UnixOnlyTheory] + [InlineData(@"\", "**")] + [InlineData("/", "**/*.cs")] + [InlineData("/", "**")] + [InlineData("//", "**")] + [InlineData("////////", "**")] + public void DriveEnumeratingWildcardFailsAndReturnsOnUnix(string directoryPart, string wildcardPart) + { + DriveEnumeratingWildcardFailsAndReturns(directoryPart, wildcardPart); + } + + private void DriveEnumeratingWildcardFailsAndReturns(string directoryPart, string wildcardPart) + { + string driveEnumeratingWildcard = string.Concat(directoryPart, wildcardPart); + + using (var env = TestEnvironment.Create()) + { + try + { + // Set env var to fail on drive enumerating wildcard detection + Helpers.ResetStateForDriveEnumeratingWildcardTests(env, "1"); + + (string[] fileList, FileMatcher.SearchAction action, string excludeFileSpec, _) = FileMatcher.Default.GetFiles( + string.Empty, + driveEnumeratingWildcard); + + action.ShouldBe(FileMatcher.SearchAction.FailOnDriveEnumeratingWildcard); + fileList.ShouldBeEmpty(); + excludeFileSpec.ShouldBe(string.Empty); + + // Handle failing with drive enumerating exclude + (fileList, action, excludeFileSpec, _) = FileMatcher.Default.GetFiles( + string.Empty, + @"/*/*.cs", + new List { driveEnumeratingWildcard }); + + action.ShouldBe(FileMatcher.SearchAction.FailOnDriveEnumeratingWildcard); + fileList.ShouldBeEmpty(); + excludeFileSpec.ShouldBe(driveEnumeratingWildcard); + } + finally + { + ChangeWaves.ResetStateForTests(); + } + } + } + + [WindowsOnlyTheory] + [InlineData(@"%DRIVE%:\**")] + [InlineData(@"%DRIVE%:\\**")] + [InlineData(@"%DRIVE%:\\\\\\\\**")] + [InlineData(@"%DRIVE%:\**\*.cs")] + public void DriveEnumeratingWildcardIsLoggedOnWindows(string driveEnumeratingWildcard) + { + using (var env = TestEnvironment.Create()) + { + try + { + driveEnumeratingWildcard = DummyMappedDriveUtils.UpdatePathToMappedDrive(driveEnumeratingWildcard, _mappedDrive.Value.MappedDriveLetter); + + // Set env var to log on drive enumerating wildcard detection + Helpers.ResetStateForDriveEnumeratingWildcardTests(env, "0"); + + (_, FileMatcher.SearchAction action, string excludeFileSpec, _) = FileMatcher.Default.GetFiles( + string.Empty, + driveEnumeratingWildcard); + + action.ShouldBe(FileMatcher.SearchAction.LogDriveEnumeratingWildcard); + excludeFileSpec.ShouldBe(string.Empty); + + // Handle logging with drive enumerating exclude + (_, action, excludeFileSpec, _) = FileMatcher.Default.GetFiles( + string.Empty, + @"/*/*.cs", + new List { driveEnumeratingWildcard }); + + action.ShouldBe(FileMatcher.SearchAction.LogDriveEnumeratingWildcard); + excludeFileSpec.ShouldBe(driveEnumeratingWildcard); + } + finally + { + ChangeWaves.ResetStateForTests(); + } + } + } + + [Theory] + [InlineData(@"\", @"*\*.cs")] + [InlineData(@"\\", @"*\*.cs")] + [InlineData(@"\", @"*\*.*")] + [InlineData(@"/", @"*/*.cs")] + [InlineData(@"//", @"*/*.cs")] + [InlineData(@"/", @"*/*.*")] + public void DriveEnumeratingWildcardIsNotObservedOnAnyPlatform(string directoryPart, string wildcardPart) => + DriveEnumeratingWildcardIsNotObserved(directoryPart, wildcardPart); + + [UnixOnlyTheory] + [InlineData(@"c:\", "**")] + [InlineData(@"c:\\", "**")] + [InlineData(@"c:\\\\\\\\", "**")] + [InlineData(@"c:\", @"**\*.cs")] + public void DriveEnumeratingWildcardIsNotObservedOnUnix(string directoryPart, string wildcardPart) + { + DriveEnumeratingWildcardIsNotObserved(directoryPart, wildcardPart); + } + + private void DriveEnumeratingWildcardIsNotObserved(string directoryPart, string wildcardPart) => + FileMatcher.IsDriveEnumeratingWildcardPattern(directoryPart, wildcardPart).ShouldBeFalse(); + + [Fact] + public void RemoveProjectDirectory() + { + string[] strings = new string[1] { NativeMethodsShared.IsWindows ? "c:\\1.file" : "/1.file" }; + strings = FileMatcher.RemoveProjectDirectory(strings, NativeMethodsShared.IsWindows ? "c:\\" : "/").ToArray(); + Assert.Equal("1.file", strings[0]); + + strings = new string[1] { NativeMethodsShared.IsWindows ? "c:\\directory\\1.file" : "/directory/1.file" }; + strings = FileMatcher.RemoveProjectDirectory(strings, NativeMethodsShared.IsWindows ? "c:\\" : "/").ToArray(); + Assert.Equal(strings[0], NativeMethodsShared.IsWindows ? "directory\\1.file" : "directory/1.file"); + + strings = new string[1] { NativeMethodsShared.IsWindows ? "c:\\directory\\1.file" : "/directory/1.file" }; + strings = FileMatcher.RemoveProjectDirectory(strings, NativeMethodsShared.IsWindows ? "c:\\directory" : "/directory").ToArray(); + Assert.Equal("1.file", strings[0]); + + strings = new string[1] { NativeMethodsShared.IsWindows ? "c:\\1.file" : "/1.file" }; + strings = FileMatcher.RemoveProjectDirectory(strings, NativeMethodsShared.IsWindows ? "c:\\directory" : "/directory").ToArray(); + Assert.Equal(strings[0], NativeMethodsShared.IsWindows ? "c:\\1.file" : "/1.file"); + + strings = new string[1] { NativeMethodsShared.IsWindows ? "c:\\directorymorechars\\1.file" : "/directorymorechars/1.file" }; + strings = FileMatcher.RemoveProjectDirectory(strings, NativeMethodsShared.IsWindows ? "c:\\directory" : "/directory").ToArray(); + Assert.Equal(strings[0], NativeMethodsShared.IsWindows ? "c:\\directorymorechars\\1.file" : "/directorymorechars/1.file"); + + if (NativeMethodsShared.IsWindows) + { + strings = new string[1] { "\\Machine\\1.file" }; + strings = FileMatcher.RemoveProjectDirectory(strings, "\\Machine").ToArray(); + Assert.Equal("1.file", strings[0]); + + strings = new string[1] { "\\Machine\\directory\\1.file" }; + strings = FileMatcher.RemoveProjectDirectory(strings, "\\Machine").ToArray(); + Assert.Equal("directory\\1.file", strings[0]); + + strings = new string[1] { "\\Machine\\directory\\1.file" }; + strings = FileMatcher.RemoveProjectDirectory(strings, "\\Machine\\directory").ToArray(); + Assert.Equal("1.file", strings[0]); + + strings = new string[1] { "\\Machine\\1.file" }; + strings = FileMatcher.RemoveProjectDirectory(strings, "\\Machine\\directory").ToArray(); + Assert.Equal("\\Machine\\1.file", strings[0]); + + strings = new string[1] { "\\Machine\\directorymorechars\\1.file" }; + strings = FileMatcher.RemoveProjectDirectory(strings, "\\Machine\\directory").ToArray(); + Assert.Equal("\\Machine\\directorymorechars\\1.file", strings[0]); + } + } + + [Theory] + [InlineData( + @"src/**/*.cs", // Include Pattern + new string[] // Matching files + { + @"src/a.cs", + @"src/a\b\b.cs", + })] + [InlineData( + @"src/test/**/*.cs", // Include Pattern + new string[] // Matching files + { + @"src/test/a.cs", + @"src/test/a\b\c.cs", + })] + [InlineData( + @"src/test/**/a/b/**/*.cs", // Include Pattern + new string[] // Matching files + { + @"src/test/dir\a\b\a.cs", + @"src/test/dir\a\b\c\a.cs", + })] + public void IncludePatternShouldNotPreserveUserSlashesInFixedDirPart(string include, string[] matching) + { + MatchDriver(include, null, matching, null, null, normalizeAllPaths: false, normalizeExpectedMatchingFiles: true); + } + + [Theory] + [InlineData( + @"**\*.cs", // Include Pattern + new[] // Exclude patterns + { + @"bin\**" + }, + new string[] // Matching files + { + }, + new string[] // Non matching files + { + }, + new[] // Non matching files that shouldn't be touched + { + @"bin\foo.cs", + @"bin\bar\foo.cs", + @"bin\bar\" + })] + [InlineData( + @"**\*.cs", // Include Pattern + new[] // Exclude patterns + { + @"bin\**" + }, + new[] // Matching files + { + "a.cs", + @"b\b.cs", + }, + new[] // Non matching files + { + @"b\b.txt" + }, + new[] // Non matching files that shouldn't be touched + { + @"bin\foo.cs", + @"bin\bar\foo.cs", + @"bin\bar\" + })] + public void ExcludePattern(string include, string[] exclude, string[] matching, string[] nonMatching, string[] untouchable) + { + MatchDriver(include, exclude, matching, nonMatching, untouchable); + } + + [Fact] + public void ExcludeSpecificFiles() + { + MatchDriverWithDifferentSlashes( + @"**\*.cs", // Include Pattern + new[] // Exclude patterns + { + @"Program_old.cs", + @"Properties\AssemblyInfo_old.cs" + }, + new[] // Matching files + { + @"foo.cs", + @"Properties\AssemblyInfo.cs", + @"Foo\Bar\Baz\Buzz.cs" + }, + new[] // Non matching files + { + @"Program_old.cs", + @"Properties\AssemblyInfo_old.cs" + }, + Array.Empty()); // Non matching files that shouldn't be touched + } + + [Fact] + public void ExcludePatternAndSpecificFiles() + { + MatchDriverWithDifferentSlashes( + @"**\*.cs", // Include Pattern + new[] // Exclude patterns + { + @"bin\**", + @"Program_old.cs", + @"Properties\AssemblyInfo_old.cs" + }, + new[] // Matching files + { + @"foo.cs", + @"Properties\AssemblyInfo.cs", + @"Foo\Bar\Baz\Buzz.cs" + }, + new[] // Non matching files + { + @"foo.txt", + @"Foo\foo.txt", + @"Program_old.cs", + @"Properties\AssemblyInfo_old.cs" + }, + new[] // Non matching files that shouldn't be touched + { + @"bin\foo.cs", + @"bin\bar\foo.cs", + @"bin\bar\" + }); + } + + [Theory] + [InlineData( + @"**\*.cs", // Include Pattern + new[] // Exclude patterns + { + @"**\bin\**\*.cs", + @"src\Common\**", + }, + new[] // Matching files + { + @"foo.cs", + @"src\Framework\Properties\AssemblyInfo.cs", + @"src\Framework\Foo\Bar\Baz\Buzz.cs" + }, + new[] // Non matching files + { + @"foo.txt", + @"src\Framework\Readme.md", + @"src\Common\foo.cs", + + // Ideally these would be untouchable + @"src\Framework\bin\foo.cs", + @"src\Framework\bin\Debug", + @"src\Framework\bin\Debug\foo.cs", + }, + new[] // Non matching files that shouldn't be touched + { + @"src\Common\Properties\", + @"src\Common\Properties\AssemblyInfo.cs", + })] + [InlineData( + @"**\*.cs", // Include Pattern + new[] // Exclude patterns + { + @"**\bin\**\*.cs", + @"src\Co??on\**", + }, + new[] // Matching files + { + @"foo.cs", + @"src\Framework\Properties\AssemblyInfo.cs", + @"src\Framework\Foo\Bar\Baz\Buzz.cs" + }, + new[] // Non matching files + { + @"foo.txt", + @"src\Framework\Readme.md", + @"src\Common\foo.cs", + + // Ideally these would be untouchable + @"src\Framework\bin\foo.cs", + @"src\Framework\bin\Debug", + @"src\Framework\bin\Debug\foo.cs", + @"src\Common\Properties\AssemblyInfo.cs" + }, + new[] // Non matching files that shouldn't be touched + { + @"src\Common\Properties\" + })] + [InlineData( + @"src\**\proj\**\*.cs", // Include Pattern + new[] // Exclude patterns + { + @"src\**\proj\**\none\**\*", + }, + new[] // Matching files + { + @"src\proj\m1.cs", + @"src\proj\a\m2.cs", + @"src\b\proj\m3.cs", + @"src\c\proj\d\m4.cs", + }, + new[] // Non matching files + { + @"nm1.cs", + @"a\nm2.cs", + @"src\nm3.cs", + @"src\a\nm4.cs", + + // Ideally these would be untouchable + @"src\proj\none\nm5.cs", + @"src\proj\a\none\nm6.cs", + @"src\b\proj\none\nm7.cs", + @"src\c\proj\d\none\nm8.cs", + @"src\e\proj\f\none\g\nm8.cs", + }, + new string[] // Non matching files that shouldn't be touched + { + })] + // patterns with excludes that ideally would prune entire recursive subtrees (files in pruned tree aren't touched at all) but the exclude pattern is too complex for that to work with the current logic + public void ExcludeComplexPattern(string include, string[] exclude, string[] matching, string[] nonMatching, string[] untouchable) + { + MatchDriverWithDifferentSlashes(include, exclude, matching, nonMatching, untouchable); + } + + [Theory] + // Empty string is valid + [InlineData( + "", + "", + "", + "", + "^(?)(?)$", + false, + true)] + // ... anywhere is invalid + [InlineData( + @"...\foo", + "", + "", + "", + "", + false, + false)] + // : not placed at second index is invalid + [InlineData( + "http://www.website.com", + "", + "", + "", + "", + false, + false)] + // ** not alone in filename part is invalid + [InlineData( + "**foo", + "", + "", + "**foo", + "", + false, + false)] + // ** not alone in filename part is invalid + [InlineData( + "foo**", + "", + "", + "foo**", + "", + false, + false)] + // ** not alone between slashes in wildcard part is invalid + [InlineData( + @"**foo\bar", + "", + @"**foo\", + "bar", + "", + false, + false)] + // .. placed after any ** is invalid + [InlineData( + @"**\..\bar", + "", + @"**\..\", + "bar", + "", + false, + false)] + // Common wildcard characters in wildcard and filename part + [InlineData( + @"*fo?ba?\*fo?ba?", + "", + @"*fo?ba?\", + "*fo?ba?", + @"^(?[^/\\]*fo.ba.[/\\]+)(?[^/\\]*fo.ba.)$", + true, + true)] + // Special case for ? and * when trailing . in filename part + [InlineData( + "?oo*.", + "", + "", + "?oo*.", + @"^(?)(?[^\.].oo[^\.]*)$", + false, + true)] + // Skip the .* portion of any *.* sequence in filename part + [InlineData( + "*.*foo*.*", + "", + "", + "*.*foo*.*", + @"^(?)(?[^/\\]*foo[^/\\]*)$", + false, + true)] + // Collapse successive directory separators + [InlineData( + @"\foo///bar\\\?foo///bar\\\foo", + @"\foo///bar\\\", + @"?foo///bar\\\", + "foo", + @"^[/\\]+foo[/\\]+bar[/\\]+(?.foo[/\\]+bar[/\\]+)(?foo)$", + true, + true)] + // Collapse successive relative separators + [InlineData( + @"\./.\foo/.\./bar\./.\?foo/.\./bar\./.\foo", + @"\./.\foo/.\./bar\./.\", + @"?foo/.\./bar\./.\", + "foo", + @"^[/\\]+foo[/\\]+bar[/\\]+(?.foo[/\\]+bar[/\\]+)(?foo)$", + true, + true)] + // Collapse successive recursive operators + [InlineData( + @"foo\**/**\bar/**\**/foo\**/**\bar", + @"foo\", + @"**/**\bar/**\**/foo\**/**\", + "bar", + @"^foo[/\\]+(?((.*/)|(.*\\)|())bar((/)|(\\)|(/.*/)|(/.*\\)|(\\.*\\)|(\\.*/))foo((/)|(\\)|(/.*/)|(/.*\\)|(\\.*\\)|(\\.*/)))(?bar)$", + true, + true)] + // Collapse all three cases combined + [InlineData( + @"foo\\\.///**\\\.///**\\\.///bar\\\.///**\\\.///**\\\.///foo\\\.///**\\\.///**\\\.///bar", + @"foo\\\.///", + @"**\\\.///**\\\.///bar\\\.///**\\\.///**\\\.///foo\\\.///**\\\.///**\\\.///", + "bar", + @"^foo[/\\]+(?((.*/)|(.*\\)|())bar((/)|(\\)|(/.*/)|(/.*\\)|(\\.*\\)|(\\.*/))foo((/)|(\\)|(/.*/)|(/.*\\)|(\\.*\\)|(\\.*/)))(?bar)$", + true, + true)] + public void GetFileSpecInfoCommon( + string filespec, + string expectedFixedDirectoryPart, + string expectedWildcardDirectoryPart, + string expectedFilenamePart, + string expectedMatchFileExpression, + bool expectedNeedsRecursion, + bool expectedIsLegalFileSpec) + { + if (NativeMethodsShared.IsUnixLike) + { + expectedFixedDirectoryPart = FileUtilities.FixFilePath(expectedFixedDirectoryPart); + expectedWildcardDirectoryPart = FileUtilities.FixFilePath(expectedWildcardDirectoryPart); + } + TestGetFileSpecInfo( + filespec, + expectedFixedDirectoryPart, + expectedWildcardDirectoryPart, + expectedFilenamePart, + expectedMatchFileExpression, + expectedNeedsRecursion, + expectedIsLegalFileSpec); + } + + [WindowsOnlyTheory] + // Escape pecial regex characters valid in Windows paths + [InlineData( + @"$()+.[^{\?$()+.[^{\$()+.[^{", + @"$()+.[^{\", + @"?$()+.[^{\", + "$()+.[^{", + @"^\$\(\)\+\.\[\^\{[/\\]+(?.\$\(\)\+\.\[\^\{[/\\]+)(?\$\(\)\+\.\[\^\{)$", + true, + true)] + // Preserve UNC paths in fixed directory part + [InlineData( + @"\\\.\foo/bar", + @"\\\.\foo/", + "", + "bar", + @"^\\\\foo[/\\]+(?)(?bar)$", + false, + true)] + public void GetFileSpecInfoWindows( + string filespec, + string expectedFixedDirectoryPart, + string expectedWildcardDirectoryPart, + string expectedFilenamePart, + string expectedMatchFileExpression, + bool expectedNeedsRecursion, + bool expectedIsLegalFileSpec) + { + TestGetFileSpecInfo( + filespec, + expectedFixedDirectoryPart, + expectedWildcardDirectoryPart, + expectedFilenamePart, + expectedMatchFileExpression, + expectedNeedsRecursion, + expectedIsLegalFileSpec); + } + + [UnixOnlyTheory] + // Escape regex characters valid in Unix paths + [InlineData( + @"$()+.[^{|\?$()+.[^{|\$()+.[^{|", + @"$()+.[^{|/", + @"?$()+.[^{|/", + "$()+.[^{|", + @"^\$\(\)\+\.\[\^\{\|[/\\]+(?.\$\(\)\+\.\[\^\{\|[/\\]+)(?\$\(\)\+\.\[\^\{\|)$", + true, + true)] + // Collapse leading successive directory separators in fixed directory part + [InlineData( + @"\\\.\foo/bar", + @"///./foo/", + "", + "bar", + @"^[/\\]+foo[/\\]+(?)(?bar)$", + false, + true)] + public void GetFileSpecInfoUnix( + string filespec, + string expectedFixedDirectoryPart, + string expectedWildcardDirectoryPart, + string expectedFilenamePart, + string expectedMatchFileExpression, + bool expectedNeedsRecursion, + bool expectedIsLegalFileSpec) + { + TestGetFileSpecInfo( + filespec, + expectedFixedDirectoryPart, + expectedWildcardDirectoryPart, + expectedFilenamePart, + expectedMatchFileExpression, + expectedNeedsRecursion, + expectedIsLegalFileSpec); + } + + private void TestGetFileSpecInfo( + string filespec, + string expectedFixedDirectoryPart, + string expectedWildcardDirectoryPart, + string expectedFilenamePart, + string expectedMatchFileExpression, + bool expectedNeedsRecursion, + bool expectedIsLegalFileSpec) + { + FileMatcher.Default.GetFileSpecInfo( + filespec, + out string fixedDirectoryPart, + out string wildcardDirectoryPart, + out string filenamePart, + out bool needsRecursion, + out bool isLegalFileSpec); + string matchFileExpression = isLegalFileSpec + ? FileMatcher.RegularExpressionFromFileSpec(fixedDirectoryPart, wildcardDirectoryPart, filenamePart) + : string.Empty; + + fixedDirectoryPart.ShouldBe(expectedFixedDirectoryPart); + wildcardDirectoryPart.ShouldBe(expectedWildcardDirectoryPart); + filenamePart.ShouldBe(expectedFilenamePart); + matchFileExpression.ShouldBe(expectedMatchFileExpression); + needsRecursion.ShouldBe(expectedNeedsRecursion); + isLegalFileSpec.ShouldBe(expectedIsLegalFileSpec); + } + + #region Support functions. + + /// + /// This support class simulates a file system. + /// It accepts multiple sets of files and keeps track of how many files were "hit" + /// In this case, "hit" means that the caller asked for that file directly. + /// + internal sealed class MockFileSystem + { + /// + /// Array of files (set1) + /// + private string[] _fileSet1; + + /// + /// Array of files (set2) + /// + private string[] _fileSet2; + + /// + /// Array of files (set3) + /// + private string[] _fileSet3; + + /// + /// Number of times a file from set 1 was requested. + /// + private int _fileSet1Hits = 0; + + /// + /// Number of times a file from set 2 was requested. + /// + private int _fileSet2Hits = 0; + + /// + /// Number of times a file from set 3 was requested. + /// + private int _fileSet3Hits = 0; + + /// + /// Construct. + /// + /// First set of files. + /// Second set of files. + /// Third set of files. + internal MockFileSystem( + string[] fileSet1, + string[] fileSet2, + string[] fileSet3) + { + _fileSet1 = fileSet1; + _fileSet2 = fileSet2; + _fileSet3 = fileSet3; + } + + /// + /// Number of times a file from set 1 was requested. + /// + internal int FileHits1 + { + get { return _fileSet1Hits; } + } + + /// + /// Number of times a file from set 2 was requested. + /// + internal int FileHits2 + { + get { return _fileSet2Hits; } + } + + /// + /// Number of times a file from set 3 was requested. + /// + internal int FileHits3 + { + get { return _fileSet3Hits; } + } + + /// + /// Return files that match the given files. + /// + /// Candidate files. + /// The path to search within + /// The pattern to search for. + /// Hashtable receives the files. + /// + private int GetMatchingFiles(string[] candidates, string path, string pattern, ISet files) + { + int hits = 0; + + if (candidates != null) + { + foreach (string candidate in candidates) + { + string normalizedCandidate = Normalize(candidate); + + // Get the candidate directory. + string candidateDirectoryName = ""; + if (normalizedCandidate.IndexOfAny(FileMatcher.directorySeparatorCharacters) != -1) + { + candidateDirectoryName = Path.GetDirectoryName(normalizedCandidate); + } + + // Does the candidate directory match the requested path? + if (FileUtilities.PathsEqual(path, candidateDirectoryName)) + { + // Match the basic *.* or null. These both match any file. + if + ( + pattern == null || + String.Equals(pattern, "*.*", StringComparison.OrdinalIgnoreCase)) + { + ++hits; + files.Add(FileMatcher.Normalize(candidate)); + } + else if (pattern.Substring(0, 2) == "*.") // Match patterns like *.cs + { + string tail = pattern.Substring(1); + string candidateTail = candidate.Substring(candidate.Length - tail.Length); + if (String.Equals(tail, candidateTail, StringComparison.OrdinalIgnoreCase)) + { + ++hits; + files.Add(FileMatcher.Normalize(candidate)); + } + } + else if (pattern.Substring(pattern.Length - 4, 2) == ".?") // Match patterns like foo.?xt + { + string leader = pattern.Substring(0, pattern.Length - 4); + string candidateLeader = candidate.Substring(candidate.Length - leader.Length - 4, leader.Length); + if (String.Equals(leader, candidateLeader, StringComparison.OrdinalIgnoreCase)) + { + string tail = pattern.Substring(pattern.Length - 2); + string candidateTail = candidate.Substring(candidate.Length - 2); + if (String.Equals(tail, candidateTail, StringComparison.OrdinalIgnoreCase)) + { + ++hits; + files.Add(FileMatcher.Normalize(candidate)); + } + } + } + else if (!FileMatcher.HasWildcards(pattern)) + { + if (normalizedCandidate == Path.Combine(path, pattern)) + { + ++hits; + files.Add(candidate); + } + } + else + { + Assert.Fail(String.Format("Unhandled case in GetMatchingFiles: {0}", pattern)); + } + } + } + } + + return hits; + } + + /// + /// Given a path and pattern, return all the simulated directories out of candidates. + /// + /// Candidate file to extract directories from. + /// The path to search. + /// The pattern to match. + /// Receives the directories. + private void GetMatchingDirectories(string[] candidates, string path, string pattern, ISet directories) + { + if (candidates != null) + { + foreach (string candidate in candidates) + { + string normalizedCandidate = Normalize(candidate); + + if (IsMatchingDirectory(path, normalizedCandidate)) + { + int nextSlash = normalizedCandidate.IndexOfAny(FileMatcher.directorySeparatorCharacters, path.Length + 1); + if (nextSlash != -1) + { + // UNC paths start with a \\ fragment. Match against \\ when path is empty (i.e., inside the current working directory) + string match = normalizedCandidate.StartsWith(@"\\") && string.IsNullOrEmpty(path) + ? @"\\" + : normalizedCandidate.Substring(0, nextSlash); + + string baseMatch = Path.GetFileName(normalizedCandidate.Substring(0, nextSlash)); + + if + ( + String.Equals(pattern, "*.*", StringComparison.OrdinalIgnoreCase) + || pattern == null) + { + directories.Add(FileMatcher.Normalize(match)); + } + else if // Match patterns like ?emp + ( + pattern.Substring(0, 1) == "?" + && pattern.Length == baseMatch.Length) + { + string tail = pattern.Substring(1); + string baseMatchTail = baseMatch.Substring(1); + if (String.Equals(tail, baseMatchTail, StringComparison.OrdinalIgnoreCase)) + { + directories.Add(FileMatcher.Normalize(match)); + } + } + else + { + Assert.Fail(String.Format("Unhandled case in GetMatchingDirectories: {0}", pattern)); + } + } + } + } + } + } + + /// + /// Method that is delegable for use by FileMatcher. This method simulates a filesystem by returning + /// files and\or folders that match the requested path and pattern. + /// + /// Files, Directories or both + /// The path to search. + /// The pattern to search (may be null) + /// The matched files or folders. + internal IReadOnlyList GetAccessibleFileSystemEntries(FileMatcher.FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory) + { + string normalizedPath = Normalize(path); + + ISet files = new HashSet(); + if (entityType == FileMatcher.FileSystemEntity.Files || entityType == FileMatcher.FileSystemEntity.FilesAndDirectories) + { + _fileSet1Hits += GetMatchingFiles(_fileSet1, normalizedPath, pattern, files); + _fileSet2Hits += GetMatchingFiles(_fileSet2, normalizedPath, pattern, files); + _fileSet3Hits += GetMatchingFiles(_fileSet3, normalizedPath, pattern, files); + } + + if (entityType == FileMatcher.FileSystemEntity.Directories || entityType == FileMatcher.FileSystemEntity.FilesAndDirectories) + { + GetMatchingDirectories(_fileSet1, normalizedPath, pattern, files); + GetMatchingDirectories(_fileSet2, normalizedPath, pattern, files); + GetMatchingDirectories(_fileSet3, normalizedPath, pattern, files); + } + + return files.ToList(); + } + + /// + /// Given a path, fix it up so that it can be compared to another path. + /// + /// The path to fix up. + /// The normalized path. + internal static string Normalize(string path) + { + if (path.Length == 0) + { + return path; + } + + string normalized = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + if (Path.DirectorySeparatorChar != '\\') + { + normalized = path.Replace("\\", Path.DirectorySeparatorChar.ToString()); + } + // Replace leading UNC. + if (normalized.StartsWith(@"\\")) + { + normalized = "<:UNC:>" + normalized.Substring(2); + } + + // Preserve parent-directory markers. + normalized = normalized.Replace(@".." + Path.DirectorySeparatorChar, "<:PARENT:>"); + + // Just get rid of doubles enough to satisfy our test cases. + string doubleSeparator = Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString(); + normalized = normalized.Replace(doubleSeparator, Path.DirectorySeparatorChar.ToString()); + normalized = normalized.Replace(doubleSeparator, Path.DirectorySeparatorChar.ToString()); + normalized = normalized.Replace(doubleSeparator, Path.DirectorySeparatorChar.ToString()); + + // Strip any .\ + normalized = normalized.Replace(@"." + Path.DirectorySeparatorChar, ""); + + // Put back the preserved markers. + normalized = normalized.Replace("<:UNC:>", @"\\"); + normalized = normalized.Replace("<:PARENT:>", @".." + Path.DirectorySeparatorChar); + + return normalized; + } + + /// + /// Determines whether candidate is in a subfolder of path. + /// + /// + /// + /// True if there is a match. + private bool IsMatchingDirectory(string path, string candidate) + { + string normalizedPath = Normalize(path); + string normalizedCandidate = Normalize(candidate); + + // Current directory always matches for non-rooted paths. + if (path.Length == 0 && !Path.IsPathRooted(candidate)) + { + return true; + } + + if (normalizedCandidate.Length > normalizedPath.Length) + { + if (String.Compare(normalizedPath, 0, normalizedCandidate, 0, normalizedPath.Length, StringComparison.OrdinalIgnoreCase) == 0) + { + if (FileUtilities.EndsWithSlash(normalizedPath)) + { + return true; + } + else if (FileUtilities.IsSlash(normalizedCandidate[normalizedPath.Length])) + { + return true; + } + } + } + + return false; + } + + /// + /// Searches the candidates array for one that matches path + /// + /// + /// + /// The index of the first match or negative one. + private int IndexOfFirstMatchingDirectory(string path, string[] candidates) + { + if (candidates != null) + { + int i = 0; + foreach (string candidate in candidates) + { + if (IsMatchingDirectory(path, candidate)) + { + return i; + } + + ++i; + } + } + + return -1; + } + + /// + /// Delegable method that returns true if the given directory exists in this simulated filesystem + /// + /// The path to check. + /// True if the directory exists. + internal bool DirectoryExists(string path) + { + if (IndexOfFirstMatchingDirectory(path, _fileSet1) != -1) + { + return true; + } + + if (IndexOfFirstMatchingDirectory(path, _fileSet2) != -1) + { + return true; + } + + if (IndexOfFirstMatchingDirectory(path, _fileSet3) != -1) + { + return true; + } + + return false; + } + } + + /// + /// A general purpose method used to: + /// + /// (1) Simulate a file system. + /// (2) Check whether all matchingFiles where hit by the filespec pattern. + /// (3) Check whether all nonmatchingFiles were *not* hit by the filespec pattern. + /// (4) Check whether all untouchableFiles were not even requested (usually for perf reasons). + /// + /// These can be used in various combinations to test the filematcher framework. + /// + /// A FileMatcher filespec, possibly with wildcards. + /// Files that exist and should be matched. + /// Files that exists and should not be matched. + /// Files that exist but should not be requested. + private static void MatchDriver( + string filespec, + string[] matchingFiles, + string[] nonmatchingFiles, + string[] untouchableFiles) + { + MatchDriver(filespec, null, matchingFiles, nonmatchingFiles, untouchableFiles); + } + + /// + /// Runs the test 4 times with the include and exclude using either forward or backward slashes. + /// Expects the and to contain only backward slashes + /// + /// To preserve current MSBuild behaviour, it only does so if the path is not rooted. Rooted paths do not support forward slashes (as observed on MSBuild 14.0.25420.1) + /// + private static void MatchDriverWithDifferentSlashes( + string filespec, + string[] excludeFilespecs, + string[] matchingFiles, + string[] nonmatchingFiles, + string[] untouchableFiles) + { + // tests should call this method with backward slashes + Assert.DoesNotContain(filespec, "/"); + foreach (var excludeFilespec in excludeFilespecs) + { + Assert.DoesNotContain(excludeFilespec, "/"); + } + + var forwardSlashFileSpec = Helpers.ToForwardSlash(filespec); + var forwardSlashExcludeSpecs = excludeFilespecs.Select(Helpers.ToForwardSlash).ToArray(); + + MatchDriver(filespec, excludeFilespecs, matchingFiles, nonmatchingFiles, untouchableFiles); + MatchDriver(filespec, forwardSlashExcludeSpecs, matchingFiles, nonmatchingFiles, untouchableFiles); + MatchDriver(forwardSlashFileSpec, excludeFilespecs, matchingFiles, nonmatchingFiles, untouchableFiles); + MatchDriver(forwardSlashFileSpec, forwardSlashExcludeSpecs, matchingFiles, nonmatchingFiles, untouchableFiles); + } + + private static void MatchDriver(string filespec, string[] excludeFilespecs, string[] matchingFiles, string[] nonmatchingFiles, string[] untouchableFiles, bool normalizeAllPaths = true, bool normalizeExpectedMatchingFiles = false) + { + MockFileSystem mockFileSystem = new MockFileSystem(matchingFiles, nonmatchingFiles, untouchableFiles); + + var fileMatcher = new FileMatcher(new FileSystemAdapter(mockFileSystem), mockFileSystem.GetAccessibleFileSystemEntries); + + string[] files = fileMatcher.GetFiles( + String.Empty, /* we don't need project directory as we use mock filesystem */ + filespec, + excludeFilespecs?.ToList()) + .FileList; + + Func normalizeAllFunc = (paths => normalizeAllPaths ? paths.Select(MockFileSystem.Normalize).ToArray() : paths); + Func normalizeMatching = (paths => normalizeExpectedMatchingFiles ? paths.Select(MockFileSystem.Normalize).ToArray() : paths); + + string[] normalizedFiles = normalizeAllFunc(files); + + // Validate the matching files. + if (matchingFiles != null) + { + string[] normalizedMatchingFiles = normalizeAllFunc(normalizeMatching(matchingFiles)); + + foreach (string matchingFile in normalizedMatchingFiles) + { + int timesFound = 0; + foreach (string file in normalizedFiles) + { + if (String.Equals(file, matchingFile, StringComparison.OrdinalIgnoreCase)) + { + ++timesFound; + } + } + Assert.Equal(1, timesFound); + } + } + + // Validate the non-matching files + if (nonmatchingFiles != null) + { + string[] normalizedNonMatchingFiles = normalizeAllFunc(nonmatchingFiles); + + foreach (string nonmatchingFile in normalizedNonMatchingFiles) + { + int timesFound = 0; + foreach (string file in normalizedFiles) + { + if (String.Equals(file, nonmatchingFile, StringComparison.OrdinalIgnoreCase)) + { + ++timesFound; + } + } + Assert.Equal(0, timesFound); + } + } + + // Check untouchable files. + Assert.Equal(0, mockFileSystem.FileHits3); // "At least one file that was marked untouchable was referenced." + } + + /// + /// Simulate GetFileSystemEntries + /// + /// + /// + /// Array of matching file system entries (can be empty). + private static IReadOnlyList GetFileSystemEntriesLoopBack(FileMatcher.FileSystemEntity entityType, string path, string pattern, string projectDirectory, bool stripProjectDirectory) + { + return new string[] { Path.Combine(path, pattern) }; + } + + /************************************************************************************* + * Validate that SplitFileSpec(...) is returning the expected constituent values. + *************************************************************************************/ + + private static FileMatcher loopBackFileMatcher = new FileMatcher(FileSystems.Default, GetFileSystemEntriesLoopBack); + + private static void ValidateSplitFileSpec( + string filespec, + string expectedFixedDirectoryPart, + string expectedWildcardDirectoryPart, + string expectedFilenamePart) + { + string fixedDirectoryPart; + string wildcardDirectoryPart; + string filenamePart; + + loopBackFileMatcher.SplitFileSpec( + filespec, + out fixedDirectoryPart, + out wildcardDirectoryPart, + out filenamePart); + + expectedFixedDirectoryPart = FileUtilities.FixFilePath(expectedFixedDirectoryPart); + expectedWildcardDirectoryPart = FileUtilities.FixFilePath(expectedWildcardDirectoryPart); + expectedFilenamePart = FileUtilities.FixFilePath(expectedFilenamePart); + + if + ( + expectedWildcardDirectoryPart != wildcardDirectoryPart + || expectedFixedDirectoryPart != fixedDirectoryPart + || expectedFilenamePart != filenamePart) + { + Console.WriteLine("Expect Fixed '{0}' got '{1}'", expectedFixedDirectoryPart, fixedDirectoryPart); + Console.WriteLine("Expect Wildcard '{0}' got '{1}'", expectedWildcardDirectoryPart, wildcardDirectoryPart); + Console.WriteLine("Expect Filename '{0}' got '{1}'", expectedFilenamePart, filenamePart); + Assert.Fail("FileMatcher Regression: Failure while validating SplitFileSpec."); + } + } + + /************************************************************************************* + * Given a pattern (filespec) and a candidate filename (fileToMatch). Verify that they + * do indeed match. + *************************************************************************************/ + private static void ValidateFileMatch( + string filespec, + string fileToMatch, + bool shouldBeRecursive) + { + ValidateFileMatch(filespec, fileToMatch, shouldBeRecursive, /* Simulate filesystem? */ true); + } + + /************************************************************************************* + * Given a pattern (filespec) and a candidate filename (fileToMatch). Verify that they + * do indeed match. + *************************************************************************************/ + private static void ValidateFileMatch( + string filespec, + string fileToMatch, + bool shouldBeRecursive, + bool fileSystemSimulation) + { + if (!IsFileMatchAssertIfIllegal(filespec, fileToMatch, shouldBeRecursive)) + { + Assert.Fail("FileMatcher Regression: Failure while validating that files match."); + } + + // Now, simulate a filesystem with only fileToMatch. Make sure the file exists that way. + if (fileSystemSimulation) + { + MatchDriver( + filespec, + new string[] { fileToMatch }, + null, + null); + } + } + + /************************************************************************************* + * Given a pattern (filespec) and a candidate filename (fileToMatch). Verify that they + * DON'T match. + *************************************************************************************/ + private static void ValidateNoFileMatch( + string filespec, + string fileToMatch, + bool shouldBeRecursive) + { + if (IsFileMatchAssertIfIllegal(filespec, fileToMatch, shouldBeRecursive)) + { + Assert.Fail("FileMatcher Regression: Failure while validating that files don't match."); + } + + // Now, simulate a filesystem with only fileToMatch. Make sure the file doesn't exist that way. + MatchDriver( + filespec, + null, + new string[] { fileToMatch }, + null); + } + + /************************************************************************************* + * Verify that the given filespec is illegal. + *************************************************************************************/ + private static void ValidateIllegal( + string filespec) + { + Regex regexFileMatch; + bool needsRecursion; + bool isLegalFileSpec; + loopBackFileMatcher.GetFileSpecInfoWithRegexObject( + filespec, + out regexFileMatch, + out needsRecursion, + out isLegalFileSpec); + + if (isLegalFileSpec) + { + Assert.Fail("FileMatcher Regression: Expected an illegal filespec, but got a legal one."); + } + + // Now, FileMatcher is supposed to take any legal file name and just return it immediately. + // Let's see if it does. + MatchDriver( + filespec, // Not legal. + new string[] { filespec }, // Should match + null, + null); + } + /************************************************************************************* + * Given a pattern (filespec) and a candidate filename (fileToMatch) return true if + * FileMatcher would say that they match. + *************************************************************************************/ + private static bool IsFileMatchAssertIfIllegal( + string filespec, + string fileToMatch, + bool shouldBeRecursive) + { + FileMatcher.Result match = FileMatcher.Default.FileMatch(filespec, fileToMatch); + + if (!match.isLegalFileSpec) + { + Console.WriteLine("Checking FileSpec: '{0}' against '{1}'", filespec, fileToMatch); + Assert.Fail("FileMatcher Regression: Invalid filespec."); + } + if (shouldBeRecursive != match.isFileSpecRecursive) + { + Console.WriteLine("Checking FileSpec: '{0}' against '{1}'", filespec, fileToMatch); + Assert.True(shouldBeRecursive); // "FileMatcher Regression: Match was recursive when it shouldn't be." + Assert.False(shouldBeRecursive); // "FileMatcher Regression: Match was not recursive when it should have been." + } + return match.isMatch; + } + + #endregion + + private sealed class FileSystemAdapter : IFileSystem + { + private readonly MockFileSystem _mockFileSystem; + + public FileSystemAdapter(MockFileSystem mockFileSystem) + { + _mockFileSystem = mockFileSystem; + } + + public TextReader ReadFile(string path) => throw new NotImplementedException(); + + public Stream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share) => throw new NotImplementedException(); + + public string ReadFileAllText(string path) => throw new NotImplementedException(); + + public byte[] ReadFileAllBytes(string path) => throw new NotImplementedException(); + + public IEnumerable EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) + { + return FileSystems.Default.EnumerateFiles(path, searchPattern, searchOption); + } + + public IEnumerable EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) + { + return FileSystems.Default.EnumerateDirectories(path, searchPattern, searchOption); + } + + public IEnumerable EnumerateFileSystemEntries(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly) + { + return FileSystems.Default.EnumerateFileSystemEntries(path, searchPattern, searchOption); + } + + public FileAttributes GetAttributes(string path) => throw new NotImplementedException(); + + public DateTime GetLastWriteTimeUtc(string path) => throw new NotImplementedException(); + + public bool DirectoryExists(string path) + { + return _mockFileSystem.DirectoryExists(path); + } + + public bool FileExists(string path) + { + return FileSystems.Default.FileExists(path); + } + + public bool FileOrDirectoryExists(string path) + { + return FileSystems.Default.FileOrDirectoryExists(path); + } + } + } +} diff --git a/src/Framework/BackEnd/InterningBinaryReader.cs b/src/Framework/BackEnd/InterningBinaryReader.cs index a795156ad35..ce7f6a4a348 100644 --- a/src/Framework/BackEnd/InterningBinaryReader.cs +++ b/src/Framework/BackEnd/InterningBinaryReader.cs @@ -1,6 +1,6 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -<<<<<<<< HEAD:src/Framework/BackEnd/InterningBinaryReader.cs using System; using System.Buffers; using System.Diagnostics; @@ -298,9 +298,3 @@ public override BinaryReader Create(Stream stream) } } } -======== -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. ->>>>>>>> darc/back/463e0cf-337c123:src/Shared/InterningBinaryReader.cs diff --git a/src/Framework/BackEnd/TranslatorHelpers.cs b/src/Framework/BackEnd/TranslatorHelpers.cs index 3ed04d2188b..b195d17daf1 100644 --- a/src/Framework/BackEnd/TranslatorHelpers.cs +++ b/src/Framework/BackEnd/TranslatorHelpers.cs @@ -1,6 +1,426 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file +using System; +using System.Collections.Frozen; +using System.Collections.Immutable; +using System.Collections.Generic; +using System.Configuration.Assemblies; +using System.Globalization; +using System.Reflection; +using AssemblyHashAlgorithm = System.Configuration.Assemblies.AssemblyHashAlgorithm; + +#nullable disable + +namespace Microsoft.Build.BackEnd +{ + /// + /// This class provides helper methods to adapt from to + /// . + /// + internal static class TranslatorHelpers + { + /// + /// Translates an object implementing which does not expose a + /// public parameterless constructor. + /// + /// The reference type. + /// The translator + /// The value to be translated. + /// The factory method used to instantiate values of type T. + public static void Translate( + this ITranslator translator, + ref T instance, + NodePacketValueFactory valueFactory) where T : ITranslatable + { + if (!translator.TranslateNullable(instance)) + { + return; + } + if (translator.Mode == TranslationDirection.ReadFromStream) + { + instance = valueFactory(translator); + } + else + { + instance.Translate(translator); + } + } + + private static ObjectTranslatorWithValueFactory AdaptFactory(NodePacketValueFactory valueFactory) where T : ITranslatable + { + static void TranslateUsingValueFactory(ITranslator translator, NodePacketValueFactory valueFactory, ref T objectToTranslate) + { + translator.Translate(ref objectToTranslate, valueFactory); + } + + return TranslateUsingValueFactory; + } + + public static void Translate( + this ITranslator translator, + ref List list, + NodePacketValueFactory valueFactory) where T : class, ITranslatable + { + translator.Translate(ref list, AdaptFactory(valueFactory), valueFactory); + } + + public static void Translate( + this ITranslator translator, + ref IList list, + NodePacketValueFactory valueFactory, + NodePacketCollectionCreator collectionFactory) where L : IList where T : ITranslatable + { + translator.Translate(ref list, AdaptFactory(valueFactory), valueFactory, collectionFactory); + } + + public static void TranslateArray( + this ITranslator translator, + ref T[] array, + NodePacketValueFactory valueFactory) where T : class, ITranslatable + { + translator.TranslateArray(ref array, AdaptFactory(valueFactory), valueFactory); + } + + public static void TranslateDictionary( + this ITranslator translator, + ref Dictionary dictionary, + IEqualityComparer comparer, + NodePacketValueFactory valueFactory) where T : class, ITranslatable + { + translator.TranslateDictionary(ref dictionary, comparer, AdaptFactory(valueFactory), valueFactory); + } + + public static void InternDictionary( + this ITranslator translator, + ref Dictionary dictionary, + IEqualityComparer comparer) + { + IDictionary localDict = dictionary; + translator.TranslateDictionary( + ref localDict, + (ITranslator translator, ref string key) => translator.Intern(ref key), + (ITranslator translator, ref string val) => translator.Intern(ref val), + capacity => new Dictionary(capacity, comparer)); + dictionary = (Dictionary)localDict; + } + + public static void InternDictionary( + this ITranslator translator, + ref Dictionary dictionary, + IEqualityComparer stringComparer, + NodePacketValueFactory valueFactory) + where T : ITranslatable + { + IDictionary localDict = dictionary; + translator.TranslateDictionary( + ref localDict, + (ITranslator translator, ref string key) => translator.Intern(ref key), + AdaptFactory(valueFactory), + valueFactory, + capacity => new Dictionary(capacity, stringComparer)); + dictionary = (Dictionary)localDict; + } + + public static void InternPathDictionary( + this ITranslator translator, + ref Dictionary dictionary, + IEqualityComparer comparer) + { + IDictionary localDict = dictionary; + + // For now, assume only the value contains path-like strings (e.g. TaskItem metadata). + translator.TranslateDictionary( + ref localDict, + (ITranslator translator, ref string key) => translator.Intern(ref key), + (ITranslator translator, ref string val) => translator.InternPath(ref val), + capacity => new Dictionary(capacity, comparer)); + dictionary = (Dictionary)localDict; + } + + public static void InternPathDictionary( + this ITranslator translator, + ref Dictionary dictionary, + IEqualityComparer stringComparer, + NodePacketValueFactory valueFactory) + where T : ITranslatable + { + IDictionary localDict = dictionary; + translator.TranslateDictionary( + ref localDict, + (ITranslator translator, ref string key) => translator.InternPath(ref key), + AdaptFactory(valueFactory), + valueFactory, + capacity => new Dictionary(capacity, stringComparer)); + dictionary = (Dictionary)localDict; + } + + public static void TranslateDictionary( + this ITranslator translator, + ref D dictionary, + NodePacketValueFactory valueFactory) + where D : IDictionary, new() + where T : class, ITranslatable + { + translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), valueFactory); + } + + public static void TranslateDictionary( + this ITranslator translator, + ref D dictionary, + NodePacketValueFactory valueFactory, + NodePacketCollectionCreator collectionCreator) + where D : IDictionary + where T : class, ITranslatable + { + translator.TranslateDictionary(ref dictionary, AdaptFactory(valueFactory), valueFactory, collectionCreator); + } + + public static void TranslateDictionary( + this ITranslator translator, + ref FrozenDictionary dictionary, + IEqualityComparer comparer) + { + IDictionary localDict = dictionary; + translator.TranslateDictionary(ref localDict, capacity => new Dictionary(capacity, comparer)); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + dictionary = localDict?.ToFrozenDictionary(comparer); + } + } + + public static void TranslateDictionary( + this ITranslator translator, + ref IReadOnlyDictionary dictionary, + IEqualityComparer comparer) + { + // Defensive copy since immutable dictionaries are expected to be overwritten. + IReadOnlyDictionary localDict = dictionary; + + if (!translator.TranslateNullable(localDict)) + { + return; + } + + if (translator.Mode == TranslationDirection.WriteToStream) + { + int count = localDict.Count; + translator.Translate(ref count); + + foreach (KeyValuePair kvp in localDict) + { + string key = kvp.Key; + string value = kvp.Value; + + translator.Translate(ref key); + translator.Translate(ref value); + } + } + else + { + int count = default; + translator.Translate(ref count); + + ImmutableDictionary.Builder builder = ImmutableDictionary.Create(comparer).ToBuilder(); + + for (int i = 0; i < count; i++) + { + string key = null; + string value = null; + + translator.Translate(ref key); + translator.Translate(ref value); + + builder[key] = value; + } + + dictionary = builder.ToImmutable(); + } + } + + public static void TranslateDictionary( + this ITranslator translator, + ref ImmutableDictionary dictionary, + IEqualityComparer comparer) + { + // Defensive copy since immutable dictionaries are expected to be overwritten. + IReadOnlyDictionary localDict = dictionary; + + TranslateDictionary(translator, ref localDict, comparer); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + dictionary = (ImmutableDictionary)localDict; + } + } + + public static void TranslateHashSet( + this ITranslator translator, + ref HashSet hashSet, + NodePacketValueFactory valueFactory, + NodePacketCollectionCreator> collectionFactory) where T : class, ITranslatable + { + if (!translator.TranslateNullable(hashSet)) + { + return; + } + + int count = default; + if (translator.Mode == TranslationDirection.WriteToStream) + { + count = hashSet.Count; + } + translator.Translate(ref count); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + hashSet = collectionFactory(count); + for (int i = 0; i < count; i++) + { + T value = default; + translator.Translate(ref value, valueFactory); + hashSet.Add(value); + } + } + + if (translator.Mode == TranslationDirection.WriteToStream) + { + foreach (T item in hashSet) + { + T value = item; + translator.Translate(ref value, valueFactory); + } + } + } + + public static void Translate(this ITranslator translator, ref CultureInfo cultureInfo) + { + if (!translator.TranslateNullable(cultureInfo)) + { + return; + } + + int lcid = default; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + lcid = cultureInfo.LCID; + } + + translator.Translate(ref lcid); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + cultureInfo = new CultureInfo(lcid); + } + } + + public static void Translate(this ITranslator translator, ref Version version) + { + if (!translator.TranslateNullable(version)) + { + return; + } + + int major = 0; + int minor = 0; + int build = 0; + int revision = 0; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + major = version.Major; + minor = version.Minor; + build = version.Build; + revision = version.Revision; + } + + translator.Translate(ref major); + translator.Translate(ref minor); + translator.Translate(ref build); + translator.Translate(ref revision); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + if (build < 0) + { + version = new Version(major, minor); + } + else if (revision < 0) + { + version = new Version(major, minor, build); + } + else + { + version = new Version(major, minor, build, revision); + } + } + } + + public static void Translate(this ITranslator translator, ref AssemblyName assemblyName) + { + if (!translator.TranslateNullable(assemblyName)) + { + return; + } + + string name = null; + Version version = null; + AssemblyNameFlags flags = default; + ProcessorArchitecture processorArchitecture = default; + CultureInfo cultureInfo = null; + AssemblyHashAlgorithm hashAlgorithm = default; + AssemblyVersionCompatibility versionCompatibility = default; + string codeBase = null; + + byte[] publicKey = null; + byte[] publicKeyToken = null; + + if (translator.Mode == TranslationDirection.WriteToStream) + { + name = assemblyName.Name; + version = assemblyName.Version; + flags = assemblyName.Flags; + processorArchitecture = assemblyName.ProcessorArchitecture; + cultureInfo = assemblyName.CultureInfo; + hashAlgorithm = assemblyName.HashAlgorithm; + versionCompatibility = assemblyName.VersionCompatibility; + codeBase = assemblyName.CodeBase; + + publicKey = assemblyName.GetPublicKey(); // TODO: no need to serialize, public key is not used anywhere in context of RAR, only public key token + publicKeyToken = assemblyName.GetPublicKeyToken(); + } + + translator.Translate(ref name); + translator.Translate(ref version); + translator.TranslateEnum(ref flags, (int)flags); + translator.TranslateEnum(ref processorArchitecture, (int)processorArchitecture); + translator.Translate(ref cultureInfo); + translator.TranslateEnum(ref hashAlgorithm, (int)hashAlgorithm); + translator.TranslateEnum(ref versionCompatibility, (int)versionCompatibility); + translator.Translate(ref codeBase); + + translator.Translate(ref publicKey); + translator.Translate(ref publicKeyToken); + + if (translator.Mode == TranslationDirection.ReadFromStream) + { + assemblyName = new AssemblyName + { + Name = name, + Version = version, + Flags = flags, + ProcessorArchitecture = processorArchitecture, + CultureInfo = cultureInfo, + HashAlgorithm = hashAlgorithm, + VersionCompatibility = versionCompatibility, + CodeBase = codeBase, + }; + + assemblyName.SetPublicKey(publicKey); + assemblyName.SetPublicKeyToken(publicKeyToken); + } + } + } +} diff --git a/src/Framework/BuildEnvironmentHelper.cs b/src/Framework/BuildEnvironmentHelper.cs index fe4233bb626..d8a9bfd9e2c 100644 --- a/src/Framework/BuildEnvironmentHelper.cs +++ b/src/Framework/BuildEnvironmentHelper.cs @@ -1,6 +1,6 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -<<<<<<<< HEAD:src/Framework/BuildEnvironmentHelper.cs using System; using System.Collections.Generic; using System.IO; @@ -730,9 +730,3 @@ internal string MSBuildSDKsPath internal string MSBuildExtensionsPath { get; set; } } } -======== -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. ->>>>>>>> darc/back/463e0cf-337c123:src/Shared/BuildEnvironmentHelper.cs diff --git a/src/Framework/Collections/CopyOnWriteDictionary.cs b/src/Framework/Collections/CopyOnWriteDictionary.cs index 3ed04d2188b..f11c87f5b5c 100644 --- a/src/Framework/Collections/CopyOnWriteDictionary.cs +++ b/src/Framework/Collections/CopyOnWriteDictionary.cs @@ -1,6 +1,406 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.Serialization; + +namespace Microsoft.Build.Collections +{ + /// + /// A dictionary that has copy-on-write semantics. + /// KEYS AND VALUES MUST BE IMMUTABLE OR COPY-ON-WRITE FOR THIS TO WORK. + /// + /// The value type. + /// + /// Thread safety: for all users, this class is as thread safe as the underlying Dictionary implementation, that is, + /// safe for concurrent readers or one writer from EACH user. It achieves this by locking itself and cloning before + /// any write, if it is being shared - i.e., stopping sharing before any writes occur. + /// + /// + /// This class must be serializable as it is used for metadata passed to tasks, which may + /// be run in a separate appdomain. + /// + [Serializable] + internal class CopyOnWriteDictionary : IDictionary, IDictionary, ISerializable + { + /// + /// Empty dictionary with a , + /// used as the basis of new dictionaries with that comparer to avoid + /// allocating new comparers objects. + /// + private static readonly ImmutableDictionary NameComparerDictionaryPrototype = ImmutableDictionary.Create(MSBuildNameIgnoreCaseComparer.Default); + + /// + /// Empty dictionary with , + /// used as the basis of new dictionaries with that comparer to avoid + /// allocating new comparers objects. + /// + private static readonly ImmutableDictionary OrdinalIgnoreCaseComparerDictionaryPrototype = ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase); + + /// + /// The backing dictionary. + /// Lazily created. + /// + private ImmutableDictionary _backing; + + /// + /// Constructor. Consider supplying a comparer instead. + /// + internal CopyOnWriteDictionary() + { + _backing = ImmutableDictionary.Empty; + } + + /// + /// Constructor taking a specified comparer for the keys + /// + internal CopyOnWriteDictionary(IEqualityComparer? keyComparer) + { + _backing = GetInitialDictionary(keyComparer); + } + + /// + /// Serialization constructor, for crossing appdomain boundaries + /// + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "context", Justification = "Not needed")] + protected CopyOnWriteDictionary(SerializationInfo info, StreamingContext context) + { + object v = info.GetValue(nameof(_backing), typeof(KeyValuePair[]))!; + + object comparer = info.GetValue(nameof(Comparer), typeof(IEqualityComparer))!; + + var b = GetInitialDictionary((IEqualityComparer?)comparer); + + _backing = b.AddRange((KeyValuePair[])v); + } + + private static ImmutableDictionary GetInitialDictionary(IEqualityComparer? keyComparer) + { + return keyComparer is MSBuildNameIgnoreCaseComparer + ? NameComparerDictionaryPrototype + : keyComparer == StringComparer.OrdinalIgnoreCase + ? OrdinalIgnoreCaseComparerDictionaryPrototype + : ImmutableDictionary.Create(keyComparer); + } + + /// + /// Cloning constructor. Defers the actual clone. + /// + private CopyOnWriteDictionary(CopyOnWriteDictionary that) + { + _backing = that._backing; + } + + public CopyOnWriteDictionary(IDictionary dictionary) + { + _backing = dictionary.GetType() == typeof(ImmutableDictionary) + ? (ImmutableDictionary)dictionary + : dictionary.ToImmutableDictionary(); + } + + /// + /// Returns the collection of keys in the dictionary. + /// + public ICollection Keys => ((IDictionary)_backing).Keys; + + /// + /// Returns the collection of values in the dictionary. + /// + public ICollection Values => ((IDictionary)_backing).Values; + + /// + /// Returns the number of items in the collection. + /// + public int Count => _backing.Count; + + /// + /// Returns true if the collection is read-only. + /// + public bool IsReadOnly => ((IDictionary)_backing).IsReadOnly; + + /// + /// IDictionary implementation + /// + bool IDictionary.IsFixedSize => false; + + /// + /// IDictionary implementation + /// + bool IDictionary.IsReadOnly => IsReadOnly; + + /// + /// IDictionary implementation + /// + ICollection IDictionary.Keys => (ICollection)Keys; + + /// + /// IDictionary implementation + /// + ICollection IDictionary.Values => (ICollection)Values; + + /// + /// IDictionary implementation + /// + int ICollection.Count => Count; + + /// + /// IDictionary implementation + /// + bool ICollection.IsSynchronized => false; + + /// + /// IDictionary implementation + /// + object ICollection.SyncRoot => this; + + /// + /// Comparer used for keys + /// + internal IEqualityComparer Comparer + { + get => _backing.KeyComparer; + private set => _backing = _backing.WithComparers(keyComparer: value); + } + + /// + /// The backing copy-on-write dictionary, safe to reuse. + /// + internal ImmutableDictionary BackingDictionary => _backing; + + /// + /// Accesses the value for the specified key. + /// + public V this[string key] + { + get => _backing[key]; + + set + { + _backing = _backing.SetItem(key, value); + } + } + + /// + /// IDictionary implementation + /// + object? IDictionary.this[object key] + { + get + { + TryGetValue((string)key, out V? val); + return val; + } +#nullable disable + set => this[(string)key] = (V)value; +#nullable enable + } + + /// + /// Adds a value to the dictionary. + /// + public void Add(string key, V value) + { + _backing = _backing.SetItem(key, value); + } + + /// + /// Adds several value to the dictionary. + /// + public void SetItems(IEnumerable> items) + { + _backing = _backing.SetItems(items); + } + + public IEnumerable> Where(Func, bool> predicate) + { + return _backing.Where(predicate); + } + /// + /// Returns true if the dictionary contains the specified key. + /// + public bool ContainsKey(string key) + { + return _backing.ContainsKey(key); + } + + /// + /// Removes the entry for the specified key from the dictionary. + /// + public bool Remove(string key) + { + ImmutableDictionary initial = _backing; + + _backing = _backing.Remove(key); + + return initial != _backing; // whether the removal occured + } + +#nullable disable + /// + /// Attempts to find the value for the specified key in the dictionary. + /// + public bool TryGetValue(string key, out V value) + { + return _backing.TryGetValue(key, out value); + } +#nullable restore + + /// + /// Adds an item to the collection. + /// + public void Add(KeyValuePair item) + { + _backing = _backing.SetItem(item.Key, item.Value); + } + + /// + /// Clears the collection. + /// + public void Clear() + { + _backing = _backing.Clear(); + } + + /// + /// Returns true ff the collection contains the specified item. + /// + public bool Contains(KeyValuePair item) + { + return _backing.Contains(item); + } + + /// + /// Copies all of the elements of the collection to the specified array. + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((IDictionary)_backing).CopyTo(array, arrayIndex); + } + + /// + /// Remove an item from the dictionary. + /// + public bool Remove(KeyValuePair item) + { + ImmutableDictionary initial = _backing; + + _backing = _backing.Remove(item.Key); + + return initial != _backing; // whether the removal occured + } + +#if NET472_OR_GREATER || NETCOREAPP + /// + /// Implementation of generic IEnumerable.GetEnumerator() + /// + public ImmutableDictionary.Enumerator GetEnumerator() + { + return _backing.GetEnumerator(); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + ImmutableDictionary.Enumerator enumerator = _backing.GetEnumerator(); + return _backing.GetEnumerator(); + } +#else + public IEnumerator> GetEnumerator() + { + return _backing.GetEnumerator(); + } +#endif + + /// + /// Implementation of IEnumerable.GetEnumerator() + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable>)this).GetEnumerator(); + } + +#nullable disable + /// + /// IDictionary implementation. + /// + void IDictionary.Add(object key, object value) + { + Add((string)key, (V)value); + } +#nullable enable + + /// + /// IDictionary implementation. + /// + void IDictionary.Clear() + { + Clear(); + } + + /// + /// IDictionary implementation. + /// + bool IDictionary.Contains(object key) + { + return ContainsKey((string)key); + } + + /// + /// IDictionary implementation. + /// + IDictionaryEnumerator IDictionary.GetEnumerator() + { + return ((IDictionary)_backing).GetEnumerator(); + } + + /// + /// IDictionary implementation. + /// + void IDictionary.Remove(object key) + { + Remove((string)key); + } + + /// + /// IDictionary implementation. + /// + void ICollection.CopyTo(Array array, int index) + { + int i = 0; + foreach (KeyValuePair entry in this) + { + array.SetValue(new DictionaryEntry(entry.Key, entry.Value), index + i); + i++; + } + } + + /// + /// Clone, with the actual clone deferred + /// + internal CopyOnWriteDictionary Clone() + { + return new CopyOnWriteDictionary(this); + } + + /// + /// Returns true if these dictionaries have the same backing. + /// + internal bool HasSameBacking(CopyOnWriteDictionary other) + { + return ReferenceEquals(other._backing, _backing); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + ImmutableDictionary snapshot = _backing; + KeyValuePair[] array = snapshot.ToArray(); + + info.AddValue(nameof(_backing), array); + info.AddValue(nameof(Comparer), Comparer); + } + } +} diff --git a/src/Framework/Collections/ReadOnlyEmptyDictionary.cs b/src/Framework/Collections/ReadOnlyEmptyDictionary.cs index b2b55dec503..917d6c2faa4 100644 --- a/src/Framework/Collections/ReadOnlyEmptyDictionary.cs +++ b/src/Framework/Collections/ReadOnlyEmptyDictionary.cs @@ -1,6 +1,6 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -<<<<<<<< HEAD:src/Framework/Collections/ReadOnlyEmptyDictionary.cs using System; using System.Collections; using System.Collections.Generic; @@ -295,9 +295,3 @@ public void CopyTo(System.Array array, int index) } } } -======== -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. ->>>>>>>> darc/back/463e0cf-337c123:src/Shared/ReadOnlyEmptyDictionary.cs diff --git a/src/Framework/Loader/LoadedType.cs b/src/Framework/Loader/LoadedType.cs index d1735caab58..fe240c49bb8 100644 --- a/src/Framework/Loader/LoadedType.cs +++ b/src/Framework/Loader/LoadedType.cs @@ -1,6 +1,6 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -<<<<<<<< HEAD:src/Framework/Loader/LoadedType.cs using System; using System.Reflection; using Microsoft.Build.Execution; @@ -244,9 +244,3 @@ private bool CheckForHardcodedSTARequirement() #endregion } } -======== -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. ->>>>>>>> darc/back/463e0cf-337c123:src/Shared/LoadedType.cs diff --git a/src/Framework/MultiThreadedTaskEnvironmentDriver.cs b/src/Framework/MultiThreadedTaskEnvironmentDriver.cs index 7908de6da32..da8e6bc4430 100644 --- a/src/Framework/MultiThreadedTaskEnvironmentDriver.cs +++ b/src/Framework/MultiThreadedTaskEnvironmentDriver.cs @@ -1,6 +1,6 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -<<<<<<<< HEAD:src/Framework/MultiThreadedTaskEnvironmentDriver.cs using System; using System.Collections; using System.Collections.Generic; @@ -132,9 +132,3 @@ public void Dispose() } } } -======== -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. ->>>>>>>> darc/back/463e0cf-337c123:src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs diff --git a/src/Framework/SolutionConfiguration.cs b/src/Framework/SolutionConfiguration.cs index 3ed04d2188b..22b086a8c71 100644 --- a/src/Framework/SolutionConfiguration.cs +++ b/src/Framework/SolutionConfiguration.cs @@ -1,6 +1,147 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Xml; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Shared +{ + internal sealed class SolutionConfiguration + { + public const string ProjectAttribute = "Project"; + + public const string AbsolutePathAttribute = "AbsolutePath"; + + public const string BuildProjectInSolutionAttribute = "BuildProjectInSolution"; + + public static readonly char[] ConfigPlatformSeparator = { '|' }; + + // This field stores pre-cached project elements for project guids for quicker access by project guid + private readonly Dictionary _cachedProjectElements; + + // This field stores pre-cached project elements for project guids for quicker access by project absolute path + private readonly Dictionary _cachedProjectElementsByAbsolutePath; + + // This field stores the project absolute path for quicker access by project guid + private readonly Dictionary _cachedProjectAbsolutePathsByGuid; + + // This field stores the project guid for quicker access by project absolute path + private readonly Dictionary _cachedProjectGuidsByAbsolutePath; + + // This field stores the list of dependency project guids by depending project guid + private readonly Dictionary> _cachedDependencyProjectGuidsByDependingProjectGuid; + + public SolutionConfiguration(string xmlString) + { + // Example: + // + // + // Debug|AnyCPU + // Debug|AnyCPU + // Debug|AnyCPU + // + // + + XmlNodeList? projectConfigurationElements = GetProjectConfigurations(xmlString); + int projectConfigurationCount = projectConfigurationElements?.Count ?? 0; + + _cachedProjectElements = new Dictionary(projectConfigurationCount, StringComparer.OrdinalIgnoreCase); + _cachedProjectElementsByAbsolutePath = new Dictionary(projectConfigurationCount, StringComparer.OrdinalIgnoreCase); + _cachedProjectAbsolutePathsByGuid = new Dictionary(projectConfigurationCount, StringComparer.OrdinalIgnoreCase); + _cachedProjectGuidsByAbsolutePath = new Dictionary(projectConfigurationCount, StringComparer.OrdinalIgnoreCase); + _cachedDependencyProjectGuidsByDependingProjectGuid = new Dictionary>(projectConfigurationCount, StringComparer.OrdinalIgnoreCase); + + if (projectConfigurationElements != null) + { + foreach (XmlElement xmlElement in projectConfigurationElements) + { + string projectGuid = xmlElement.GetAttribute(ProjectAttribute); + string projectAbsolutePath = xmlElement.GetAttribute(AbsolutePathAttribute); + + // What we really want here is the normalized path, like we'd get with an item's "FullPath" metadata. However, + // if there's some bogus full path in the solution configuration (e.g. a website with a "full path" of c:\solutiondirectory\http://localhost) + // we do NOT want to throw -- chances are extremely high that that's information that will never actually be used. So resolve the full path + // but just swallow any IO-related exceptions that result. If the path is bogus, the method will return null, so we'll just quietly fail + // to cache it below. + projectAbsolutePath = FileUtilities.GetFullPathNoThrow(projectAbsolutePath); + + if (!string.IsNullOrEmpty(projectGuid)) + { + _cachedProjectElements[projectGuid] = xmlElement; + if (!string.IsNullOrEmpty(projectAbsolutePath)) + { + _cachedProjectElementsByAbsolutePath[projectAbsolutePath] = xmlElement; + _cachedProjectAbsolutePathsByGuid[projectGuid] = projectAbsolutePath; + _cachedProjectGuidsByAbsolutePath[projectAbsolutePath] = projectGuid; + } + + foreach (XmlNode dependencyNode in xmlElement.ChildNodes) + { + if (dependencyNode.NodeType != XmlNodeType.Element) + { + continue; + } + + XmlElement dependencyElement = ((XmlElement)dependencyNode); + + if (!String.Equals(dependencyElement.Name, "ProjectDependency", StringComparison.Ordinal)) + { + continue; + } + + string dependencyGuid = dependencyElement.GetAttribute("Project"); + + if (dependencyGuid.Length == 0) + { + continue; + } + + if (!_cachedDependencyProjectGuidsByDependingProjectGuid.TryGetValue(projectGuid, out List? list)) + { + list = new List(); + _cachedDependencyProjectGuidsByDependingProjectGuid.Add(projectGuid, list); + } + + list.Add(dependencyGuid); + } + } + } + } + } + + public static SolutionConfiguration Empty { get; } = new SolutionConfiguration(string.Empty); + + public ICollection ProjectConfigurations => _cachedProjectElements.Values; + + public static XmlNodeList? GetProjectConfigurations(string xmlString) + { + XmlDocument? doc = null; + + if (!string.IsNullOrEmpty(xmlString)) + { + doc = new XmlDocument(); + var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }; + using (XmlReader reader = XmlReader.Create(new StringReader(xmlString), settings)) + { + doc.Load(reader); + } + } + + return doc?.DocumentElement?.ChildNodes; + } + + public bool TryGetProjectByGuid(string projectGuid, [NotNullWhen(true)] out XmlElement? projectElement) => _cachedProjectElements.TryGetValue(projectGuid, out projectElement); + + public bool TryGetProjectByAbsolutePath(string projectFullPath, [NotNullWhen(true)] out XmlElement? projectElement) => _cachedProjectElementsByAbsolutePath.TryGetValue(projectFullPath, out projectElement); + + public bool TryGetProjectGuidByAbsolutePath(string projectFullPath, [NotNullWhen(true)] out string? projectGuid) => _cachedProjectGuidsByAbsolutePath.TryGetValue(projectFullPath, out projectGuid); + + public bool TryGetProjectDependencies(string projectGuid, [NotNullWhen(true)] out List? dependencyProjectGuids) => _cachedDependencyProjectGuidsByDependingProjectGuid.TryGetValue(projectGuid, out dependencyProjectGuids); + + public bool TryGetProjectPathByGuid(string projectGuid, [NotNullWhen(true)] out string? projectPath) => _cachedProjectAbsolutePathsByGuid.TryGetValue(projectGuid, out projectPath); + } +} diff --git a/src/Framework/Utilities/AssemblyLoadInfo.cs b/src/Framework/Utilities/AssemblyLoadInfo.cs index e5cf6cbed81..d347119e4b7 100644 --- a/src/Framework/Utilities/AssemblyLoadInfo.cs +++ b/src/Framework/Utilities/AssemblyLoadInfo.cs @@ -1,6 +1,6 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -<<<<<<<< HEAD:src/Framework/Utilities/AssemblyLoadInfo.cs using System; using System.Diagnostics; using System.IO; @@ -234,9 +234,3 @@ internal override bool IsInlineTask } } } -======== -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. ->>>>>>>> darc/back/463e0cf-337c123:src/Shared/AssemblyLoadInfo.cs diff --git a/src/Framework/Utilities/CollectionHelpers.cs b/src/Framework/Utilities/CollectionHelpers.cs index 3ed04d2188b..624502e3196 100644 --- a/src/Framework/Utilities/CollectionHelpers.cs +++ b/src/Framework/Utilities/CollectionHelpers.cs @@ -1,6 +1,80 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + /// + /// Utilities for collections + /// + internal static class CollectionHelpers + { + /// + /// Returns a new list containing the input list + /// contents, except for nulls + /// + /// Type of list elements + internal static List RemoveNulls(List inputs) + { + List inputsWithoutNulls = new List(inputs.Count); + + foreach (T entry in inputs) + { + if (entry != null) + { + inputsWithoutNulls.Add(entry); + } + } + + // Avoid possibly having two identical lists floating around + return (inputsWithoutNulls.Count == inputs.Count) ? inputs : inputsWithoutNulls; + } + + /// + /// Extension method -- combines a TryGet with a check to see that the value is equal. + /// + internal static bool ContainsValueAndIsEqual(this Dictionary dictionary, string key, string value, StringComparison comparer) + { + string valueFromDictionary; + if (dictionary.TryGetValue(key, out valueFromDictionary)) + { + return String.Equals(value, valueFromDictionary, comparer); + } + + return false; + } + + internal static bool SetEquivalent(IEnumerable a, IEnumerable b) + { + return a.ToHashSet().SetEquals(b); + } + + internal static bool DictionaryEquals(IReadOnlyDictionary a, IReadOnlyDictionary b) + { + if (a.Count != b.Count) + { + return false; + } + + foreach (var aKvp in a) + { + if (!b.TryGetValue(aKvp.Key, out var bValue)) + { + return false; + } + + if (!Equals(aKvp.Value, bValue)) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Framework/Utilities/FileMatcher.cs b/src/Framework/Utilities/FileMatcher.cs index e2bea23d34e..b6411ec342e 100644 --- a/src/Framework/Utilities/FileMatcher.cs +++ b/src/Framework/Utilities/FileMatcher.cs @@ -1,6 +1,6 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -<<<<<<<< HEAD:src/Framework/Utilities/FileMatcher.cs using System; using System.Buffers; using System.Collections.Concurrent; @@ -2700,9 +2700,3 @@ private static bool DirectoryEndsWithPattern(string directoryPath, string patter internal static bool IsRecursiveDirectoryMatch(string path) => path.TrimTrailingSlashes() == recursiveDirectoryMatch; } } -======== -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. ->>>>>>>> darc/back/463e0cf-337c123:src/Shared/FileMatcher.cs diff --git a/src/Framework/Utilities/FrameworkLocationHelper.cs b/src/Framework/Utilities/FrameworkLocationHelper.cs index c121ae59385..1f6d047d4c9 100644 --- a/src/Framework/Utilities/FrameworkLocationHelper.cs +++ b/src/Framework/Utilities/FrameworkLocationHelper.cs @@ -1,6 +1,6 @@ -PLEASE READ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -<<<<<<<< HEAD:src/Framework/Utilities/FrameworkLocationHelper.cs using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -1791,9 +1791,3 @@ public override string GetPathToDotNetFrameworkReferenceAssemblies() } } } -======== -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. ->>>>>>>> darc/back/463e0cf-337c123:src/Shared/FrameworkLocationHelper.cs diff --git a/src/Shared/BinaryReaderExtensions.cs b/src/Shared/BinaryReaderExtensions.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/BinaryReaderExtensions.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/BinaryWriterExtensions.cs b/src/Shared/BinaryWriterExtensions.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/BinaryWriterExtensions.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/Constants.cs b/src/Shared/Constants.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/Constants.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/EnvironmentUtilities.cs b/src/Shared/EnvironmentUtilities.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/EnvironmentUtilities.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/EscapingUtilities.cs b/src/Shared/EscapingUtilities.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/EscapingUtilities.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/ExceptionHandling.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/CachingFileSystemWrapper.cs b/src/Shared/FileSystem/CachingFileSystemWrapper.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileSystem/CachingFileSystemWrapper.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/FileSystems.cs b/src/Shared/FileSystem/FileSystems.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileSystem/FileSystems.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/IFileSystem.cs b/src/Shared/FileSystem/IFileSystem.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileSystem/IFileSystem.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs b/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileSystem/MSBuildOnWindowsFileSystem.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/ManagedFileSystem.cs b/src/Shared/FileSystem/ManagedFileSystem.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileSystem/ManagedFileSystem.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/NativeWin32Exception.cs b/src/Shared/FileSystem/NativeWin32Exception.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileSystem/NativeWin32Exception.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/SafeFileHandle.cs b/src/Shared/FileSystem/SafeFileHandle.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileSystem/SafeFileHandle.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/WindowsFileSystem.cs b/src/Shared/FileSystem/WindowsFileSystem.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileSystem/WindowsFileSystem.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystem/WindowsNative.cs b/src/Shared/FileSystem/WindowsNative.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileSystem/WindowsNative.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileSystemSources.proj b/src/Shared/FileSystemSources.proj deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileSystemSources.proj +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileUtilities.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/FileUtilitiesRegex.cs b/src/Shared/FileUtilitiesRegex.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/FileUtilitiesRegex.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/IMSBuildElementLocation.cs b/src/Shared/IMSBuildElementLocation.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/IMSBuildElementLocation.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/Modifiers.cs b/src/Shared/Modifiers.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/Modifiers.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.cs.xlf b/src/Shared/Resources/xlf/Strings.shared.cs.xlf index 849c8056d00..7c27da24ffb 100644 --- a/src/Shared/Resources/xlf/Strings.shared.cs.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.cs.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.de.xlf b/src/Shared/Resources/xlf/Strings.shared.de.xlf index d2f313d6ad7..96e44a116e6 100644 --- a/src/Shared/Resources/xlf/Strings.shared.de.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.de.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.es.xlf b/src/Shared/Resources/xlf/Strings.shared.es.xlf index a4bc963bdbc..28992cf81d7 100644 --- a/src/Shared/Resources/xlf/Strings.shared.es.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.es.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.fr.xlf b/src/Shared/Resources/xlf/Strings.shared.fr.xlf index c300e1b5251..c33ef71d145 100644 --- a/src/Shared/Resources/xlf/Strings.shared.fr.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.fr.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.it.xlf b/src/Shared/Resources/xlf/Strings.shared.it.xlf index 11d616a31a1..13d9fc42900 100644 --- a/src/Shared/Resources/xlf/Strings.shared.it.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.it.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.ja.xlf b/src/Shared/Resources/xlf/Strings.shared.ja.xlf index f07f4185a32..bf5724129aa 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ja.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ja.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.ko.xlf b/src/Shared/Resources/xlf/Strings.shared.ko.xlf index d8eef7e2479..f36fecb0d5d 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ko.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ko.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.pl.xlf b/src/Shared/Resources/xlf/Strings.shared.pl.xlf index 61c8747b748..e0cf71f3188 100644 --- a/src/Shared/Resources/xlf/Strings.shared.pl.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.pl.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf b/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf index e8ea01505ff..01ca48b9f29 100644 --- a/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.pt-BR.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.ru.xlf b/src/Shared/Resources/xlf/Strings.shared.ru.xlf index 2bfb3efe06c..d0f2c1e081f 100644 --- a/src/Shared/Resources/xlf/Strings.shared.ru.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.ru.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.tr.xlf b/src/Shared/Resources/xlf/Strings.shared.tr.xlf index 510fd93fbe2..359306dbdc8 100644 --- a/src/Shared/Resources/xlf/Strings.shared.tr.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.tr.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf b/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf index d3e0d78a845..6b1f3ae581e 100644 --- a/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.zh-Hans.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf b/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf index 70777704d7a..518e09fdb4c 100644 --- a/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf +++ b/src/Shared/Resources/xlf/Strings.shared.zh-Hant.xlf @@ -341,4 +341,4 @@ - + \ No newline at end of file diff --git a/src/Shared/TempFileUtilities.cs b/src/Shared/TempFileUtilities.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/TempFileUtilities.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/UnitTests/EscapingUtilities_Tests.cs b/src/Shared/UnitTests/EscapingUtilities_Tests.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/UnitTests/EscapingUtilities_Tests.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file diff --git a/src/Shared/UnitTests/FileUtilities_Tests.cs b/src/Shared/UnitTests/FileUtilities_Tests.cs deleted file mode 100644 index 3ed04d2188b..00000000000 --- a/src/Shared/UnitTests/FileUtilities_Tests.cs +++ /dev/null @@ -1,6 +0,0 @@ -PLEASE READ - -Please remove this file during conflict resolution in your PR. -This file has been reverted (removed) in the source repository but the PR branch -does not have the file yet as it's based on an older commit. This means the file is -not getting removed in the PR due to the other conflicts. \ No newline at end of file From 15003c890be3047a9528323685bc7981b459f312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Prokop?= Date: Mon, 20 Jul 2026 10:21:51 +0200 Subject: [PATCH 4/4] Bump VersionPrefix to 18.6.14 for servicing branch vs18.6 is a servicing branch; the CI "Check Version Bump On Release Branches" gate requires the VersionPrefix revision to be greater than the target branch. Base is at 18.6.13, so bump to 18.6.14. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1f81c60b-d12b-42df-bae1-af82864104f5 --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 7fc5b9b9da9..ee21a626c48 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -3,7 +3,7 @@ - 18.6.13release + 18.6.14release servicing 18.5.0-preview-26126-01 15.1.0.0