xamarin-macios/tests/xharness/Microsoft.DotNet.XHarness.i.../Execution/ProcessManager.cs

417 строки
14 KiB
C#
Исходник Обычный вид История

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.DotNet.XHarness.iOS.Shared.Logging;
using Microsoft.DotNet.XHarness.iOS.Shared.Utilities;
using Microsoft.DotNet.XHarness.iOS.Shared.Execution.Mlaunch;
namespace Microsoft.DotNet.XHarness.iOS.Shared.Execution {
public class ProcessManager : IProcessManager {
static readonly Lazy<string> autoDetectedXcodeRoot = new Lazy<string>(DetectXcodePath, LazyThreadSafetyMode.PublicationOnly);
readonly string xcodeRoot;
public string XcodeRoot => xcodeRoot ?? autoDetectedXcodeRoot.Value;
public string MlaunchPath { get; }
public GetDotNetExecutableDelegate GetDotNetExecutable { get; private set; }
public string MSBuildPath { get; private set; }
Version xcode_version;
public Version XcodeVersion {
get {
if (xcode_version == null) {
var doc = new XmlDocument ();
doc.Load (Path.Combine (XcodeRoot, "Contents", "version.plist"));
xcode_version = Version.Parse (doc.SelectSingleNode ("//key[text() = 'CFBundleShortVersionString']/following-sibling::string").InnerText);
}
return xcode_version;
}
}
public ProcessManager (string xcodeRoot = null, string mlaunchPath = "/Library/Frameworks/Xamarin.iOS.framework/Versions/Current/bin/mlaunch", GetDotNetExecutableDelegate dotNetPath = null, string msBuildPath = null)
{
this.xcodeRoot = xcodeRoot;
MlaunchPath = mlaunchPath;
GetDotNetExecutable = dotNetPath;
MSBuildPath = msBuildPath;
}
public async Task<ProcessExecutionResult> ExecuteCommandAsync (string filename,
IList<string> args,
ILog log,
TimeSpan timeout,
Dictionary<string, string> environmentVariables = null,
CancellationToken? cancellationToken = null)
{
using var p = new Process ();
p.StartInfo.FileName = filename ?? throw new ArgumentNullException (nameof (filename));
p.StartInfo.Arguments = StringUtils.FormatArguments (args);
return await RunAsync (p, log, timeout, environmentVariables, cancellationToken);
}
public async Task<ProcessExecutionResult> ExecuteCommandAsync (MlaunchArguments args,
ILog log,
TimeSpan timeout,
Dictionary<string, string> environmentVariables = null,
CancellationToken? cancellationToken = null)
{
using var p = new Process ();
return await RunAsync (p, args, log, timeout, environmentVariables, cancellationToken);
}
public async Task<ProcessExecutionResult> ExecuteXcodeCommandAsync (string executable, IList<string> args, ILog log, TimeSpan timeout)
{
using var p = new Process ();
p.StartInfo.FileName = Path.Combine (XcodeRoot, "Contents", "Developer", "usr", "bin", executable);
p.StartInfo.Arguments = StringUtils.FormatArguments (args);
return await RunAsync (p, log, timeout, diagnostics: false);
}
[DllImport ("/usr/lib/libc.dylib")]
internal static extern int kill (int pid, int sig);
public Task<ProcessExecutionResult> RunAsync (Process process,
ILog log,
TimeSpan? timeout = null,
Dictionary<string, string> environment_variables = null,
CancellationToken? cancellationToken = null,
bool? diagnostics = null)
{
return RunAsync (process, log, log, log, timeout, environment_variables, cancellationToken, diagnostics);
}
public Task<ProcessExecutionResult> RunAsync (Process process,
MlaunchArguments args,
ILog log,
TimeSpan? timeout = null,
Dictionary<string, string> environmentVariables = null,
CancellationToken? cancellationToken = null,
bool? diagnostics = null)
{
if (!args.Any (a => a is SdkRootArgument))
args.Prepend (new SdkRootArgument (XcodeRoot));
process.StartInfo.FileName = MlaunchPath;
process.StartInfo.Arguments = args.AsCommandLine ();
return RunAsync (process, log, timeout, environmentVariables, cancellationToken, diagnostics);
}
public Task<ProcessExecutionResult> RunAsync (Process process,
ILog log,
ILog stdout,
ILog stderr,
TimeSpan? timeout = null,
Dictionary<string, string> environmentVariables = null,
CancellationToken? cancellationToken = null,
bool? diagnostics = null)
{
return RunAsyncInternal (process, log, stdout, stderr, timeout, environmentVariables, cancellationToken, diagnostics);
}
public Task KillTreeAsync (Process process, ILog log, bool? diagnostics = true)
{
return KillTreeAsyncInternal (process.Id, log, diagnostics);
}
public Task KillTreeAsync (int pid, ILog log, bool? diagnostics = true)
{
return KillTreeAsyncInternal (pid, log, diagnostics);
}
static async Task<ProcessExecutionResult> RunAsyncInternal (Process process,
ILog log,
ILog stdout,
ILog stderr,
TimeSpan? timeout = null,
Dictionary<string, string> environmentVariables = null,
CancellationToken? cancellationToken = null,
bool? diagnostics = null)
{
var stdout_completion = new TaskCompletionSource<bool> ();
var stderr_completion = new TaskCompletionSource<bool> ();
var rv = new ProcessExecutionResult ();
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
// Make cute emojiis show up as cute emojiis in the output instead of ugly text symbols!
process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
process.StartInfo.UseShellExecute = false;
if (environmentVariables != null) {
foreach (var kvp in environmentVariables) {
if (kvp.Value == null) {
process.StartInfo.EnvironmentVariables.Remove (kvp.Key);
} else {
process.StartInfo.EnvironmentVariables [kvp.Key] = kvp.Value;
}
}
}
process.OutputDataReceived += (sender, e) => {
if (e.Data != null) {
lock (stdout) {
stdout.WriteLine (e.Data);
stdout.Flush ();
}
} else {
stdout_completion.TrySetResult (true);
}
};
process.ErrorDataReceived += (sender, e) => {
if (e.Data != null) {
lock (stderr) {
stderr.WriteLine (e.Data);
stderr.Flush ();
}
} else {
stderr_completion.TrySetResult (true);
}
};
var sb = new StringBuilder ();
if (process.StartInfo.EnvironmentVariables != null) {
var currentEnvironment = Environment.GetEnvironmentVariables ().Cast<System.Collections.DictionaryEntry> ().ToDictionary ((v) => (string) v.Key, (v) => (string) v.Value, StringComparer.Ordinal);
var processEnvironment = process.StartInfo.EnvironmentVariables.Cast<System.Collections.DictionaryEntry> ().ToDictionary ((v) => (string) v.Key, (v) => (string) v.Value, StringComparer.Ordinal);
var allKeys = currentEnvironment.Keys.Union (processEnvironment.Keys).Distinct ();
foreach (var key in allKeys) {
string a = null, b = null;
currentEnvironment.TryGetValue (key, out a);
processEnvironment.TryGetValue (key, out b);
if (a != b)
sb.Append ($"{key}={StringUtils.Quote (b)} ");
}
}
sb.Append ($"{StringUtils.Quote (process.StartInfo.FileName)} {process.StartInfo.Arguments}");
log.WriteLine (sb);
process.Start ();
var pid = process.Id;
process.BeginErrorReadLine ();
process.BeginOutputReadLine ();
cancellationToken?.Register (() => {
[xharness] Handle Process.HasExited throwing exceptions. (#5239) Apparently Process.HasExited may throw exceptions, so handle those gracefully (by ignoring them completely): Unhandled Exception: System.AggregateException: One or more errors occurred. (No process is associated with this object.) ---> System.InvalidOperationException: No process is associated with this object. at System.Diagnostics.Process.EnsureState (System.Diagnostics.Process+State state) [0x00018] in <d012d9eca6d14975a47488863de2f4c6>:0 at System.Diagnostics.Process.get_HasExited () [0x0000b] in <d012d9eca6d14975a47488863de2f4c6>:0 at (wrapper remoting-invoke-with-check) System.Diagnostics.Process.get_HasExited() at xharness.Process_Extensions+<>c__DisplayClass2_0.<RunAsync>b__2 () [0x00001] in <285dbdcf9e034cd496a3fef953fac640>:0 at System.Threading.CancellationToken.ActionToActionObjShunt (System.Object obj) [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationCallbackInfo.ExecutionContextCallback (System.Object obj) [0x00007] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state) [0x0002b] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationCallbackInfo.ExecuteCallback () [0x00024] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork (System.Threading.CancellationCallbackCoreWorkArguments args) [0x00042] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers (System.Boolean throwOnFirstException) [0x000c0] in <96207d0baa204f48a53ad6be05f5ecba>:0 --- End of inner exception stack trace --- at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers (System.Boolean throwOnFirstException) [0x00132] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationTokenSource.NotifyCancellation (System.Boolean throwOnFirstException) [0x0005f] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationTokenSource.Cancel (System.Boolean throwOnFirstException) [0x00006] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationTokenSource.Cancel () [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0 at xharness.AppRunner+<>c__DisplayClass73_0.<RunAsync>b__0 (System.Object v) [0x00029] in <285dbdcf9e034cd496a3fef953fac640>:0 at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context (System.Object state) [0x00007] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () [0x00021] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00074] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0 ---> (Inner Exception #0) System.InvalidOperationException: No process is associated with this object. at System.Diagnostics.Process.EnsureState (System.Diagnostics.Process+State state) [0x00018] in <d012d9eca6d14975a47488863de2f4c6>:0 at System.Diagnostics.Process.get_HasExited () [0x0000b] in <d012d9eca6d14975a47488863de2f4c6>:0 at (wrapper remoting-invoke-with-check) System.Diagnostics.Process.get_HasExited() at xharness.Process_Extensions+<>c__DisplayClass2_0.<RunAsync>b__2 () [0x00001] in <285dbdcf9e034cd496a3fef953fac640>:0 at System.Threading.CancellationToken.ActionToActionObjShunt (System.Object obj) [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationCallbackInfo.ExecutionContextCallback (System.Object obj) [0x00007] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00071] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state) [0x0002b] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationCallbackInfo.ExecuteCallback () [0x00024] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork (System.Threading.CancellationCallbackCoreWorkArguments args) [0x00042] in <96207d0baa204f48a53ad6be05f5ecba>:0 at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers (System.Boolean throwOnFirstException) [0x000c0] in <96207d0baa204f48a53ad6be05f5ecba>:0 <---
2018-12-07 17:07:21 +03:00
var hasExited = false;
try {
hasExited = process.HasExited;
} catch {
// Process.HasExited can sometimes throw exceptions, so
// just ignore those and to be safe treat it as the
// process didn't exit (the safe option being to not leave
// processes behind).
}
if (!hasExited) {
stderr.WriteLine ($"Execution of {pid} was cancelled.");
kill (pid, 9);
}
});
if (timeout.HasValue) {
if (!await WaitForExitAsync (process, timeout.Value)) {
await KillTreeAsyncInternal (process.Id, log, diagnostics ?? true);
rv.TimedOut = true;
lock (stderr)
log.WriteLine ($"{pid} Execution timed out after {timeout.Value.TotalSeconds} seconds and the process was killed.");
}
}
await WaitForExitAsync (process);
Task.WaitAll (new Task [] { stderr_completion.Task, stdout_completion.Task }, TimeSpan.FromSeconds (1));
try {
rv.ExitCode = process.ExitCode;
} catch (Exception e) {
rv.ExitCode = 12345678;
log.WriteLine ($"Failed to get ExitCode: {e}");
}
return rv;
}
static async Task KillTreeAsyncInternal (int pid, ILog log, bool? diagnostics = true)
{
var pids = GetChildrenPS (log, pid);
if (diagnostics == true) {
log.WriteLine ($"Pids to kill: {string.Join (", ", pids.Select ((v) => v.ToString ()).ToArray ())}");
using (var ps = new Process ()) {
log.WriteLine ("Writing process list:");
ps.StartInfo.FileName = "ps";
ps.StartInfo.Arguments = "-A -o pid,ruser,ppid,pgid,%cpu=%CPU,%mem=%MEM,flags=FLAGS,lstart,rss,vsz,tty,state,time,command";
await RunAsyncInternal (ps, log, log, log, TimeSpan.FromSeconds (5), diagnostics: false);
}
foreach (var diagnose_pid in pids) {
var template = Path.GetTempFileName ();
[xharness] Try harder to make lldb quit after trying to get a backtrace. (#9119) When a process times out, we try to print a stack trace for all threads. This involves executing "lldb --source <script>", where the script contains: process attach --pid 1234 thread list thread backtrace all detach quit Basically we attach to the project, and ask lldb to print stack traces. The problem: 16:09:02.9522580 Printing backtrace for pid=25276 16:09:02.9528060 /usr/bin/lldb --source /var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp 16:09:04.6127570 (lldb) command source -s 0 '/var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp' 16:09:04.6130020 Executing commands in '/var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp'. 16:09:04.6130200 (lldb) process attach --pid 25276 16:09:05.6458750 error: attach failed: Error 1 16:09:05.7529100 25276 Execution timed out after 1200 seconds and the process was killed. 16:09:05.7588770 Execution timed out after 1200 seconds. If any of those commands fail, the subsequent commands aren't executed. This includes the final "quit" command, which means we end up waiting forever for lldb to do its thing, when lldb doesn't think it needs to do anything at all. The fix: pass two different scripts. It turns out subsequent scripts are executed even if previous scripts fail, so we do the equivalent of: lldb --source <attach script> --source <quit script> And now lldb will quit no matter what the attach script does (it still works even if the attach script succeeds, in which case we'll ask lldb to quit twice).
2020-07-17 18:34:03 +03:00
var templateQuit = Path.GetTempFileName ();
try {
var commands = new StringBuilder ();
using (var dbg = new Process ()) {
commands.AppendLine ($"process attach --pid {diagnose_pid}");
commands.AppendLine ("thread list");
commands.AppendLine ("thread backtrace all");
commands.AppendLine ("detach");
commands.AppendLine ("quit");
dbg.StartInfo.FileName = "/usr/bin/lldb";
[xharness] Try harder to make lldb quit after trying to get a backtrace. (#9119) When a process times out, we try to print a stack trace for all threads. This involves executing "lldb --source <script>", where the script contains: process attach --pid 1234 thread list thread backtrace all detach quit Basically we attach to the project, and ask lldb to print stack traces. The problem: 16:09:02.9522580 Printing backtrace for pid=25276 16:09:02.9528060 /usr/bin/lldb --source /var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp 16:09:04.6127570 (lldb) command source -s 0 '/var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp' 16:09:04.6130020 Executing commands in '/var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp'. 16:09:04.6130200 (lldb) process attach --pid 25276 16:09:05.6458750 error: attach failed: Error 1 16:09:05.7529100 25276 Execution timed out after 1200 seconds and the process was killed. 16:09:05.7588770 Execution timed out after 1200 seconds. If any of those commands fail, the subsequent commands aren't executed. This includes the final "quit" command, which means we end up waiting forever for lldb to do its thing, when lldb doesn't think it needs to do anything at all. The fix: pass two different scripts. It turns out subsequent scripts are executed even if previous scripts fail, so we do the equivalent of: lldb --source <attach script> --source <quit script> And now lldb will quit no matter what the attach script does (it still works even if the attach script succeeds, in which case we'll ask lldb to quit twice).
2020-07-17 18:34:03 +03:00
dbg.StartInfo.Arguments = StringUtils.FormatArguments ("--source", template, "--source", templateQuit);
File.WriteAllText (template, commands.ToString ());
[xharness] Try harder to make lldb quit after trying to get a backtrace. (#9119) When a process times out, we try to print a stack trace for all threads. This involves executing "lldb --source <script>", where the script contains: process attach --pid 1234 thread list thread backtrace all detach quit Basically we attach to the project, and ask lldb to print stack traces. The problem: 16:09:02.9522580 Printing backtrace for pid=25276 16:09:02.9528060 /usr/bin/lldb --source /var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp 16:09:04.6127570 (lldb) command source -s 0 '/var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp' 16:09:04.6130020 Executing commands in '/var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp'. 16:09:04.6130200 (lldb) process attach --pid 25276 16:09:05.6458750 error: attach failed: Error 1 16:09:05.7529100 25276 Execution timed out after 1200 seconds and the process was killed. 16:09:05.7588770 Execution timed out after 1200 seconds. If any of those commands fail, the subsequent commands aren't executed. This includes the final "quit" command, which means we end up waiting forever for lldb to do its thing, when lldb doesn't think it needs to do anything at all. The fix: pass two different scripts. It turns out subsequent scripts are executed even if previous scripts fail, so we do the equivalent of: lldb --source <attach script> --source <quit script> And now lldb will quit no matter what the attach script does (it still works even if the attach script succeeds, in which case we'll ask lldb to quit twice).
2020-07-17 18:34:03 +03:00
File.WriteAllText (templateQuit, "quit\n");
log.WriteLine ($"Printing backtrace for pid={pid}");
await RunAsyncInternal (dbg, log, log, log, TimeSpan.FromSeconds (30), diagnostics: false);
}
} finally {
try {
File.Delete (template);
[xharness] Try harder to make lldb quit after trying to get a backtrace. (#9119) When a process times out, we try to print a stack trace for all threads. This involves executing "lldb --source <script>", where the script contains: process attach --pid 1234 thread list thread backtrace all detach quit Basically we attach to the project, and ask lldb to print stack traces. The problem: 16:09:02.9522580 Printing backtrace for pid=25276 16:09:02.9528060 /usr/bin/lldb --source /var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp 16:09:04.6127570 (lldb) command source -s 0 '/var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp' 16:09:04.6130020 Executing commands in '/var/folders/q7/mkzwrzcn7bzf3g2v38f3c1cw0000gn/T/tmp58e75d85.tmp'. 16:09:04.6130200 (lldb) process attach --pid 25276 16:09:05.6458750 error: attach failed: Error 1 16:09:05.7529100 25276 Execution timed out after 1200 seconds and the process was killed. 16:09:05.7588770 Execution timed out after 1200 seconds. If any of those commands fail, the subsequent commands aren't executed. This includes the final "quit" command, which means we end up waiting forever for lldb to do its thing, when lldb doesn't think it needs to do anything at all. The fix: pass two different scripts. It turns out subsequent scripts are executed even if previous scripts fail, so we do the equivalent of: lldb --source <attach script> --source <quit script> And now lldb will quit no matter what the attach script does (it still works even if the attach script succeeds, in which case we'll ask lldb to quit twice).
2020-07-17 18:34:03 +03:00
File.Delete (templateQuit);
} catch {
// Don't care
}
}
}
}
// Send SIGABRT since that produces a crash report
// lldb may fail to attach to system processes, but crash reports will still be produced with potentially helpful stack traces.
for (int i = 0; i < pids.Count; i++) {
log.WriteLine ($"kill -6 {pids [i]}");
kill (pids [i], 6);
}
// Wait a little bit for the OS to process the SIGABRTs
await Task.Delay (TimeSpan.FromSeconds (5));
// send kill -9 anyway as a last resort
for (int i = 0; i < pids.Count; i++) {
log.WriteLine ($"kill -9 {pids [i]}");
kill (pids [i], 9);
}
}
static async Task<bool> WaitForExitAsync (Process process, TimeSpan? timeout = null)
{
if (process.HasExited)
return true;
var tcs = new TaskCompletionSource<bool> ();
void ProcessExited (object sender, EventArgs ea)
{
process.Exited -= ProcessExited;
tcs.TrySetResult (true);
}
process.Exited += ProcessExited;
process.EnableRaisingEvents = true;
// Check if process exited again, in case it exited after we checked
// the last time, but before we attached the event handler.
if (process.HasExited) {
process.Exited -= ProcessExited;
tcs.TrySetResult (true);
return true;
}
if (timeout.HasValue) {
return await tcs.Task.TimeoutAfter (timeout.Value);
} else {
await tcs.Task;
return true;
}
}
static List<int> GetChildrenPS (ILog log, int pid)
{
var list = new List<int> ();
using (Process ps = new Process ()) {
ps.StartInfo.FileName = "ps";
ps.StartInfo.Arguments = "-eo ppid,pid";
ps.StartInfo.UseShellExecute = false;
ps.StartInfo.RedirectStandardOutput = true;
ps.Start ();
string stdout = ps.StandardOutput.ReadToEnd ();
if (!ps.WaitForExit (1000)) {
log.WriteLine ("ps didn't finish in a reasonable amount of time (1 second).");
return list;
}
if (ps.ExitCode != 0)
return list;
stdout = stdout.Trim ();
if (string.IsNullOrEmpty (stdout))
return list;
var dict = new Dictionary<int, List<int>> ();
foreach (string line in stdout.Split (new char [] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) {
var l = line.Trim ();
var space = l.IndexOf (' ');
if (space <= 0)
continue;
var parent = l.Substring (0, space);
var process = l.Substring (space + 1);
if (int.TryParse (parent, out var parent_id) && int.TryParse (process, out var process_id)) {
if (!dict.TryGetValue (parent_id, out var children))
dict [parent_id] = children = new List<int> ();
children.Add (process_id);
}
}
var queue = new Queue<int> ();
queue.Enqueue (pid);
do {
var parent_id = queue.Dequeue ();
list.Add (parent_id);
if (dict.TryGetValue (parent_id, out var children)) {
foreach (var child in children)
queue.Enqueue (child);
}
} while (queue.Count > 0);
}
return list;
}
static string DetectXcodePath ()
{
using var process = new Process ();
process.StartInfo.FileName = "xcode-select";
process.StartInfo.Arguments = "-p";
var log = new MemoryLog ();
var stdout = new MemoryLog () { Timestamp = false };
var stderr = new ConsoleLog ();
var timeout = TimeSpan.FromSeconds (30);
var result = RunAsyncInternal (process, log, stdout, stderr, timeout).GetAwaiter ().GetResult ();
if (!result.Succeeded)
throw new Exception ("Failed to detect Xcode path from xcode-select!");
// Something like /Applications/Xcode114.app/Contents/Developers
var xcodeRoot = stdout.ToString ().Trim ();
if (string.IsNullOrEmpty (xcodeRoot))
throw new Exception ("Failed to detect Xcode path from xcode-select!");
// We need /Applications/Xcode114.app only
return Path.GetDirectoryName(Path.GetDirectoryName(xcodeRoot));
}
}
}