From 6535850d61af276c8d85ff814207979e7f4173b9 Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Thu, 14 Jun 2018 12:07:49 +0100 Subject: [PATCH 1/2] [Core] Fix editor errors when .NET Standard assembly referenced With a Xamarin.iOS project that uses an assembly that is compiled for .NET Standard, such as the assembly used in the System.Collections.Immutable NuGet package, the facade assemblies were not being included in the list of referenced assemblies given to the type system. This then resulted in the text editor showing errors even though the project could be compiled without succesfully. The error displayed was similar to: The type 'ValueType' is defined in an assembly that is not referenced. You must add a reference to assembly 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeytoken=cc7b1dffcd2ddd51'. A short term fix has been added here. A check is made to determine if the assembly referencing netstandard and if so the facade assemblies are included. Previously only a check was made for the assembly referencing System.Runtime. Ideally the information would be found by using MSBuild. Fixes VSTS #632353 - Editor doesn't work with .NET Standard 2 NuGets --- .../SystemAssemblyService.cs | 46 +++++++++++++++ .../MonoDevelop.Projects/DotNetProject.cs | 2 +- .../SystemAssemblyServiceTests.cs | 16 ++++++ .../MonoDevelop.Projects/ProjectTests.cs | 44 +++++++++++++++ .../iOSImmutableCollections.sln | 17 ++++++ .../iOSImmutableCollections/MyClass.cs | 15 +++++ .../Properties/AssemblyInfo.cs | 27 +++++++++ .../iOSImmutableCollections.csproj | 56 +++++++++++++++++++ 8 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections.sln create mode 100644 main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/MyClass.cs create mode 100644 main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/Properties/AssemblyInfo.cs create mode 100644 main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/iOSImmutableCollections.csproj diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs index 0e978a8a8d9..6f8e4f90548 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs @@ -460,6 +460,35 @@ static bool ContainsReferenceToSystemRuntimeInternal (string fileName) return false; } + static Dictionary facadeReferenceDict = new Dictionary (); + + static bool RequiresFacadeAssembliesInternal (string fileName) + { + bool result; + if (facadeReferenceDict.TryGetValue (fileName, out result)) + return result; + + AssemblyDefinition assembly = null; + try { + try { + assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly (fileName); + } catch { + return false; + } + foreach (var r in assembly.MainModule.AssemblyReferences) { + // Don't compare the version number since it may change depending on the version of .net standard + if (r.Name.Equals ("System.Runtime") || r.Name.Equals ("netstandard")) { + facadeReferenceDict [fileName] = true; ; + return true; + } + } + } finally { + assembly?.Dispose (); + } + facadeReferenceDict [fileName] = false; + return false; + } + static object referenceLock = new object (); public static bool ContainsReferenceToSystemRuntime (string fileName) { @@ -479,6 +508,23 @@ public static async System.Threading.Tasks.Task ContainsReferenceToSystemR } } + internal static bool RequiresFacadeAssemblies (string fileName) + { + lock (referenceLock) { + return RequiresFacadeAssembliesInternal (fileName); + } + } + + internal static async System.Threading.Tasks.Task RequiresFacadeAssembliesAsync (string filename) + { + try { + await referenceLockAsync.WaitAsync ().ConfigureAwait (false); + return RequiresFacadeAssembliesInternal (filename); + } finally { + referenceLockAsync.Release (); + } + } + public class ManifestResource { public string Name { diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProject.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProject.cs index a3dd50f4bdc..dd5eebfb474 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProject.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProject.cs @@ -964,7 +964,7 @@ internal protected virtual async Task> OnGetReferencedAs } else { fullPath = Path.GetFullPath (refFilename.FilePath); } - if (await SystemAssemblyService.ContainsReferenceToSystemRuntimeAsync (fullPath)) { + if (await SystemAssemblyService.RequiresFacadeAssembliesAsync (fullPath)) { addFacadeAssemblies = true; break; } diff --git a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Core.Assemblies/SystemAssemblyServiceTests.cs b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Core.Assemblies/SystemAssemblyServiceTests.cs index d538367609b..709e1d65455 100644 --- a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Core.Assemblies/SystemAssemblyServiceTests.cs +++ b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Core.Assemblies/SystemAssemblyServiceTests.cs @@ -51,6 +51,22 @@ public async Task ImmutableCollectionsContainReferenceToSystemRuntimeAsync (bool Assert.That(result, Is.EqualTo(withSystemRuntime)); } + [TestCase (true, "System.Collections.Immutable.dll")] + [TestCase (false, "MonoDevelop.Core.dll")] + public void RequiresFacadeAssemblies (bool addFacades, string relativeDllPath) + { + var result = SystemAssemblyService.RequiresFacadeAssemblies (relativeDllPath); + Assert.That (result, Is.EqualTo (addFacades)); + } + + [TestCase (true, "System.Collections.Immutable.dll")] + [TestCase (false, "MonoDevelop.Core.dll")] + public async Task RequiresFacadeAssembliesAsync (bool addFacades, string relativeDllPath) + { + var result = await SystemAssemblyService.RequiresFacadeAssembliesAsync (relativeDllPath); + Assert.That (result, Is.EqualTo (addFacades)); + } + [Test] public void CheckReferencesAreOk() { diff --git a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectTests.cs b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectTests.cs index 0546831d45e..5381fff6520 100644 --- a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectTests.cs +++ b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectTests.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using NUnit.Framework; using UnitTests; @@ -816,6 +817,49 @@ public void GetDefaultNamespaceWhenProjectRootNamespaceContainsHyphen () Assert.AreEqual (expectedDefaultNamespace, result); } + [Test] + public async Task XamarinIOSProjectReferencesCollectionsImmutableNetStandardAssembly_GetReferencedAssembliesShouldIncludeNetStandard () + { + if (!Platform.IsMac) { + // NUnit platform attribute does not seem to work. + Assert.Ignore ("Only supported on Mac."); + } + + FilePath solFile = Util.GetSampleProject ("iOSImmutableCollections", "iOSImmutableCollections.sln"); + CreateNuGetConfigFile (solFile.ParentDirectory); + + var process = Process.Start ("msbuild", $"/t:Restore /p:RestoreDisableParallel=true \"{solFile}\""); + Assert.IsTrue (process.WaitForExit (120000), "Timeout restoring NuGet packages."); + Assert.AreEqual (0, process.ExitCode); + + using (var sol = (Solution) await Services.ProjectService.ReadWorkspaceItem (Util.GetMonitor (), solFile)) { + var p = (DotNetProject) sol.Items [0]; + + var refs = (await p.GetReferencedAssemblies (ConfigurationSelector.Default)).ToArray (); + + Assert.IsTrue (refs.Any (r => r.FilePath.FileName == "netstandard.dll")); + } + } + + /// + /// Clear all other package sources and just use the main NuGet package source when + /// restoring the packages for the project tests. + /// + static void CreateNuGetConfigFile (FilePath directory) + { + var fileName = directory.Combine ("NuGet.Config"); + + string xml = + "\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + ""; + + File.WriteAllText (fileName, xml); + } + [Test] public async Task ProjectExtensionOnModifiedCalledWhenProjectModified () { diff --git a/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections.sln b/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections.sln new file mode 100644 index 00000000000..c5d4fe96bb2 --- /dev/null +++ b/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections.sln @@ -0,0 +1,17 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOSImmutableCollections", "iOSImmutableCollections\iOSImmutableCollections.csproj", "{548A1099-6C66-410D-9D72-CD5105E51D9D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {548A1099-6C66-410D-9D72-CD5105E51D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {548A1099-6C66-410D-9D72-CD5105E51D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {548A1099-6C66-410D-9D72-CD5105E51D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {548A1099-6C66-410D-9D72-CD5105E51D9D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/MyClass.cs b/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/MyClass.cs new file mode 100644 index 00000000000..c628251f503 --- /dev/null +++ b/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/MyClass.cs @@ -0,0 +1,15 @@ + +using System; +using System.Collections.Immutable; + +namespace iOSImmutableCollections +{ + public class MyClass + { + ImmutableArray array = ImmutableArray.Empty; + + public MyClass() + { + } + } +} diff --git a/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/Properties/AssemblyInfo.cs b/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..159bbde0a64 --- /dev/null +++ b/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ + +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("iOSImmutableCollections")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Microsoft")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/iOSImmutableCollections.csproj b/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/iOSImmutableCollections.csproj new file mode 100644 index 00000000000..b164564206a --- /dev/null +++ b/main/tests/test-projects/iOSImmutableCollections/iOSImmutableCollections/iOSImmutableCollections.csproj @@ -0,0 +1,56 @@ + + + + Debug + AnyCPU + {548A1099-6C66-410D-9D72-CD5105E51D9D} + {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + iOSImmutableCollections + iOSImmutableCollections + Resources + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + iPhone Developer + true + true + true + 50484 + NSUrlSessionHandler + + + pdbonly + true + bin\Release + prompt + 4 + iPhone Developer + true + SdkOnly + NSUrlSessionHandler + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 279b084f25719f1c161dd0b553b4adfc5edd50b1 Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Thu, 14 Jun 2018 12:45:32 +0100 Subject: [PATCH 2/2] [Core] Deprecate obsolete methods in SystemAssemblyService The following methods are obsolete: ContainsReferenceToSystemRuntime ContainsReferenceToSystemRuntimeAsync The RequiresFacadeAssemblies methods should be used instead since these support checking for .NET Standard. --- .../MonoDevelop.Core.Assemblies/SystemAssemblyService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs index 6f8e4f90548..0b7ea27c378 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs @@ -490,6 +490,8 @@ static bool RequiresFacadeAssembliesInternal (string fileName) } static object referenceLock = new object (); + + [Obsolete ("Use RequiresFacadeAssemblies (string fileName)")] public static bool ContainsReferenceToSystemRuntime (string fileName) { lock (referenceLock) { @@ -498,6 +500,8 @@ public static bool ContainsReferenceToSystemRuntime (string fileName) } static SemaphoreSlim referenceLockAsync = new SemaphoreSlim (1, 1); + + [Obsolete ("Use RequiresFacadeAssembliesAsync (string fileName)")] public static async System.Threading.Tasks.Task ContainsReferenceToSystemRuntimeAsync (string filename) { try {