Make polling use the symbolic link target's LastWriteTime (#55664)
authorDavid Cantú <dacantu@microsoft.com>
Thu, 22 Jul 2021 18:11:49 +0000 (11:11 -0700)
committerGitHub <noreply@github.com>
Thu, 22 Jul 2021 18:11:49 +0000 (11:11 -0700)
* Relax LinkTarget so it always returns null when steps on an error

* Make polling use the symbolic link target's LastWriteTime

* Fix for the case where the link can change its target

* Add more test cases and exclude them from non-netcoreapp tfms

* Fix project references in ref projects

* Do not use UnsupportedOSPlatforms on test project in order to fix CI issue

* Do not return link's LastWriteTime when target not exists

* Address feedback on tests and improve them to cover more scenarios.

* Make the project unsupported in browser.

* Fix duplicate reference to PlatformAttributes with IncludePlatformAttributes=false

* Disable default items for browser

* Undo unrelated changes to Strings.resx

* Replace Thread.Sleep with Task.Delay, add assertion messages to try to debug CI failures and increase delay between writes

* Replace HasChanged for RegisterChangeCallback in tests

* Add messages to asserts to attempt to debug CI issues

* Add date format to assertion messages.

* Increase delay between writes to one second since OSX doesn't report milliseconds

14 files changed:
src/libraries/Microsoft.Extensions.FileProviders.Abstractions/ref/Microsoft.Extensions.FileProviders.Abstractions.csproj
src/libraries/Microsoft.Extensions.FileProviders.Abstractions/src/Microsoft.Extensions.FileProviders.Abstractions.csproj
src/libraries/Microsoft.Extensions.FileProviders.Physical/Directory.Build.props
src/libraries/Microsoft.Extensions.FileProviders.Physical/ref/Microsoft.Extensions.FileProviders.Physical.csproj
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/FileSystemInfoHelper.cs
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Microsoft.Extensions.FileProviders.Physical.csproj
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingFileChangeToken.cs
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/PollingWildCardChangeToken.cs
src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Resources/Strings.resx
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/Microsoft.Extensions.FileProviders.Physical.Tests.csproj
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.cs
src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.netcoreapp.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.FileSystemGlobbing/ref/Microsoft.Extensions.FileSystemGlobbing.csproj
src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Microsoft.Extensions.FileSystemGlobbing.csproj

index 3223979..bd02f83 100644 (file)
@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
+    <TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;net461</TargetFrameworks>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="Microsoft.Extensions.FileProviders.Abstractions.cs" />
@@ -8,4 +8,7 @@
   <ItemGroup>
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Primitives\ref\Microsoft.Extensions.Primitives.csproj" />
   </ItemGroup>
+  <ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
+    <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime\ref\System.Runtime.csproj" />
+  </ItemGroup>
 </Project>
index 43164c7..07289f5 100644 (file)
@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <RootNamespace>Microsoft.Extensions.FileProviders</RootNamespace>
-    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
+    <TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;net461</TargetFrameworks>
     <EnableDefaultItems>true</EnableDefaultItems>
     <PackageDescription>Abstractions of files and directories.
 
@@ -21,4 +21,9 @@ Microsoft.Extensions.FileProviders.IFileProvider</PackageDescription>
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Primitives\src\Microsoft.Extensions.Primitives.csproj" />
   </ItemGroup>
 
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
+    <Reference Include="System.Runtime" />
+    <Reference Include="System.Linq" />
+    <Reference Include="System.Linq.Expressions" />
+  </ItemGroup>
 </Project>
index 668e395..8ec207d 100644 (file)
@@ -2,5 +2,6 @@
   <Import Project="..\Directory.Build.props" />
   <PropertyGroup>
     <IsAspNetCoreApp>true</IsAspNetCoreApp>
+    <UnsupportedOSPlatforms>browser</UnsupportedOSPlatforms>
   </PropertyGroup>
 </Project>
\ No newline at end of file
index cb4948b..0f7dc64 100644 (file)
@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
+    <TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;net461</TargetFrameworks>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="Microsoft.Extensions.FileProviders.Physical.cs" />
@@ -10,4 +10,7 @@
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.FileSystemGlobbing\ref\Microsoft.Extensions.FileSystemGlobbing.csproj" />
     <ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Primitives\ref\Microsoft.Extensions.Primitives.csproj" />
   </ItemGroup>
+  <ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
+    <ProjectReference Include="$(LibrariesProjectRoot)System.IO.FileSystem.Watcher\ref\System.IO.FileSystem.Watcher.csproj" />
+  </ItemGroup>
 </Project>
index 067a154..5ae854a 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Diagnostics;
 using System.IO;
 
 namespace Microsoft.Extensions.FileProviders.Physical
@@ -27,5 +28,40 @@ namespace Microsoft.Extensions.FileProviders.Physical
 
             return false;
         }
+
+        public static DateTime? GetFileLinkTargetLastWriteTimeUtc(string filePath)
+        {
+#if NETCOREAPP
+            var fileInfo = new FileInfo(filePath);
+            if (fileInfo.Exists)
+            {
+                return GetFileLinkTargetLastWriteTimeUtc(fileInfo);
+            }
+#endif
+            return null;
+        }
+
+        // If file is a link and link target exists, return target's LastWriteTimeUtc.
+        // If file is a link, and link target does not exists, return DateTime.MinValue
+        //   since the link's LastWriteTimeUtc doesn't convey anything for this scenario.
+        // If file is not a link, return null to inform the caller that file is not a link.
+        public static DateTime? GetFileLinkTargetLastWriteTimeUtc(FileInfo fileInfo)
+        {
+#if NETCOREAPP
+            Debug.Assert(fileInfo.Exists);
+            if (fileInfo.LinkTarget != null)
+            {
+                FileSystemInfo targetInfo = fileInfo.ResolveLinkTarget(returnFinalTarget: true);
+                if (targetInfo.Exists)
+                {
+                    return targetInfo.LastWriteTimeUtc;
+                }
+
+                return DateTime.MinValue;
+            }
+#endif
+
+            return null;
+        }
     }
 }
index 9801bac..534a08b 100644 (file)
@@ -1,12 +1,17 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <RootNamespace>Microsoft.Extensions.FileProviders</RootNamespace>
-    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
+    <TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppCurrent)-Browser;netstandard2.0;net461</TargetFrameworks>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <EnableDefaultItems>true</EnableDefaultItems>
     <PackageDescription>File provider for physical files for Microsoft.Extensions.FileProviders.</PackageDescription>
   </PropertyGroup>
+  
+  <PropertyGroup Condition="'$(TargetsBrowser)' == 'true'">
+    <EnableDefaultItems>false</EnableDefaultItems>
+    <GeneratePlatformNotSupportedAssemblyMessage>SR.FileProvidersPhysical_PlatformNotSupported</GeneratePlatformNotSupportedAssemblyMessage>
+  </PropertyGroup>
 
   <ItemGroup>
     <Compile Include="$(CommonPath)Extensions\EmptyDisposable.cs"
     <PackageReference Include="System.Security.Cryptography.Algorithms" Version="$(SystemSecurityCryptographyAlgorithmsVersion)" />
   </ItemGroup>
 
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
+    <Reference Include="System.Collections.Concurrent" />
+    <Reference Include="System.ComponentModel.Primitives" />
+    <Reference Include="System.IO.FileSystem.Watcher" />
+    <Reference Include="System.Linq" />
+    <Reference Include="System.Linq.Expressions" />
+    <Reference Include="System.Runtime" />
+    <Reference Include="System.Security.Cryptography.Algorithms" />
+    <Reference Include="System.Security.Cryptography.Primitives" />
+    <Reference Include="System.Threading" />
+  </ItemGroup>
 </Project>
