Skip to content
This repository has been archived by the owner on Oct 4, 2021. It is now read-only.

Commit

Permalink
Merge pull request #6228 from mono/master-issue6061
Browse files Browse the repository at this point in the history
Fixes issue #6061 Add using writes the wrong reference down for a
  • Loading branch information
Therzok committed Oct 16, 2018
2 parents 1a60d91 + 0aad4b1 commit 7137ac5
Show file tree
Hide file tree
Showing 16 changed files with 632 additions and 22 deletions.
@@ -0,0 +1,117 @@
//
// MonoDevelopFrameworkAssemblyPathResolver.cs
//
// Author:
// Mike Krüger <mikkrg@microsoft.com>
//
// Copyright (c) 2018 Microsoft Corporation. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Composition;
using System.IO;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using MonoDevelop.Core;
using MonoDevelop.Core.Assemblies;
using MonoDevelop.Projects;

namespace MonoDevelop.Ide.TypeSystem
{
[ExportWorkspaceServiceFactory (typeof (IFrameworkAssemblyPathResolver), ServiceLayer.Host), Shared]
class MonoDevelopFrameworkAssemblyPathResolverFactory : IWorkspaceServiceFactory
{
public IWorkspaceService CreateService (HostWorkspaceServices workspaceServices)
{
return new MonoDevelopFrameworkAssemblyPathResolver (workspaceServices.Workspace as MonoDevelopWorkspace);
}

class MonoDevelopFrameworkAssemblyPathResolver : IFrameworkAssemblyPathResolver
{
readonly MonoDevelopWorkspace workspace;
public MonoDevelopFrameworkAssemblyPathResolver (MonoDevelopWorkspace workspace)
{
this.workspace = workspace;
}

public string ResolveAssemblyPath (ProjectId projectId, string assemblyName, string fullyQualifiedName = null)
{
if (workspace == null)
return null;

if (!(workspace.GetMonoProject (projectId) is DotNetProject monoProject))
return null;

string assemblyFile = monoProject.AssemblyContext.GetAssemblyLocation (assemblyName, monoProject.TargetFramework);
if (assemblyFile != null) {
//if (string.IsNullOrEmpty(fullyQualifiedName) || CanResolveType(ResolveAssembly (projectId, assemblyName), fullyQualifiedName))
return assemblyFile;
}

return null;
}

//static bool CanResolveType (Assembly assembly, string fullyQualifiedTypeName)
//{
// if (fullyQualifiedTypeName == null) {
// // nothing to resolve.
// return true;
// }

// // We only get a type name without generic indicators. So try to few different
// // generic versions of the type name in case any of those hit. it's highly
// // unlikely we'd find something with more than 4 generic parameters, so only try
// // up that point.
// for (var i = 0; i < 5; i++) {
// var name = i == 0
// ? fullyQualifiedTypeName
// : fullyQualifiedTypeName + "`" + i;

// try {
// var type = assembly.GetType (name, throwOnError: false);
// if (type != null) {
// return true;
// }
// } catch (FileNotFoundException) {
// } catch (FileLoadException) {
// } catch (BadImageFormatException) {
// }
// }
// return false;
//}

//Assembly ResolveAssembly (ProjectId projectId, string assemblyLocation)
//{
// Runtime.AssertMainThread ();

// try {
// return Assembly.LoadFrom (assemblyLocation);
// } catch (Exception e) {
// // Something wrong with our TFM. We don't have enough information to
// // properly resolve this assembly name.
// LoggingService.LogError ("Error while resolving assembly assemblyName");
// return null;
// }
//}
}
}
}
Expand Up @@ -54,7 +54,8 @@
using Microsoft.CodeAnalysis.SolutionCrawler;
using MonoDevelop.Ide.Composition;
using MonoDevelop.Ide.RoslynServices;

using MonoDevelop.Core.Assemblies;

