Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Xamarin.Android.Build.Tasks] Use binutils for Aot and LLVM #6901

Merged
merged 1 commit into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions build-tools/automation/azure-pipelines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -697,10 +697,6 @@ stages:

- template: yaml-templates/apk-instrumentation.yaml
parameters:
# TODO: disable LLVM test, see:
# https://github.com/dotnet/runtime/issues/68914
# https://github.com/dotnet/runtime/issues/73304
condition: false
configuration: $(XA.Build.Configuration)
testName: Mono.Android.NET_Tests-AotLlvm
project: tests/Mono.Android-Tests/Runtime-Microsoft.Android.Sdk/Mono.Android.NET-Tests.csproj
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ They run in a context of an inner build with a single $(RuntimeIdentifier).
</ItemGroup>
<GetAotAssemblies
AndroidAotMode="$(AndroidAotMode)"
AndroidNdkDirectory="$(_AndroidNdkDirectory)"
AndroidNdkDirectory="$(AndroidNdkDirectory)"
AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)"
AndroidApiLevel="$(_AndroidApiLevel)"
MinimumSupportedApiLevel="$(AndroidMinimumSupportedApiLevel)"
Expand Down
4 changes: 2 additions & 2 deletions src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ static string QuoteFileName(string fileName)

public async override System.Threading.Tasks.Task RunTaskAsync ()
{
NdkTools ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: EnableLLVM, log: Log);
NdkTools ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: UseAndroidNdk, log: Log);
if (Log.HasLoggedErrors) {
return; // NdkTools.Create will log appropriate error
}
Expand Down Expand Up @@ -114,7 +114,7 @@ IEnumerable<Config> GetAotConfigs (NdkTools ndk)
foreach (var abi in SupportedAbis) {
(string aotCompiler, string outdir, string mtriple, AndroidTargetArch arch) = GetAbiSettings (abi);

if (EnableLLVM && !ndk.ValidateNdkPlatform (LogMessage, LogCodedError, arch, enableLLVM:EnableLLVM)) {
if (UseAndroidNdk && !ndk.ValidateNdkPlatform (LogMessage, LogCodedError, arch, enableLLVM:EnableLLVM)) {
yield return Config.Invalid;
yield break;
}
Expand Down
13 changes: 8 additions & 5 deletions src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public abstract class GetAotArguments : AndroidAsyncTask
protected AotMode AotMode;
protected SequencePointsMode SequencePointsMode;
protected string SdkBinDirectory = "";
protected bool UseAndroidNdk => !string.IsNullOrWhiteSpace (AndroidNdkDirectory);

public static bool GetAndroidAotMode(string androidAotMode, out AotMode aotMode)
{
Expand Down Expand Up @@ -125,7 +126,7 @@ public static bool TryGetSequencePointsMode (string value, out SequencePointsMod
protected string GetToolPrefix (NdkTools ndk, AndroidTargetArch arch, out int level)
{
level = 0;
return EnableLLVM
return UseAndroidNdk
? ndk.GetNdkToolPrefixForAOT (arch, level = GetNdkApiLevel (ndk, arch))
: Path.Combine (AndroidBinUtilsDirectory, $"{ndk.GetArchDirName (arch)}-");
}
Expand Down Expand Up @@ -230,7 +231,7 @@ protected void GetAotOptions (NdkTools ndk, AndroidTargetArch arch, int level, s
MsymPath = outdir;

string ldName;
if (EnableLLVM) {
if (UseAndroidNdk) {
ldName = ndk.GetToolPath (NdkToolKind.Linker, arch, level);
if (!string.IsNullOrEmpty (ldName)) {
ldName = Path.GetFileName (ldName);
Expand All @@ -250,11 +251,11 @@ protected void GetAotOptions (NdkTools ndk, AndroidTargetArch arch, int level, s
}
}

string GetLdFlags(NdkTools ndk, AndroidTargetArch arch, int level, string toolPrefix)
string GetLdFlags (NdkTools ndk, AndroidTargetArch arch, int level, string toolPrefix)
{
var toolchainPath = toolPrefix.Substring (0, toolPrefix.LastIndexOf (Path.DirectorySeparatorChar));
var ldFlags = new StringBuilder ();
if (EnableLLVM) {
if (UseAndroidNdk && EnableLLVM) {
string androidLibPath = string.Empty;
try {
androidLibPath = ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, arch, level);
Expand Down Expand Up @@ -306,7 +307,9 @@ string GetLdFlags(NdkTools ndk, AndroidTargetArch arch, int level, string toolPr
// Without the flag, `lld` will modify AOT-generated code in a way that the Mono runtime doesn't support. Until
// the runtime issue is fixed, we need to pass this flag then.
//
ldFlags.Append ("--no-relax");
if (!UseAndroidNdk) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if (at some point, not necessarily in this PR) we could detect the version of LLVM in the NDK and append --no-relax if that version is >= 14

ldFlags.Append ("--no-relax");
}
}

pjcollins marked this conversation as resolved.
Show resolved Hide resolved
if (StripLibraries) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class GetAotAssemblies : GetAotArguments

public override Task RunTaskAsync ()
{
NdkTools ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: EnableLLVM, log: Log);
NdkTools ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: UseAndroidNdk, log: Log);
if (Log.HasLoggedErrors) {
return Task.CompletedTask; // NdkTools.Create will log appropriate error
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,74 +111,43 @@ public void BuildBasicApplicationReleaseProfiledAotWithoutDefaultProfile ()
}

static object [] AotChecks () => new object [] {
new object[] {
/* supportedAbis */ "armeabi-v7a",
/* enableLLVM */ false,
/* expectedResult */ true,
/* usesAssemblyBlobs */ false,
},
new object[] {
/* supportedAbis */ "armeabi-v7a",
/* enableLLVM */ false,
/* expectedResult */ true,
/* usesAssemblyBlobs */ true,
},
new object[] {
/* supportedAbis */ "armeabi-v7a",
/* enableLLVM */ true,
/* expectedResult */ true,
/* usesAssemblyBlobs */ false,
},
new object[] {
/* supportedAbis */ "arm64-v8a",
/* enableLLVM */ false,
/* expectedResult */ true,
/* usesAssemblyBlobs */ false,
},
new object[] {
/* supportedAbis */ "arm64-v8a",
/* enableLLVM */ true,
/* expectedResult */ true,
/* usesAssemblyBlobs */ false,
},
new object[] {
/* supportedAbis */ "x86",
/* enableLLVM */ false,
/* expectedResult */ true,
/* usesAssemblyBlobs */ false,
},
new object[] {
/* supportedAbis */ "x86",
/* supportedAbis */ "armeabi-v7a;x86",
/* enableLLVM */ true,
/* expectedResult */ true,
/* usesAssemblyBlobs */ false,
/* usesAssemblyBlobs */ true,
},
new object[] {
/* supportedAbis */ "x86_64",
/* supportedAbis */ "armeabi-v7a;arm64-v8a;x86;x86_64",
/* enableLLVM */ false,
/* expectedResult */ true,
/* usesAssemblyBlobs */ false,
/* usesAssemblyBlobs */ true,
},
new object[] {
/* supportedAbis */ "x86_64",
/* supportedAbis */ "armeabi-v7a;arm64-v8a;x86;x86_64",
/* enableLLVM */ true,
/* expectedResult */ true,
/* usesAssemblyBlobs */ false,
},
};

[Test]
[TestCaseSource (nameof (AotChecks))]
public void BuildAotApplicationAndÜmläüts (string supportedAbis, bool enableLLVM, bool expectedResult, bool usesAssemblyBlobs)
public void BuildAotApplicationWithNdkAndBundleAndÜmläüts (string supportedAbis, bool enableLLVM, bool usesAssemblyBlobs)
{
var path = Path.Combine ("temp", string.Format ("BuildAotApplication AndÜmläüts_{0}_{1}_{2}_{3}", supportedAbis, enableLLVM, expectedResult, usesAssemblyBlobs));
var abisSanitized = supportedAbis.Replace (";", "").Replace ("-", "").Replace ("_", "");
var path = Path.Combine ("temp", string.Format ("BuildAotNdk AndÜmläüts_{0}_{1}_{2}", abisSanitized, enableLLVM, usesAssemblyBlobs));
var proj = new XamarinAndroidApplicationProject () {
IsRelease = true,
BundleAssemblies = false,
AotAssemblies = true,
PackageName = "com.xamarin.buildaotappwithspecialchars",
};
proj.SetProperty (KnownProperties.TargetFrameworkVersion, "v5.1");
if (!Builder.UseDotNet) {
proj.BundleAssemblies = true;
}
proj.SetProperty ("AndroidNdkDirectory", AndroidNdkPath);
proj.SetAndroidSupportedAbis (supportedAbis);
proj.SetProperty ("EnableLLVM", enableLLVM.ToString ());
proj.SetProperty ("AndroidUseAssemblyStore", usesAssemblyBlobs.ToString ());
Expand All @@ -193,12 +162,8 @@ public void BuildAotApplicationAndÜmläüts (string supportedAbis, bool enableL
</manifest>";
}
using (var b = CreateApkBuilder (path)) {
if (!b.GetSupportedRuntimes ().Any (x => supportedAbis == x.Abi))
Assert.Ignore ($"Runtime for {supportedAbis} was not available.");
b.ThrowOnBuildFailure = false;
Assert.AreEqual (expectedResult, b.Build (proj), "Build should have {0}.", expectedResult ? "succeeded" : "failed");
if (!expectedResult)
return;
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
//NOTE: Windows has shortened paths such as: C:\Users\myuser\ANDROI~3\ndk\PLATFO~1\AN3971~1\arch-x86\usr\lib\libc.so
if (checkMinLlvmPath && !IsWindows && !Builder.UseDotNet) {
Xamarin.Android.Tasks.NdkTools ndk = Xamarin.Android.Tasks.NdkTools.Create (AndroidNdkPath);
Expand All @@ -216,8 +181,11 @@ public void BuildAotApplicationAndÜmläüts (string supportedAbis, bool enableL
}
foreach (var abi in supportedAbis.Split (new char [] { ';' })) {
var intermediate = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath);
var libapp = Path.Combine (intermediate, "bundles", abi, "libmonodroid_bundle_app.so");
FileAssert.DoesNotExist (libapp);
if (!Builder.UseDotNet) {
var libapp = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath,
"bundles", abi, "libmonodroid_bundle_app.so");
Assert.IsTrue (File.Exists (libapp), abi + " libmonodroid_bundle_app.so does not exist");
}
var aotNativeLibrary = Builder.UseDotNet ?
Path.Combine (intermediate, AbiUtils.AbiToRuntimeIdentifier (abi), "aot", "UnnamedProject.dll.so") :
Path.Combine (intermediate, "aot", abi, "libaot-UnnamedProject.dll.so");
Expand All @@ -226,14 +194,19 @@ public void BuildAotApplicationAndÜmläüts (string supportedAbis, bool enableL
proj.OutputPath, $"{proj.PackageName}-Signed.apk");

var helper = new ArchiveAssemblyHelper (apk, usesAssemblyBlobs);
Assert.IsTrue (helper.Exists ("assemblies/UnnamedProject.dll"), $"UnnamedProject.dll should be in the {proj.PackageName}-Signed.apk");
if (!Builder.UseDotNet) {
// BundleAssemblies=true
Assert.IsFalse (helper.Exists ("assemblies/UnnamedProject.dll"), $"UnnamedProject.dll should not be in the {proj.PackageName}-Signed.apk");
} else {
Assert.IsTrue (helper.Exists ("assemblies/UnnamedProject.dll"), $"UnnamedProject.dll should be in the {proj.PackageName}-Signed.apk");
}
using (var zipFile = ZipHelper.OpenZip (apk)) {
Assert.IsNotNull (ZipHelper.ReadFileFromZip (zipFile,
string.Format ("lib/{0}/libaot-UnnamedProject.dll.so", abi)),
$"lib/{0}/libaot-UnnamedProject.dll.so should be in the {proj.PackageName}-Signed.apk", abi);
}
}
Assert.AreEqual (expectedResult, b.Build (proj), "Second Build should have {0}.", expectedResult ? "succeeded" : "failed");
Assert.IsTrue (b.Build (proj), "Second Build should have succeeded.");
Assert.IsTrue (
b.Output.IsTargetSkipped ("_CompileJava"),
"the _CompileJava target should be skipped");
Expand All @@ -245,46 +218,39 @@ public void BuildAotApplicationAndÜmläüts (string supportedAbis, bool enableL

[Test]
[TestCaseSource (nameof (AotChecks))]
[Category ("Minor"), Category ("MkBundle")]
public void BuildAotApplicationAndBundleAndÜmläüts (string supportedAbis, bool enableLLVM, bool expectedResult, bool usesAssemblyBlobs)
public void BuildAotApplicationAndÜmläüts (string supportedAbis, bool enableLLVM, bool usesAssemblyBlobs)
{
var path = Path.Combine ("temp", string.Format ("BuildAotApplicationAndBundle AndÜmläüts_{0}_{1}_{2}_{3}", supportedAbis, enableLLVM, expectedResult, usesAssemblyBlobs));
var abisSanitized = supportedAbis.Replace (";", "").Replace ("-", "").Replace ("_", "");
var path = Path.Combine ("temp", string.Format ("BuildAot AndÜmläüts_{0}_{1}_{2}", abisSanitized, enableLLVM, usesAssemblyBlobs));
var proj = new XamarinAndroidApplicationProject () {
IsRelease = true,
BundleAssemblies = true,
AotAssemblies = true,
PackageName = "com.xamarin.buildaotappandbundlewithspecialchars",
};
proj.SetProperty ("AndroidNdkDirectory", AndroidNdkPath);
proj.SetProperty (KnownProperties.TargetFrameworkVersion, "v5.1");
proj.SetAndroidSupportedAbis (supportedAbis);
proj.SetProperty ("EnableLLVM", enableLLVM.ToString ());
proj.SetProperty ("AndroidUseAssemblyStore", usesAssemblyBlobs.ToString ());
using (var b = CreateApkBuilder (path)) {
if (!b.GetSupportedRuntimes ().Any (x => supportedAbis == x.Abi))
Assert.Ignore ($"Runtime for {supportedAbis} was not available.");
b.ThrowOnBuildFailure = false;
Assert.AreEqual (expectedResult, b.Build (proj), "Build should have {0}.", expectedResult ? "succeeded" : "failed");
if (!expectedResult)
return;
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
foreach (var abi in supportedAbis.Split (new char [] { ';' })) {
var libapp = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath,
"bundles", abi, "libmonodroid_bundle_app.so");
Assert.IsTrue (File.Exists (libapp), abi + " libmonodroid_bundle_app.so does not exist");
var assemblies = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath,
"aot", abi, "libaot-UnnamedProject.dll.so");
Assert.IsTrue (File.Exists (assemblies), "{0} libaot-UnnamedProject.dll.so does not exist", abi);
var intermediate = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath);
var aotNativeLibrary = Builder.UseDotNet ?
Path.Combine (intermediate, AbiUtils.AbiToRuntimeIdentifier (abi), "aot", "UnnamedProject.dll.so") :
Path.Combine (intermediate, "aot", abi, "libaot-UnnamedProject.dll.so");
FileAssert.Exists (aotNativeLibrary);
var apk = Path.Combine (Root, b.ProjectDirectory,
proj.OutputPath, $"{proj.PackageName}-Signed.apk");

var helper = new ArchiveAssemblyHelper (apk, usesAssemblyBlobs);
Assert.IsFalse (helper.Exists ("assemblies/UnnamedProject.dll"), $"UnnamedProject.dll should not be in the {proj.PackageName}-Signed.apk");
Assert.IsTrue (helper.Exists ("assemblies/UnnamedProject.dll"), $"UnnamedProject.dll should be in the {proj.PackageName}-Signed.apk");
using (var zipFile = ZipHelper.OpenZip (apk)) {
Assert.IsNotNull (ZipHelper.ReadFileFromZip (zipFile,
string.Format ("lib/{0}/libaot-UnnamedProject.dll.so", abi)),
$"lib/{0}/libaot-UnnamedProject.dll.so should be in the {proj.PackageName}-Signed.apk", abi);
}
}
Assert.AreEqual (expectedResult, b.Build (proj), "Second Build should have {0}.", expectedResult ? "succeeded" : "failed");
Assert.IsTrue (b.Build (proj), "Second Build should have succeeded.");
Assert.IsTrue (
b.Output.IsTargetSkipped ("_CompileJava"),
"the _CompileJava target should be skipped");
Expand Down Expand Up @@ -441,7 +407,6 @@ public static void Foo () {
}

[Test]
[Ignore ("Ignore while investigating/fixing.")]
[Category ("LLVM")]
public void NoSymbolsArgShouldReduceAppSize ([Values ("", "Hybrid")] string androidAotMode, [Values (false, true)] bool skipDebugSymbols)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,13 @@ public void XA5104AndroidNdkNotFound (string androidNdkDirectory)
IntermediateAssemblyDir = path
};

Assert.IsFalse (task2.Execute (), "Task should fail!");
BuildErrorEventArgs error2 = errors [1];
Assert.AreEqual (error1.Message, error2.Message, "Aot and MakeBundleNativeCodeExternal should produce the same error messages.");
if (androidNdkDirectory == "DoesNotExist") {
Assert.IsFalse (task2.Execute (), "Task should fail!");
BuildErrorEventArgs error2 = errors [1];
Assert.AreEqual (error1.Message, error2.Message, "Aot and MakeBundleNativeCodeExternal should produce the same error messages.");
} else {
Assert.IsTrue (task2.Execute (), "Aot task should succeed with null or empty NDK!");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ projects. .NET 5 projects will not import this file.
<Aot
Condition="'$(AotAssemblies)' == 'True'"
AndroidAotMode="$(AndroidAotMode)"
AndroidNdkDirectory="$(_AndroidNdkDirectory)"
AndroidNdkDirectory="$(AndroidNdkDirectory)"
AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)"
AndroidApiLevel="$(_AndroidApiLevel)"
ManifestFile="$(IntermediateOutputPath)android\AndroidManifest.xml"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ projects.
LatestSupportedJavaVersion="$(LatestSupportedJavaVersion)"
ReferenceAssemblyPaths="@(_AndroidApiInfoDirectories)">
<Output TaskParameter="CommandLineToolsPath" PropertyName="_AndroidToolsDirectory" />
<Output TaskParameter="AndroidNdkPath" PropertyName="AndroidNdkDirectory" Condition=" '$(AndroidNdkDirectory)' == '' " />
<Output TaskParameter="AndroidSdkPath" PropertyName="AndroidSdkDirectory" Condition=" '$(AndroidSdkDirectory)' == '' " />
<Output TaskParameter="JavaSdkPath" PropertyName="JavaSdkDirectory" Condition=" '$(JavaSdkDirectory)' == '' " />
<Output TaskParameter="AndroidNdkPath" PropertyName="_AndroidNdkDirectory" />
Expand Down
28 changes: 28 additions & 0 deletions tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,34 @@ public void RunWithInterpreterEnabled ([Values (false, true)] bool isRelease)
Assert.IsTrue (didStart, "Activity should have started.");
}

[Test]
public void RunWithLLVMEnabled ()
{
AssertHasDevices ();

var proj = new XamarinAndroidApplicationProject () {
IsRelease = true,
};
proj.SetAndroidSupportedAbis ("armeabi-v7a", "x86", "x86_64");
proj.SetProperty ("EnableLLVM", true.ToString ());
if (!Builder.UseDotNet) {
proj.AotAssemblies = true;
}

builder = CreateApkBuilder ();
Assert.IsTrue (builder.Install (proj), "Install should have succeeded.");

if (Builder.UseDotNet)
Assert.True (builder.RunTarget (proj, "Run"), "Project should have run.");
else if (CommercialBuildAvailable)
Assert.True (builder.RunTarget (proj, "_Run"), "Project should have run.");
else
AdbStartActivity ($"{proj.PackageName}/{proj.JavaPackageName}.MainActivity");

Assert.IsTrue (WaitForActivityToStart (proj.PackageName, "MainActivity",
Path.Combine (Root, builder.ProjectDirectory, "startup-logcat.log")));
}

[Test]
public void SingleProject_ApplicationId ()
{
Expand Down
1 change: 1 addition & 0 deletions tests/Mono.Android-Tests/Mono.Android-Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<Import Project="..\..\Configuration.props" />
<PropertyGroup>
<TargetFrameworkVersion>$(AndroidLatestStableFrameworkVersion)</TargetFrameworkVersion>
<AndroidNdkDirectory></AndroidNdkDirectory>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<Import Project="..\..\..\Configuration.props" />
<PropertyGroup>
<TargetFrameworkVersion>$(AndroidFrameworkVersion)</TargetFrameworkVersion>
<AndroidNdkDirectory></AndroidNdkDirectory>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down
Loading