index 23d8c8d..ddf2ae0 100644 (file)
@@ -48,7 +48,13 @@ namespace Microsoft.Extensions.FileProviders.Physical
         private DateTime GetLastWriteTimeUtc()
         {
             _fileInfo.Refresh();
-            return _fileInfo.Exists ? _fileInfo.LastWriteTimeUtc : DateTime.MinValue;
+
+            if (!_fileInfo.Exists)
+            {
+                return DateTime.MinValue;
+            }
+
+            return FileSystemInfoHelper.GetFileLinkTargetLastWriteTimeUtc(_fileInfo) ?? _fileInfo.LastWriteTimeUtc;
         }
 
         /// <summary>
index 998aaa2..28148d2 100644 (file)
@@ -143,7 +143,8 @@ namespace Microsoft.Extensions.FileProviders.Physical
         /// <returns>The <see cref="DateTime"/> that the file was last modified.</returns>
         protected virtual DateTime GetLastWriteUtc(string path)
         {
-            return File.GetLastWriteTimeUtc(Path.Combine(_directoryInfo.FullName, path));
+            string filePath = Path.Combine(_directoryInfo.FullName, path);
+            return FileSystemInfoHelper.GetFileLinkTargetLastWriteTimeUtc(filePath) ?? File.GetLastWriteTimeUtc(filePath);
         }
 
         private static bool ArrayEquals(byte[] previousHash, byte[] currentHash)
index 4d234cc..2ff2106 100644 (file)
   <data name="UnexpectedFileSystemInfo" xml:space="preserve">
     <value>Unexpected type of FileSystemInfo</value>
   </data>
+  <data name="FileProvidersPhysical_PlatformNotSupported" xml:space="preserve">
+    <value>Microsoft.Extensions.FileProviders.Physical is not supported on this platform.</value>
+  </data>
 </root>
\ No newline at end of file
index d57fed0..6b4ab37 100644 (file)
@@ -1,10 +1,11 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <RootNamespace>Microsoft.Extensions.FileProviders.Physical</RootNamespace>
     <TargetFrameworks>$(NetCoreAppCurrent);net461</TargetFrameworks>
     <EnableDefaultItems>true</EnableDefaultItems>
     <IgnoreForCI Condition="'$(TargetOS)' == 'Browser'">true</IgnoreForCI>
+    <IncludePlatformAttributes>false</IncludePlatformAttributes>
   </PropertyGroup>
 
   <ItemGroup>
              Link="Common\System\Threading\Tasks\TaskTimeoutExtensions.cs" />
   </ItemGroup>
 
+  <ItemGroup Condition="'$(TargetFramework)' != '$(NetCoreAppCurrent)'">
+    <Compile Remove="PhysicalFileProviderTests.netcoreapp.cs" />
+  </ItemGroup>
+
   <ItemGroup>
     <PackageReference Include="Moq" Version="$(MoqVersion)" />
     <ProjectReference Include="..\src\Microsoft.Extensions.FileProviders.Physical.csproj" SkipUseReferenceAssembly="true" />