namespace MonoDevelop.Ide.TypeSystem
{
public partial class MonoDevelopWorkspace : Workspace
Expand Down Expand Up @@ -722,6 +723,12 @@ static int ApplyChanges (Projection projection, ITextDocument data, List<Microso
List<Task> tryApplyState_documentTextChangedTasks = new List<Task> ();
Dictionary<string, SourceText> tryApplyState_documentTextChangedContents = new Dictionary<string, SourceText> ();

/// <summary>
/// Used by tests to validate that project has been saved.
/// </summary>
/// <value>The task that can be awaited to validate saving has finished.</value>
internal Task ProjectSaveTask { get; private set; } = Task.FromResult<object> (null);

internal override bool TryApplyChanges (Solution newSolution, IProgressTracker progressTracker)
{
// this is supported on the main thread only
Expand Down Expand Up @@ -752,7 +759,7 @@ internal override bool TryApplyChanges (Solution newSolution, IProgressTracker p
}

if (tryApplyState_changedProjects.Count > 0) {
IdeApp.ProjectOperations.SaveAsync (tryApplyState_changedProjects);
ProjectSaveTask = IdeApp.ProjectOperations.SaveAsync (tryApplyState_changedProjects);
}

return ret;
Expand Down Expand Up @@ -894,40 +901,101 @@ string DetermineFilePath (DocumentId id, string name, string filePath, IReadOnly
return path;
}

bool TryGetMetadataReferenceMapping (ProjectId projectId, MetadataReference metadataReference, out MonoDevelop.Projects.DotNetProject mdProject, out string path, out SystemAssembly systemAssemblyOpt)
{
mdProject = GetMonoProject (projectId) as MonoDevelop.Projects.DotNetProject;
path = GetMetadataPath (metadataReference);
systemAssemblyOpt = null;
if (mdProject == null || path == null)
return false;

// PERF: Maybe break IAssemblyContext API and add GetAssemblyFromPath.
// GetPackageFromPath could be implemented by querying the SystemAssembly's package.
var package = mdProject.AssemblyContext.GetPackageFromPath (path);
if (package != null) {
foreach (var asm in package.Assemblies) {
if (asm.Location == path)
systemAssemblyOpt = asm;
}
}

// This code would handle assemblies like glib-sharp.
// Enabling this causes a NRE, as there's no package associated with the SystemAssembly.
//if (systemAssemblyOpt == null) {
// try {
// var aName = AssemblyName.GetAssemblyName (path).FullName;
// var isGac = mdProject.AssemblyContext.AssemblyIsInGac (aName);
// if (isGac) {
// systemAssemblyOpt = new SystemAssembly (path, aName);
// }
// } catch {
// }
//}

return true;
}

protected override void ApplyMetadataReferenceAdded (ProjectId projectId, MetadataReference metadataReference)
{
var mdProject = GetMonoProject (projectId) as MonoDevelop.Projects.DotNetProject;
var path = GetMetadataPath (metadataReference);
if (mdProject == null || path == null)
if (!TryGetMetadataReferenceMapping (projectId, metadataReference, out var mdProject, out string path, out var systemAssemblyOpt))
return;

foreach (var r in mdProject.References) {
if (r.ReferenceType == MonoDevelop.Projects.ReferenceType.Assembly && r.Reference == path) {
LoggingService.LogWarning ("Warning duplicate reference is added " + path);
return;
if (systemAssemblyOpt != null) {
if (r.ReferenceType == MonoDevelop.Projects.ReferenceType.Package) {
var nameToCheck = r.SpecificVersion ? systemAssemblyOpt.FullName : systemAssemblyOpt.Name;
if (r.Reference == nameToCheck) {
LoggingService.LogWarning ("Warning duplicate reference is added " + path);
return;
}
}
}

if (r.ReferenceType == MonoDevelop.Projects.ReferenceType.Project) {
foreach (var fn in r.GetReferencedFileNames (MonoDevelop.Projects.ConfigurationSelector.Default)) {
if (fn == path) {
LoggingService.LogWarning ("Warning duplicate reference is added " + path + " for project " + r.Reference);
return;
}
}
if (r.ReferenceType == MonoDevelop.Projects.ReferenceType.Assembly && r.Reference == path) {
LoggingService.LogWarning ("Warning duplicate reference is added " + path);
return;
}
}

mdProject.AddReference (path);

if (r.ReferenceType == MonoDevelop.Projects.ReferenceType.Project) {
foreach (var fn in r.GetReferencedFileNames (MonoDevelop.Projects.ConfigurationSelector.Default)) {
if (fn == path) {
LoggingService.LogWarning ("Warning duplicate reference is added " + path + " for project " + r.Reference);
return;
}
}
}
}

if (systemAssemblyOpt != null) {
mdProject.References.Add (MonoDevelop.Projects.ProjectReference.CreateAssemblyReference (systemAssemblyOpt));
} else {
mdProject.AddReference (path);
}

tryApplyState_changedProjects.Add (mdProject);
this.OnMetadataReferenceAdded (projectId, metadataReference);
}

protected override void ApplyMetadataReferenceRemoved (ProjectId projectId, MetadataReference metadataReference)
{
var mdProject = GetMonoProject (projectId) as MonoDevelop.Projects.DotNetProject;
var path = GetMetadataPath (metadataReference);
if (mdProject == null || path == null)
if (!TryGetMetadataReferenceMapping (projectId, metadataReference, out var mdProject, out string path, out var systemAssemblyOpt))
return;
var item = mdProject.References.FirstOrDefault (r => r.ReferenceType == MonoDevelop.Projects.ReferenceType.Assembly && r.Reference == path);

MonoDevelop.Projects.ProjectReference item;
// if we're trying to remove a system package, try removing a system package first
if (systemAssemblyOpt != null) {
item = mdProject.References.FirstOrDefault (r => {
if (r.ReferenceType != MonoDevelop.Projects.ReferenceType.Package)
return false;
var nameToCheck = r.SpecificVersion ? systemAssemblyOpt.FullName : systemAssemblyOpt.Name;
return r.ReferenceType == MonoDevelop.Projects.ReferenceType.Package && r.Reference == nameToCheck;
});
} else {
// Remove a normal assembly reference.
item = mdProject.References.FirstOrDefault (r => r.ReferenceType == MonoDevelop.Projects.ReferenceType.Assembly && r.Reference == path);
}

if (item == null)
return;
mdProject.References.Remove (item);
Expand Down
1 change: 1 addition & 0 deletions main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj
Expand Up @@ -4197,6 +4197,7 @@
<Compile Include="MonoDevelop.Ide.Updater\UpdateInfo.cs" />
<Compile Include="MonoDevelop.Ide.Desktop\ThermalMonitor.cs" />
<Compile Include="MonoDevelop.Ide\PlatformThermalStatusEventArgs.cs" />
<Compile Include="MonoDevelop.Ide.TypeSystem\MonoDevelopFrameworkAssemblyPathResolver.cs" />
</ItemGroup>
<ItemGroup>
<Data Include="options\DefaultEditingLayout.xml" />
Expand Down
@@ -0,0 +1,66 @@
//
// MonoDevelopFrameworkAssemblyPathResolverFactoryTests.cs
//
// Author:
// Mike Krüger <mikkrg@microsoft.com>
//
// Copyright (c) 2018 Microsoft Corporation. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
using MonoDevelop.Ide.Composition;
using Microsoft.CodeAnalysis.Host;

namespace MonoDevelop.Ide.RoslynServices
{
[TestFixture]
public class MonoDevelopFrameworkAssemblyPathResolverFactoryTests : TextEditorExtensionTestBase
{
protected override EditorExtensionTestData GetContentData () => EditorExtensionTestData.CSharp;

[Test]
public async Task ServiceIsRegistered ()
{
using (var testCase = await SetupTestCase ("class MyTest {}")) {
var doc = testCase.Document;

var service = doc.RoslynWorkspace.Services.GetService<IFrameworkAssemblyPathResolver> ();
Assert.IsNotNull (service);
}
}

[Test]
public async Task TestSimpleCase ()
{
using (var testCase = await SetupTestCase ("class MyTest {}")) {
var doc = testCase.Document;

var service = doc.RoslynWorkspace.Services.GetService<IFrameworkAssemblyPathResolver> ();
string path = service.ResolveAssemblyPath (doc.AnalysisDocument.Project.Id, "System");
Assert.IsNotNull (path);
}
}

}
}
2 changes: 2 additions & 0 deletions main/tests/Ide.Tests/MonoDevelop.Ide.Tests.csproj
Expand Up @@ -144,6 +144,8 @@
<Compile Include="MonoDevelop.Ide.FindInFiles\SearchResultWidgetTests.cs" />
<Compile Include="MonoDevelop.Ide.Editor\AbstractCodeFormatterTests.cs" />
<Compile Include="MonoDevelop.Ide.TypeSystem\TypeSystemServiceTests.cs" />
<Compile Include="MonoDevelop.Ide.RoslynServices\MonoDevelopFrameworkAssemblyPathResolverFactoryTests.cs" />
<Compile Include="MonoDevelop.Ide.TypeSystem\MonoDevelopWorkspaceTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\core\MonoDevelop.Ide\MonoDevelop.Ide.csproj">
Expand Down

0 comments on commit 7137ac5

Please sign in to comment.