Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Roslyn: workspace loads in console application but not in msbuild task

Tags:

c#

windows

roslyn

I have a custom msbuild task with this command:

var workspace = Workspace.LoadStandAloneProject(csprojPath);

When I run it, it throws the following error:

  System.InvalidCastException was unhandled by user code
  Message=Unable to cast transparent proxy to type 'Roslyn.Utilities.SerializableDataStorage'.
  Source=Roslyn.Services
  StackTrace:
       at Roslyn.Utilities.RemoteServices.CreateInstance[T]()
       at Roslyn.Services.Host.TemporaryStorageServiceFactory.CreateService(IWorkspaceServiceProvider workspaceServices)
       at Roslyn.Services.Host.WorkspaceServiceProviderFactory.Provider.c__DisplayClass7.b__4()
       at Roslyn.Utilities.NonReentrantLazy`1.get_Value()
       at Roslyn.Services.Host.WorkspaceServiceProviderFactory.Provider.GetService[TWorkspaceService]()
       at Roslyn.Services.SolutionServices..ctor(IWorkspaceServiceProvider workspaceServices, ILanguageServiceProviderFactory languageServicesFactory)
       at Roslyn.Services.Solution..ctor(SolutionId id, String filePath, VersionStamp version, VersionStamp latestProjectVersion, ILanguageServiceProviderFactory languageServiceProviderFactory, IWorkspaceServiceProvider workspaceServices)
       at Roslyn.Services.Host.SolutionFactoryServiceFactory.SolutionFactoryService.CreateSolution(SolutionId id)
       at Roslyn.Services.Host.TrackingWorkspace.CreateNewSolution(ISolutionFactoryService solutionFactory, SolutionId id)
       at Roslyn.Services.Host.TrackingWorkspace..ctor(IWorkspaceServiceProvider workspaceServiceProvider, Boolean enableBackgroundCompilation, Boolean enableInProgressSolutions)
       at Roslyn.Services.Host.HostWorkspace..ctor(IWorkspaceServiceProvider workspaceServiceProvider, Boolean enableBackgroundCompilation, Boolean enableInProgressSolutions, Boolean enableFileTracking)
       at Roslyn.Services.Host.LoadedWorkspace..ctor(ILanguageServiceProviderFactory languageServiceProviderFactory, IWorkspaceServiceProvider workspaceServiceProvider, IProjectFileService projectFileFactsService, IDictionary`2 globalProperties, Boolean enableBackgroundCompilation, Boolean enableFileTracking)
       at Roslyn.Services.Host.LoadedWorkspace..ctor(ExportProvider exportProvider, Boolean solutionLoadOnly, Boolean enableFileTracking)
       at Roslyn.Services.Host.LoadedWorkspace..ctor(Boolean enableFileTracking)
       at Roslyn.Services.Host.LoadedWorkspace.LoadStandAloneProject(String projectFileName, String configuration, String platform, String language, Boolean enableFileTracking)
       at Roslyn.Services.Workspace.LoadStandAloneProject(String projectFileName, String configuration, String platform, String language, Boolean enableFileTracking)
       ...

The same code, when run in a console application, with the same project, runs fine.

Any ideas? Googling has not been helpful!

like image 582
Janik Zikovsky Avatar asked Oct 24 '12 15:10

Janik Zikovsky


1 Answers

Here's a sample MsBuild task with Roslyn.

In order to reconstruct the command line needed by the Workspace.LoadProjectFromCommandLineArguments method, we have to pass some info from the msbuild file into our task.

  • The referenced assemblies: the @(ReferencePath) item group.
  • The cs files to be compiled: the @(Compile) item group.
  • The base directory: the $(MSBuildProjectDirectory) built-in property.

That's all that Roslyn needs to parse your source files. (See the note at the end of this post.)

So create a C# class library project. These are the project references that you'll need:

Microsoft.Build.Framework
Microsoft.Build.Utilities.v4.0
Roslyn.Compilers
Roslyn.Services

The code for the custom MsBuild task:

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Roslyn.Services;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RoslynMsBuildTask
{
    public class RoslynTask : Task
    {
        [Required]
        public ITaskItem[] ReferencePath { get; set; }

        [Required]
        public ITaskItem[] Compile { get; set; }

        [Required]
        public ITaskItem BaseDirectory { get; set; }

        public override bool Execute()
        {
            Log.LogMessage(MessageImportance.High, "RoslynTask.Execute called...\n");

            // Format the command line with the minimal info needed for Roslyn to create a workspace.
            var commandLineForProject = string.Format("/reference:{0} {1}",
                ReferencePath.Select(i => i.ItemSpec).ToSingleString(",", "\"", "\""),
                Compile.Select(i => i.ItemSpec).ToSingleString(" ", "\"", "\""));

            // Create the Roslyn workspace.
            var workspace = Workspace.LoadProjectFromCommandLineArguments("MyProject", "C#", commandLineForProject, BaseDirectory.ItemSpec);

            // Make sure that Roslyn actually parsed the project: dump the source from a syntax tree to the build log.
            Log.LogMessage(MessageImportance.High, workspace.CurrentSolution.Projects.First()
                .Documents.First(i => i.FilePath.EndsWith(".cs")).GetSyntaxRoot().GetText().ToString());

            return true;
        }
    }

    public static class IEnumerableExtension
    {
        public static string ToSingleString<T>(this IEnumerable<T> collection, string separator, string leftWrapper, string rightWrapper)
        {
            var stringBuilder = new StringBuilder();

            foreach (var item in collection)
            {
                if (stringBuilder.Length > 0)
                {
                    if (!string.IsNullOrEmpty(separator))
                        stringBuilder.Append(separator);
                }

                if (!string.IsNullOrEmpty(leftWrapper))
                    stringBuilder.Append(leftWrapper);

                stringBuilder.Append(item.ToString());

                if (!string.IsNullOrEmpty(rightWrapper))
                    stringBuilder.Append(rightWrapper);
            }

            return stringBuilder.ToString();
        }
    }
}

To demonstrate that it actually works, add the following lines at the end of your csproj file (just before the closing Project tag). But only if the project was already built successfully and it can find your task dll in the output folder.

  <Target Name="AfterBuild" DependsOnTargets="RoslynTask"/>
  <UsingTask AssemblyFile="$(OutputPath)\RoslynMsBuildTask.dll" TaskName="RoslynMsBuildTask.RoslynTask" />
  <Target Name="RoslynTask">
    <RoslynTask ReferencePath="@(ReferencePath)" Compile="@(Compile)" BaseDirectory="$(MSBuildProjectDirectory)" />
  </Target>

It will dump the source of your first cs file to the build output.


Note that other csc.exe switches (like ConditionalDirectives, output type, etc) may also matter depending on the type of analysis you are trying to do. You can also pass them to your task using this pattern. See $(MSBuildToolsPath)\Microsoft.CSharp.targets file, CoreCompile target, Csc task for a complete list of properties that MsBuild passes to csc.exe.

like image 166
Vizu Avatar answered Oct 24 '22 22:10

Vizu