index 5bb882e..b4e37df 100644 (file)
@@ -15,7 +15,7 @@ using Xunit;
 
 namespace Microsoft.Extensions.FileProviders
 {
-    public class PhysicalFileProviderTests
+    public partial class PhysicalFileProviderTests
     {
         private const int WaitTimeForTokenToFire = 500;
         private const int WaitTimeForTokenCallback = 10000;
@@ -1512,6 +1512,58 @@ namespace Microsoft.Extensions.FileProviders
             }
         }
 
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
+        public async Task UsePollingFileWatcher_UseActivePolling_HasChanged(bool useWildcard)
+        {
+            // Arrange
+            using var root = new DisposableFileSystem();
+            string fileName = Path.GetRandomFileName();
+            string filePath = Path.Combine(root.RootPath, fileName);
+            File.WriteAllText(filePath, "v1.1");
+
+            using var provider = new PhysicalFileProvider(root.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
+            IChangeToken token = provider.Watch(useWildcard ? "*" : fileName);
+
+            var tcs = new TaskCompletionSource<object>();
+            token.RegisterChangeCallback(_ => { tcs.TrySetResult(null); }, null);
+
+            // Act
+            await Task.Delay(1000); // Wait a second before writing again, see https://github.com/dotnet/runtime/issues/55951.
+            File.WriteAllText(filePath, "v1.2");
+
+            // Assert
+            Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
+                $"Change event was not raised - current time: {DateTime.UtcNow:O}, file LastWriteTimeUtc: {File.GetLastWriteTimeUtc(filePath):O}");
+        }
+
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
+        public void UsePollingFileWatcher_UseActivePolling_HasChanged_FileDeleted(bool useWildcard)
+        {
+            // Arrange
+            using var root = new DisposableFileSystem();
+            string fileName = Path.GetRandomFileName();
+            string filePath = Path.Combine(root.RootPath, fileName);
+            File.WriteAllText(filePath, "v1.1");
+
+            string filter = useWildcard ? "*" : fileName;
+            using var provider = new PhysicalFileProvider(root.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
+            IChangeToken token = provider.Watch(filter);
+
+            var tcs = new TaskCompletionSource<object>();
+            token.RegisterChangeCallback(_ => { tcs.TrySetResult(null); }, null);
+
+            // Act
+            File.Delete(filePath);
+
+            // Assert
+            Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
+                $"Change event was not raised - current time: {DateTime.UtcNow:O}, file Exists: {File.Exists(filePath)}.");
+        }
+
         [Fact]
         public void CreateFileWatcher_CreatesWatcherWithPollingAndActiveFlags()
         {
diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.netcoreapp.cs b/src/libraries/Microsoft.Extensions.FileProviders.Physical/tests/PhysicalFileProviderTests.netcoreapp.cs
new file mode 100644 (file)
index 0000000..d8b332b
--- /dev/null
@@ -0,0 +1,139 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.Extensions.FileProviders
+{
+    public partial class PhysicalFileProviderTests
+    {
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
+        public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink(bool useWildcard)
+        {
+            // Arrange
+            using var rootOfFile = new DisposableFileSystem();
+            string filePath = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());
+            File.WriteAllText(filePath, "v1.1");
+
+            using var rootOfLink = new DisposableFileSystem();
+            string linkName = Path.GetRandomFileName();
+            string linkPath = Path.Combine(rootOfLink.RootPath, linkName);
+            File.CreateSymbolicLink(linkPath, filePath);
+
+            using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
+            IChangeToken token = provider.Watch(useWildcard ? "*" : linkName);
+
+            var tcs = new TaskCompletionSource();
+            token.RegisterChangeCallback(_ => { tcs.TrySetResult(); }, null);
+
+            // Act
+            await Task.Delay(1000); // Wait a second before writing again, see https://github.com/dotnet/runtime/issues/55951.
+            File.WriteAllText(filePath, "v1.2");
+
+            // Assert
+            Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
+                $"Change event was not raised - current time: {DateTime.UtcNow:O}, file LastWriteTimeUtc: {File.GetLastWriteTimeUtc(filePath):O}.");
+        }
+
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
+        public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetNotExists(bool useWildcard)
+        {
+            // Arrange
+            using var rootOfLink = new DisposableFileSystem();
+            string linkName = Path.GetRandomFileName();
+            string linkPath = Path.Combine(rootOfLink.RootPath, linkName);
+            File.CreateSymbolicLink(linkPath, "not-existent-file");
+
+            // Act
+            using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
+            IChangeToken token = provider.Watch(useWildcard ? "*" : linkName);
+
+            var tcs = new TaskCompletionSource();
+            token.RegisterChangeCallback(_ => { tcs.TrySetResult(); }, null);
+
+            // Assert
+            Assert.False(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
+                "Change event was raised when it was not expected.");
+        }
+
+        [Theory]
+        [InlineData(false, false)]
+        [InlineData(false, true)]
+        [InlineData(true, false)]
+        [InlineData(true, true)]
+        public async Task UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetChanged(bool useWildcard, bool linkWasBroken)
+        {
+            // Arrange
+            using var rootOfFile = new DisposableFileSystem();
+            // Create file 2 first as we want to verify that the change is reported regardless of the timestamp being older.
+            string file2Path = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());
+            File.WriteAllText(file2Path, "v2.1");
+
+            string file1Path = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());
+            if (!linkWasBroken)
+            {
+                await Task.Delay(1000); // Wait a second before writing again, see https://github.com/dotnet/runtime/issues/55951.
+                File.WriteAllText(file1Path, "v1.1");
+            }
+
+            using var rootOfLink = new DisposableFileSystem();
+            string linkName = Path.GetRandomFileName();
+            string linkPath = Path.Combine(rootOfLink.RootPath, linkName);
+            File.CreateSymbolicLink(linkPath, file1Path);
+
+            string filter = useWildcard ? "*" : linkName;
+            using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
+            IChangeToken token = provider.Watch(filter);
+
+            var tcs = new TaskCompletionSource();
+            token.RegisterChangeCallback(_ => { tcs.TrySetResult(); }, null);
+
+            // Act - Change link target to file 2.
+            File.Delete(linkPath);
+            File.CreateSymbolicLink(linkPath, file2Path);
+
+            // Assert - It should report the change regardless of the timestamp being older.
+            Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
+                $"Change event was not raised - current time: {DateTime.UtcNow:O}, file1 LastWriteTimeUtc: {File.GetLastWriteTimeUtc(file1Path):O}, file2 LastWriteTime: {File.GetLastWriteTimeUtc(file2Path):O}.");
+        }
+
+        [Theory]
+        [InlineData(false)]
+        [InlineData(true)]
+        public void UsePollingFileWatcher_UseActivePolling_HasChanged_SymbolicLink_TargetDeleted(bool useWildcard)
+        {
+            // Arrange
+            using var rootOfFile = new DisposableFileSystem();
+
+            string filePath = Path.Combine(rootOfFile.RootPath, Path.GetRandomFileName());
+            File.WriteAllText(filePath, "v1.1");
+
+            using var rootOfLink = new DisposableFileSystem();
+            string linkName = Path.GetRandomFileName();
+            string linkPath = Path.Combine(rootOfLink.RootPath, linkName);
+            File.CreateSymbolicLink(linkPath, filePath);
+
+            string filter = useWildcard ? "*" : linkName;
+            using var provider = new PhysicalFileProvider(rootOfLink.RootPath) { UsePollingFileWatcher = true, UseActivePolling = true };
+            IChangeToken token = provider.Watch(filter);
+
+            var tcs = new TaskCompletionSource();
+            token.RegisterChangeCallback(_ => { tcs.TrySetResult(); }, null);
+
+            // Act
+            File.Delete(linkPath);
+
+            // Assert
+            Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(30)),
+                $"Change event was not raised - current time: {DateTime.UtcNow:O}, file LastWriteTimeUtc: {File.GetLastWriteTimeUtc(filePath):O}.");
+        }
+    }
+}
index b939996..ade2783 100644 (file)
@@ -1,8 +1,12 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
+    <TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;net461</TargetFrameworks>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="Microsoft.Extensions.FileSystemGlobbing.cs" />
   </ItemGroup>
+  <ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)'">
+    <ProjectReference Include="$(LibrariesProjectRoot)System.Collections\ref\System.Collections.csproj" />
+    <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime\ref\System.Runtime.csproj" />
+  </ItemGroup>
 </Project>
index 3b3239f..2b6f76b 100644 (file)
@@ -1,7 +1,7 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
+    <TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;net461</TargetFrameworks>
     <EnableDefaultItems>true</EnableDefaultItems>
     <PackageDescription>File system globbing to find files matching a specified pattern.</PackageDescription>
   </PropertyGroup>
     <Compile Include="$(CoreLibSharedDir)\System\Numerics\Hashing\HashHelpers.cs"
              Link="System\Numerics\Hashing\HashHelpers.cs" />
   </ItemGroup>
-
+  
+  <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
+    <Reference Include="System.Collections" />
+    <Reference Include="System.Linq" />
+    <Reference Include="System.Runtime" />
+  </ItemGroup>
 </Project>