From 3fddf3869bee891ca1dd6e24bc343060d7a1c0ee Mon Sep 17 00:00:00 2001 From: itn3000 Date: Thu, 21 Feb 2019 15:22:26 +0900 Subject: [PATCH] replacing Buildalyzer to MSBuild.StructuredLogger for Unity project(#386) --- sandbox/TestData.InvalidProject/Class1.cs | 35 +++ .../TestData.InvalidProject.csproj | 12 + sandbox/TestData.InvalidSyntax/Class1.cs | 37 +++ .../TestData.InvalidSyntax.csproj | 10 + .../CodeAnalysis/TypeCollector.cs | 5 + .../MessagePack.UniversalCodeGenerator.csproj | 38 +-- .../Utils/RoslynExtensions.cs | 227 +++++++++++++----- 7 files changed, 266 insertions(+), 98 deletions(-) create mode 100644 sandbox/TestData.InvalidProject/Class1.cs create mode 100644 sandbox/TestData.InvalidProject/TestData.InvalidProject.csproj create mode 100644 sandbox/TestData.InvalidSyntax/Class1.cs create mode 100644 sandbox/TestData.InvalidSyntax/TestData.InvalidSyntax.csproj diff --git a/sandbox/TestData.InvalidProject/Class1.cs b/sandbox/TestData.InvalidProject/Class1.cs new file mode 100644 index 000000000..ac037a4cd --- /dev/null +++ b/sandbox/TestData.InvalidProject/Class1.cs @@ -0,0 +1,35 @@ +using MessagePack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestData2 +{ + [MessagePackObject(true)] + public class A { public int a; public List bs; public C c; } + + [MessagePackObject(true)] + public class B { public List ass; public C c; public int a; } + + [MessagePackObject(true)] + public class C { public B b; public int a; } + + + [MessagePackObject(true)] + public class PropNameCheck1 + { + public string MyProperty1 { get; set; } + public virtual string MyProperty2 { get; set; } + } + + [MessagePackObject(true)] + public class PropNameCheck2 : PropNameCheck1 + { + public override string MyProperty2 + { + get => base.MyProperty2; + set => base.MyProperty2 = value; } + } +} diff --git a/sandbox/TestData.InvalidProject/TestData.InvalidProject.csproj b/sandbox/TestData.InvalidProject/TestData.InvalidProject.csproj new file mode 100644 index 000000000..3743f78a0 --- /dev/null +++ b/sandbox/TestData.InvalidProject/TestData.InvalidProject.csproj @@ -0,0 +1,12 @@ + + + net461 + + + + + + + + + diff --git a/sandbox/TestData.InvalidSyntax/Class1.cs b/sandbox/TestData.InvalidSyntax/Class1.cs new file mode 100644 index 000000000..63aa71256 --- /dev/null +++ b/sandbox/TestData.InvalidSyntax/Class1.cs @@ -0,0 +1,37 @@ +using MessagePack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestData.InvalidSyntax +{ + // delibrated syntax error + abcde + [MessagePackObject(true)] + public class A { public int a; public List bs; public C c; } + + [MessagePackObject(true)] + public class B { public List ass; public C c; public int a; } + + [MessagePackObject(true)] + public class C { public B b; public int a; } + + + [MessagePackObject(true)] + public class PropNameCheck1 + { + public string MyProperty1 { get; set; } + public virtual string MyProperty2 { get; set; } + } + + [MessagePackObject(true)] + public class PropNameCheck2 : PropNameCheck1 + { + public override string MyProperty2 + { + get => base.MyProperty2; + set => base.MyProperty2 = value; } + } +} diff --git a/sandbox/TestData.InvalidSyntax/TestData.InvalidSyntax.csproj b/sandbox/TestData.InvalidSyntax/TestData.InvalidSyntax.csproj new file mode 100644 index 000000000..d992280d8 --- /dev/null +++ b/sandbox/TestData.InvalidSyntax/TestData.InvalidSyntax.csproj @@ -0,0 +1,10 @@ + + + net461 + + + + + + + diff --git a/src/MessagePack.UniversalCodeGenerator/CodeAnalysis/TypeCollector.cs b/src/MessagePack.UniversalCodeGenerator/CodeAnalysis/TypeCollector.cs index ee21819a8..ea610ff88 100644 --- a/src/MessagePack.UniversalCodeGenerator/CodeAnalysis/TypeCollector.cs +++ b/src/MessagePack.UniversalCodeGenerator/CodeAnalysis/TypeCollector.cs @@ -242,6 +242,11 @@ public TypeCollector(string csProjPath, IEnumerable conditinalSymbols, b { this.csProjPath = csProjPath; var compilation = RoslynExtensions.GetCompilationFromProject(csProjPath, conditinalSymbols.Concat(new[] { CodegeneratorOnlyPreprocessorSymbol }).ToArray()).GetAwaiter().GetResult(); + var compilationErrors = compilation.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error).ToArray(); + if(compilationErrors.Length != 0) + { + throw new InvalidOperationException($"detect compilation error:{string.Join("\n", compilationErrors.Select(x => x.ToString()))}"); + } this.typeReferences = new ReferenceSymbols(compilation); this.disallowInternal = disallowInternal; this.isForceUseMap = isForceUseMap; diff --git a/src/MessagePack.UniversalCodeGenerator/MessagePack.UniversalCodeGenerator.csproj b/src/MessagePack.UniversalCodeGenerator/MessagePack.UniversalCodeGenerator.csproj index a9af7f78e..f48305ec8 100644 --- a/src/MessagePack.UniversalCodeGenerator/MessagePack.UniversalCodeGenerator.csproj +++ b/src/MessagePack.UniversalCodeGenerator/MessagePack.UniversalCodeGenerator.csproj @@ -9,48 +9,22 @@ - + + - + True True - EnumTemplate.tt - - - True - True - FormatterTemplate.tt - - - True - True - ResolverTemplate.tt - - - True - True - UnionTemplate.tt + %(FileName).tt - - TextTemplatingFilePreprocessor - EnumTemplate.cs - - - TextTemplatingFilePreprocessor - FormatterTemplate.cs - - - TextTemplatingFilePreprocessor - ResolverTemplate.cs - - + TextTemplatingFilePreprocessor - UnionTemplate.cs + %(FileName).cs diff --git a/src/MessagePack.UniversalCodeGenerator/Utils/RoslynExtensions.cs b/src/MessagePack.UniversalCodeGenerator/Utils/RoslynExtensions.cs index 3274f983c..a257ae04d 100644 --- a/src/MessagePack.UniversalCodeGenerator/Utils/RoslynExtensions.cs +++ b/src/MessagePack.UniversalCodeGenerator/Utils/RoslynExtensions.cs @@ -1,8 +1,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Buildalyzer; -using Buildalyzer.Workspaces; +using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Generic; using System.IO; @@ -10,7 +10,8 @@ using System.Text; using System.Threading.Tasks; using System.Xml.Linq; -using Buildalyzer.Environment; + +using StLogger = Microsoft.Build.Logging.StructuredLogger; namespace MessagePack.CodeGenerator { @@ -20,18 +21,16 @@ internal static class RoslynExtensions static (string fname, string args) GetBuildCommandLine(string csprojPath, string tempPath, bool useDotNet) { string fname = "dotnet"; - const string tasks = "ResolveAssemblyReferencesDesignTime;ResolveProjectReferencesDesignTime;ResolveComReferencesDesignTime;Compile"; + const string tasks = "Restore;ResolveReferences"; // from Buildalyzer implementation // https://github.com/daveaglick/Buildalyzer/blob/b42d2e3ba1b3673a8133fb41e72b507b01bce1d6/src/Buildalyzer/Environment/BuildEnvironment.cs#L86-L96 Dictionary properties = new Dictionary() { - // trailing '\' may cause unexpected escape - {"IntermediateOutputPath", tempPath + "/"}, {"ProviderCommandLineArgs", "true"}, {"GenerateResourceMSBuildArchitecture", "CurrentArchitecture"}, {"DesignTimeBuild", "true"}, {"BuildProjectReferences","false"}, - {"SkipCompilerExecution","true"}, + // {"SkipCompilerExecution","true"}, {"DisableRarCache", "true"}, {"AutoGenerateBindingRedirects", "false"}, {"CopyBuildOutputToOutputDirectory", "false"}, @@ -62,15 +61,20 @@ static async Task TryExecute(string csprojPath, string tempPath, bool useD var (fname, args) = GetBuildCommandLine(csprojPath, tempPath, useDotNet); try { + var buildlogpath = Path.Combine(tempPath, "build.binlog"); + if (File.Exists(buildlogpath)) + { + try + { + File.Delete(buildlogpath); + } + catch { } + } using (var stdout = new MemoryStream()) using (var stderr = new MemoryStream()) { var exitCode = await ProcessUtil.ExecuteProcessAsync(fname, args, stdout, stderr, null).ConfigureAwait(false); - if (exitCode == 0) - { - return true; - } - else + if (exitCode != 0) { // write process output to stdout and stderr when error. using (var stdout2 = new MemoryStream(stdout.ToArray())) @@ -81,8 +85,8 @@ static async Task TryExecute(string csprojPath, string tempPath, bool useD await stdout2.CopyToAsync(consoleStdout).ConfigureAwait(false); await stderr2.CopyToAsync(consoleStderr).ConfigureAwait(false); } - return false; } + return File.Exists(buildlogpath); } } catch (Exception e) @@ -91,21 +95,72 @@ static async Task TryExecute(string csprojPath, string tempPath, bool useD return false; } } - static async Task GetAnalyzerResults(AnalyzerManager analyzerManager, string csprojPath, params string[] preprocessorSymbols) + static IEnumerable FindAllErrors(StLogger.Build build) + { + var lst = new List(); + build.VisitAllChildren(er => lst.Add(er)); + return lst; + } + static (StLogger.Build, IEnumerable) ProcessBuildLog(string tempPath) + { + var reader = new StLogger.BinLogReader(); + var stlogger = new StLogger.StructuredLogger(); + // prevent output temporary file + StLogger.StructuredLogger.SaveLogToDisk = false; + // never output, but if not set, throw exception when initializing + stlogger.Parameters = "tmp.buildlog"; + stlogger.Initialize(reader); + reader.Replay(Path.Combine(tempPath, "build.binlog")); + stlogger.Shutdown(); + var buildlog = stlogger.Construction.Build; + if (buildlog.Succeeded) + { + return (buildlog, null); + } + else + { + var errors = FindAllErrors(buildlog); + return (null, errors); + } + } + static async Task<(StLogger.Build, IEnumerable)> TryGetBuildResultAsync(string csprojPath, string tempPath, bool useDotNet, params string[] preprocessorSymbols) + { + try + { + if (!await TryExecute(csprojPath, tempPath, useDotNet).ConfigureAwait(false)) + { + return (null, Array.Empty()); + } + else + { + return ProcessBuildLog(tempPath); + } + } + finally + { + if (Directory.Exists(tempPath)) + { + Directory.Delete(tempPath, true); + } + } + } + static async Task GetBuildResult(string csprojPath, params string[] preprocessorSymbols) { var tempPath = Path.Combine(new FileInfo(csprojPath).Directory.FullName, "__buildtemp"); try { - if (!await TryExecute(csprojPath, tempPath, true).ConfigureAwait(false)) + (StLogger.Build build, IEnumerable errors) = await TryGetBuildResultAsync(csprojPath, tempPath, true, preprocessorSymbols).ConfigureAwait(false); + if (build == null) { Console.WriteLine("execute `dotnet msbuild` failed, retry with `msbuild`"); - if (!await TryExecute(csprojPath, tempPath, false).ConfigureAwait(false)) + var dotnetException = new InvalidOperationException($"failed to build project with dotnet:{string.Join("\n", errors)}"); + (build, errors) = await TryGetBuildResultAsync(csprojPath, tempPath, false, preprocessorSymbols).ConfigureAwait(false); + if (build == null) { - throw new Exception("failed to build project"); + throw new InvalidOperationException($"failed to build project: {string.Join("\n", errors)}"); } } - // get results of analysis from binarylog - return analyzerManager.Analyze(Path.Combine(tempPath, "build.binlog")).ToArray(); + return build; } finally { @@ -115,64 +170,104 @@ static async Task GetAnalyzerResults(AnalyzerManager analyzerM } } } - public static async Task GetCompilationFromProject(string csprojPath, params string[] preprocessorSymbols) + static Workspace GetWorkspaceFromBuild(this StLogger.Build build, params string[] preprocessorSymbols) { - var analyzerOptions = new AnalyzerManagerOptions(); - // analyzerOptions.LogWriter = Console.Out; - - var manager = new AnalyzerManager(); - var projectAnalyzer = manager.GetProject(csprojPath); // addproj - // projectAnalyzer.AddBuildLogger(new Microsoft.Build.Logging.ConsoleLogger(Microsoft.Build.Framework.LoggerVerbosity.Minimal)); - - var workspace = await manager.GetWorkspaceWithPreventBuildEventAsync().ConfigureAwait(false); - - workspace.WorkspaceFailed += WorkSpaceFailed; - var project = workspace.CurrentSolution.Projects.First(); - project = project - .WithParseOptions((project.ParseOptions as CSharpParseOptions).WithPreprocessorSymbols(preprocessorSymbols)) - .WithCompilationOptions((project.CompilationOptions as CSharpCompilationOptions).WithAllowUnsafe(true)); - - var compilation = await project.GetCompilationAsync().ConfigureAwait(false); - return compilation; - } - - private static void WorkSpaceFailed(object sender, WorkspaceDiagnosticEventArgs e) - { - Console.WriteLine(e); - } - - // WIP function for getting Roslyn's workspace from csproj - public static async Task GetWorkspaceWithPreventBuildEventAsync(this AnalyzerManager manager) - { - var projPath = manager.Projects.First().Value.ProjectFile.Path; - var ws = new AdhocWorkspace(); - foreach (var result in await GetAnalyzerResults(manager, projPath)) + var csproj = build.Children.OfType().FirstOrDefault(); + if (csproj == null) + { + throw new InvalidOperationException("cannot find cs project build"); + } + StLogger.Item[] compileItems = Array.Empty(); + var properties = new Dictionary(); + foreach (var folder in csproj.Children.OfType()) { - // getting only successful build - if (result.Succeeded) + if (folder.Name == "Items") { - result.AddToWorkspace(ws); + var compileFolder = folder.Children.OfType().FirstOrDefault(x => x.Name == "Compile"); + if (compileFolder == null) + { + throw new InvalidOperationException("failed to get compililation documents"); + } + compileItems = compileFolder.Children.OfType().ToArray(); + } + else if (folder.Name == "Properties") + { + properties = folder.Children.OfType().ToDictionary(x => x.Name); } } + var assemblies = Array.Empty(); + foreach (var target in csproj.Children.OfType()) + { + if (target.Name == "ResolveReferences") + { + var folder = target.Children.OfType().Where(x => x.Name == "TargetOutputs").FirstOrDefault(); + if (folder == null) + { + throw new InvalidOperationException("cannot find result of resolving assembly"); + } + assemblies = folder.Children.OfType().ToArray(); + } + } + var ws = new AdhocWorkspace(); + var roslynProject = ws.AddProject(Path.GetFileNameWithoutExtension(csproj.ProjectFile), Microsoft.CodeAnalysis.LanguageNames.CSharp); + var projectDir = properties["ProjectDir"].Value; + var pguid = properties.ContainsKey("ProjectGuid") ? Guid.Parse(properties["ProjectGuid"].Value) : Guid.NewGuid(); + var projectGuid = ProjectId.CreateFromSerialized(pguid); + foreach (var compile in compileItems) + { + var filePath = compile.Text; + var absFilePath = Path.Combine(projectDir, filePath); + roslynProject = roslynProject.AddDocument(filePath, File.ReadAllText(absFilePath)).Project; + } + foreach (var asm in assemblies) + { + roslynProject = roslynProject.AddMetadataReference(MetadataReference.CreateFromFile(asm.Text)); + } + var compopt = roslynProject.CompilationOptions as CSharpCompilationOptions; + compopt = roslynProject.CompilationOptions as CSharpCompilationOptions; + compopt = compopt ?? new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + OutputKind kind; + switch (properties["OutputType"].Value) + { + case "Exe": + kind = OutputKind.ConsoleApplication; + break; + case "Library": + kind = OutputKind.DynamicallyLinkedLibrary; + break; + default: + kind = OutputKind.DynamicallyLinkedLibrary; + break; + } + roslynProject = roslynProject.WithCompilationOptions(compopt.WithOutputKind(kind).WithAllowUnsafe(true)); + var parseopt = roslynProject.ParseOptions as CSharpParseOptions; + roslynProject = roslynProject.WithParseOptions(parseopt.WithPreprocessorSymbols(preprocessorSymbols)); + if (!ws.TryApplyChanges(roslynProject.Solution)) + { + throw new InvalidOperationException("failed to apply solution changes to workspace"); + } return ws; } - - public static AdhocWorkspace GetWorkspace(this AnalyzerManager manager, EnvironmentOptions envOptions) + public static async Task GetCompilationFromProject(string csprojPath, params string[] preprocessorSymbols) { - // Run builds in parallel - List results = manager.Projects.Values - .AsParallel() - .Select(p => p.Build(envOptions).FirstOrDefault()) // with envoption - .Where(x => x != null) - .ToList(); + var build = await GetBuildResult(csprojPath, preprocessorSymbols).ConfigureAwait(false); - // Add each result to a new workspace - AdhocWorkspace workspace = new AdhocWorkspace(); - foreach (AnalyzerResult result in results) + using (var workspace = GetWorkspaceFromBuild(build, preprocessorSymbols)) { - result.AddToWorkspace(workspace); + workspace.WorkspaceFailed += WorkSpaceFailed; + var project = workspace.CurrentSolution.Projects.First(); + project = project + .WithParseOptions((project.ParseOptions as CSharpParseOptions).WithPreprocessorSymbols(preprocessorSymbols)) + .WithCompilationOptions((project.CompilationOptions as CSharpCompilationOptions).WithAllowUnsafe(true)); + + var compilation = await project.GetCompilationAsync().ConfigureAwait(false); + return compilation; } - return workspace; + } + + private static void WorkSpaceFailed(object sender, WorkspaceDiagnosticEventArgs e) + { + Console.WriteLine(e); } public static IEnumerable GetNamedTypeSymbols(this Compilation compilation)