Skip to content

Instantly share code, notes, and snippets.

@DustinCampbell
Created April 10, 2018 21:36
Show Gist options
  • Star 47 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save DustinCampbell/32cd69d04ea1c08a16ae5c4cd21dd3a3 to your computer and use it in GitHub Desktop.
Save DustinCampbell/32cd69d04ea1c08a16ae5c4cd21dd3a3 to your computer and use it in GitHub Desktop.
Using MSBuildWorkspace

Introduction

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

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.

Setting up MSBuild

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.

Using MSBuild from Visual Studio 2017 (or newer)

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.

Gotchas

  • Do not include any Microsoft.Build.* assemblies in your application output except for Microsoft.Build.Locator. If your application includes Microsoft.Build.dll, Microsoft.Build.Framework.dll, Microsoft.Build.Tasks.Core or Microsoft.Build.Utiltiies.Core, they can interfere with the assembly resolution handler that MSBuildLocator has installed. If this happens, you'll often see strange failures with MSBuildWorkspace, 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 of MSBuildWorkspace. 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.

Troubleshooting

If there any warnings or errors are encountered when MSBuildWorkspace loads a project or solution, you can access them with MSBuildWorkspace.Diagnostics.

@hovik-aghajanyan
Copy link

Same question about the .NET Core support of MSBuildWorkspace.

@MatthewSteeples
Copy link

microsoft/MSBuildLocator#51 - this seems to indicate that support is already in

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment