Skip to content

Commit

Permalink
avoiding problem which empty proxy class is created in linux and mac(M…
Browse files Browse the repository at this point in the history
…essagePack-CSharp#355)

It is because dotnet SDK's msbuild in linux and mac cannot
resolve `net4x` targetframework.

This fix changes to following behavior in generating project info.

1. execute `dotnet msbuild` with output msbuild's binary log
2. if failed, execute `msbuild` with output msbuild's binary log
3. if success with `dotnet msbuild` or `msbuild`, analyze binary log
  • Loading branch information
itn3000 committed Dec 25, 2018
1 parent 527dadf commit 0ad4ae0
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 25 deletions.
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

0 comments on commit 0ad4ae0

Please sign in to comment.