Skip to content

Commit

Permalink
[build] Fix AOT build (#2125)
Browse files Browse the repository at this point in the history
The `make leeroy` target failed with a "file not found" exception
while attempting to invoke the `cross-arm` cross-compiler:

	[AOT] MONO_PATH="/Users/builder/jenkins/workspace/xamarin-android/xamarin-android/src/Mono.Android/Test/obj/Release/android/assets/shrunk" MONO_ENV_OPTIONS="" /Users/builder/jenkins/workspace/xamarin-android/xamarin-android/bin/Release/lib/xamarin.android/xbuild/Xamarin/Android/Darwin/cross-arm --aot=outfile=/Users/builder/jenkins/workspace/xamarin-android/xamarin-android/src/Mono.Android/Test/obj/Release/aot/armeabi-v7a/libaot-System.dll.so,asmwriter,mtriple=armv7-linux-gnueabi,tool-prefix=/Users/builder/android-toolchain/ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-,ld-flags=,llvm-path=/Users/builder/jenkins/workspace/xamarin-android/xamarin-android/bin/Release/lib/xamarin.android/xbuild/Xamarin/Android/Darwin,temp-path=/Users/builder/jenkins/workspace/xamarin-android/xamarin-android/src/Mono.Android/Test/obj/Release/aot/armeabi-v7a/System.dll /Users/builder/jenkins/workspace/xamarin-android/xamarin-android/src/Mono.Android/Test/obj/Release/android/assets/shrunk/System.dll
	...
	error XA3001: System.AggregateException: One or more errors occurred. ---> 
	  System.AggregateException: One or more errors occurred. ---> 
	  System.ComponentModel.Win32Exception: ApplicationName='/Users/builder/jenkins/workspace/xamarin-android/xamarin-android/bin/Release/lib/xamarin.android/xbuild/Xamarin/Android/Darwin/cross-arm', CommandLine='…', CurrentDirectory='/Users/builder/jenkins/workspace/xamarin-android/xamarin-android/src/Mono.Android/Test',
	    Native error= Cannot find the specified file

The reason for this was that `cross-arm` was never built, because
commit 9abbfc4 removed `armeabi` builds, which built `cross-arm`.

Fix these errors by adding `armeabi-v7a` to `$(ALL_AOT_ABIS)`, which
will cause `cross-arm` to once again be built.

Additionally, modify the `.apk` tests to:

  * Target Android API-22 to maintain uniformity across tests and
    also to make sure that the code which determines location of the
    on-device test result files returns the same location for all the
    tests - it relies on APIs introduced in API level 19)
  * Request external storage permissions.  This ensures that we can
    create the test result files in a location that's publicly
    accessible.  If access to external storage is denied our test
    code will create the results file in the application data
    directory and on Android 28 the file will be only readable by
    owner, thus making `adb pull` fail to download it.
  * Update the `<Adb/>` task so that it *no longer* is a `ToolTask`,
    because we want to be able to *ignore* `adb` errors, and
    `ToolTask` doesn't appear to have a way to ignore the exit value.
  • Loading branch information
grendello authored and jonpryor committed Sep 7, 2018
1 parent f6c3288 commit 2e8f96f
Show file tree
Hide file tree
Showing 15 changed files with 408 additions and 197 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
Expand All @@ -12,56 +13,267 @@

