Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reload and update MSBuild projects in response to dotnet restore #1003

Merged
merged 11 commits into from
Nov 6, 2017

Conversation

DustinCampbell
Copy link
Contributor

Fixes dotnet/vscode-csharp#1583, dotnet/vscode-csharp#1661, dotnet/vscode-csharp#1785

This is a fairly substantial change in order to address a problem where MSBuild projects would not be updated in response to a dotnet restore. Essentially, the MSBuild project system must watch four different files per project in order to respond to a dotnet restore:

  • project.asset.json
  • .nuget.cache
  • .nuget.g.props
  • .nuget.g.targets

To ensure that we don't reload and update a project multiple times in response to multiple file
changes, this PR introduces a simple queue and processing loop using a TPL DataFlow BufferBlock. The implication of this is that the MSBuild project system is now considered "initialized" only once the initial project files are queued for an update. So, OmniSharp is ready to respond to requests more quickly, but the projects won't necessarily be loaded yet.

In addition, I've made a few other changes while fixing this issue:

  1. It turns out that metadata references were not properly removed from the workspace due to the fact that Roslyn's MetadataReference does not implement value equality. To address this, I create our own IEqualityComparer<MetadataReference> implementation.
  2. I've simplified file watching/notification by unifying file and directory watching, and fixed two issues:
    a. Paths are now treated case-insensitively as they are in the rest of OmniSharp.
    b. Access to the internal callback dictionary is now serialized.
  3. Most code in ProjectFileInfo and MSBuildProjectSystem have moved into two new classes: ProjectLoader and ProjectManager. This will facilitate some MSBuild performance improvements I'll be making later.

I recently noticed an issue that has likely been present for a long while. Essentially, metadata
references are never removed from the workspace. The reason for this is because Roslyn's MetadataReference
class does not implement value equality -- it just inherits the default `Equals()`/`GetHashCode()`
implementations from `System.Object`. However, the algorithm for adding and removing metadata references
assumes value equality. This is easily fixed by adding our own `IEqualityComparer<MetadataReference>`
that first checks to ensure that `MetadataReferences` are `PortableExecutableMetadataReferences` and then
uses their file paths for equality.
This change unifies file and directory watching, separates watching and notification concerns in the API,
and fixes the following issues:

1. Paths are treated case-insensitively as they are in the rest of OmniSharp.
2. Access to the internal dictionary of callbacks is synchronized.
This is a pretty substantial change that reworks the core logic of the MSBuild project
system to enable an important scenario: updating a project when several files change in quick
succession.

In order to fix an issue with OmniSharp not reloading and updating a project in response to
a 'dotnet restore', we must watch four files that might be touched during a restore:

* project.asset.json
* <project-file>.nuget.cache
* <project-file>.nuget.g.props
* <project-file>.nuget.g.targets

To ensure that we don't reload and update a project multiple times in response to multiple file
changes, this PR introduces a simple queue and processing loop using a TPL DataFlow BufferBlock.

public PackageDependencyResolver(ILoggerFactory loggerFactory)
public PackageDependencyChecker(ILoggerFactory loggerFactory, IEventEmitter eventEmitter, DotNetCliService dotNetCli, MSBuildOptions options)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when we resolve inline package references (#r "nuget: Foo, 1.0.0") in scripts, we create behind the scenes a temp csproj and restore using .NET CLI to resolve them. However, currently, we don't handle changes after project system init anymore. Looks like we'll be able to reuse this logic there 👍

}
}

private class MetadataReferenceComparer : IEqualityComparer<MetadataReference>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be extracted into a reusable place - it will be useful i.e. here

var commonReferences = new HashSet<MetadataReference>();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good idea. I'll make it a utility in OmniSharp.Roslyn.

Previously, we tracked whether a project update *could* invoke an automatic
'dotnet restore' and this PR adds that back. Essentially, we allow auto restore
in all cases that a project is updated *except* when the update comes via a restore.
@DustinCampbell
Copy link
Contributor Author

Note: Because this PR changes the MSBuild project system at a fundamental level, I'm going to hold off on merging this until we can get a C# for VS Code release out early next week.

@DustinCampbell DustinCampbell merged commit 0a390e5 into OmniSharp:master Nov 6, 2017
@DustinCampbell DustinCampbell deleted the package-restore branch April 11, 2018 21:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants