Roslyn provides a rich set of APIs for analyzing C# and Visual Basic source code, but constructing a context in which
to perform analysis can be challenging. For simple tasks, creating a Compilation
populated with SyntaxTrees
,
MetadataReferences
and a handful of options may suffice. However, if there are multiple projects involved in the
analysis, it is more complicated because multiple Compilations
need to be created with references between them.
To simplify the construction process. Roslyn provides the Workspace
API, which can be used to model solutions,
projects and documents. The Workspace
API performs all of the heavy lifting needed to parse SyntaxTrees
from source
code, load MetadataReferences
, and construct Compilations
and add references between them.
This works very well when the source files, references and compiler options are known up front, but what if you want to
analyze a MSBuild project or the projects in a Visual Studio solution file? For this purpose, Roslyn provides the
MSBuildWorkspace
API.
MSBuildWorkspace
is a special Workspace
implementation that can load MSBuild projects and solutions. Once created,
loading projects into an MSBuildWorkspace
instance is easy: it's just a matter of calling OpenProjectAsync(...)
or
OpenSolutionAsync(...)
.
using Microsoft.CodeAnalysis.MSBuild;
...
using (var workspace = MSBuildWorkspace.Create())
{
var project = await workspace.OpenProjectAsync("MyProject.csproj");
var compilation = await project.GetCompilationAsync();
// Perform analysis...
}
When a project is opened, MSBuildWorkspace
loads it by using MSBuild to perform a design-time build of the
project. This is a special build that retrieves all the necessary information to analyze the project (such as source
files, references, compilation options, etc.), but stops short of actually compiling the project and emiting a binary
on disk.
In addition, by default MSBuildWorkspace
will attempt to load any project references that it finds when loading a
project. This can be controlled with the MSBuildWorkspace.LoadMetadataForReferencedProjects
property. If set to true,
MSBuildWorkspace
will avoid loading a project reference if that project's output binary already exists on disk. If it
does, MSBuildWorkspace
will reference the binary instead of the project.
As mentioned earlier, Visual Studio solutions can be loaded into an MSBuildWorkspace
instance by calling the
OpenSolutionAsync(...)
API. This loads all of the projects in the solution.
using Microsoft.CodeAnalysis.MSBuild;
...
using (var workspace = MSBuildWorkspace.Create())
{
var solution = await workspace.OpenSolutionAsync("MySolution.sln");
foreach (var project in solution.Projects)
{
var compilation = await project.GetCompilationAsync();
// Perform analysis...
}
}
In older versions of Roslyn (2.8 and earlier), MSBuildWorkspace
can be found in the Microsoft.CodeAnalysis.Workspaces.Common
NuGet package. In Roslyn 2.9 and later, it can be found in the Microsoft.CodeAnalysis.Workspaces.MSBuild
NuGet package.
In order to successfully load an MSBuild project, MSBuildWorkspace
must have access to a properly configured MSBuild.
There are a few options available for setting up MSBuild.
The recommended way to set up MSBuild is to install Visual Studio 2017 (or
newer) or the Build Tools for Visual Studio 2017
(or newer) on the machine that MSBuildWorkspace
will run on. The important detail is to ensure that the installation
has the necessary workloads and components to support the projects you'll be loading. For example, if C# is not
installed, you will not be able to load C# projects. Similarly, if a project targets .NET Framework 4.7.3, that
targeting pack must be installed. A good rule of thumb is, if you can build the project with MSBuild at the
command-line, MSBuildWorkspace
should be able to load it.
Once a version of MSBuild is installed, the application using MSBuildWorkspace
needs to be able to find it. To do
this, reference the Microsoft.Build.Locator
NuGet package
from the application. This package provides faciltiies for locating and registering a valid installation of MSBuild on
the current machine. In the simplest case, you can simply instruct the MSBuildLocator
to RegisterDefaults
, that is,
register the first instance that it finds.
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.MSBuild;
...
MSBuildLocator.RegisterDefaults();
using (var workspace = MSBuildWorkspace.Create())
{
// Use MSBuildWorkspace...
}
When you register an instance with MSBuildLocator
(or register the defaults), an assembly resolution handler is added
to the current AppDomain
via AppDomain.AssemblyResolve
.
This handler resolves various Microsoft.Build.*
assemblies needed by MSBuildWorkspace
from the location of the
registered MSBuild. This results in MSBuildWorkspace
using versions of the Microsoft.Build.*
assemblies that are
compatible with the MSBuild tasks that run when it loads projects.
The Microsoft.Build.Locator
source code and issue tracking are available at https://github.com/Microsoft/MSBuildLocator.
- Do not include any
Microsoft.Build.*
assemblies in your application output except forMicrosoft.Build.Locator
. If your application includesMicrosoft.Build.dll
,Microsoft.Build.Framework.dll
,Microsoft.Build.Tasks.Core
orMicrosoft.Build.Utiltiies.Core
, they can interfere with the assembly resolution handler thatMSBuildLocator
has installed. If this happens, you'll often see strange failures withMSBuildWorkspace
, such as "Unable to cast object of type 'Microsoft.CodeAnalysis.BuildTasks.Csc' to type 'Microsoft.Build.Framework.ITask'". - Be sure use the
MSBuildLocator
to register an instance of MSBuild before creating an instance ofMSBuildWorkspace
. Otherwise, MSBuildWorkspace will fail with a MEF composition error. - In older versions of MSBuild, there is a bug that causes the current application directory to be considered an instance of MSBuild if your executable's name contains the text "MSBuild" within it. This bug is fixed, but it can still be a problem if the machine still has an older version of MSBuild installed See dotnet/msbuild#2194 for more details.
If there any warnings or errors are encountered when MSBuildWorkspace
loads a project or solution, you can access
them with MSBuildWorkspace.Diagnostics
.
@daveaglick:
The MSBuildWorkspace API doesn't actually expose any MSBuild objects. However, the old NuGet package pulled
Microsoft.Build
and friends into your project. The newMicrosoft.CodeAnalysis.Workspaces.MSBuild
will *not do this. It defines thoseMicrosoft.Build
dependencies asExcludeAssets="Runtime"
andPrivateAssets="All"
(code)You can use the old MSBuildWorkspace with the
Microsoft.Build.Locator
package by deleting theMicrosoft.Build.*
assemblies from your application's output directory (except forMicrosoft.Build.Locator.dll
of course). I showed another customer how to do this here: FizzerWL/ErrorRepro#1.I'm still considering whether
MSBuildWorkspace
will move to netstandard2.0 or not. My chief concern is around putting consumers into a pit of success. After all, ifMSBuildWorkspace
is used in a netcoreapp2.0 is it expected behavior that it can only load a subset of projects? What is their recourse when it doesn't work? By targeting .NET Framework, it means that more MSBuild tasks will just work.