Skip to content

Commit

Permalink
[NDK] Locate and select only compatible NDK versions
Browse files Browse the repository at this point in the history
Context: dotnet/android#5499
Context: dotnet/android#5526
Context: android/ndk#1427
Context: https://developer.android.com/studio/command-line/variables#envar

Xamarin.Android is not (yet) compatible with the recently released
Android NDK r22 version.  Azure build images have recently rolled out an
update which includes NDK r22 and, thus, it breaks builds for customers
using any form of Xamarin.Android AOT build.

In attempt to detect broken/incompatible NDK versions as well as select
the "best one", this commit adds code to scan the known NDK locations in
search of the preferred version.  The search is conducted as follows:

  1. If the user selected a preferred NDK location, it is always used.
  2. Locations specified in the `ANDROID_{HOME,SDK_ROOT}` environment
     variables are returned next.
  3. Directories in the `PATH` environment variable are examined to find
     a valid NDK location.
  4. OS-specific known NDK locations are considered.

For each of the returned locations, we now look for the Android SDK
packages containing the NDK.  There are two kinds of such packages:

  * `ndk-bundle` is the older package which allows for installation of
    only one NDK inside the SDK directory
  * `ndk/*` is a newer package which allows for installation of several
    NDK versions in parallel.  Each subdirectory of `ndk` is an `X.Y.Z`
    version number of the NDK.

In each of these directories we look for the `source.properties` file
from which we then extract the NDK version and then we sort thus
discovered NDK instances using their version as the key, in the
descending order.  The latest compatible (currently: less than 22 and
more than 15) version is selected and its path returned to the caller.
  • Loading branch information
grendello committed Jan 20, 2021
1 parent 26d65d9 commit 2ed78b5
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 3 deletions.
83 changes: 80 additions & 3 deletions src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ namespace Xamarin.Android.Tools
{
abstract class AndroidSdkBase
{
const int MinimumCompatibleNDKMajorVersion = 16;
const int MaximumCompatibleNDKMajorVersion = 21;

static readonly char[] SourcePropertiesKeyValueSplit = new char[] { '=' };

// Per https://developer.android.com/studio/command-line/variables#envar
protected static readonly string[] AndroidSdkEnvVars = {"ANDROID_HOME", "ANDROID_SDK_ROOT"};

string[]? allAndroidSdks;

public string[] AllAndroidSdks {
Expand Down Expand Up @@ -97,8 +105,9 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd
if (pathValidator (ctorParam))
return ctorParam;
foreach (var path in getAllPaths ()) {
if (pathValidator (path))
if (pathValidator (path)) {
return path;
}
}
return null;
}
Expand All @@ -108,7 +117,7 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd
if (ValidateAndroidNdkLocation (ctorParam))
return ctorParam;
if (AndroidSdkPath != null) {
string bundle = Path.Combine (AndroidSdkPath, "ndk-bundle");
string bundle = FindBestNDK (AndroidSdkPath);
if (Directory.Exists (bundle) && ValidateAndroidNdkLocation (bundle))
return bundle;
}
Expand All @@ -125,6 +134,18 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd
protected abstract IEnumerable<string> GetAllAvailableAndroidSdks ();
protected abstract string GetShortFormPath (string path);

protected IEnumerable<string> GetSdkFromEnvironmentVariables ()
{
foreach (string envVar in AndroidSdkEnvVars) {
string ev = Environment.GetEnvironmentVariable (envVar);
if (String.IsNullOrEmpty (ev)) {
continue;
}

yield return ev;
}
}

protected virtual IEnumerable<string> GetAllAvailableAndroidNdks ()
{
// Look in PATH
Expand All @@ -139,7 +160,63 @@ protected virtual IEnumerable<string> GetAllAvailableAndroidNdks ()
foreach (var sdk in GetAllAvailableAndroidSdks ()) {
if (sdk == AndroidSdkPath)
continue;
yield return Path.Combine (sdk, "ndk-bundle");
yield return FindBestNDK (sdk);
}
}

string FindBestNDK (string androidSdkPath)
{
var ndkInstances = new SortedDictionary<Version, string> (Comparer<Version>.Create ((Version l, Version r) => r.CompareTo (l)));

foreach (string ndkPath in Directory.EnumerateDirectories (androidSdkPath, "ndk*", SearchOption.TopDirectoryOnly)) {
if (String.Compare ("ndk-bundle", Path.GetFileName (ndkPath), StringComparison.OrdinalIgnoreCase) == 0) {
LoadNDKVersion (ndkPath);
continue;
}

if (String.Compare ("ndk", Path.GetFileName (ndkPath), StringComparison.OrdinalIgnoreCase) != 0) {
continue;
}

foreach (string versionedNdkPath in Directory.EnumerateDirectories (ndkPath, "*", SearchOption.TopDirectoryOnly)) {
LoadNDKVersion (versionedNdkPath);
}
}

if (ndkInstances.Count == 0) {
return String.Empty;
}

return ndkInstances.First ().Value;

void LoadNDKVersion (string path)
{
string propsFilePath = Path.Combine (path, "source.properties");
if (!File.Exists (propsFilePath)) {
return;
}

foreach (string line in File.ReadLines (propsFilePath)) {
string[] parts = line.Split (SourcePropertiesKeyValueSplit, 2, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2) {
continue;
}

if (String.Compare ("Pkg.Revision", parts[0].Trim (), StringComparison.Ordinal) != 0) {
continue;
}

if (!Version.TryParse (parts[1].Trim (), out Version? ndkVer) || ndkVer == null || ndkInstances.ContainsKey (ndkVer)) {
continue;
}

if (ndkVer.Major < MinimumCompatibleNDKMajorVersion || ndkVer.Major > MaximumCompatibleNDKMajorVersion) {
continue;
}

ndkInstances.Add (ndkVer, path);
return;
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ protected override IEnumerable<string> GetAllAvailableAndroidSdks ()
if (!string.IsNullOrEmpty (preferedSdkPath))
yield return preferedSdkPath!;

foreach (string dir in GetSdkFromEnvironmentVariables ()) {
yield return dir;
}

// Look in PATH
foreach (var adb in ProcessUtils.FindExecutablesInPath (Adb)) {
var path = Path.GetDirectoryName (adb);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ protected override IEnumerable<string> GetAllAvailableAndroidNdks ()
if (CheckRegistryKeyForExecutable (root, regKey, MDREG_ANDROID_NDK, wow, ".", NdkStack))
yield return RegistryEx.GetValueString (root, regKey, MDREG_ANDROID_NDK, wow) ?? "";

foreach (string dir in GetSdkFromEnvironmentVariables ()) {
yield return dir;
}

/*
// Check for the key written by the Xamarin installer
if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow, "platform-tools", Adb))
Expand Down

0 comments on commit 2ed78b5

Please sign in to comment.