-
Notifications
You must be signed in to change notification settings - Fork 420
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
Re-architect how MSBuild is loaded by OmniSharp #988
Re-architect how MSBuild is loaded by OmniSharp #988
Conversation
.travis.yml
Outdated
if [ "$TRAVIS_OS_NAME" == "osx" ]; then | ||
rvm install ruby-2.3.3 | ||
fi | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is old code. Will remove this.
8f1a4d2
to
7aa51bc
Compare
var msbuildLocator = _serviceProvider.GetRequiredService<IMSBuildLocator>(); | ||
var instances = msbuildLocator.GetInstances(); | ||
var instance = instances.FirstOrDefault(); | ||
if (instance != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we supposed to do anything if we couldn't locate any instances?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably report an error to the log. Will add that.
} | ||
} | ||
|
||
return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my own edification, what happens if Resolve returns null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The runtime will continue to the next AssemblyResolve handler it finds. From https://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396:
If more than one event handler is registered for this event, the event handlers are called in order until an event handler returns a value that isn't null. Subsequent event handlers are ignored.
return msbuildDirectory; | ||
} | ||
|
||
private static string FindLocalMSBuildFromSolution() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about adding a command line argument that we can pass while debugging that specifies the standalone msbuild location? That way we don't have to talk about "OmniSharp.sln" in the product code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, but where would it get populated from? Note that the product code already referenced "OmniSharp.sln". This PR just moved that code to here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We would add the argument to the arguments passed by the debugger or by tests. Not a huge deal either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose we could add some MSBuild logic to add an argument passed to the debugger (which still worked when other arguments were added). I'm not sure how it'd work for tests, but I'm guessing we could figure something out. I'm just not sure it's all that valuable.
var instances = new ISetupInstance[1]; | ||
do | ||
{ | ||
instanceEnum.Next(1, instances, out fetched); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this only enumerate Dev15 versions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's all it can do. This is the API for the Willow installer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which gets picked if someone has dev15 and a dev15 preview installed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit arbitrary. We don't have a way for the user to select one, so it's whichever is first in the list. However, we check for the Visual Studio tied to a developer command prompt first. So, if the user wants to launch OmniSharp with a specific Visual Studio they can launch it from the command prompt that was installed for that VS. Alternatively, they can set the environment variables that the developer command prompt uses.
|
||
return ImmutableArray.Create( | ||
new MSBuildInstance( | ||
"DEVCONSOLE", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nameof(DiscoveryType.DevConsole)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea
This change re-organizes the layout of the MSBuild folder to more closely match the layout of Visual Studio: * "msbuild" - This will be the MSBuildExtensionsPath * "msbuild\15.0" - common props go here * "msbuild\15.0\bin" - The MSBuild tools path, where much of the MSBuild actually goes. In addition, the MSBuild runtime and library packages have been updated to the latest, and the package references for the libraries are marked as `PrivateAssets="all"` in the OmniSharp.MSBuild project. This last change means that OmniSharp no longer runs, since the MSBuild libraries aren't copied to the OmniSharp base application path any longer. Later commits will fix this.
The MSBuildLocator service has provider model for discovering MSBuild in various scenarios: * DevConsole * VS 2017 installation * Mono * Stand alone (i.e. the MSBuild that we ship with OmniSharp) Once an MSBuild instance is discovered and registered, an AssemblyResolve event is used to ensure that various MSBuild assemblies are used from that instance rather than using MSBuild assemblies included with OmniSharp.
…d on the installed Mono
3de0818
to
1c5f21f
Compare
@david-driscoll, @filipw, @rchande : I've done a fair amount of manual testing with this change on OSX and am feeling pretty confident about it. The core MSBuild project scenarios are still working, some scenarios work better, and some new scenarios have starting working. Here are the scenarios I tested:
|
{ | ||
var assemblyPath = Path.Combine(_registeredInstance.MSBuildPath, assemblyName.Name + ".dll"); | ||
var result = File.Exists(assemblyPath) | ||
? Assembly.LoadFrom(assemblyPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're right. I didn't use it initially because I was still trying to figure out when/how the IMSBuildLocator
would be initialized. New commit adds this.
Looks like a massive improvement 👍👍👍 One question, should this also recognize that I have just the standalone MsBuild Tools installed, even if I don't have VS installed? |
@filipw: I haven't specifically tested that scenario, but it should already work. The MSBuild 15.0 build tools use the same installer as VS 2017. So, the MSBuild discovery that looks for Visual Studio using the setup API should also work for the Build Tools as well. |
var instances = new ISetupInstance[1]; | ||
do | ||
{ | ||
instanceEnum.Next(1, instances, out fetched); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which gets picked if someone has dev15 and a dev15 preview installed?
@@ -20,6 +21,32 @@ public override ImmutableArray<MSBuildInstance> GetInstances() | |||
return NoInstances; | |||
} | |||
|
|||
// Don't resolve to MSBuild assemblies under the installed Mono unless OmniSharp | |||
// is actually running under the installed Mono. | |||
var monoRuntimePath = PlatformHelper.GetMonoRuntimePath(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this obvious? Maybe explain a bit further what goes wrong if this is not the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of this is obvious. 😄 But you're right, I'll add a little bit more detail about how it can go wrong.
Thanks for testing this so thoroughly and documenting what you tested @DustinCampbell. cc @piotrpMSFT #988 (comment) explains the size of the test matrix. |
FWIW, I don't normally test all of those scenarios, but they were necessary for this particular change. Too often, changes like these have broken unity support. |
OK. I think it's good now. I'm going to go ahead and merge. Let me know if you think of anything else. |
excellent! |
awesome. i am going to steal it. thx @DustinCampbell ! |
@DustinCampbell Did you try mono mdk? the package provided by Xamarin people through brew? 👍 |
I installed the Mono MDK with the installer |
👍 Awesome |
FWIW, I'll check |
SAME 👍 |
This seems to have broken mono/windows, see #1001 |
@corngood: Why are you running OmniSharp on Mono/Windows? We've never tested that scenario. The assumption has generally been that desktop CLR on Windows is the way to go for OmniSharp. |
@Casz: I just checked |
@DustinCampbell I'm working around a bug w/ net46. We can discuss on #1001 |
@DustinCampbell I tested it with our dotnet 4.5 project and it did not work 😢 |
@Casz: Bummer! Could you file an issue with your OmniSharp log? |
@DustinCampbell did I have to do something special or was downloading c# extension enough for VS Code? |
Oh found the beta install guide... my bad |
@DustinCampbell it works almost dotnet/vscode-csharp#1821 |
With MSBuild 15, locating MSBuild on the user's box can be a significant challenge. OmniSharp provides a version of MSBuild as a fallback, but that can often only work with .NET Core/Standard SDK-style projects; projects targeting .NET Framework can run into significant issues.
This PR introduces an
IMSBuildLocator
service that is used to discover instances of MSBuild 15 installed on the user's machine. It can discover MSBuild 15 using a few methods:Once MSBuild is located, OmniSharp will install an
AppDomain.AssemblyResolve
event to ensure that it loads theMicrosoft.Build.*
assemblies from that location. This ensures that the MSBuild used by OmniSharp will be compatible with whatever MSBuild toolset is used to process the user's project. In addition, this can speed up processing depending on whether theMicrosoft.Build.*
assemblies are NGen'd or AOT'd in the location they're loaded from.Note that, because we're using
AssemblyResolve
to loadMicrosoft.Build.*
assemblies, the event must be hooked before OmniSharp is composed with MEF. Otherwise, MEF will fail to compose.In addition, as part of this change, I've reorganized the
.msbuild
folder to layout more closely to how it's laid out in Visual Studio.A few things I want to do before merging this:
MSBuildEnvironment
class. This is still there because there's still logic in this class that hasn't been replicated in an MSBuild discoverer yet.Final note: There's an issue with Mono and AssemblyResolve where it can get into a bad state an attempt is made to load an assembly before the AssemblyResolve event is hooked. On desktop CLR, this works as expected. The core issue here is that xUnit uses
Assembly.Load
on every assembly in the directory to try and locate xUnit types and that happens before our AssemblyResolve is installed. Currently, this is working OK on OSX/Linux, but it's going to be a problem at some point. I've filed the following bug on Mono to get this fixed: https://bugzilla.xamarin.com/show_bug.cgi?id=60100.