Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] $(AndroidNdkDirectory) is optional (#6539)
Browse files Browse the repository at this point in the history
Context: #3299
Context: f0b1e8a

Commit f0b1e8a updated the Xamarin.Android installation so that
NDK tools required for Profiled AOT were redistributed as part of
Xamarin.Android itself, removing the need to have an NDK installation
in order to use Profiled AOT; this was principally the `*-ld` and
`*-strip` utilities.

(`*-as` were already bundled into Xamarin.Android via decfbcc.)

However, in a classic "the left hand isn't necessarily communicating
with the right hand" scenario, commit f0b1e8a didn't add any
unit tests enforcing this, so if you *actually* tried to build an app
with Profiled AOT when the NDK wasn't available, the build would fail:

	error XA5104: Could not locate the Android NDK.
	Please make sure the Android NDK is installed in the Android SDK Manager,
	or if using a custom NDK path, please ensure the $(AndroidNdkDirectory)
	MSBuild property is set to the custom path.

Make it possible to build with AOT or Profiled AOT without the NDK:

 1. Make a `NullNdkTools` class to use when an NDK is not found.

 2. Make the NDK only actually required when `$(EnableLLVM)` is `true`.

 3. Update our MSBuild tests so they leave the
    `$(AndroidNdkDirectory)` property blank.

After these changes, I'm able to build apps with both normal AOT and
Profiled AOT enabled, without having an Android NDK used.

After this is merged, we might be able to make `Release` builds in
.NET 6 default to using profiled AOT.
  • Loading branch information
jonathanpeppers committed Dec 6, 2021
1 parent 5dcf294 commit c96d9f4
Show file tree
Hide file tree
Showing 14 changed files with 77 additions and 57 deletions.
4 changes: 2 additions & 2 deletions src/Xamarin.Android.Build.Tasks/Tasks/Aot.cs
Expand Up @@ -57,8 +57,8 @@ static string QuoteFileName(string fileName)

public async override System.Threading.Tasks.Task RunTaskAsync ()
{
NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log);
if (ndk == null) {
NdkTools ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: EnableLLVM, log: Log);
if (Log.HasLoggedErrors) {
return; // NdkTools.Create will log appropriate error
}

Expand Down
4 changes: 2 additions & 2 deletions src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs
Expand Up @@ -694,8 +694,8 @@ private void AddNativeLibraries (ArchiveFileList files, string [] supportedAbis)
return;
}

NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log);
if (ndk == null) {
NdkTools ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: false, log: Log);
if (Log.HasLoggedErrors) {
return; // NdkTools.Create will log appropriate error
}

Expand Down
35 changes: 16 additions & 19 deletions src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs
@@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -16,50 +17,50 @@ namespace Xamarin.Android.Tasks
public abstract class GetAotArguments : AndroidAsyncTask
{
[Required]
public string AndroidApiLevel { get; set; }
public string AndroidApiLevel { get; set; } = "";

[Required]
public string AndroidAotMode { get; set; }
public string AndroidAotMode { get; set; } = "";

[Required]
public string AotOutputDirectory { get; set; }
public string AotOutputDirectory { get; set; } = "";

[Required]
public string AndroidBinUtilsDirectory { get; set; }
public string AndroidBinUtilsDirectory { get; set; } = "";

[Required]
public string TargetName { get; set; }
public string TargetName { get; set; } = "";

/// <summary>
/// Will be blank in .NET 6+
/// </summary>
public string ManifestFile { get; set; }
public string ManifestFile { get; set; } = "";

/// <summary>
/// $(AndroidMinimumSupportedApiLevel) in .NET 6+
/// </summary>
public string MinimumSupportedApiLevel { get; set; }
public string MinimumSupportedApiLevel { get; set; } = "";

public string RuntimeIdentifier { get; set; }
public string RuntimeIdentifier { get; set; } = "";

public string AndroidNdkDirectory { get; set; }
public string AndroidNdkDirectory { get; set; } = "";

public bool EnableLLVM { get; set; }

public string AndroidSequencePointsMode { get; set; }
public string AndroidSequencePointsMode { get; set; } = "";

public ITaskItem [] Profiles { get; set; }
public ITaskItem [] Profiles { get; set; } = Array.Empty<ITaskItem> ();

public bool UsingAndroidNETSdk { get; set; }

public string AotAdditionalArguments { get; set; }
public string AotAdditionalArguments { get; set; } = "";

[Required, Output]
public ITaskItem [] ResolvedAssemblies { get; set; }
public ITaskItem [] ResolvedAssemblies { get; set; } = Array.Empty<ITaskItem> ();

protected AotMode AotMode;
protected SequencePointsMode SequencePointsMode;
protected string SdkBinDirectory;
protected string SdkBinDirectory = "";

public static bool GetAndroidAotMode(string androidAotMode, out AotMode aotMode)
{
Expand Down Expand Up @@ -117,7 +118,7 @@ protected string GetToolPrefix (NdkTools ndk, AndroidTargetArch arch, out int le

int GetNdkApiLevel (NdkTools ndk, AndroidTargetArch arch)
{
AndroidAppManifest manifest = null;
AndroidAppManifest? manifest = null;
if (!string.IsNullOrEmpty (ManifestFile)) {
manifest = AndroidAppManifest.Load (ManifestFile, MonoAndroidHelper.SupportedVersions);
}
Expand Down Expand Up @@ -254,10 +255,6 @@ string GetLdFlags(NdkTools ndk, AndroidTargetArch arch, int level, string toolPr
var toolchainPath = toolPrefix.Substring (0, toolPrefix.LastIndexOf (Path.DirectorySeparatorChar));
var ldFlags = string.Empty;
if (EnableLLVM) {
if (string.IsNullOrEmpty (AndroidNdkDirectory)) {
return null;
}

string androidLibPath = string.Empty;
try {
androidLibPath = ndk.GetDirectoryPath (NdkToolchainDir.PlatformLib, arch, level);
Expand Down
4 changes: 2 additions & 2 deletions src/Xamarin.Android.Build.Tasks/Tasks/GetAotAssemblies.cs
Expand Up @@ -15,8 +15,8 @@ public class GetAotAssemblies : GetAotArguments

public override Task RunTaskAsync ()
{
NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log);
if (ndk == null) {
NdkTools ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: EnableLLVM, log: Log);
if (Log.HasLoggedErrors) {
return Task.CompletedTask; // NdkTools.Create will log appropriate error
}

Expand Down
Expand Up @@ -23,7 +23,6 @@ public class MakeBundleNativeCodeExternal : AndroidTask

const string BundleSharedLibraryName = "libmonodroid_bundle_app.so";

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

[Required]
Expand Down Expand Up @@ -57,8 +56,8 @@ public MakeBundleNativeCodeExternal ()

public override bool RunTask ()
{
NdkTools? ndk = NdkTools.Create (AndroidNdkDirectory, Log);
if (ndk == null) {
NdkTools ndk = NdkTools.Create (AndroidNdkDirectory, logErrors: true, log: Log);
if (Log.HasLoggedErrors) {
return false; // NdkTools.Create will log appropriate error
}

Expand Down
Expand Up @@ -258,6 +258,7 @@ public void BuildAotApplicationAndBundleAndÜmläüts (string supportedAbis, boo
AotAssemblies = true,
PackageName = "com.xamarin.buildaotappandbundlewithspecialchars",
};
proj.SetProperty ("AndroidNdkDirectory", AndroidNdkPath);
proj.SetProperty (KnownProperties.TargetFrameworkVersion, "v5.1");
proj.SetAndroidSupportedAbis (supportedAbis);
proj.SetProperty ("EnableLLVM", enableLLVM.ToString ());
Expand Down
Expand Up @@ -1003,6 +1003,7 @@ public void CSharp8Features ([Values (true, false)] bool bindingProject)
public void BuildMkBundleApplicationRelease ()
{
var proj = new XamarinAndroidApplicationProject () { IsRelease = true, BundleAssemblies = true };
proj.SetProperty ("AndroidNdkDirectory", AndroidNdkPath);
using (var b = CreateApkBuilder ("temp/BuildMkBundleApplicationRelease", false)) {
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
var assemblies = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath,
Expand All @@ -1029,6 +1030,7 @@ public void BuildMkBundleApplicationRelease ()
public void BuildMkBundleApplicationReleaseAllAbi ()
{
var proj = new XamarinAndroidApplicationProject () { IsRelease = true, BundleAssemblies = true };
proj.SetProperty ("AndroidNdkDirectory", AndroidNdkPath);
proj.SetAndroidSupportedAbis ("armeabi-v7a", "x86");
using (var b = CreateApkBuilder ("temp/BuildMkBundleApplicationReleaseAllAbi", false)) {
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
Expand Down
Expand Up @@ -33,7 +33,7 @@ public void TestNdkUtil ()
using (var builder = new Builder ()) {
var ndkDir = AndroidNdkPath;
var sdkDir = AndroidSdkPath;
NdkTools ndk = NdkTools.Create (ndkDir, log);
NdkTools ndk = NdkTools.Create (ndkDir, log: log);
ndk.OSBinPath = SetUp.OSBinDirectory;
MonoAndroidHelper.AndroidSdk = new AndroidSdkInfo ((arg1, arg2) => { }, sdkDir, ndkDir, AndroidSdkResolver.GetJavaSdkPath ());
var platforms = ndk.GetSupportedPlatforms ();
Expand Down
Expand Up @@ -30,8 +30,6 @@ public JarContentBuilder ()
}
};

var sdkPath = AndroidSdkResolver.GetAndroidSdkPath ();
var ndkPath = AndroidSdkResolver.GetAndroidNdkPath ();
var jdkPath = AndroidSdkResolver.GetJavaSdkPath ();
JavacFullPath = Path.Combine (jdkPath, "bin", "javac");
JarFullPath = Path.Combine (jdkPath, "bin", "jar");
Expand Down
Expand Up @@ -311,10 +311,6 @@ protected bool BuildInternal (string projectOrSolution, string target, string []
if (Directory.Exists (sdkPath)) {
sw.WriteLine (" /p:AndroidSdkDirectory=\"{0}\" ", sdkPath);
}
string ndkPath = AndroidSdkResolver.GetAndroidNdkPath ();
if (Directory.Exists (ndkPath)) {
sw.WriteLine (" /p:AndroidNdkDirectory=\"{0}\" ", ndkPath);
}
string jdkPath = AndroidSdkResolver.GetJavaSdkPath ();
if (Directory.Exists (jdkPath)) {
sw.WriteLine (" /p:JavaSdkDirectory=\"{0}\" ", jdkPath);
Expand Down
Expand Up @@ -13,7 +13,6 @@ public class DotNetCLI
public string ProcessLogFile { get; set; }
public string Verbosity { get; set; } = "diag";
public string AndroidSdkPath { get; set; } = AndroidSdkResolver.GetAndroidSdkPath ();
public string AndroidNdkPath { get; set; } = AndroidSdkResolver.GetAndroidNdkPath ();
public string JavaSdkPath { get; set; } = AndroidSdkResolver.GetJavaSdkPath ();

public string ProjectDirectory { get; private set; }
Expand Down Expand Up @@ -159,9 +158,6 @@ List<string> GetDefaultCommandLineArgs (string verb, string target = null, strin
if (Directory.Exists (AndroidSdkPath)) {
arguments.Add ($"/p:AndroidSdkDirectory=\"{AndroidSdkPath}\"");
}
if (Directory.Exists (AndroidNdkPath)) {
arguments.Add ($"/p:AndroidNdkDirectory=\"{AndroidNdkPath}\"");
}
if (Directory.Exists (JavaSdkPath)) {
arguments.Add ($"/p:JavaSdkDirectory=\"{JavaSdkPath}\"");
}
Expand Down
39 changes: 21 additions & 18 deletions src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NdkTools.cs
Expand Up @@ -60,37 +60,38 @@ public abstract class NdkTools
}
}

protected NdkTools (string androidNdkPath, NdkVersion version, TaskLoggingHelper? log = null)
protected NdkTools (NdkVersion version, TaskLoggingHelper? log = null)
{
if (String.IsNullOrEmpty (androidNdkPath)) {
throw new ArgumentException ("must be a non-empty string", nameof (androidNdkPath));
}

Log = log;
NdkRootDirectory = androidNdkPath;
Version = version;
}

public static NdkTools? Create (string androidNdkPath, TaskLoggingHelper? log = null, string? osBinPath = null)
protected NdkTools (string androidNdkPath, NdkVersion version, TaskLoggingHelper? log = null) : this (version, log)
{
if (String.IsNullOrEmpty (androidNdkPath)) {
log?.LogCodedError ("XA5104", Properties.Resources.XA5104);
return null;
throw new ArgumentException ("must be a non-empty string", nameof (androidNdkPath));
}

if (!Directory.Exists (androidNdkPath)) {
log?.LogCodedError ("XA5104", Properties.Resources.XA5104);
return null;
NdkRootDirectory = androidNdkPath;
}

public static NdkTools Create (string androidNdkPath, bool logErrors = true, TaskLoggingHelper? log = null)
{
if (String.IsNullOrEmpty (androidNdkPath) || !Directory.Exists (androidNdkPath)) {
if (logErrors)
log?.LogCodedError ("XA5104", Properties.Resources.XA5104);
return new NullNdkTools (log);
}

NdkVersion? version = ReadVersion (androidNdkPath, log);
NdkVersion? version = ReadVersion (androidNdkPath, logErrors, log);
if (version == null) {
return null;
return new NullNdkTools (log);
}

if (version.Main.Major < 14) {
if (log != null) {
log.LogCodedError ("XA5104", Properties.Resources.XA5104);
if (logErrors)
log.LogCodedError ("XA5104", Properties.Resources.XA5104);
log.LogDebugMessage ($"Unsupported NDK version {version}");
}
} else if (version.Main.Major < 16) {
Expand Down Expand Up @@ -378,12 +379,13 @@ bool HasPrebuiltDir (string name)
}
}

static NdkVersion? ReadVersion (string androidNdkPath, TaskLoggingHelper? log = null)
static NdkVersion? ReadVersion (string androidNdkPath, bool logErrors = true, TaskLoggingHelper? log = null)
{
string sourcePropertiesPath = Path.Combine (androidNdkPath, "source.properties");
if (!File.Exists (sourcePropertiesPath)) {
if (log != null) {
log.LogCodedError ("XA5104", Properties.Resources.XA5104);
if (logErrors)
log.LogCodedError ("XA5104", Properties.Resources.XA5104);
log.LogDebugMessage ("Could not read NDK version information, '{sourcePropertiesPath}' not found.");
}
return null;
Expand All @@ -400,7 +402,8 @@ bool HasPrebuiltDir (string name)
string[] parts = line.Split (splitChars, 2);
if (parts.Length != 2) {
if (log != null) {
log.LogCodedError ("XA5104", Properties.Resources.XA5104);
if (logErrors)
log.LogCodedError ("XA5104", Properties.Resources.XA5104);
log.LogDebugMessage ($"Invalid NDK version format in '{sourcePropertiesPath}'.");
}
return null;
Expand Down
Expand Up @@ -7,6 +7,8 @@ public class NdkVersion
public Version Main { get; }
public string Tag { get; } = String.Empty;

public NdkVersion () => Main = new Version (0, 0);

public NdkVersion (string? version)
{
string? ver = version?.Trim ();
Expand Down
26 changes: 26 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Utilities/NdkTools/NullNdkTools.cs
@@ -0,0 +1,26 @@
#nullable enable
using System;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Utilities;
using Xamarin.Android.Tools;

namespace Xamarin.Android.Tasks
{
class NullNdkTools : NdkTools
{
public NullNdkTools (TaskLoggingHelper? log = null) : base (new NdkVersion (), log)
{
log?.LogDebugMessage ("No Android NDK found");
}

public override int GetMinimumApiLevelFor (AndroidTargetArch arch) => throw new NotImplementedException ();

public override string GetToolPath (NdkToolKind kind, AndroidTargetArch arch, int apiLevel) => throw new NotImplementedException ();

public override string GetToolPath (string name, AndroidTargetArch arch, int apiLevel) => throw new NotImplementedException ();

public override bool ValidateNdkPlatform (Action<string> logMessage, Action<string, string> logError, AndroidTargetArch arch, bool enableLLVM) => throw new NotImplementedException ();

protected override string GetPlatformIncludeDirPath (AndroidTargetArch arch, int apiLevel) => throw new NotImplementedException ();
}
}

0 comments on commit c96d9f4

Please sign in to comment.