Skip to content

Commit 2ed78b5

Browse files
committed
[NDK] Locate and select only compatible NDK versions
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.
1 parent 26d65d9 commit 2ed78b5

File tree

3 files changed

+88
-3
lines changed

3 files changed

+88
-3
lines changed

src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ namespace Xamarin.Android.Tools
99
{
1010
abstract class AndroidSdkBase
1111
{
12+
const int MinimumCompatibleNDKMajorVersion = 16;
13+
const int MaximumCompatibleNDKMajorVersion = 21;
14+
15+
static readonly char[] SourcePropertiesKeyValueSplit = new char[] { '=' };
16+
17+
// Per https://developer.android.com/studio/command-line/variables#envar
18+
protected static readonly string[] AndroidSdkEnvVars = {"ANDROID_HOME", "ANDROID_SDK_ROOT"};
19+
1220
string[]? allAndroidSdks;
1321

1422
public string[] AllAndroidSdks {
@@ -97,8 +105,9 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd
97105
if (pathValidator (ctorParam))
98106
return ctorParam;
99107
foreach (var path in getAllPaths ()) {
100-
if (pathValidator (path))
108+
if (pathValidator (path)) {
101109
return path;
110+
}
102111
}
103112
return null;
104113
}
@@ -108,7 +117,7 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd
108117
if (ValidateAndroidNdkLocation (ctorParam))
109118
return ctorParam;
110119
if (AndroidSdkPath != null) {
111-
string bundle = Path.Combine (AndroidSdkPath, "ndk-bundle");
120+
string bundle = FindBestNDK (AndroidSdkPath);
112121
if (Directory.Exists (bundle) && ValidateAndroidNdkLocation (bundle))
113122
return bundle;
114123
}
@@ -125,6 +134,18 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd
125134
protected abstract IEnumerable<string> GetAllAvailableAndroidSdks ();
126135
protected abstract string GetShortFormPath (string path);
127136

137+
protected IEnumerable<string> GetSdkFromEnvironmentVariables ()
138+
{
139+
foreach (string envVar in AndroidSdkEnvVars) {
140+
string ev = Environment.GetEnvironmentVariable (envVar);
141+
if (String.IsNullOrEmpty (ev)) {
142+
continue;
143+
}
144+
145+
yield return ev;
146+
}
147+
}
148+
128149
protected virtual IEnumerable<string> GetAllAvailableAndroidNdks ()
129150
{
130151
// Look in PATH
@@ -139,7 +160,63 @@ protected virtual IEnumerable<string> GetAllAvailableAndroidNdks ()
139160
foreach (var sdk in GetAllAvailableAndroidSdks ()) {
140161
if (sdk == AndroidSdkPath)
141162
continue;
142-
yield return Path.Combine (sdk, "ndk-bundle");
163+
yield return FindBestNDK (sdk);
164+
}
165+
}
166+
167+
string FindBestNDK (string androidSdkPath)
168+
{
169+
var ndkInstances = new SortedDictionary<Version, string> (Comparer<Version>.Create ((Version l, Version r) => r.CompareTo (l)));
170+
171+
foreach (string ndkPath in Directory.EnumerateDirectories (androidSdkPath, "ndk*", SearchOption.TopDirectoryOnly)) {
172+
if (String.Compare ("ndk-bundle", Path.GetFileName (ndkPath), StringComparison.OrdinalIgnoreCase) == 0) {
173+
LoadNDKVersion (ndkPath);
174+
continue;
175+
}
176+
177+
if (String.Compare ("ndk", Path.GetFileName (ndkPath), StringComparison.OrdinalIgnoreCase) != 0) {
178+
continue;
179+
}
180+
181+
foreach (string versionedNdkPath in Directory.EnumerateDirectories (ndkPath, "*", SearchOption.TopDirectoryOnly)) {
182+
LoadNDKVersion (versionedNdkPath);
183+
}
184+
}
185+
186+
if (ndkInstances.Count == 0) {
187+
return String.Empty;
188+
}
189+
190+
return ndkInstances.First ().Value;
191+
192+
void LoadNDKVersion (string path)
193+
{
194+
string propsFilePath = Path.Combine (path, "source.properties");
195+
if (!File.Exists (propsFilePath)) {
196+
return;
197+
}
198+
199+
foreach (string line in File.ReadLines (propsFilePath)) {
200+
string[] parts = line.Split (SourcePropertiesKeyValueSplit, 2, StringSplitOptions.RemoveEmptyEntries);
201+
if (parts.Length != 2) {
202+
continue;
203+
}
204+
205+
if (String.Compare ("Pkg.Revision", parts[0].Trim (), StringComparison.Ordinal) != 0) {
206+
continue;
207+
}
208+
209+
if (!Version.TryParse (parts[1].Trim (), out Version? ndkVer) || ndkVer == null || ndkInstances.ContainsKey (ndkVer)) {
210+
continue;
211+
}
212+
213+
if (ndkVer.Major < MinimumCompatibleNDKMajorVersion || ndkVer.Major > MaximumCompatibleNDKMajorVersion) {
214+
continue;
215+
}
216+
217+
ndkInstances.Add (ndkVer, path);
218+
return;
219+
}
143220
}
144221
}
145222

src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ protected override IEnumerable<string> GetAllAvailableAndroidSdks ()
9494
if (!string.IsNullOrEmpty (preferedSdkPath))
9595
yield return preferedSdkPath!;
9696

97+
foreach (string dir in GetSdkFromEnvironmentVariables ()) {
98+
yield return dir;
99+
}
100+
97101
// Look in PATH
98102
foreach (var adb in ProcessUtils.FindExecutablesInPath (Adb)) {
99103
var path = Path.GetDirectoryName (adb);

src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ protected override IEnumerable<string> GetAllAvailableAndroidNdks ()
230230
if (CheckRegistryKeyForExecutable (root, regKey, MDREG_ANDROID_NDK, wow, ".", NdkStack))
231231
yield return RegistryEx.GetValueString (root, regKey, MDREG_ANDROID_NDK, wow) ?? "";
232232

233+
foreach (string dir in GetSdkFromEnvironmentVariables ()) {
234+
yield return dir;
235+
}
236+
233237
/*
234238
// Check for the key written by the Xamarin installer
235239
if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow, "platform-tools", Adb))

0 commit comments

Comments
 (0)