Unify code for executing processes. (#8848)

* Create a simple Xamarin.Utils.Execution class that can handle all our
  process execution needs:
    * Captures or streams stdout/stderr (in UTF8).
    * Supports async
    * Supports a timeout
    * Does not depend on any other source file we have, only uses BCL API.
* Have the execution helper classes from mtouch/mmp
  (Xamarin.BundlerDriver.RunCommand) and the tests
  (Xamarin.Tests.ExecutionHelper) use this new class.
* Some simplifications were made:
    * All API that took a string array for the environment now takes a
      Dictionary<string, string>.
    * The Driver.RunCommand methods were split out to a separate file. This
      file also contains a Verbosity field, which is conditioned on not being
      in mtouch nor mmp, which makes including this file from other projects
      simpler (such as bgen - in particular bgen was modified to use this
      Verbosity field instead of its own).
This commit is contained in:
Rolf Bjarne Kvinge 2020-06-18 12:34:07 +02:00 коммит произвёл GitHub
Родитель ac7056ccb9
Коммит c3bcfac582
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
29 изменённых файлов: 473 добавлений и 638 удалений

Просмотреть файл

@ -4,6 +4,7 @@
<TargetFramework>net461</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<!--
@ -51,6 +52,9 @@
<Compile Include="..\..\..\tests\common\ExecutionHelper.cs">
<Link>ExecutionHelper.cs</Link>
</Compile>
<Compile Include="..\..\..\tools\common\Execution.cs">
<Link>Execution.cs</Link>
</Compile>
<Compile Include="..\..\..\tests\mtouch\Cache.cs">
<Link>Cache.cs</Link>
</Compile>

Просмотреть файл

@ -328,6 +328,9 @@
<Compile Include="..\Resources.Designer.cs">
<DependentUpon>..\Resources.resx</DependentUpon>
</Compile>
<Compile Include="..\..\tools\common\Driver.execution.cs">
<Link>Driver.execution.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\Resources.resx">

Просмотреть файл

@ -217,7 +217,6 @@ public class BindingTouch {
string tmpdir = null;
string ns = null;
bool delete_temp = true, debug = false;
bool verbose = false;
bool unsafef = true;
bool external = false;
bool public_mode = true;
@ -256,8 +255,8 @@ public class BindingTouch {
{ "d=", "Defines a symbol", v => defines.Add (v) },
{ "api=", "Adds a API definition source file", v => api_sources.Add (v) },
{ "s=", "Adds a source file required to build the API", v => core_sources.Add (v) },
{ "q", "Quiet", v => verbose = false },
{ "v", "Sets verbose mode", v => verbose = true },
{ "q", "Quiet", v => Driver.Verbosity++ },
{ "v", "Sets verbose mode", v => Driver.Verbosity-- },
{ "x=", "Adds the specified file to the build, used after the core files are compiled", v => extra_sources.Add (v) },
{ "e", "Generates smaller classes that can not be subclassed (previously called 'external mode')", v => external = true },
{ "p", "Sets private mode", v => public_mode = false },
@ -450,7 +449,7 @@ public class BindingTouch {
if (!string.IsNullOrEmpty (Path.GetDirectoryName (baselibdll)))
cargs.Add ("-lib:" + Path.GetDirectoryName (baselibdll));
if (Driver.RunCommand (compiler, cargs, null, out var compile_output, true, verbose ? 1 : 0) != 0)
if (Driver.RunCommand (compiler, cargs, null, out var compile_output, true, Driver.Verbosity) != 0)
throw ErrorHelper.CreateError (2, compile_output.ToString ().Replace ("\n", "\n\t"));
@ -493,7 +492,7 @@ public class BindingTouch {
try {
api = universe.LoadFile (tmpass);
} catch (Exception e) {
if (verbose)
if (Driver.Verbosity > 0)
Console.WriteLine (e);
Console.Error.WriteLine ("Error loading API definition from {0}", tmpass);
@ -504,7 +503,7 @@ public class BindingTouch {
try {
baselib = universe.LoadFile (baselibdll);
} catch (Exception e){
if (verbose)
if (Driver.Verbosity > 0)
Console.WriteLine (e);
Console.Error.WriteLine ("Error loading base library {0}", baselibdll);
@ -604,7 +603,7 @@ public class BindingTouch {
if (!string.IsNullOrEmpty (Path.GetDirectoryName (baselibdll)))
cargs.Add ("-lib:" + Path.GetDirectoryName (baselibdll));
if (Driver.RunCommand (compiler, cargs, null, out var generated_compile_output, true, verbose ? 1 : 0) != 0)
if (Driver.RunCommand (compiler, cargs, null, out var generated_compile_output, true, Driver.Verbosity) != 0)
throw ErrorHelper.CreateError (1000, generated_compile_output.ToString ().Replace ("\n", "\n\t"));
} finally {
if (delete_temp)

Просмотреть файл

@ -389,6 +389,9 @@
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="packages.config" />
<Compile Include="..\tools\common\Driver.execution.cs">
<Link>Driver.execution.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources.resx">

Просмотреть файл

@ -31,5 +31,8 @@
<Compile Include="..\..\tools\common\StringUtils.cs">
<Link>StringUtils.cs</Link>
</Compile>
<Compile Include="..\..\tools\common\Execution.cs">
<Link>Execution.cs</Link>
</Compile>
</ItemGroup>
</Project>

Просмотреть файл

@ -2,11 +2,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Diagnostics;
using NUnit.Framework;
using Xamarin.Utils;
@ -438,29 +435,6 @@ namespace Xamarin.Tests
}
static class ExecutionHelper {
static int Execute (string fileName, IList<string> arguments, StringBuilder stdout, StringBuilder stderr, TimeSpan? timeout = null)
{
var psi = new ProcessStartInfo (fileName, StringUtils.FormatArguments (arguments));
return Execute (psi, (line) => {
lock (stdout)
stdout.AppendLine (line);
}, (line) => {
lock (stderr)
stderr.AppendLine (line);
}, timeout);
}
static int Execute (ProcessStartInfo psi, StringBuilder stdout, StringBuilder stderr, TimeSpan? timeout = null)
{
return Execute (psi, (line) => {
lock (stdout)
stdout.AppendLine (line);
}, (line) => {
lock (stderr)
stderr.AppendLine (line);
}, timeout);
}
public static int Execute (string fileName, IList<string> arguments)
{
return Execute (fileName, arguments, null, null, null, null);
@ -471,16 +445,9 @@ namespace Xamarin.Tests
return Execute (fileName, arguments, null, null, null, timeout);
}
public static int Execute (string fileName, IList<string> arguments, Action<string> stdout_callback = null, Action<string> stderr_callback = null, TimeSpan? timeout = null)
{
return Execute (fileName, arguments, null, stdout_callback, stderr_callback, timeout);
}
public static int Execute (string fileName, IList<string> arguments, string working_directory = null, Action<string> stdout_callback = null, Action<string> stderr_callback = null, TimeSpan? timeout = null)
{
var psi = new ProcessStartInfo (fileName, StringUtils.FormatArguments (arguments));
psi.WorkingDirectory = working_directory;
return Execute (psi, stdout_callback, stderr_callback, timeout);
return Execute (fileName, arguments, timed_out: out var _, workingDirectory: working_directory, stdout_callback: stdout_callback, stderr_callback: stderr_callback, timeout: timeout);
}
public static int Execute (string fileName, IList<string> arguments, out StringBuilder output)
@ -491,190 +458,58 @@ namespace Xamarin.Tests
public static int Execute (string fileName, IList<string> arguments, out StringBuilder output, string working_directory, TimeSpan? timeout = null)
{
output = new StringBuilder ();
var psi = new ProcessStartInfo (fileName, StringUtils.FormatArguments (arguments));
psi.WorkingDirectory = working_directory;
var capturedOutput = output;
var callback = new Action<string> ((v) => {
lock (psi)
capturedOutput.AppendLine (v);
});
return Execute (psi, callback, callback, timeout);
return Execute (fileName, arguments, out var _, workingDirectory: working_directory, stdout: output, stderr: output, timeout: timeout);
}
public static int Execute (string fileName, IList<string> arguments, out bool timed_out, Dictionary<string, string> environment_variables = null, Action<string> stdout_callback = null, Action<string> stderr_callback = null, TimeSpan? timeout = null)
public static int Execute (string fileName, IList<string> arguments, out bool timed_out, string workingDirectory = null, Dictionary<string, string> environment_variables = null, StringBuilder stdout = null, StringBuilder stderr = null, TimeSpan? timeout = null)
{
var psi = new ProcessStartInfo (fileName, StringUtils.FormatArguments (arguments));
if (environment_variables != null) {
foreach (var ev in environment_variables)
psi.EnvironmentVariables [ev.Key] = ev.Value;
}
return Execute (psi, out timed_out, stdout_callback, stderr_callback, timeout);
var rv = Execution.RunWithStringBuildersAsync (fileName, arguments, workingDirectory: workingDirectory, environment: environment_variables, standardOutput: stdout, standardError: stderr, timeout: timeout).Result;
timed_out = rv.TimedOut;
if (rv.TimedOut)
Console.WriteLine ($"Command '{fileName} {StringUtils.FormatArguments (arguments)}' didn't finish in {timeout.Value.TotalMilliseconds} minutes, and was killed.", timeout.Value.TotalMinutes);
return rv.ExitCode;
}
public static int Execute (string fileName, IList<string> arguments, out bool timed_out, string working_directory = null, Dictionary<string, string> environment_variables = null, Action<string> stdout_callback = null, Action<string> stderr_callback = null, TimeSpan? timeout = null)
public static int Execute (string fileName, IList<string> arguments, out bool timed_out, string workingDirectory = null, Dictionary<string, string> environment_variables = null, Action<string> stdout_callback = null, Action<string> stderr_callback = null, TimeSpan? timeout = null)
{
var psi = new ProcessStartInfo (fileName, StringUtils.FormatArguments (arguments));
psi.WorkingDirectory = working_directory;
if (environment_variables != null) {
foreach (var ev in environment_variables)
psi.EnvironmentVariables [ev.Key] = ev.Value;
}
return Execute (psi, out timed_out, stdout_callback, stderr_callback, timeout);
}
public static int Execute (ProcessStartInfo psi, Action<string> stdout_callback = null, Action<string> stderr_callback = null, TimeSpan? timeout = null)
{
return Execute (psi, out var _, stdout_callback, stderr_callback, timeout);
}
public static int Execute (ProcessStartInfo psi, out bool timed_out, Action<string> stdout_callback = null, Action<string> stderr_callback = null, TimeSpan? timeout = null)
{
var watch = new Stopwatch ();
watch.Start ();
if (stdout_callback == null)
stdout_callback = Console.WriteLine;
if (stderr_callback == null)
stderr_callback = Console.Error.WriteLine;
try {
psi.UseShellExecute = false;
psi.RedirectStandardError = true;
psi.RedirectStandardOutput = true;
if (!string.IsNullOrEmpty (psi.WorkingDirectory))
Console.Write ($"cd {StringUtils.Quote (psi.WorkingDirectory)} && ");
Console.WriteLine ("{0} {1}", psi.FileName, psi.Arguments);
using (var p = new Process ()) {
p.StartInfo = psi;
// mtouch/mmp writes UTF8 data outside of the ASCII range, so we need to make sure
// we read it in the same format. This also means we can't use the events to get
// stdout/stderr, because mono's Process class parses those using Encoding.Default.
p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
p.StartInfo.StandardErrorEncoding = Encoding.UTF8;
p.Start ();
var outReader = new Thread (() =>
{
string l;
while ((l = p.StandardOutput.ReadLine ()) != null) {
stdout_callback (l);
}
})
{
IsBackground = true,
};
outReader.Start ();
var errReader = new Thread (() =>
{
string l;
while ((l = p.StandardError.ReadLine ()) != null) {
stderr_callback (l);
}
})
{
IsBackground = true,
};
errReader.Start ();
if (timeout == null)
timeout = TimeSpan.FromMinutes (5);
if (!p.WaitForExit ((int) timeout.Value.TotalMilliseconds)) {
timed_out = true;
Console.WriteLine ("Command didn't finish in {0} minutes:", timeout.Value.TotalMinutes);
Console.WriteLine ("{0} {1}", p.StartInfo.FileName, p.StartInfo.Arguments);
Console.WriteLine ("Will now kill the process");
kill (p.Id, 9);
if (!p.WaitForExit (1000 /* killing should be fairly quick */)) {
Console.WriteLine ("Kill failed to kill in 1 second !?");
return 1;
}
} else {
timed_out = false;
}
outReader.Join (TimeSpan.FromSeconds (1));
errReader.Join (TimeSpan.FromSeconds (1));
return p.ExitCode;
}
} finally {
Console.WriteLine ("{0} Executed in {1}: {2} {3}", DateTime.Now, watch.Elapsed.ToString (), psi.FileName, psi.Arguments);
}
var rv = Execution.RunWithCallbacksAsync (fileName, arguments, workingDirectory: workingDirectory, environment: environment_variables, standardOutput: stdout_callback, standardError: stderr_callback, timeout: timeout).Result;
timed_out = rv.TimedOut;
if (rv.TimedOut)
Console.WriteLine ($"Command '{fileName} {StringUtils.FormatArguments (arguments)}' didn't finish in {timeout.Value.TotalMilliseconds} minutes, and was killed.", timeout.Value.TotalMinutes);
return rv.ExitCode;
}
public static int Execute (string fileName, IList<string> arguments, out string output, TimeSpan? timeout = null)
{
var sb = new StringBuilder ();
var psi = new ProcessStartInfo ();
psi.FileName = fileName;
psi.Arguments = StringUtils.FormatArguments (arguments);
var rv = Execute (psi, sb, sb, timeout);
var rv = Execute (fileName, arguments, timed_out: out var _, stdout: sb, stderr: sb, timeout: timeout);
output = sb.ToString ();
return rv;
}
// The arguments are automatically quoted.
public static int Execute (string fileName, IList<string> arguments, Dictionary<string, string> environmentVariables, StringBuilder stdout, StringBuilder stderr, TimeSpan? timeout = null, string workingDirectory = null)
{
return Execute (fileName, StringUtils.FormatArguments (arguments), environmentVariables, stdout, stderr, timeout, workingDirectory);
return Execute (fileName, arguments, timed_out: out var _, workingDirectory: workingDirectory, environment_variables: environmentVariables, stdout: stdout, stderr: stderr, timeout: timeout);
}
static int Execute (string fileName, string arguments, Dictionary<string, string> environmentVariables, StringBuilder stdout, StringBuilder stderr, TimeSpan? timeout = null, string workingDirectory = null)
{
if (stdout == null)
stdout = new StringBuilder ();
if (stderr == null)
stderr = new StringBuilder ();
var psi = new ProcessStartInfo ();
psi.FileName = fileName;
psi.Arguments = arguments;
if (!string.IsNullOrEmpty (workingDirectory))
psi.WorkingDirectory = workingDirectory;
if (environmentVariables != null) {
var envs = psi.EnvironmentVariables;
foreach (var kvp in environmentVariables) {
if (kvp.Value == null) {
envs.Remove (kvp.Key);
} else {
envs [kvp.Key] = kvp.Value;
}
}
}
return Execute (psi, stdout, stderr, timeout);
}
[DllImport ("libc")]
private static extern void kill (int pid, int sig);
public static string Execute (string fileName, IList<string> arguments, bool throwOnError = true, Dictionary<string, string> environmentVariables = null, bool hide_output = false, TimeSpan? timeout = null)
{
return Execute (fileName, StringUtils.FormatArguments (arguments), throwOnError, environmentVariables, hide_output, timeout);
}
static string Execute (string fileName, string arguments, bool throwOnError = true, Dictionary<string, string> environmentVariables = null,
bool hide_output = false, TimeSpan? timeout = null
)
{
StringBuilder output = new StringBuilder ();
int exitCode = Execute (fileName, arguments, environmentVariables, output, output, timeout);
var throw_exc = throwOnError && exitCode != 0;
var rv = Execution.RunAsync (fileName, arguments, mergeOutput: true, environment: environmentVariables, timeout: timeout).Result;
var output = rv.StandardOutput.ToString ();
var throw_exc = throwOnError && rv.ExitCode != 0;
if (!hide_output || throw_exc) {
Console.WriteLine ("{0} {1}", fileName, arguments);
Console.WriteLine ($"{fileName} {StringUtils.FormatArguments (arguments)} (exit code: {rv.ExitCode})");
Console.WriteLine (output);
Console.WriteLine ("Exit code: {0}", exitCode);
Console.WriteLine ("Exit code: {0}", rv.ExitCode);
}
if (throw_exc)
throw new TestExecutionException ($"Execution failed (exit code: {exitCode}) for '{fileName} {arguments}'");
return output.ToString ();
}
}
class TestExecutionException : Exception {
public TestExecutionException (string output)
: base (output)
{
if (throwOnError && rv.ExitCode != 0)
throw new Exception ();
return output;
}
}
}

Просмотреть файл

@ -209,10 +209,11 @@ namespace Xamarin.MMP.Tests
return RunAndAssert (exe, args, "Command: " + exe);
}
public static string RunAndAssert (string exe, IList<string> args, string stepName, bool shouldFail = false, Func<string> getAdditionalFailInfo = null, string[] environment = null)
public static string RunAndAssert (string exe, IList<string> args, string stepName, bool shouldFail = false, Func<string> getAdditionalFailInfo = null, Dictionary<string, string> environment = null)
{
StringBuilder output = new StringBuilder ();
Environment.SetEnvironmentVariable ("MONO_PATH", null);
environment ??= new Dictionary<string, string> ();
environment ["MONO_PATH"] = null;
int compileResult = Xamarin.Bundler.Driver.RunCommand (exe, args, environment, output, suppressPrintOnErrors: shouldFail);
if (!shouldFail && compileResult != 0 && Xamarin.Bundler.Driver.Verbosity < 1) {
Console.WriteLine ($"Execution failed; exit code: {compileResult}");
@ -247,29 +248,32 @@ namespace Xamarin.MMP.Tests
// TODO - This is not enough for MSBuild to really work. We need stuff to have it not use system targets!
// These are required to have xbuild use are local build instead of system install
Environment.SetEnvironmentVariable ("TargetFrameworkFallbackSearchPaths", rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild-frameworks");
Environment.SetEnvironmentVariable ("MSBuildExtensionsPathFallbackPathsOverride", rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild");
Environment.SetEnvironmentVariable ("XAMMAC_FRAMEWORK_PATH", rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current");
Environment.SetEnvironmentVariable ("XamarinMacFrameworkRoot", rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current");
var env = new Dictionary<string, string> {
{ "TargetFrameworkFallbackSearchPaths", rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild-frameworks" },
{ "MSBuildExtensionsPathFallbackPathsOverride", rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild" },
{ "XAMMAC_FRAMEWORK_PATH", rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current" },
{ "XamarinMacFrameworkRoot", rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current" },
};
// This is to force build to use our mmp and not system mmp
var buildArgs = new List<string> ();
buildArgs.Add ("build");
buildArgs.Add (csprojTarget);
return RunAndAssert ("/Applications/Visual Studio.app/Contents/MacOS/vstool", buildArgs, "Compile", shouldFail: true);
return RunAndAssert ("/Applications/Visual Studio.app/Contents/MacOS/vstool", buildArgs, "Compile", shouldFail: true, environment: env);
}
public static string BuildProject (string csprojTarget, bool shouldFail = false, bool release = false, string[] environment = null, IList<string> extraArgs = null)
public static string BuildProject (string csprojTarget, bool shouldFail = false, bool release = false, Dictionary<string, string> environment = null, IList<string> extraArgs = null)
{
string rootDirectory = FindRootDirectory ();
// TODO - This is not enough for MSBuild to really work. We need stuff to have it not use system targets!
// These are required to have xbuild use are local build instead of system install
Environment.SetEnvironmentVariable ("TargetFrameworkFallbackSearchPaths", rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild-frameworks");
Environment.SetEnvironmentVariable ("MSBuildExtensionsPathFallbackPathsOverride", rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild");
Environment.SetEnvironmentVariable ("XAMMAC_FRAMEWORK_PATH", rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current");
Environment.SetEnvironmentVariable ("XamarinMacFrameworkRoot", rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current");
environment ??= new Dictionary<string, string> ();
environment ["TargetFrameworkFallbackSearchPaths"] = rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild-frameworks";
environment ["MSBuildExtensionsPathFallbackPathsOverride"] = rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild";
environment ["XAMMAC_FRAMEWORK_PATH"] = rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current";
environment ["XamarinMacFrameworkRoot"] = rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current";
// This is to force build to use our mmp and not system mmp
var buildArgs = new List<string> ();
@ -433,7 +437,7 @@ namespace Xamarin.MMP.Tests
return GenerateEXEProject (config);
}
public static string GenerateAndBuildUnifiedExecutable (UnifiedTestConfig config, bool shouldFail = false, string[] environment = null)
public static string GenerateAndBuildUnifiedExecutable (UnifiedTestConfig config, bool shouldFail = false, Dictionary<string, string> environment = null)
{
string csprojTarget = GenerateUnifiedExecutableProject (config);
return BuildProject (csprojTarget, shouldFail: shouldFail, release: config.Release, environment: environment);
@ -444,7 +448,7 @@ namespace Xamarin.MMP.Tests
return RunEXEAndVerifyGUID (config.TmpDir, config.guid, config.ExecutablePath);
}
public static OutputText TestUnifiedExecutable (UnifiedTestConfig config, bool shouldFail = false, string[] environment = null)
public static OutputText TestUnifiedExecutable (UnifiedTestConfig config, bool shouldFail = false, Dictionary<string, string> environment = null)
{
AddGUIDTestCode (config);
@ -583,12 +587,15 @@ namespace TestCase
{
string rootDirectory = FindRootDirectory ();
Environment.SetEnvironmentVariable ("TargetFrameworkFallbackSearchPaths", rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild-frameworks");
Environment.SetEnvironmentVariable ("MSBuildExtensionsPathFallbackPathsOverride", rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild");
Environment.SetEnvironmentVariable ("XAMMAC_FRAMEWORK_PATH", rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current");
Environment.SetEnvironmentVariable ("XamarinMacFrameworkRoot", rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current");
var env = new Dictionary<string, string> {
{ "TargetFrameworkFallbackSearchPaths", rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild-frameworks" },
{ "MSBuildExtensionsPathFallbackPathsOverride", rootDirectory + "/Library/Frameworks/Mono.framework/External/xbuild" },
{ "XAMMAC_FRAMEWORK_PATH", rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current" },
{ "XamarinMacFrameworkRoot", rootDirectory + "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current" },
};
var rv = ExecutionHelper.Execute (Configuration.XIBuildPath, new [] { $"--", "/t:Restore", project}, out var output);
var output = new StringBuilder ();
var rv = ExecutionHelper.Execute (Configuration.XIBuildPath, new [] { $"--", "/t:Restore", project}, stdout: output, stderr: output, environmentVariables: env);
if (rv != 0) {
Console.WriteLine ("nuget restore failed:");
Console.WriteLine (output);
@ -629,88 +636,3 @@ namespace TestCase
}
}
}
// A bit of a hack so we can reuse all of the RunCommand logic
#if !MMP_TEST
namespace Xamarin.Bundler {
public static partial class Driver
{
public static int verbose { get { return 0; } }
public static int Verbosity { get { return verbose; }}
public static int RunCommand (string path, IList<string> args, string[] env = null, StringBuilder output = null, bool suppressPrintOnErrors = false)
{
Exception stdin_exc = null;
var info = new ProcessStartInfo (path, StringUtils.FormatArguments (args));
info.UseShellExecute = false;
info.RedirectStandardInput = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
System.Threading.ManualResetEvent stdout_completed = new System.Threading.ManualResetEvent (false);
System.Threading.ManualResetEvent stderr_completed = new System.Threading.ManualResetEvent (false);
if (output == null)
output = new StringBuilder ();
if (env != null){
if (env.Length % 2 != 0)
throw new Exception ("You passed an environment key without a value");
for (int i = 0; i < env.Length; i += 2) {
if (env [i + 1] == null) {
info.EnvironmentVariables.Remove (env [i]);
} else {
info.EnvironmentVariables [env [i]] = env [i + 1];
}
}
}
if (verbose > 0)
Console.WriteLine ("{0} {1}", path, args);
using (var p = Process.Start (info)) {
p.OutputDataReceived += (s, e) => {
if (e.Data != null) {
lock (output)
output.AppendLine (e.Data);
} else {
stdout_completed.Set ();
}
};
p.ErrorDataReceived += (s, e) => {
if (e.Data != null) {
lock (output)
output.AppendLine (e.Data);
} else {
stderr_completed.Set ();
}
};
p.BeginOutputReadLine ();
p.BeginErrorReadLine ();
p.WaitForExit ();
stderr_completed.WaitOne (TimeSpan.FromSeconds (1));
stdout_completed.WaitOne (TimeSpan.FromSeconds (1));
if (p.ExitCode != 0) {
// note: this repeat the failing command line. However we can't avoid this since we're often
// running commands in parallel (so the last one printed might not be the one failing)
if (!suppressPrintOnErrors)
Console.Error.WriteLine ("Process exited with code {0}, command:\n{1} {2}{3}", p.ExitCode, path, StringUtils.FormatArguments (args), output.Length > 0 ? "\n" + output.ToString () : string.Empty);
return p.ExitCode;
} else if (verbose > 0 && output.Length > 0 && !suppressPrintOnErrors) {
Console.WriteLine (output.ToString ());
}
if (stdin_exc != null)
throw stdin_exc;
}
return 0;
}
}
}
#endif

Просмотреть файл

@ -29,6 +29,9 @@
<Compile Include="..\..\..\tools\common\ApplePlatform.cs">
<Link>external\ApplePlatform.cs</Link>
</Compile>
<Compile Include="..\..\..\tools\common\Execution.cs">
<Link>external\Execution.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="external\" />

Просмотреть файл

@ -59,12 +59,11 @@ namespace Xamarin.MMP.Tests.Unit
commandsRun.Clear ();
}
int OnRunCommand (string path, IList<string> args, string [] env, StringBuilder output, bool suppressPrintOnErrors)
int OnRunCommand (string path, IList<string> args, Dictionary<string, string> env, StringBuilder output, bool suppressPrintOnErrors)
{
commandsRun.Add (Tuple.Create <string, IList<string>>(path, args));
if (path != AOTCompiler.StripCommand && path != AOTCompiler.DeleteDebugSymbolCommand) {
Assert.IsTrue (env[0] == "MONO_PATH", "MONO_PATH should be first env set");
Assert.IsTrue (env[1] == TestRootDir, "MONO_PATH should be set to our expected value");
Assert.AreEqual (TestRootDir, env ["MONO_PATH"], "MONO_PATH should be set to our expected value");
}
return 0;
}

Просмотреть файл

@ -11,6 +11,7 @@
<RootNamespace>Xamarin.MMP.Tests</RootNamespace>
<AssemblyName>mmptest</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>

Просмотреть файл

@ -26,7 +26,7 @@ namespace Xamarin.MMP.Tests
TI.UnifiedTestConfig test = new TI.UnifiedTestConfig (tmpDir) { CSProjConfig = projectConfig };
string buildOutput = TI.TestUnifiedExecutable (test).BuildOutput;
string [] splitBuildOutput = TI.TestUnifiedExecutable (test).BuildOutput.Split (new string[] { Environment.NewLine }, StringSplitOptions.None);
string clangInvocation = splitBuildOutput.Single (x => x.Contains ("usr/bin/clang"));
string clangInvocation = splitBuildOutput.Single (x => x.Contains ("usr/bin/clang") && x.Contains ("mmacosx-version-min"));
return clangInvocation.Split (new string[] { " " }, StringSplitOptions.None);
}
@ -548,7 +548,7 @@ namespace Xamarin.MMP.Tests
TI.UnifiedTestConfig test = new TI.UnifiedTestConfig (tmpDir) {
CSProjConfig = "<DebugSymbols>True</DebugSymbols>", // This makes the msbuild tasks pass /debug to mmp
};
TI.TestUnifiedExecutable (test, shouldFail: false, environment: new [] { "MD_APPLE_SDK_ROOT", Path.GetDirectoryName (Path.GetDirectoryName (oldXcode)) });
TI.TestUnifiedExecutable (test, shouldFail: false, environment: new Dictionary<string, string> { { "MD_APPLE_SDK_ROOT", Path.GetDirectoryName (Path.GetDirectoryName (oldXcode)) } });
});
}

Просмотреть файл

@ -112,8 +112,10 @@ namespace Xamarin.MMP.Tests
MMPTests.RunMMPTest (tmpDir => {
TI.UnifiedTestConfig test = new TI.UnifiedTestConfig (tmpDir) { ItemGroup = CreateSingleNativeRef (SimpleStaticPath, "Static") };
NativeReferenceTestCore (tmpDir, test, "Unified_WithNativeReferences_InMainProjectWorks - Static", null, true, false, s => {
string clangLine = s.Split ('\n').First (x => x.Contains ("usr/bin/clang"));
return clangLine.Contains ("SimpleClassStatic.a");
var clangLines = s.Split ('\n').Where (x => x.Contains ("usr/bin/clang"));
var staticLib = clangLines.Where (x => x.Contains ("SimpleClassStatic.a"));
Assert.That (staticLib, Is.Not.Empty, "SimpleClassStatic.a:\n\t{0}", string.Join ("\n\t", clangLines));
return true;
});
});
}

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
@ -31,10 +32,10 @@ namespace Xamarin.MMP.Tests {
string netStandardProject = TI.GenerateNetStandardProject (config);
var environment = new string [] {
"MSBUILD_EXE_PATH", null,
"MSBuildExtensionsPathFallbackPathsOverride", null,
"MSBuildSDKsPath", null,
var environment = new Dictionary<string, string> {
{ "MSBUILD_EXE_PATH", null },
{ "MSBuildExtensionsPathFallbackPathsOverride", null },
{ "MSBuildSDKsPath", null },
};
TI.RunAndAssert("/usr/local/share/dotnet/dotnet", new [] { "restore", netStandardProject }, "Restore", environment: environment);

Просмотреть файл

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
@ -20,7 +21,7 @@ namespace Xamarin.MMP.Tests
MMPTests.RunMMPTest (tmpDir => {
TI.UnifiedTestConfig test = new TI.UnifiedTestConfig (tmpDir);
var output = TI.TestUnifiedExecutable (test, environment: new string[] { "MD_APPLE_SDK_ROOT", Path.GetDirectoryName (Path.GetDirectoryName (oldXcode)) });
var output = TI.TestUnifiedExecutable (test, environment: new Dictionary<string, string> { { "MD_APPLE_SDK_ROOT", Path.GetDirectoryName (Path.GetDirectoryName (oldXcode)) } });
output.Messages.AssertWarningPattern (135, $"Did not link system framework");
});
}

Просмотреть файл

@ -11,6 +11,7 @@
<RootNamespace>msbuildMac</RootNamespace>
<AssemblyName>msbuild-mac</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
@ -64,6 +65,12 @@
<Compile Include="..\common\Profile.cs">
<Link>Profile.cs</Link>
</Compile>
<Compile Include="..\..\tools\common\Driver.execution.cs">
<Link>Driver.execution.cs</Link>
</Compile>
<Compile Include="..\..\tools\common\Execution.cs">
<Link>Execution.cs</Link>
</Compile>
<Compile Include="..\mtouch\Cache.cs">
<Link>Cache.cs</Link>
</Compile>

Просмотреть файл

@ -130,7 +130,7 @@ namespace Xamarin.MMP.Tests
Assert.IsTrue (File.Exists (Path.Combine (tmpDir, "bin/Debug/UnifiedExample.app/Contents/MonoBundle/SimpleClassDylib.dylib")));
StringBuilder output = new StringBuilder ();
Xamarin.Bundler.Driver.RunCommand ("/usr/bin/otool", new [] { "-L", Path.Combine (tmpDir, "bin/Debug/UnifiedExample.app/Contents/MacOS/UnifiedExample") }, null, output);
Xamarin.Bundler.Driver.RunCommand ("/usr/bin/otool", new [] { "-L", Path.Combine (tmpDir, "bin/Debug/UnifiedExample.app/Contents/MacOS/UnifiedExample") }, output);
Assert.IsTrue (output.ToString ().Contains ("SimpleClassDylib.dylib"));
});
}

Просмотреть файл

@ -107,6 +107,12 @@
</Compile>
<Compile Include="Setup.cs" />
<Compile Include="Compat.cs" />
<Compile Include="..\..\tools\common\Driver.execution.cs">
<Link>Driver.execution.cs</Link>
</Compile>
<Compile Include="..\..\tools\common\Execution.cs">
<Link>Execution.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

Просмотреть файл

@ -60,6 +60,9 @@
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Samples.cs" />
<Compile Include="..\..\tools\common\Execution.cs">
<Link>external\Execution.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

Просмотреть файл

@ -101,6 +101,12 @@
<Compile Include="..\..\tests\test-libraries\TrampolineTest.generated.cs" />
<Compile Include="..\..\tests\test-libraries\RegistrarTest.generated.cs" />
<Compile Include="AppKit\NSGridViewTest.cs" />
<Compile Include="..\..\tools\common\Driver.execution.cs">
<Link>Driver.execution.cs</Link>
</Compile>
<Compile Include="..\..\tools\common\Execution.cs">
<Link>Execution.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="Info.plist" />

Просмотреть файл

@ -490,174 +490,6 @@ namespace Xamarin.Bundler {
ErrorHelper.Warning (90, Errors.MX0090, /* The target framework '{0}' is deprecated. Use '{1}' instead. */ fx, TargetFramework);
}
public static int RunCommand (string path, params string [] args)
{
return RunCommand (path, args, null, (Action<string>) null, (Action<string>) null, false);
}
public static int RunCommand (string path, IList<string> args)
{
return RunCommand (path, args, null, (Action<string>) null, (Action<string>) null, false);
}
public static int RunCommand (string path, IList<string> args, StringBuilder output)
{
return RunCommand (path, args, null, output, output, false);
}
public static int RunCommand (string path, IList<string> args, StringBuilder output, bool suppressPrintOnErrors)
{
return RunCommand (path, args, null, output, output, suppressPrintOnErrors);
}
public static int RunCommand (string path, IList<string> args, string [] env, StringBuilder output)
{
return RunCommand (path, args, env, output, output, false);
}
public static int RunCommand (string path, IList<string> args, string [] env, StringBuilder output, bool suppressPrintOnErrors)
{
return RunCommand (path, args, env, output, output, suppressPrintOnErrors);
}
public static int RunCommand (string path, IList<string> args, string [] env, StringBuilder output, StringBuilder error)
{
return RunCommand (path, args, env, output, error, false);
}
public static int RunCommand (string path, IList<string> args, StringBuilder output, StringBuilder error)
{
return RunCommand (path, args, null, output, error, false);
}
public static int RunCommand (string path, IList<string> args, StringBuilder output, StringBuilder error, bool suppressPrintOnErrors)
{
return RunCommand (path, args, null, output, error, suppressPrintOnErrors);
}
public static int RunCommand (string path, IList<string> args, string [] env, StringBuilder output, StringBuilder error, bool suppressPrintOnErrors)
{
var output_received = output == null ? null : new Action<string> ((v) => { if (v != null) output.AppendLine (v); });
var error_received = error == null ? null : new Action<string> ((v) => { if (v != null) error.AppendLine (v); });
return RunCommand (path, args, env, output_received, error_received, suppressPrintOnErrors);
}
static int RunCommand (string path, IList<string> args, string [] env, Action<string> output_received, bool suppressPrintOnErrors)
{
return RunCommand (path, args, env, output_received, output_received, suppressPrintOnErrors);
}
static int RunCommand (string path, IList<string> args, string [] env, Action<string> output_received, Action<string> error_received)
{
return RunCommand (path, args, env, output_received, error_received, false);
}
static int RunCommand (string path, IList<string> args, string[] env, Action<string> output_received, Action<string> error_received, bool suppressPrintOnErrors)
{
Exception stdin_exc = null;
var info = new ProcessStartInfo (path, StringUtils.FormatArguments (args));
info.UseShellExecute = false;
info.RedirectStandardInput = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
System.Threading.ManualResetEvent stdout_completed = new System.Threading.ManualResetEvent (false);
System.Threading.ManualResetEvent stderr_completed = new System.Threading.ManualResetEvent (false);
if (output_received == null ^ error_received == null)
throw new ArgumentException ("Either both or neither of 'output_received' and 'error_received' can be specified.");
var lockobj = new object ();
StringBuilder output = null;
if (output_received == null) {
output = new StringBuilder ();
output_received = (line) => {
if (line != null)
output.AppendLine (line);
};
error_received = output_received;
}
if (env != null){
if (env.Length % 2 != 0)
throw new Exception ("You passed an environment key without a value");
for (int i = 0; i < env.Length; i += 2) {
if (env [i + 1] == null) {
info.EnvironmentVariables.Remove (env [i]);
} else {
info.EnvironmentVariables [env [i]] = env [i + 1];
}
}
}
Log (1, "{0} {1}", info.FileName, info.Arguments);
using (var p = Process.Start (info)) {
p.OutputDataReceived += (s, e) => {
if (e.Data != null) {
lock (lockobj)
output_received (e.Data);
} else {
stdout_completed.Set ();
}
};
p.ErrorDataReceived += (s, e) => {
if (e.Data != null) {
lock (lockobj)
error_received (e.Data);
} else {
stderr_completed.Set ();
}
};
p.BeginOutputReadLine ();
p.BeginErrorReadLine ();
p.WaitForExit ();
stderr_completed.WaitOne (TimeSpan.FromSeconds (1));
stdout_completed.WaitOne (TimeSpan.FromSeconds (1));
output_received (null);
if (p.ExitCode != 0) {
// note: this repeat the failing command line. However we can't avoid this since we're often
// running commands in parallel (so the last one printed might not be the one failing)
if (!suppressPrintOnErrors) {
// We re-use the stringbuilder so that we avoid duplicating the amount of required memory,
// while only calling Console.WriteLine once to make it less probable that other threads
// also write to the Console, confusing the output.
if (output == null)
output = new StringBuilder ();
output.Insert (0, $"Process exited with code {p.ExitCode}, command:\n{path} {StringUtils.FormatArguments (args)}\n");
Console.Error.WriteLine (output);
}
return p.ExitCode;
} else if (Verbosity > 0 && output != null && output.Length > 0 && !suppressPrintOnErrors) {
Console.WriteLine (output.ToString ());
}
if (stdin_exc != null)
throw stdin_exc;
}
return 0;
}
public static Task<int> RunCommandAsync (string path, string[] args, string [] env = null, StringBuilder output = null, bool suppressPrintOnErrors = false)
{
if (output != null)
return RunCommandAsync (path, args, env, (v) => { if (v != null) output.AppendLine (v); }, suppressPrintOnErrors);
return RunCommandAsync (path, args, env, (Action<string>) null, suppressPrintOnErrors);
}
public static Task<int> RunCommandAsync (string path, string[] args, string [] env = null, Action<string> output_received = null, bool suppressPrintOnErrors = false)
{
return Task.Run (() => RunCommand (path, args, env, output_received, suppressPrintOnErrors));
}
#if !MMP_TEST
static void FileMove (string source, string target)
{
@ -1262,15 +1094,12 @@ namespace Xamarin.Bundler {
static bool XcrunFind (ApplePlatform platform, bool is_simulator, string tool, out string path)
{
var env = new List<string> ();
var env = new Dictionary<string, string> ();
// Unset XCODE_DEVELOPER_DIR_PATH. See https://github.com/xamarin/xamarin-macios/issues/3931.
env.Add ("XCODE_DEVELOPER_DIR_PATH");
env.Add (null);
env.Add ("XCODE_DEVELOPER_DIR_PATH", null);
// Set DEVELOPER_DIR if we have it
if (!string.IsNullOrEmpty (DeveloperDirectory)) {
env.Add ("DEVELOPER_DIR");
env.Add (DeveloperDirectory);
}
if (!string.IsNullOrEmpty (DeveloperDirectory))
env.Add ("DEVELOPER_DIR", DeveloperDirectory);
path = null;
@ -1304,7 +1133,7 @@ namespace Xamarin.Bundler {
// We also want to print out what happened if something went wrong, and in that case we don't want stdout
// and stderr captured separately, because related lines could end up printed far from eachother in time,
// and that's confusing. So capture stdout and stderr by themselves, and also capture both together.
int ret = RunCommand ("xcrun", args, env.ToArray (),
int ret = RunCommand ("xcrun", args, env,
(v) => {
lock (both) {
both.AppendLine (v);

Просмотреть файл

@ -0,0 +1,146 @@
/*
* Copyright 2014 Xamarin Inc. All rights reserved.
* Copyright 2019 Microsoft Corp. All rights reserved.
*
* Authors:
* Rolf Bjarne Kvinge <rolf@xamarin.com>
*
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Utils;
namespace Xamarin.Bundler {
public partial class Driver {
public static int RunCommand (string path, IList<string> args, Dictionary<string, string> env, out StringBuilder output, bool suppressPrintOnErrors, int verbose)
{
output = new StringBuilder ();
return RunCommand (path, args, env, output, suppressPrintOnErrors, verbose);
}
public static int RunCommand (string path, IList<string> args, Dictionary<string, string> env, out StringBuilder output, bool suppressPrintOnErrors)
{
output = new StringBuilder ();
return RunCommand (path, args, env, output, suppressPrintOnErrors, Verbosity);
}
public static int RunCommand (string path, IList<string> args, Dictionary<string, string> env, StringBuilder output, bool suppressPrintOnErrors, int verbosity)
{
return RunCommand (path, args, env, output, output, suppressPrintOnErrors, verbosity);
}
public static int RunCommand (string path, IList<string> args, Dictionary<string, string> env, StringBuilder output, bool suppressPrintOnErrors = false)
{
return RunCommand (path, args, env, output, output, suppressPrintOnErrors, Verbosity);
}
public static int RunCommand (string path, params string [] args)
{
return RunCommand (path, args, null, (Action<string>) null, (Action<string>) null, false, Verbosity);
}
public static int RunCommand (string path, IList<string> args)
{
return RunCommand (path, args, null, (Action<string>) null, (Action<string>) null, false, Verbosity);
}
public static int RunCommand (string path, IList<string> args, StringBuilder output)
{
return RunCommand (path, args, null, output, output, false, Verbosity);
}
public static int RunCommand (string path, IList<string> args, StringBuilder output, bool suppressPrintOnErrors)
{
return RunCommand (path, args, null, output, output, suppressPrintOnErrors, Verbosity);
}
public static int RunCommand (string path, IList<string> args, StringBuilder output, StringBuilder error)
{
return RunCommand (path, args, null, output, error, false, Verbosity);
}
public static int RunCommand (string path, IList<string> args, StringBuilder output, StringBuilder error, bool suppressPrintOnErrors)
{
return RunCommand (path, args, null, output, error, suppressPrintOnErrors, Verbosity);
}
public static int RunCommand (string path, IList<string> args, Dictionary<string, string> env, StringBuilder output, StringBuilder error, bool suppressPrintOnErrors, int verbosity)
{
var output_received = output == null ? null : new Action<string> ((v) => { if (v != null) output.AppendLine (v); });
var error_received = error == null ? null : new Action<string> ((v) => { if (v != null) error.AppendLine (v); });
return RunCommand (path, args, env, output_received, error_received, suppressPrintOnErrors, verbosity);
}
static int RunCommand (string path, IList<string> args, Dictionary<string, string> env, Action<string> output_received, bool suppressPrintOnErrors)
{
return RunCommand (path, args, env, output_received, output_received, suppressPrintOnErrors, Verbosity);
}
static int RunCommand (string path, IList<string> args, Dictionary<string, string> env, Action<string> output_received, Action<string> error_received)
{
return RunCommand (path, args, env, output_received, error_received, false, Verbosity);
}
static int RunCommand (string path, IList<string> args, Dictionary<string, string> env, Action<string> output_received, Action<string> error_received, bool suppressPrintOnErrors, int verbosity)
{
var output = new StringBuilder ();
var outputCallback = new Action<string> ((string line) => {
if (output_received != null)
output_received (line);
lock (output)
output.AppendLine (line);
});
var errorCallback = new Action<string> ((string line) => {
if (error_received != null)
error_received (line);
lock (output)
output.AppendLine (line);
});
if (verbosity > 0)
Console.WriteLine ($"{path} {StringUtils.FormatArguments (args)}");
var p = Execution.RunWithCallbacksAsync (path, args, env, outputCallback, errorCallback).Result;
if (output_received != null)
output_received (null);
if (error_received != null)
error_received (null);
if (p.ExitCode != 0) {
// note: this repeats the failing command line. However we can't avoid this since we're often
// running commands in parallel (so the last one printed might not be the one failing)
if (!suppressPrintOnErrors) {
// We re-use the stringbuilder so that we avoid duplicating the amount of required memory,
// while only calling Console.WriteLine once to make it less probable that other threads
// also write to the Console, confusing the output.
output.Insert (0, $"Process exited with code {p.ExitCode}, command:\n{path} {StringUtils.FormatArguments (args)}\n");
Console.Error.WriteLine (output);
}
return p.ExitCode;
} else if (verbosity > 0 && output?.Length > 0 && !suppressPrintOnErrors) {
Console.WriteLine (output.ToString ());
}
return p.ExitCode;
}
public static Task<int> RunCommandAsync (string path, IList<string> args, Dictionary<string, string> env = null, Action<string> output_received = null, bool suppressPrintOnErrors = false, int? verbosity = null)
{
return Task.Run (() => RunCommand (path, args, env, output_received, output_received, suppressPrintOnErrors, verbosity ?? Verbosity));
}
public static Task<int> RunCommandAsync (string path, IList<string> args, Dictionary<string, string> env = null, StringBuilder output = null, bool suppressPrintOnErrors = false, int? verbosity = null)
{
return Task.Run (() => RunCommand (path, args, env, output, output, suppressPrintOnErrors, verbosity ?? Verbosity));
}
#if !MTOUCH && !MMP
internal static int Verbosity;
#endif
}
}

Просмотреть файл

@ -1,6 +1,6 @@
/*
* Copyright 2014 Xamarin Inc. All rights reserved.
* Copyright 2019 Microsoft Corp. All rights reserved.
* Copyright 2019, 2020 Microsoft Corp. All rights reserved.
*
* Authors:
* Rolf Bjarne Kvinge <rolf@xamarin.com>
@ -10,94 +10,200 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Utils;
namespace Xamarin.Utils {
public class Execution {
public string FileName;
public IList<string> Arguments;
public IDictionary<string, string> Environment;
public string WorkingDirectory;
public TimeSpan? Timeout;
public CancellationToken? CancellationToken;
namespace Xamarin.Bundler {
public partial class Driver {
public static int RunCommand (string path, IList<string> args, string [] env, out StringBuilder output, bool suppressPrintOnErrors, int verbose)
public TextWriter Log;
public int ExitCode { get; private set; }
public bool TimedOut { get; private set; }
public TextWriter StandardOutput { get; private set; }
public TextWriter StandardError { get; private set; }
static Thread StartOutputThread (TaskCompletionSource<Execution> tcs, object lockobj, StreamReader reader, TextWriter writer, string thread_name)
{
output = new StringBuilder ();
return RunCommand (path, StringUtils.FormatArguments (args), env, output, suppressPrintOnErrors, verbose);
}
public static int RunCommand (string path, IList<string> args, string [] env, StringBuilder output, bool suppressPrintOnErrors, int verbose)
{
return RunCommand (path, StringUtils.FormatArguments (args), env, output, suppressPrintOnErrors, verbose);
}
static int RunCommand (string path, string args, string[] env, StringBuilder output, bool suppressPrintOnErrors, int verbose)
{
var info = new ProcessStartInfo (path, args);
info.UseShellExecute = false;
info.RedirectStandardInput = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
var stdout_completed = new ManualResetEvent (false);
var stderr_completed = new ManualResetEvent (false);
if (output == null)
output = new StringBuilder ();
if (env != null){
if (env.Length % 2 != 0)
throw new Exception ("You passed an environment key without a value");
for (int i = 0; i < env.Length; i+= 2)
info.EnvironmentVariables [env[i]] = env[i+1];
}
if (verbose > 0)
Console.WriteLine ("{0} {1}", path, args);
using (var p = Process.Start (info)) {
p.OutputDataReceived += (s, e) => {
if (e.Data != null) {
lock (output)
output.AppendLine (e.Data);
} else {
stdout_completed.Set ();
var thread = new Thread (() => {
try {
string line;
while ((line = reader.ReadLine ()) != null) {
lock (lockobj)
writer.WriteLine (line);
}
};
p.ErrorDataReceived += (s, e) => {
if (e.Data != null) {
lock (output)
output.AppendLine (e.Data);
} else {
stderr_completed.Set ();
}
};
p.BeginOutputReadLine ();
p.BeginErrorReadLine ();
p.WaitForExit ();
stderr_completed.WaitOne (TimeSpan.FromSeconds (1));
stdout_completed.WaitOne (TimeSpan.FromSeconds (1));
if (p.ExitCode != 0) {
// note: this repeat the failing command line. However we can't avoid this since we're often
// running commands in parallel (so the last one printed might not be the one failing)
if (!suppressPrintOnErrors)
Console.Error.WriteLine ("Process exited with code {0}, command:\n{1} {2}{3}", p.ExitCode, path, args, output.Length > 0 ? "\n" + output.ToString () : string.Empty);
return p.ExitCode;
} else if (verbose > 0 && output.Length > 0 && !suppressPrintOnErrors) {
Console.WriteLine (output.ToString ());
} catch (Exception e) {
tcs.TrySetException (e);
} finally {
// The Process instance doesn't dispose these streams, which means we need to do it,
// otherwise we can run out of file descriptors while waiting for the GC to kick in.
// Ref: https://bugzilla.xamarin.com/show_bug.cgi?id=43462
reader.Dispose ();
}
}
return 0;
}) {
IsBackground = true,
Name = thread_name,
};
thread.Start ();
return thread;
}
public static Task<int> RunCommandAsync (string path, string args, string [] env, StringBuilder output, bool suppressPrintOnErrors, int verbose)
public Task<Execution> RunAsync ()
{
return Task.Run (() => RunCommand (path, args, env, output, suppressPrintOnErrors, verbose));
var tcs = new TaskCompletionSource<Execution> ();
var lockobj = new object ();
try {
var p = new Process ();
p.StartInfo.FileName = FileName;
p.StartInfo.Arguments = StringUtils.FormatArguments (Arguments);
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
if (!string.IsNullOrEmpty (WorkingDirectory))
p.StartInfo.WorkingDirectory = WorkingDirectory;
// mtouch/mmp writes UTF8 data outside of the ASCII range, so we need to make sure
// we read it in the same format. This also means we can't use the events to get
// stdout/stderr, because mono's Process class parses those using Encoding.Default.
p.StartInfo.StandardOutputEncoding = Encoding.UTF8;
p.StartInfo.StandardErrorEncoding = Encoding.UTF8;
if (Environment != null) {
foreach (var kvp in Environment) {
if (kvp.Value == null) {
p.StartInfo.EnvironmentVariables.Remove (kvp.Key);
} else {
p.StartInfo.EnvironmentVariables [kvp.Key] = kvp.Value;
}
}
}
StandardOutput ??= new StringWriter ();
StandardError ??= new StringWriter ();
var thread = new Thread (() => {
try {
if (Log != null) {
if (!string.IsNullOrEmpty (p.StartInfo.WorkingDirectory))
Log.Write ($"cd {StringUtils.Quote (p.StartInfo.WorkingDirectory)} && ");
Log.WriteLine ("{0} {1}", p.StartInfo.FileName, p.StartInfo.Arguments);
}
p.Start ();
var pid = p.Id;
var stdoutThread = StartOutputThread (tcs, lockobj, p.StandardOutput, StandardOutput, $"StandardOutput reader for {p.StartInfo.FileName} (PID: {pid})");
var stderrThread = StartOutputThread (tcs, lockobj, p.StandardError, StandardError, $"StandardError reader for {p.StartInfo.FileName} (PID: {pid})");
CancellationToken?.Register (() => {
// Don't call tcs.TrySetCanceled, that won't return an Execution result to the caller.
try {
p.Kill ();
} catch (Exception ex) {
// The process could be disposed already. Just ignore any exceptions here.
Log?.WriteLine ($"Failed to cancel and kill PID {pid}: {ex.Message}");
}
});
if (Timeout.HasValue) {
if (!p.WaitForExit ((int) Timeout.Value.TotalMilliseconds)) {
Log?.WriteLine ($"Command '{p.StartInfo.FileName} {p.StartInfo.Arguments}' didn't finish in {Timeout.Value.TotalMilliseconds} minutes, and will be killed.");
TimedOut = true;
try {
p.Kill ();
} catch (Exception ex) {
// According to the documentation, there can be exceptions here we can't prepare for, so just ignore them.
Log?.WriteLine ($"Failed to kill PID {pid}: {ex.Message}");
}
}
}
// Always call this WaitForExit overload to be make sure the stdout/stderr buffers have been flushed,
// even if we've called the WaitForExit (int) overload
p.WaitForExit ();
ExitCode = p.ExitCode;
stdoutThread.Join (TimeSpan.FromSeconds (1));
stderrThread.Join (TimeSpan.FromSeconds (1));
tcs.TrySetResult (this);
} catch (Exception e) {
tcs.TrySetException (e);
} finally {
p.Dispose ();
}
}) {
IsBackground = true,
Name = $"Thread waiting for {p.StartInfo.FileName} to finish",
};
thread.Start ();
} catch (Exception e) {
tcs.TrySetException (e);
}
return tcs.Task;
}
public static Task<Execution> RunWithCallbacksAsync (string filename, IList<string> arguments, Dictionary<string, string> environment = null, Action<string> standardOutput = null, Action<string> standardError = null, TextWriter log = null, string workingDirectory = null, TimeSpan? timeout = null, CancellationToken? cancellationToken = null)
{
CallbackWriter outputCallback = null;
CallbackWriter errorCallback = null;
if (standardOutput != null)
outputCallback = new CallbackWriter { Callback = standardOutput };
if (standardOutput == standardError)
errorCallback = outputCallback;
else if (standardError != null)
errorCallback = new CallbackWriter { Callback = standardError };
return RunAsync (filename, arguments, environment, outputCallback, errorCallback, log, workingDirectory, timeout, cancellationToken);
}
public static Task<Execution> RunAsync (string filename, IList<string> arguments, Dictionary<string, string> environment = null, TextWriter standardOutput = null, TextWriter standardError = null, TextWriter log = null, string workingDirectory = null, TimeSpan? timeout = null, CancellationToken? cancellationToken = null)
{
return new Execution {
FileName = filename,
Arguments = arguments,
Environment = environment,
StandardOutput = standardOutput,
StandardError = standardError,
WorkingDirectory = workingDirectory,
CancellationToken = cancellationToken,
Timeout = timeout,
Log = log,
}.RunAsync ();
}
public static Task<Execution> RunAsync (string filename, IList<string> arguments, Dictionary<string, string> environment = null, bool mergeOutput = false, string workingDirectory = null, TextWriter log = null, TimeSpan? timeout = null, CancellationToken? cancellationToken = null)
{
var standardOutput = new StringWriter ();
var standardError = mergeOutput ? standardOutput : new StringWriter ();
return RunAsync (filename, arguments, environment, standardOutput, standardError, log, workingDirectory, timeout, cancellationToken);
}
public static Task<Execution> RunWithStringBuildersAsync (string filename, IList<string> arguments, Dictionary<string, string> environment = null, StringBuilder standardOutput = null, StringBuilder standardError = null, TextWriter log = null, string workingDirectory = null, TimeSpan? timeout = null, CancellationToken? cancellationToken = null)
{
var stdout = standardOutput == null ? null : new StringWriter (standardOutput);
var stderr = standardError == null ? null : (standardOutput == standardError ? stdout : new StringWriter (standardError));
return RunAsync (filename, arguments, environment, stdout, stderr, log, workingDirectory, timeout, cancellationToken);
}
class CallbackWriter : TextWriter {
public Action<string> Callback;
public override void WriteLine (string value)
{
Callback (value);
}
public override Encoding Encoding => Encoding.UTF8;
}
}
}

Просмотреть файл

@ -55,7 +55,7 @@ namespace Xamarin.Bundler {
}
}
public delegate int RunCommandDelegate (string path, IList<string> args, string[] env = null, StringBuilder output = null, bool suppressPrintOnErrors = false);
public delegate int RunCommandDelegate (string path, IList<string> args, Dictionary<string, string> env = null, StringBuilder output = null, bool suppressPrintOnErrors = false);
public enum AOTCompilerType {
Invalid,
@ -202,7 +202,7 @@ namespace Xamarin.Bundler {
if (!options.IsAOT)
throw ErrorHelper.CreateError (0099, Errors.MX0099, $"\"AOTBundle with aot: {options.CompilationType}\" ");
var monoEnv = new string [] {"MONO_PATH", files.RootDir };
var monoEnv = new Dictionary<string, string> { { "MONO_PATH", files.RootDir } };
List<string> filesToAOT = GetFilesToAOT (files);
Parallel.ForEach (filesToAOT, ParallelOptions, file => {

Просмотреть файл

@ -35,7 +35,6 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.InteropServices;
@ -1084,10 +1083,10 @@ namespace Xamarin.Bundler {
static string RunPkgConfig (string option, bool force_system_mono = false)
{
string [] env = null;
Dictionary<string, string> env = null;
if (!IsUnifiedFullSystemFramework && !force_system_mono)
env = new [] { "PKG_CONFIG_PATH", Path.Combine (FrameworkLibDirectory, "pkgconfig") };
env = new Dictionary<string, string> { { "PKG_CONFIG_PATH", Path.Combine (FrameworkLibDirectory, "pkgconfig") } };
var sb = new StringBuilder ();
int rv;

Просмотреть файл

@ -446,6 +446,9 @@
<Compile Include="..\common\ErrorHelper.tools.cs">
<Link>tools\common\ErrorHelper.tools.cs</Link>
</Compile>
<Compile Include="..\common\Driver.execution.cs">
<Link>tools\common\Driver.execution.cs</Link>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
@ -480,6 +483,7 @@
<None Include="..\..\docs\website\mmp-errors.md">
<Link>docs\website\mmp-errors.md</Link>
</None>
<None Include="..\.editorconfig" />
</ItemGroup>
<Import Project="..\..\packages\XliffTasks.1.0.0-beta.20060.1\build\XliffTasks.targets" Condition="Exists('..\..\packages\XliffTasks.1.0.0-beta.20060.1\build\XliffTasks.targets')" />
<Target Name="BuildSdkVersions" Inputs="../common/SdkVersions.cs.in" Outputs="../common/SdkVersions.cs">

Просмотреть файл

@ -288,14 +288,16 @@ namespace Xamarin.Bundler {
AssemblyName = assembly_path,
AddBitcodeMarkerSection = BuildTarget != AssemblyBuildTarget.StaticObject && App.EnableMarkerOnlyBitCode,
AssemblyPath = asm,
ProcessStartInfo = Driver.CreateStartInfo (App, aotCompiler, aotArgs, Path.GetDirectoryName (assembly_path)),
FileName = aotCompiler,
Arguments = aotArgs,
Environment = new Dictionary<string, string> { { "MONO_PATH", Path.GetDirectoryName (assembly_path) } },
AotInfo = aotInfo,
};
if (App.Platform == ApplePlatform.WatchOS) {
// Visual Studio for Mac sets this environment variable, and it confuses the AOT compiler.
// So unset it.
// See https://github.com/mono/mono/issues/11765
task.ProcessStartInfo.EnvironmentVariables.Remove ("MONO_THREADS_SUSPEND");
task.Environment ["MONO_THREADS_SUSPEND"] = null;
}
aotInfo.Task = task;

Просмотреть файл

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.MacDev;
@ -14,20 +12,22 @@ namespace Xamarin.Bundler
{
public abstract class ProcessTask : BuildTask
{
public ProcessStartInfo ProcessStartInfo;
public string FileName;
public IList<string> Arguments;
public Dictionary<string, string> Environment = new Dictionary<string, string> ();
protected StringBuilder Output;
protected string Command {
get {
var result = new StringBuilder ();
if (ProcessStartInfo.EnvironmentVariables.ContainsKey ("MONO_PATH")) {
if (Environment.TryGetValue ("MONO_PATH", out var mono_path)) {
result.Append ("MONO_PATH=");
result.Append (StringUtils.Quote (ProcessStartInfo.EnvironmentVariables ["MONO_PATH"]));
result.Append (StringUtils.Quote (mono_path));
result.Append (' ');
}
result.Append (ProcessStartInfo.FileName);
result.Append (FileName);
result.Append (' ');
result.Append (ProcessStartInfo.Arguments);
result.Append (StringUtils.FormatArguments (Arguments));
return result.ToString ();
}
}
@ -51,51 +51,13 @@ namespace Xamarin.Bundler
if (Driver.Verbosity > 0)
Console.WriteLine (Command);
var info = ProcessStartInfo;
var stdout_completed = new ManualResetEvent (false);
var stderr_completed = new ManualResetEvent (false);
var lockobj = new object ();
Output = new StringBuilder ();
using (var p = Process.Start (info)) {
p.OutputDataReceived += (sender, e) =>
{
if (e.Data != null) {
lock (lockobj)
OutputReceived (e.Data);
} else {
stdout_completed.Set ();
}
};
var rv = Execution.RunWithCallbacksAsync (FileName, Arguments, environment: Environment, standardOutput: OutputReceived, standardError: OutputReceived).Result;
p.ErrorDataReceived += (sender, e) =>
{
if (e.Data != null) {
lock (lockobj)
OutputReceived (e.Data);
} else {
stderr_completed.Set ();
}
};
OutputReceived (null);
p.BeginOutputReadLine ();
p.BeginErrorReadLine ();
p.WaitForExit ();
stderr_completed.WaitOne (TimeSpan.FromSeconds (1));
stdout_completed.WaitOne (TimeSpan.FromSeconds (1));
OutputReceived (null);
GC.Collect (); // Workaround for: https://bugzilla.xamarin.com/show_bug.cgi?id=43462#c14
if (Driver.Verbosity >= 2 && Output.Length > 0)
Console.Error.WriteLine (Output.ToString ());
return p.ExitCode;
}
return rv.ExitCode;
}
}
@ -310,7 +272,7 @@ namespace Xamarin.Bundler
var cmd_length = Target.App.CompilerPath.Length + 1 + CompilerFlags.ToString ().Length;
try {
var code = await Driver.RunCommandAsync (Target.App.CompilerPath, CompilerFlags.ToArray (), null, output, suppressPrintOnErrors: true);
var code = await Driver.RunCommandAsync (Target.App.CompilerPath, CompilerFlags.ToArray (), output: output, suppressPrintOnErrors: true);
Application.ProcessNativeLinkerOutput (Target, output.ToString (), CompilerFlags.AllLibraries, linker_errors, code != 0);
@ -589,7 +551,7 @@ namespace Xamarin.Bundler
CheckFor5107 (assembly_name, line, exceptions);
});
var rv = await Driver.RunCommandAsync (App.CompilerPath, CompilerFlags.ToArray (), null, output_received, suppressPrintOnErrors: true);
var rv = await Driver.RunCommandAsync (App.CompilerPath, CompilerFlags.ToArray (), output_received: output_received, suppressPrintOnErrors: true);
WriteLimitedOutput (rv != 0 ? $"Compilation failed with code {rv}, command:\n{App.CompilerPath} {CompilerFlags.ToString ()}" : null, output, exceptions);

Просмотреть файл

@ -300,20 +300,6 @@ namespace Xamarin.Bundler
return args;
}
public static ProcessStartInfo CreateStartInfo (Application app, string file_name, IList<string> arguments, string mono_path, string mono_debug = null)
{
var info = new ProcessStartInfo (file_name, StringUtils.FormatArguments (arguments));
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.EnvironmentVariables ["MONO_PATH"] = mono_path;
if (mono_debug != null)
info.EnvironmentVariables ["MONO_DEBUG"] = mono_debug;
return info;
}
static string EncodeAotSymbol (string symbol)
{
var sb = new StringBuilder ();

Просмотреть файл

@ -463,6 +463,9 @@
<Compile Include="..\common\ErrorHelper.tools.cs">
<Link>tools\common\ErrorHelper.tools.cs</Link>
</Compile>
<Compile Include="..\common\Driver.execution.cs">
<Link>tools\common\Driver.execution.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Reference Include="System.Core" />