-
Notifications
You must be signed in to change notification settings - Fork 25
/
AndroidSdkBase.cs
327 lines (278 loc) · 11.2 KB
/
AndroidSdkBase.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.IO;
using System.Collections.Generic;
namespace Xamarin.Android.Tools
{
abstract class AndroidSdkBase
{
// When this changes, update the test: Xamarin.Android.Tools.Tests.AndroidSdkInfoTests.Ndk_MultipleNdkVersionsInSdk
const int MinimumCompatibleNDKMajorVersion = 16;
const int MaximumCompatibleNDKMajorVersion = 23;
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 {
get {
if (allAndroidSdks == null) {
var dirs = new List<string?> ();
dirs.Add (AndroidSdkPath);
dirs.AddRange (GetAllAvailableAndroidSdks ());
allAndroidSdks = dirs.Where (d => ValidateAndroidSdkLocation ("AllAndroidSdks", d))
.Select (d => d!)
.Distinct ()
.ToArray ();
}
return allAndroidSdks;
}
}
public readonly Action<TraceLevel, string> Logger;
public AndroidSdkBase (Action<TraceLevel, string> logger)
{
Logger = logger;
}
public string? AndroidSdkPath { get; private set; }
public string? AndroidNdkPath { get; private set; }
public string? JavaSdkPath { get; private set; }
public string? JavaBinPath { get; private set; }
public string? AndroidPlatformToolsPath { get; private set; }
public string? AndroidPlatformToolsPathShort { get; private set; }
public virtual string Adb { get; protected set; } = "adb";
public virtual string ZipAlign { get; protected set; } = "zipalign";
public virtual string JarSigner { get; protected set; } = "jarsigner";
public virtual string KeyTool { get; protected set; } = "keytool";
public virtual string NdkStack { get; protected set; } = "ndk-stack";
public abstract string NdkHostPlatform32Bit { get; }
public abstract string NdkHostPlatform64Bit { get; }
public virtual string Javac { get; protected set; } = "javac";
public abstract string? PreferedAndroidSdkPath { get; }
public abstract string? PreferedAndroidNdkPath { get; }
public abstract string? PreferedJavaSdkPath { get; }
public virtual void Initialize (string? androidSdkPath = null, string? androidNdkPath = null, string? javaSdkPath = null)
{
AndroidSdkPath = GetValidPath (ValidateAndroidSdkLocation, androidSdkPath, () => PreferedAndroidSdkPath, () => GetAllAvailableAndroidSdks ());
JavaSdkPath = GetValidPath (ValidateJavaSdkLocation, javaSdkPath, () => PreferedJavaSdkPath, () => GetJavaSdkPaths ());
AndroidNdkPath = GetValidNdkPath (androidNdkPath);
if (!string.IsNullOrEmpty (JavaSdkPath)) {
JavaBinPath = Path.Combine (JavaSdkPath, "bin");
} else {
JavaBinPath = null;
}
if (!string.IsNullOrEmpty (AndroidSdkPath)) {
AndroidPlatformToolsPath = Path.Combine (AndroidSdkPath, "platform-tools");
AndroidPlatformToolsPathShort = GetShortFormPath (AndroidPlatformToolsPath);
} else {
AndroidPlatformToolsPath = null;
AndroidPlatformToolsPathShort = null;
}
if (!string.IsNullOrEmpty (AndroidNdkPath)) {
// It would be nice if .NET had real globbing support in System.IO...
string toolchainsDir = Path.Combine (AndroidNdkPath, "toolchains");
string llvmNdkToolchainDir = Path.Combine (toolchainsDir, "llvm", "prebuilt", NdkHostPlatform64Bit);
if (Directory.Exists (llvmNdkToolchainDir)) {
IsNdk64Bit = true;
} else if (Directory.Exists (toolchainsDir)) {
IsNdk64Bit = Directory.EnumerateDirectories (toolchainsDir, "arm-linux-androideabi-*")
.Any (dir => Directory.Exists (Path.Combine (dir, "prebuilt", NdkHostPlatform64Bit)));
}
}
// we need to look for extensions other than the default .exe|.bat
// google have a habbit of changing them.
Adb = GetExecutablePath (AndroidPlatformToolsPath, Adb);
NdkStack = GetExecutablePath (AndroidNdkPath, NdkStack);
}
static string? GetValidPath (Func<string, string?, bool> pathValidator, string? ctorParam, Func<string?> getPreferredPath, Func<IEnumerable<string>> getAllPaths)
{
if (pathValidator ("constructor param", ctorParam))
return ctorParam;
ctorParam = getPreferredPath ();
if (pathValidator ("preferred path", ctorParam))
return ctorParam;
foreach (var path in getAllPaths ()) {
if (pathValidator ("all paths", path)) {
return path;
}
}
return null;
}
string? GetValidNdkPath (string? ctorParam)
{
if (ValidateAndroidNdkLocation ("constructor param", ctorParam))
return ctorParam;
if (AndroidSdkPath != null) {
string bundle = FindBestNDK (AndroidSdkPath);
if (Directory.Exists (bundle) && ValidateAndroidNdkLocation ("within Android SDK", bundle))
return bundle;
}
ctorParam = PreferedAndroidNdkPath;
if (ValidateAndroidNdkLocation ("preferred path", ctorParam))
return ctorParam;
foreach (var path in GetAllAvailableAndroidNdks ()) {
if (ValidateAndroidNdkLocation ("all paths", path))
return path;
}
return null;
}
protected abstract IEnumerable<string> GetAllAvailableAndroidSdks ();
protected abstract string GetShortFormPath (string path);
protected IEnumerable<string> GetSdkFromEnvironmentVariables ()
{
foreach (string envVar in AndroidSdkEnvVars) {
var ev = Environment.GetEnvironmentVariable (envVar);
if (String.IsNullOrEmpty (ev)) {
continue;
}
yield return ev;
}
}
protected virtual IEnumerable<string> GetAllAvailableAndroidNdks ()
{
// Look in PATH
foreach (var ndkStack in ProcessUtils.FindExecutablesInPath (NdkStack)) {
var ndkDir = Path.GetDirectoryName (ndkStack);
if (string.IsNullOrEmpty (ndkDir))
continue;
yield return ndkDir;
}
// Check for the "ndk-bundle" directory inside other SDK directories
foreach (var sdk in GetAllAvailableAndroidSdks ()) {
if (sdk == AndroidSdkPath)
continue;
var ndkDir = FindBestNDK (sdk);
if (string.IsNullOrEmpty (ndkDir))
continue;
yield return ndkDir;
}
}
string FindBestNDK (string androidSdkPath)
{
if (!Directory.Exists (androidSdkPath)) {
return String.Empty;
}
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;
}
var kvp = ndkInstances.First ();
Logger (TraceLevel.Verbose, $"Best NDK selected: v{kvp.Key} in {kvp.Value}");
return kvp.Value;
void LoadNDKVersion (string path)
{
string propsFilePath = Path.Combine (path, "source.properties");
if (!File.Exists (propsFilePath)) {
Logger (TraceLevel.Verbose, $"Skipping NDK in '{path}': no source.properties, cannot determine version");
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) {
Logger (TraceLevel.Verbose, $"Skipping NDK in '{path}': version {ndkVer} is out of the accepted range (major version must be between {MinimumCompatibleNDKMajorVersion} and {MaximumCompatibleNDKMajorVersion}");
continue;
}
ndkInstances.Add (ndkVer, path);
return;
}
}
}
public abstract void SetPreferredAndroidSdkPath (string? path);
public abstract void SetPreferredJavaSdkPath (string? path);
public abstract void SetPreferredAndroidNdkPath (string? path);
public bool IsNdk64Bit { get; private set; }
public string NdkHostPlatform {
get { return IsNdk64Bit ? NdkHostPlatform64Bit : NdkHostPlatform32Bit; }
}
IEnumerable<string> GetJavaSdkPaths ()
{
return JdkInfo.GetKnownSystemJdkInfos (Logger)
.Select (jdk => jdk.HomePath);
}
/// <summary>
/// Checks that a value is the location of an Android SDK.
/// </summary>
public bool ValidateAndroidSdkLocation (string locator, [NotNullWhen (true)] string? loc)
{
bool result = !string.IsNullOrEmpty (loc);
if (result) {
bool foundAdb = false;
foreach (var p in ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc!, "platform-tools"), Adb)) {
Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidSdkLocation)}: for locator={locator}, path=`{loc}`, found adb `{p}`");
foundAdb = true;
}
result = foundAdb;
}
Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidSdkLocation)}: for locator={locator}, path=`{loc}`, result={result}");
return result;
}
/// <summary>
/// Checks that a value is the location of a Java SDK.
/// </summary>
public virtual bool ValidateJavaSdkLocation (string locator, [NotNullWhen (true)] string? loc)
{
bool result = !string.IsNullOrEmpty (loc);
if (result) {
bool foundSigner = false;
foreach (var p in ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc!, "bin"), JarSigner)) {
Logger (TraceLevel.Verbose, $"{nameof (ValidateJavaSdkLocation)}: for locator={locator}, path=`{loc}`, found jarsigner `{p}`");
foundSigner = true;
}
result = foundSigner;
}
Logger (TraceLevel.Verbose, $"{nameof (ValidateJavaSdkLocation)}: locator={locator}, path=`{loc}`, result={result}");
return result;
}
/// <summary>
/// Checks that a value is the location of an Android SDK.
/// </summary>
public bool ValidateAndroidNdkLocation (string locator, [NotNullWhen (true)] string? loc)
{
bool result = !string.IsNullOrEmpty (loc) &&
ProcessUtils.FindExecutablesInDirectory (loc!, NdkStack).Any ();
Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidNdkLocation)}: for locator={locator}, path=`{loc}`, result={result}");
return result;
}
internal static string? NullIfEmpty (string? s)
{
if (s == null || s.Length != 0)
return s;
return null;
}
static string GetExecutablePath (string? dir, string exe)
{
if (string.IsNullOrEmpty (dir))
return exe;
foreach (var e in ProcessUtils.ExecutableFiles (exe)) {
try {
if (File.Exists (Path.Combine (dir, e)))
return e;
} catch (ArgumentException) {
continue;
}
}
return exe;
}
}
}