diff --git a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks.UnitTests/GivenAGetDependsOnNETStandardTask.cs b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks.UnitTests/GivenAGetDependsOnNETStandardTask.cs index 893c0634cce8..cae332c597c1 100644 --- a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks.UnitTests/GivenAGetDependsOnNETStandardTask.cs +++ b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks.UnitTests/GivenAGetDependsOnNETStandardTask.cs @@ -28,8 +28,8 @@ public void CanCheckThisAssembly() }; task.Execute().Should().BeTrue(); - // this test happens to compile against System.Runtime, update if the test TFM changes to netstandard2.x - task.DependsOnNETStandard.Should().BeFalse(); + // this test compiles against a sufficiently high System.Runtime + task.DependsOnNETStandard.Should().BeTrue(); } [Fact] @@ -51,8 +51,8 @@ public void CanCheckThisAssemblyByHintPath() }; task.Execute().Should().BeTrue(); - // this test happens to compile against System.Runtime, update if the test TFM changes to netstandard2.x - task.DependsOnNETStandard.Should().BeFalse(); + // this test compiles against a sufficiently high System.Runtime + task.DependsOnNETStandard.Should().BeTrue(); } diff --git a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.cs b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.cs index 8d45c63c8b84..3e7b5908c6bd 100644 --- a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.cs +++ b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.cs @@ -14,6 +14,14 @@ namespace Microsoft.NET.Build.Tasks public partial class GetDependsOnNETStandard : TaskBase { private const string NetStandardAssemblyName = "netstandard"; + + // System.Runtime from netstandard1.5 + // We also treat this as depending on netstandard so that we can provide netstandard1.5 and netstandard1.6 compatible + // facades since net461 was previously only compatible with netstandard1.4 and thus packages only provided netstandard1.4 + // compatible facades. + private const string SystemRuntimeAssemblyName = "System.Runtime"; + private static readonly Version SystemRuntimeMinVersion = new Version(4, 1, 0, 0); + /// /// Set of reference items to analyze. /// diff --git a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.net46.cs b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.net46.cs index 513988b9f0a2..cec0cfb6163a 100644 --- a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.net46.cs +++ b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.net46.cs @@ -48,6 +48,17 @@ internal static bool GetFileDependsOnNETStandard(string filePath) var asmRefEnum = IntPtr.Zero; var asmRefTokens = new UInt32[16]; UInt32 fetched; + + var assemblyMD = new ASSEMBLYMETADATA() + { + rpLocale = IntPtr.Zero, + cchLocale = 0, + rpProcessors = IntPtr.Zero, + cProcessors = 0, + rOses = IntPtr.Zero, + cOses = 0 + }; + // Ensure the enum handle is closed. try { @@ -72,7 +83,7 @@ internal static bool GetFileDependsOnNETStandard(string filePath) null, 0, out asmNameLength, - IntPtr.Zero, + ref assemblyMD, out hashDataPtr, out hashDataLength, out flags); @@ -88,7 +99,7 @@ internal static bool GetFileDependsOnNETStandard(string filePath) assemblyNameBuffer, (uint)assemblyNameBuffer.Capacity, out asmNameLength, - IntPtr.Zero, + ref assemblyMD, out hashDataPtr, out hashDataLength, out flags); @@ -99,6 +110,16 @@ internal static bool GetFileDependsOnNETStandard(string filePath) { return true; } + + if (assemblyName.Equals(SystemRuntimeAssemblyName, StringComparison.Ordinal)) + { + var assemblyVersion = new Version(assemblyMD.usMajorVersion, assemblyMD.usMinorVersion, assemblyMD.usBuildNumber, assemblyMD.usRevisionNumber); + + if (assemblyVersion >= SystemRuntimeMinVersion) + { + return true; + } + } } } while (fetched > 0); } @@ -145,7 +166,7 @@ internal interface IMetaDataDispenser internal interface IMetaDataAssemblyImport { void GetAssemblyProps(UInt32 mdAsm, out IntPtr pPublicKeyPtr, out UInt32 ucbPublicKeyPtr, out UInt32 uHashAlg, StringBuilder strName, UInt32 cchNameIn, out UInt32 cchNameRequired, IntPtr amdInfo, out UInt32 dwFlags); - void GetAssemblyRefProps(UInt32 mdAsmRef, out IntPtr ppbPublicKeyOrToken, out UInt32 pcbPublicKeyOrToken, StringBuilder strName, UInt32 cchNameIn, out UInt32 pchNameOut, IntPtr amdInfo, out IntPtr ppbHashValue, out UInt32 pcbHashValue, out UInt32 pdwAssemblyRefFlags); + void GetAssemblyRefProps(UInt32 mdAsmRef, out IntPtr ppbPublicKeyOrToken, out UInt32 pcbPublicKeyOrToken, StringBuilder strName, UInt32 cchNameIn, out UInt32 pchNameOut, ref ASSEMBLYMETADATA amdInfo, out IntPtr ppbHashValue, out UInt32 pcbHashValue, out UInt32 pdwAssemblyRefFlags); void GetFileProps([In] UInt32 mdFile, StringBuilder strName, UInt32 cchName, out UInt32 cchNameRequired, out IntPtr bHashData, out UInt32 cchHashBytes, out UInt32 dwFileFlags); void GetExportedTypeProps(); void GetManifestResourceProps(); @@ -162,6 +183,39 @@ internal interface IMetaDataAssemblyImport void CloseEnum([In] IntPtr phEnum); void FindAssembliesByName(); } + + + /* + From cor.h: + typedef struct + { + USHORT usMajorVersion; // Major Version. + USHORT usMinorVersion; // Minor Version. + USHORT usBuildNumber; // Build Number. + USHORT usRevisionNumber; // Revision Number. + LPWSTR szLocale; // Locale. + ULONG cbLocale; // [IN/OUT] Size of the buffer in wide chars/Actual size. + DWORD *rProcessor; // Processor ID array. + ULONG ulProcessor; // [IN/OUT] Size of the Processor ID array/Actual # of entries filled in. + OSINFO *rOS; // OSINFO array. + ULONG ulOS; // [IN/OUT]Size of the OSINFO array/Actual # of entries filled in. + } ASSEMBLYMETADATA; + */ + [StructLayout(LayoutKind.Sequential)] + internal struct ASSEMBLYMETADATA + { + public UInt16 usMajorVersion; + public UInt16 usMinorVersion; + public UInt16 usBuildNumber; + public UInt16 usRevisionNumber; + public IntPtr rpLocale; + public UInt32 cchLocale; + public IntPtr rpProcessors; + public UInt32 cProcessors; + public IntPtr rOses; + public UInt32 cOses; + } + #endregion } } diff --git a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.netstandard.cs b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.netstandard.cs index 871f6da70732..b7ed956079be 100644 --- a/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.netstandard.cs +++ b/src/Tasks/Microsoft.NET.Build.Extensions.Tasks/GetDependsOnNETStandard.netstandard.cs @@ -29,6 +29,12 @@ internal static bool GetFileDependsOnNETStandard(string filePath) { return true; } + + if (reader.StringComparer.Equals(reference.Name, SystemRuntimeAssemblyName) && + reference.Version >= SystemRuntimeMinVersion) + { + return true; + } } } } diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildADesktopExeWtihNetStandardLib.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildADesktopExeWtihNetStandardLib.cs index b59e7c79493a..e9a743884db6 100644 --- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildADesktopExeWtihNetStandardLib.cs +++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildADesktopExeWtihNetStandardLib.cs @@ -294,7 +294,7 @@ public void It_does_not_include_netstandard_when_inbox(bool isSdk) [WindowsOnlyTheory] [InlineData(true)] [InlineData(false)] - public void It_does_not_include_netstandard_when_libary_targets_netstandard1(bool isSdk) + public void It_does_not_include_netstandard_when_libary_targets_netstandard14(bool isSdk) { var testAsset = _testAssetsManager .CopyTestAsset(GetTemplateName(isSdk)) @@ -311,7 +311,7 @@ public void It_does_not_include_netstandard_when_libary_targets_netstandard1(boo var ns = project.Root.Name.Namespace; var propertyGroup = project.Root.Elements(ns + "PropertyGroup").First(); var targetFrameworkProperty = propertyGroup.Element(ns + "TargetFramework"); - targetFrameworkProperty.Value = "netstandard1.6"; + targetFrameworkProperty.Value = "netstandard1.4"; } }); @@ -330,5 +330,51 @@ public void It_does_not_include_netstandard_when_libary_targets_netstandard1(boo outputDirectory.Should().NotHaveFile("netstandard.dll"); } + + [WindowsOnlyTheory] + [InlineData(true)] + [InlineData(false)] + public void It_includes_netstandard_when_libary_targets_netstandard15(bool isSdk) + { + var testAsset = _testAssetsManager + .CopyTestAsset(GetTemplateName(isSdk)) + .WithSource() + .WithProjectChanges((projectPath, project) => + { + if (IsAppProject(projectPath)) + { + AddReferenceToLibrary(project, ReferenceScenario.ProjectReference); + } + + if (IsLibraryProject(projectPath)) + { + var ns = project.Root.Name.Namespace; + var propertyGroup = project.Root.Elements(ns + "PropertyGroup").First(); + var targetFrameworkProperty = propertyGroup.Element(ns + "TargetFramework"); + targetFrameworkProperty.Value = "netstandard1.5"; + } + }); + + testAsset.Restore(Log, relativePath: AppName); + + var buildCommand = new BuildCommand(Log, Path.Combine(testAsset.TestRoot, AppName)); + buildCommand + .Execute() + .Should() + .Pass(); + + var outputDirectory = isSdk ? + buildCommand.GetOutputDirectory("net461") : + buildCommand.GetNonSDKOutputDirectory(); + + // NET461 didn't originally support netstandard2.0, (nor netstandard1.5 or netstandard1.6) + // Since support was added after we need to ensure we apply the shims for netstandard1.5 projects as well. + + outputDirectory.Should().HaveFiles(new[] { + "netstandard.dll", + $"{AppName}.exe.config" + }); + } + } }