namespace Xamarin.Android.Tools.BootstrapTasks
{
public class Adb : PathToolTask
public class Adb : Task
{
public string Arguments { get; set; }

[Output]
public string[] Output { get; set; }
protected class CommandInfo
{
public string ArgumentsString { get; set; }
public Func<string> ArgumentsGenerator { get; set; }
public bool MergeStdoutAndStderr { get; set; } = true;
public bool IgnoreExitCode { get; set; }
public string StdoutFilePath { get; set; }
public bool StdoutAppend { get; set; }
public string StderrFilePath { get; set; }
public bool StderrAppend { get; set; }
public StreamWriter StdoutWriter { get; set; }
public StreamWriter StderrWriter { get; set; }
public Func<bool> ShouldRun { get; set; } = () => true;
public bool SuppressMSbuildLog { get; set; }

protected virtual bool LogTaskMessages {
get { return true; }
public string Arguments => ArgumentsGenerator != null ? ArgumentsGenerator () : ArgumentsString;
}

protected override string ToolBaseName {
get { return "adb"; }
}
List<string> lines = new List <string> ();
object linesLock = new object ();

List<string> lines;
List<string> Lines {
get { return lines ?? (lines = new List<string> ()); }
}
public string AdbTarget { get; set; }
public string AdbOptions { get; set; }
public string Arguments { get; set; }
public string WorkingDirectory { get; set; }
public bool IgnoreExitCode { get; set; }
public int Timeout { get; set; } = -1;
public string[] EnvironmentVariables { get; set; }

[Required]
public string ToolPath { get; set; }

[Required]
public string ToolExe { get; set; }

[Output]
public string[] Output { get; set; }

protected virtual int OutputTimeout => 30; // seconds

public override bool Execute ()
{
if (LogTaskMessages) {
Log.LogMessage (MessageImportance.Low, $"Task {nameof (Adb)}");
Log.LogMessage (MessageImportance.Low, $" {nameof (Arguments)}: {Arguments}");
}
List <CommandInfo> commandArguments = GenerateCommandArguments ();
if (commandArguments == null || commandArguments.Count == 0)
return !Log.HasLoggedErrors;

base.Execute ();
string adbPath = Path.Combine (ToolPath, ToolExe);
for (int i = 0; i < commandArguments.Count; i++) {
CommandInfo info = commandArguments [i];
if (info.ShouldRun != null && !info.ShouldRun ())
continue;

Output = lines?.ToArray ();
info.StdoutWriter = OpenOutputFile (info.StdoutFilePath, info.StdoutAppend);
if (!info.MergeStdoutAndStderr)
info.StderrWriter = OpenOutputFile (info.StderrFilePath, info.StderrAppend);
BeforeCommand (i, info);
try {
Log.LogMessage (MessageImportance.Normal, $"Executing: {adbPath} {info.Arguments}");
if (info.StdoutWriter != null)
LogFileWrite ("stdout", info.StdoutFilePath, info.StdoutAppend);
if (info.StderrWriter != null)
LogFileWrite ("stderr", info.StderrFilePath, info.StderrAppend);
int exitCode = RunCommand (adbPath, info);
if (exitCode == 0)
continue;

if (LogTaskMessages) {
Log.LogMessage (MessageImportance.Low, $" [Output] {nameof (Output)}:");
foreach (var line in (Output ?? new string [0]))
Log.LogMessage (MessageImportance.Low, $" {line}");
bool ignoreExit = IgnoreExitCode | info.IgnoreExitCode;
string message = $" Command {adbPath} {info.Arguments} failed with exit code {exitCode}";
if (!ignoreExit) {
Log.LogError (message);
break;
}
Log.LogWarning (message);
} catch {
throw;
} finally {
AfterCommand (i, info);
info.StdoutWriter?.Dispose ();
info.StdoutWriter = null;
info.StderrWriter?.Dispose ();
info.StderrWriter = null;
}
}

Output = lines?.ToArray ();

return !Log.HasLoggedErrors;
}

protected override string GenerateCommandLineCommands ()
void LogFileWrite (string streamName, string outputFileName, bool appends)
{
return Arguments;
string op = appends ? "appending" : "writing";
Log.LogMessage (MessageImportance.Normal, $" {op} {streamName} to file: {outputFileName}");
}

protected override void LogEventsFromTextOutput (string singleLine, MessageImportance messageImportance)
StreamWriter OpenOutputFile (string path, bool appendIfExists)
{
Log.LogMessage (MessageImportance.Low, singleLine);
Lines.Add (singleLine);
if (String.IsNullOrEmpty (path))
return null;

return new StreamWriter (appendIfExists ? File.Open (path, FileMode.Append) : File.Create (path));
}

protected virtual List <CommandInfo> GenerateCommandArguments ()
{
return new List <CommandInfo> {
new CommandInfo {
ArgumentsString = Arguments
}
};
}

protected virtual void BeforeCommand (int commandIndex, CommandInfo info)
{}

protected virtual void AfterCommand (int commandIndex, CommandInfo info)
{}

protected virtual void CustomizeProcessStartInfo (ProcessStartInfo psi)
{}

protected virtual void ProcessStdout (string line)
{}

protected virtual void ProcessStderr (string line)
{}

void OnOutput (string line, bool isStdout, CommandInfo info)
{
lock (linesLock) lines.Add (line);

if (!info.SuppressMSbuildLog) {
if (isStdout)
Log.LogMessage (MessageImportance.Low, line);
else
Log.LogWarning (line);
}

if (isStdout || info.MergeStdoutAndStderr) {
info.StdoutWriter?.WriteLine (line);
ProcessStdout (line);
} else {
info.StderrWriter?.WriteLine (line);
ProcessStderr (line);
}
}

int RunCommand (string commandPath, CommandInfo info)
{
var si = new ProcessStartInfo (commandPath) {
UseShellExecute = false,
CreateNoWindow = true,
};

if (!String.IsNullOrEmpty (WorkingDirectory))
si.WorkingDirectory = WorkingDirectory;

si.RedirectStandardOutput = true;
si.RedirectStandardError = true;
si.StandardOutputEncoding = Encoding.Default;
si.StandardErrorEncoding = Encoding.Default;
si.Arguments = info.Arguments;

if (EnvironmentVariables != null && EnvironmentVariables.Length > 0) {
foreach (string ev in EnvironmentVariables) {
string name;
string value;

int idx = ev.IndexOf ('=');
if (idx < 0) {
name = ev.Trim ();
value = String.Empty;
} else {
name = ev.Substring (0, idx).Trim ();
value = ev.Substring (idx + 1).Trim ();
}

if (String.IsNullOrEmpty (name)) {
Log.LogWarning ($" Invalid environment variable definition: '{ev}'");
continue;
}

if (String.IsNullOrEmpty (value))
value = "1";

Log.LogMessage (MessageImportance.Low, $" Defining environment variable: {name} = {value}");
si.EnvironmentVariables.Add (name, value);
}
}

CustomizeProcessStartInfo (si);

ManualResetEvent stdout_completed = null;
if (!si.RedirectStandardError)
si.StandardErrorEncoding = null;
else
stdout_completed = new ManualResetEvent (false);

ManualResetEvent stderr_completed = null;
if (!si.RedirectStandardOutput)
si.StandardOutputEncoding = null;
else
stderr_completed = new ManualResetEvent (false);

var p = new Process {
StartInfo = si
};
p.Start ();

var outputLock = new Object ();

if (si.RedirectStandardOutput) {
p.OutputDataReceived += (sender, e) => {
if (e.Data != null)
OnOutput (e.Data, true, info);
else
stdout_completed.Set ();
};
p.BeginOutputReadLine ();
}

if (si.RedirectStandardError) {
p.ErrorDataReceived += (sender, e) => {
if (e.Data != null)
OnOutput (e.Data, false, info);
else
stderr_completed.Set ();
};
p.BeginErrorReadLine ();
}

bool needToWait = true;
bool exited = true;
if (Timeout > 0) {
exited = p.WaitForExit (Timeout);
if (!exited) {
Log.LogWarning ($" Process '{commandPath} {si.Arguments}' failed to exit within the timeout of {Timeout}ms, killing the process");
p.Kill ();
}

// We need to call the parameter-less WaitForExit only if any of the standard output
// streams have been redirected (see
// https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit?view=netframework-4.7.2#System_Diagnostics_Process_WaitForExit)
//
if (!si.RedirectStandardOutput && !si.RedirectStandardError)
needToWait = false;
}

if (needToWait)
p.WaitForExit ();

if (si.RedirectStandardError && stderr_completed != null)
stderr_completed.WaitOne (TimeSpan.FromSeconds (OutputTimeout));
if (si.RedirectStandardOutput && stdout_completed != null)
stdout_completed.WaitOne (TimeSpan.FromSeconds (OutputTimeout));

return exited? p.ExitCode : -1;
}

}
}

Loading

0 comments on commit 2e8f96f

Please sign in to comment.