Skip to content

Commit

Permalink
New tool runner, based on the new process runner
Browse files Browse the repository at this point in the history
AdbRunner implemented with the new tool runner
  • Loading branch information
grendello committed Jan 19, 2023
1 parent 371d3bb commit 61ce724
Show file tree
Hide file tree
Showing 11 changed files with 514 additions and 70 deletions.
8 changes: 4 additions & 4 deletions tools/xadebug/Xamarin.Android.Debug/AndroidDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class AndroidDevice
string outputDir;

XamarinLoggingHelper log;
AdbRunner adb;
AdbRunner2 adb;
AndroidNdk ndk;

public int ApiLevel => apiLevel;
Expand All @@ -62,9 +62,9 @@ class AndroidDevice
public string LldbBaseDir => appLldbBaseDir ?? String.Empty;
public string AppDataDir => appDataDir ?? String.Empty;
public string? DeviceLddPath => deviceLdd;
public AdbRunner AdbRunner => adb;
public AdbRunner2 AdbRunner => adb;

public AndroidDevice (XamarinLoggingHelper log, AndroidNdk ndk, string outputDir, string adbPath, string packageName, List<string> supportedAbis, string? adbTargetDevice = null)
public AndroidDevice (XamarinLoggingHelper log, IProcessOutputLogger processLogger, AndroidNdk ndk, string outputDir, string adbPath, string packageName, List<string> supportedAbis, string? adbTargetDevice = null)
{
this.adbPath = adbPath;
this.log = log;
Expand All @@ -73,7 +73,7 @@ public AndroidDevice (XamarinLoggingHelper log, AndroidNdk ndk, string outputDir
this.ndk = ndk;
this.outputDir = outputDir;

adb = new AdbRunner (log, adbPath, adbTargetDevice);
adb = new AdbRunner2 (log, processLogger, adbPath, adbTargetDevice);
}

// TODO: implement manual error checking on API 21, since `adb` won't ever return any error code other than 0 - we need to look at the output of any command to determine
Expand Down
3 changes: 2 additions & 1 deletion tools/xadebug/Xamarin.Android.Debug/DebugSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public bool Prepare ()

ndk = new AndroidNdk (log, parsedOptions.NdkDirPath!, supportedAbis);
device = new AndroidDevice (
log,
log, // general logger
log, // process output logger
ndk,
workDirectory,
parsedOptions.AdbPath,
Expand Down
4 changes: 2 additions & 2 deletions tools/xadebug/Xamarin.Android.Debug/DeviceLibrariesCopier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ abstract class DeviceLibraryCopier
protected XamarinLoggingHelper Log { get; }
protected bool AppIs64Bit { get; }
protected string LocalDestinationDir { get; }
protected AdbRunner Adb { get; }
protected AdbRunner2 Adb { get; }
protected AndroidDevice Device { get; }

protected DeviceLibraryCopier (XamarinLoggingHelper log, AdbRunner adb, bool appIs64Bit, string localDestinationDir, AndroidDevice device)
protected DeviceLibraryCopier (XamarinLoggingHelper log, AdbRunner2 adb, bool appIs64Bit, string localDestinationDir, AndroidDevice device)
{
Log = log;
Adb = adb;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
using System.Collections.Generic;

using Xamarin.Android.Utilities;
using Xamarin.Android.Tasks;

namespace Xamarin.Android.Debug;

class LddDeviceLibraryCopier : DeviceLibraryCopier
{
public LddDeviceLibraryCopier (XamarinLoggingHelper log, AdbRunner adb, bool appIs64Bit, string localDestinationDir, AndroidDevice device)
public LddDeviceLibraryCopier (XamarinLoggingHelper log, AdbRunner2 adb, bool appIs64Bit, string localDestinationDir, AndroidDevice device)
: base (log, adb, appIs64Bit, localDestinationDir, device)
{}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class NoLddDeviceLibraryCopier : DeviceLibraryCopier
"/system/vendor/@LIB@/mediadrm",
};

public NoLddDeviceLibraryCopier (XamarinLoggingHelper log, AdbRunner adb, bool appIs64Bit, string localDestinationDir, AndroidDevice device)
public NoLddDeviceLibraryCopier (XamarinLoggingHelper log, AdbRunner2 adb, bool appIs64Bit, string localDestinationDir, AndroidDevice device)
: base (log, adb, appIs64Bit, localDestinationDir, device)
{}

Expand All @@ -61,7 +61,7 @@ public override bool Copy (out string? zygotePath)

void AddSharedLibraries (List<string> sharedLibraries, string deviceDirPath, HashSet<string> permittedPaths)
{
AdbRunner.OutputLineFilter filterOutErrors = (bool isStdError, string line) => {
AdbRunner2.OutputLineFilter filterOutErrors = (bool isStdError, string line) => {
if (!isStdError) {
return false; // don't suppress any lines on stdout
}
Expand Down
230 changes: 230 additions & 0 deletions tools/xadebug/Xamarin.Android.Utilities/AdbRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Xamarin.Android.Utilities;

class AdbRunner2 : ToolRunner2
{
public delegate bool OutputLineFilter (bool isStdErr, string line);

sealed class CaptureOutputState
{
public OutputLineFilter? LineFilter;
public CaptureProcessOutputLogger? Logger;
}

sealed class CaptureProcessOutputLogger : IProcessOutputLogger
{
IProcessOutputLogger? wrappedLogger;
OutputLineFilter? lineFilter;
List<string> lines;
string? stderrPrefix;
string? stdoutPrefix;

public List<string> Lines => lines;

public IProcessOutputLogger? WrappedLogger => wrappedLogger;

public string? StdoutPrefix {
get => stdoutPrefix ?? wrappedLogger?.StdoutPrefix ?? String.Empty;
set => stdoutPrefix = value;
}

public string? StderrPrefix {
get => stderrPrefix ?? wrappedLogger?.StderrPrefix ?? String.Empty;
set => stderrPrefix = value;
}

public CaptureProcessOutputLogger (IProcessOutputLogger? wrappedLogger, OutputLineFilter? lineFilter = null)
{
this.wrappedLogger = wrappedLogger;
this.lineFilter = lineFilter;

lines = new List<string> ();
}

public void WriteStderr (string text, bool writeLine = true)
{
if (LineFiltered (text, isStdError: true)) {
return;
}

wrappedLogger?.WriteStderr (text, writeLine);
}

public void WriteStdout (string text, bool writeLine = true)
{
if (LineFiltered (text, isStdError: false)) {
return;
}

lines.Add (text);
}

bool LineFiltered (string text, bool isStdError)
{
if (lineFilter == null) {
return false;
}

return lineFilter (isStdError, text);
}
}

string[]? initialParams;

public AdbRunner2 (ILogger logger, IProcessOutputLogger processOutputLogger, string adbPath, string? deviceSerial = null)
: base (adbPath, logger, processOutputLogger)
{
if (!String.IsNullOrEmpty (deviceSerial)) {
initialParams = new string[] { "-s", deviceSerial };
}
}

public async Task<bool> Pull (string remotePath, string localPath)
{
var runner = CreateAdbRunner ();
runner.AddArgument ("pull");
runner.AddArgument (remotePath);
runner.AddArgument (localPath);

return await RunAdbAsync (runner);
}

public async Task<bool> Push (string localPath, string remotePath)
{
var runner = CreateAdbRunner ();
runner.AddArgument ("push");
runner.AddArgument (localPath);
runner.AddArgument (remotePath);

return await RunAdbAsync (runner);
}

public async Task<bool> Install (string apkPath, bool apkIsDebuggable = false, bool replaceExisting = true, bool noStreaming = true)
{
var runner = CreateAdbRunner ();
runner.AddArgument ("install");

if (replaceExisting) {
runner.AddArgument ("-r");
}

if (apkIsDebuggable) {
runner.AddArgument ("-d"); // Allow version code downgrade
}

if (noStreaming) {
runner.AddArgument ("--no-streaming");
}

runner.AddQuotedArgument (apkPath);

return await RunAdbAsync (runner);
}

public async Task<(bool success, string output)> GetAppDataDirectory (string packageName)
{
return await RunAs (packageName, "/system/bin/sh", "-c", "pwd");
}


public async Task<(bool success, string output)> CreateDirectoryAs (string packageName, string directoryPath)
{
return await RunAs (packageName, "mkdir", "-p", directoryPath);
}

public async Task<(bool success, string output)> GetPropertyValue (string propertyName)
{
var runner = CreateAdbRunner ();
return await Shell ("getprop", propertyName);
}

public async Task<(bool success, string output)> RunAs (string packageName, string command, params string[] args)
{
if (String.IsNullOrEmpty (packageName)) {
throw new ArgumentException ("must not be null or empty", nameof (packageName));
}

var shellArgs = new List<string> {
packageName,
command,
};

if (args != null && args.Length > 0) {
shellArgs.AddRange (args);
}

return await Shell ("run-as", (IEnumerable<string>)shellArgs, lineFilter: null);
}

public async Task<(bool success, string output)> Shell (string command, List<string> args, OutputLineFilter? lineFilter = null)
{
return await Shell (command, (IEnumerable<string>)args, lineFilter);
}

public async Task<(bool success, string output)> Shell (string command, params string[] args)
{
return await Shell (command, (IEnumerable<string>)args, lineFilter: null);
}

public async Task<(bool success, string output)> Shell (OutputLineFilter lineFilter, string command, params string[] args)
{
return await Shell (command, (IEnumerable<string>)args, lineFilter);
}

async Task<(bool success, string output)> Shell (string command, IEnumerable<string>? args, OutputLineFilter? lineFilter)
{
if (String.IsNullOrEmpty (command)) {
throw new ArgumentException ("must not be null or empty", nameof (command));
}

var captureState = new CaptureOutputState {
LineFilter = lineFilter,
};

var runner = CreateAdbRunner (captureState);

runner.AddArgument ("shell");
runner.AddArgument (command);
runner.AddArguments (args);

return await CaptureAdbOutput (runner, captureState);
}

async Task<bool> RunAdbAsync (ProcessRunner2 runner)
{
ProcessStatus status = await runner.RunAsync ();
return status.Success;
}

async Task<(bool success, string output)> CaptureAdbOutput (ProcessRunner2 runner, CaptureOutputState captureState)
{
ProcessStatus status = await runner.RunAsync ();

string output = captureState.Logger != null ? String.Join (Environment.NewLine, captureState.Logger.Lines) : String.Empty;
return (status.Success, output);
}

ProcessRunner2 CreateAdbRunner (CaptureOutputState? state = null) => InitProcessRunner (state, initialParams);

protected override ProcessRunner2 CreateProcessRunner (IProcessOutputLogger consoleProcessLogger, object? state, params string?[]? initialParams)
{
IProcessOutputLogger outputLogger;

if (state is CaptureOutputState captureState) {
captureState.Logger = new CaptureProcessOutputLogger (consoleProcessLogger, captureState.LineFilter);
outputLogger = captureState.Logger;
} else {
outputLogger = consoleProcessLogger;
}

outputLogger.StderrPrefix = "adb> ";
ProcessRunner2 ret = base.CreateProcessRunner (outputLogger, initialParams);

// Let's make sure all the messages we get are in English, since we need to parse some of them to detect problems
ret.Environment["LANG"] = "C";
return ret;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ namespace Xamarin.Android.Utilities;

interface IProcessOutputLogger
{
void WriteStdout (string text);
void WriteStderr (string text);
IProcessOutputLogger? WrappedLogger { get; }
string? StdoutPrefix { get; set; }
string? StderrPrefix { get; set; }

void WriteStdout (string text, bool writeLine = true);
void WriteStderr (string text, bool writeLine = true);
}
Loading

0 comments on commit 61ce724

Please sign in to comment.