Skip to content
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

avoiding problem which empty proxy class is created in linux and mac(#355) #357

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
130 changes: 130 additions & 0 deletions src/MessagePack.UniversalCodeGenerator/Utils/ProcessUtil.cs
@@ -0,0 +1,130 @@
using System.Diagnostics;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace MessagePack.CodeGenerator
{
internal static class ProcessUtil
{
public static async Task<int> ExecuteProcessAsync(string fileName, string args, Stream stdout, Stream stderr, TextReader stdin, CancellationToken ct = default(CancellationToken))
{
var psi = new ProcessStartInfo(fileName, args);
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
psi.RedirectStandardError = stderr != null;
psi.RedirectStandardOutput = stdout != null;
psi.RedirectStandardInput = stdin != null;
using (var proc = new Process())
using (var cts = new CancellationTokenSource())
using (var exitedct = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, ct))
{
proc.StartInfo = psi;
proc.EnableRaisingEvents = true;
proc.Exited += (sender, ev) =>
{
cts.Cancel();
};
if (!proc.Start())
{
throw new InvalidOperationException($"failed to start process(fileName = {fileName}, args = {args})");
}
int exitCode = 0;
await Task.WhenAll(
Task.Run(() =>
{
exitCode = StdinTask(proc, stdin, exitedct, cts);
if(exitCode < 0)
{
proc.Dispose();
}
})
,
Task.Run(async () =>
{
if (stdout != null)
{
await RedirectOutputTask(proc.StandardOutput.BaseStream, stdout, exitedct.Token, "stdout");
}
})
,
Task.Run(async () =>
{
if (stderr != null)
{
await RedirectOutputTask(proc.StandardError.BaseStream, stderr, exitedct.Token, "stderr");
}
})
);
if(exitCode >= 0)
{
return proc.ExitCode;
}
else
{
return -1;
}
}
}
static int StdinTask(Process proc, TextReader stdin, CancellationTokenSource exitedct, CancellationTokenSource cts)
{
if (stdin != null)
{
while (!exitedct.Token.IsCancellationRequested)
{
var l = stdin.ReadLine();
if (l == null)
{
break;
}
proc.StandardInput.WriteLine(l);
}
proc.StandardInput.Dispose();
}
exitedct.Token.WaitHandle.WaitOne();
if (cts.IsCancellationRequested)
{
proc.WaitForExit();
var exitCode = proc.ExitCode;
return exitCode;
}
else
{
proc.StandardOutput.Dispose();
proc.StandardError.Dispose();
proc.Kill();
return -1;
}
}

static async Task RedirectOutputTask(Stream procStdout, Stream stdout, CancellationToken ct, string suffix)
{
if (stdout != null)
{
var buf = new byte[1024];
while (!ct.IsCancellationRequested)
{
try
{
var bytesread = await procStdout.ReadAsync(buf, 0, 1024, ct).ConfigureAwait(false);
if(bytesread <= 0)
{
break;
}
stdout.Write(buf, 0, bytesread);
}
catch(NullReferenceException)
{
break;
}
catch(ObjectDisposedException)
{
break;
}
}
}
}

}
}
131 changes: 106 additions & 25 deletions src/MessagePack.UniversalCodeGenerator/Utils/RoslynExtensions.cs
Expand Up @@ -17,6 +17,103 @@ namespace MessagePack.CodeGenerator
// Utility and Extension methods for Roslyn
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";
// from Buildalyzer implementation
// https://github.com/daveaglick/Buildalyzer/blob/b42d2e3ba1b3673a8133fb41e72b507b01bce1d6/src/Buildalyzer/Environment/BuildEnvironment.cs#L86-L96
Dictionary<string, string> properties = new Dictionary<string, string>()
{
{"IntermediateOutputPath", tempPath},
{"ProviderCommandLineArgs", "true"},
{"GenerateResourceMSBuildArchitecture", "CurrentArchitecture"},
{"DesignTimeBuild", "true"},
{"BuildProjectReferences","false"},
{"SkipCompilerExecution","true"},
{"DisableRarCache", "true"},
{"AutoGenerateBindingRedirects", "false"},
{"CopyBuildOutputToOutputDirectory", "false"},
{"CopyOutputSymbolsToOutputDirectory", "false"},
{"SkipCopyBuildProduct", "true"},
{"AddModules", "false"},
{"UseCommonOutputDirectory", "true"},
{"GeneratePackageOnBuild", "false"},
{"RunPostBuildEvent", "false"},
{"SolutionDir", new FileInfo(csprojPath).FullName}
};
var propargs = string.Join(" ", properties.Select(kv => $"/p:{kv.Key}=\"{kv.Value}\""));
// how to determine whether command should be executed('dotnet msbuild' or 'msbuild')?
if (useDotNet)
{
fname = "dotnet";
return (fname, $"msbuild \"{csprojPath}\" /t:{tasks} {propargs} /bl:\"{Path.Combine(tempPath, "build.binlog")}\" /v:n");
}
else
{
fname = "msbuild";
return (fname, $"\"{csprojPath}\" /t:{tasks} {propargs} /bl:\"{Path.Combine(tempPath, "build.binlog")}\" /v:n");
}
}
static async Task<bool> TryExecute(string csprojPath, string tempPath, bool useDotNet)
{
// executing build command with output binary log
var (fname, args) = GetBuildCommandLine(csprojPath, tempPath, useDotNet);
try
{
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
{
// write process output to stdout and stderr when error.
using (var stdout2 = new MemoryStream(stdout.ToArray()))
using (var stderr2 = new MemoryStream(stderr.ToArray()))
using (var consoleStdout = Console.OpenStandardOutput())
using (var consoleStderr = Console.OpenStandardError())
{
await stdout2.CopyToAsync(consoleStdout).ConfigureAwait(false);
await stderr2.CopyToAsync(consoleStderr).ConfigureAwait(false);
}
return false;
}
}
}
catch (Exception e)
{
Console.WriteLine($"exception occured(fname={fname}, args={args}):{e}");
return false;
}
}
static async Task<AnalyzerResult[]> GetAnalyzerResults(AnalyzerManager analyzerManager, string csprojPath, params string[] preprocessorSymbols)
{
var tempPath = Path.Combine(new FileInfo(csprojPath).Directory.FullName, "__buildtemp");
try
{
if (!await TryExecute(csprojPath, tempPath, true).ConfigureAwait(false))
{
Console.WriteLine("execute `dotnet msbuild` failed, retry with `msbuild`");
if (!await TryExecute(csprojPath, tempPath, false).ConfigureAwait(false))
{
throw new Exception("failed to build project");
}
}
// get results of analysis from binarylog
return analyzerManager.Analyze(Path.Combine(tempPath, "build.binlog")).ToArray();
}
finally
{
if (Directory.Exists(tempPath))
{
Directory.Delete(tempPath, true);
}
}
}
public static async Task<Compilation> GetCompilationFromProject(string csprojPath, params string[] preprocessorSymbols)
{
var analyzerOptions = new AnalyzerManagerOptions();
Expand All @@ -26,7 +123,7 @@ public static async Task<Compilation> GetCompilationFromProject(string csprojPat
var projectAnalyzer = manager.GetProject(csprojPath); // addproj
// projectAnalyzer.AddBuildLogger(new Microsoft.Build.Logging.ConsoleLogger(Microsoft.Build.Framework.LoggerVerbosity.Minimal));

var workspace = manager.GetWorkspaceWithPreventBuildEvent();
var workspace = await manager.GetWorkspaceWithPreventBuildEventAsync().ConfigureAwait(false);

workspace.WorkspaceFailed += WorkSpaceFailed;
var project = workspace.CurrentSolution.Projects.First();
Expand All @@ -43,36 +140,20 @@ private static void WorkSpaceFailed(object sender, WorkspaceDiagnosticEventArgs
Console.WriteLine(e);
}

public static AdhocWorkspace GetWorkspaceWithPreventBuildEvent(this AnalyzerManager manager)
// WIP function for getting Roslyn's workspace from csproj
public static async Task<AdhocWorkspace> GetWorkspaceWithPreventBuildEventAsync(this AnalyzerManager manager)
{
// info article: https://qiita.com/skitoy4321/items/9edfb094549f5167a57f
var projPath = manager.Projects.First().Value.ProjectFile.Path;
var tempPath = Path.Combine(new FileInfo(projPath).Directory.FullName, "__buildtemp") + System.IO.Path.DirectorySeparatorChar;

var envopts = new EnvironmentOptions();
// "Clean" and "Build" is listed in default
// Modify to designtime system https://github.com/dotnet/project-system/blob/master/docs/design-time-builds.md#targets-that-run-during-design-time-builds
// that prevent Pre/PostBuildEvent

envopts.TargetsToBuild.Clear();
// Clean should not use(if use pre/post build, dll was deleted).
// envopts.TargetsToBuild.Add("Clean");
envopts.TargetsToBuild.Add("ResolveAssemblyReferencesDesignTime");
envopts.TargetsToBuild.Add("ResolveProjectReferencesDesignTime");
envopts.TargetsToBuild.Add("ResolveComReferencesDesignTime");
envopts.TargetsToBuild.Add("Compile");
envopts.GlobalProperties["IntermediateOutputPath"] = tempPath;
try
var ws = new AdhocWorkspace();
foreach (var result in await GetAnalyzerResults(manager, projPath))
{
return GetWorkspace(manager, envopts);
}
finally
{
if (Directory.Exists(tempPath))
// getting only successful build
if (result.Succeeded)
{
Directory.Delete(tempPath, true);
result.AddToWorkspace(ws);
}
}
return ws;
}

public static AdhocWorkspace GetWorkspace(this AnalyzerManager manager, EnvironmentOptions envOptions)
Expand Down