1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'application_package.dart'; |
6 | library; |
7 | |
8 | import '../base/common.dart'; |
9 | import '../base/config.dart'; |
10 | import '../base/file_system.dart'; |
11 | import '../base/platform.dart'; |
12 | import '../base/process.dart'; |
13 | import '../base/version.dart'; |
14 | import '../convert.dart'; |
15 | import '../globals.dart' as globals; |
16 | import 'java.dart'; |
17 | |
18 | // ANDROID_SDK_ROOT is deprecated. |
19 | // See https://developer.android.com/studio/command-line/variables.html#envar |
20 | const kAndroidSdkRoot = 'ANDROID_SDK_ROOT' ; |
21 | const kAndroidHome = 'ANDROID_HOME' ; |
22 | |
23 | // No official environment variable for the NDK root is documented: |
24 | // https://developer.android.com/tools/variables#envar |
25 | // The follow three seem to be most commonly used. |
26 | const kAndroidNdkHome = 'ANDROID_NDK_HOME' ; |
27 | const kAndroidNdkPath = 'ANDROID_NDK_PATH' ; |
28 | const kAndroidNdkRoot = 'ANDROID_NDK_ROOT' ; |
29 | |
30 | final _numberedAndroidPlatformRe = RegExp(r'^android-([0-9]+)$' ); |
31 | final _sdkVersionRe = RegExp(r'^ro.build.version.sdk=([0-9]+)$' ); |
32 | |
33 | // Android SDK layout: |
34 | |
35 | // $ANDROID_HOME/platform-tools/adb |
36 | |
37 | // $ANDROID_HOME/build-tools/19.1.0/aapt, dx, zipalign |
38 | // $ANDROID_HOME/build-tools/22.0.1/aapt |
39 | // $ANDROID_HOME/build-tools/23.0.2/aapt |
40 | // $ANDROID_HOME/build-tools/24.0.0-preview/aapt |
41 | // $ANDROID_HOME/build-tools/25.0.2/apksigner |
42 | |
43 | // $ANDROID_HOME/platforms/android-22/android.jar |
44 | // $ANDROID_HOME/platforms/android-23/android.jar |
45 | // $ANDROID_HOME/platforms/android-N/android.jar |
46 | class AndroidSdk { |
47 | AndroidSdk(this.directory, {Java? java, FileSystem? fileSystem}) : _java = java { |
48 | reinitialize(fileSystem: fileSystem); |
49 | } |
50 | |
51 | /// The Android SDK root directory. |
52 | final Directory directory; |
53 | |
54 | final Java? _java; |
55 | |
56 | var _sdkVersions = <AndroidSdkVersion>[]; |
57 | AndroidSdkVersion? _latestVersion; |
58 | |
59 | /// Whether the `cmdline-tools` directory exists in the Android SDK. |
60 | /// |
61 | /// This is required to use the newest SDK manager which only works with |
62 | /// the newer JDK. |
63 | bool get cmdlineToolsAvailable => directory.childDirectory('cmdline-tools' ).existsSync(); |
64 | |
65 | /// Whether the `platform-tools` or `cmdline-tools` directory exists in the Android SDK. |
66 | /// |
67 | /// It is possible to have an Android SDK folder that is missing this with |
68 | /// the expectation that it will be downloaded later, e.g. by gradle or the |
69 | /// sdkmanager. The [licensesAvailable] property should be used to determine |
70 | /// whether the licenses are at least possibly accepted. |
71 | bool get platformToolsAvailable => |
72 | cmdlineToolsAvailable || directory.childDirectory('platform-tools' ).existsSync(); |
73 | |
74 | /// Whether the `licenses` directory exists in the Android SDK. |
75 | /// |
76 | /// The existence of this folder normally indicates that the SDK licenses have |
77 | /// been accepted, e.g. via the sdkmanager, Android Studio, or by copying them |
78 | /// from another workstation such as in CI scenarios. If these files are valid |
79 | /// gradle or the sdkmanager will be able to download and use other parts of |
80 | /// the SDK on demand. |
81 | bool get licensesAvailable => directory.childDirectory('licenses' ).existsSync(); |
82 | |
83 | static AndroidSdk? locateAndroidSdk() { |
84 | String? findAndroidHomeDir() { |
85 | String? androidHomeDir; |
86 | if (globals.config.containsKey('android-sdk' )) { |
87 | androidHomeDir = globals.config.getValue('android-sdk' ) as String?; |
88 | } else if (globals.platform.environment.containsKey(kAndroidHome)) { |
89 | androidHomeDir = globals.platform.environment[kAndroidHome]; |
90 | } else if (globals.platform.environment.containsKey(kAndroidSdkRoot)) { |
91 | androidHomeDir = globals.platform.environment[kAndroidSdkRoot]; |
92 | } else if (globals.platform.isLinux) { |
93 | if (globals.fsUtils.homeDirPath != null) { |
94 | androidHomeDir = globals.fs.path.join(globals.fsUtils.homeDirPath!, 'Android' , 'Sdk' ); |
95 | } |
96 | } else if (globals.platform.isMacOS) { |
97 | if (globals.fsUtils.homeDirPath != null) { |
98 | androidHomeDir = globals.fs.path.join( |
99 | globals.fsUtils.homeDirPath!, |
100 | 'Library' , |
101 | 'Android' , |
102 | 'sdk' , |
103 | ); |
104 | } |
105 | } else if (globals.platform.isWindows) { |
106 | if (globals.fsUtils.homeDirPath != null) { |
107 | androidHomeDir = globals.fs.path.join( |
108 | globals.fsUtils.homeDirPath!, |
109 | 'AppData' , |
110 | 'Local' , |
111 | 'Android' , |
112 | 'sdk' , |
113 | ); |
114 | } |
115 | } |
116 | |
117 | if (androidHomeDir != null) { |
118 | if (validSdkDirectory(androidHomeDir)) { |
119 | return androidHomeDir; |
120 | } |
121 | if (validSdkDirectory(globals.fs.path.join(androidHomeDir, 'sdk' ))) { |
122 | return globals.fs.path.join(androidHomeDir, 'sdk' ); |
123 | } |
124 | } |
125 | |
126 | // in build-tools/$version/aapt |
127 | final List<File> aaptBins = globals.os.whichAll('aapt' ); |
128 | for (var aaptBin in aaptBins) { |
129 | // Make sure we're using the aapt from the SDK. |
130 | aaptBin = globals.fs.file(aaptBin.resolveSymbolicLinksSync()); |
131 | final String dir = aaptBin.parent.parent.parent.path; |
132 | if (validSdkDirectory(dir)) { |
133 | return dir; |
134 | } |
135 | } |
136 | |
137 | // in platform-tools/adb |
138 | final List<File> adbBins = globals.os.whichAll('adb' ); |
139 | for (var adbBin in adbBins) { |
140 | // Make sure we're using the adb from the SDK. |
141 | adbBin = globals.fs.file(adbBin.resolveSymbolicLinksSync()); |
142 | final String dir = adbBin.parent.parent.path; |
143 | if (validSdkDirectory(dir)) { |
144 | return dir; |
145 | } |
146 | } |
147 | |
148 | return null; |
149 | } |
150 | |
151 | final String? androidHomeDir = findAndroidHomeDir(); |
152 | if (androidHomeDir == null) { |
153 | // No dice. |
154 | globals.printTrace('Unable to locate an Android SDK.' ); |
155 | return null; |
156 | } |
157 | |
158 | return AndroidSdk(globals.fs.directory(androidHomeDir)); |
159 | } |
160 | |
161 | static bool validSdkDirectory(String dir) { |
162 | return sdkDirectoryHasLicenses(dir) || sdkDirectoryHasPlatformTools(dir); |
163 | } |
164 | |
165 | static bool sdkDirectoryHasPlatformTools(String dir) { |
166 | return globals.fs.isDirectorySync(globals.fs.path.join(dir, 'platform-tools' )); |
167 | } |
168 | |
169 | static bool sdkDirectoryHasLicenses(String dir) { |
170 | return globals.fs.isDirectorySync(globals.fs.path.join(dir, 'licenses' )); |
171 | } |
172 | |
173 | List<AndroidSdkVersion> get sdkVersions => _sdkVersions; |
174 | |
175 | AndroidSdkVersion? get latestVersion => _latestVersion; |
176 | |
177 | late final String? adbPath = getPlatformToolsPath(globals.platform.isWindows ? 'adb.exe' : 'adb' ); |
178 | |
179 | String? get emulatorPath => getEmulatorPath(); |
180 | |
181 | String? get avdManagerPath => getAvdManagerPath(); |
182 | |
183 | /// Locate the path for storing AVD emulator images. Returns null if none found. |
184 | String? getAvdPath() { |
185 | final String? avdHome = globals.platform.environment['ANDROID_AVD_HOME' ]; |
186 | final String? home = globals.platform.environment['HOME' ]; |
187 | final searchPaths = <String>[ |
188 | if (avdHome != null) avdHome, |
189 | if (home != null) globals.fs.path.join(home, '.android' , 'avd' ), |
190 | ]; |
191 | |
192 | if (globals.platform.isWindows) { |
193 | final String? homeDrive = globals.platform.environment['HOMEDRIVE' ]; |
194 | final String? homePath = globals.platform.environment['HOMEPATH' ]; |
195 | |
196 | if (homeDrive != null && homePath != null) { |
197 | // Can't use path.join for HOMEDRIVE/HOMEPATH |
198 | // https://github.com/dart-lang/path/issues/37 |
199 | final String home = homeDrive + homePath; |
200 | searchPaths.add(globals.fs.path.join(home, '.android' , 'avd' )); |
201 | } |
202 | } |
203 | |
204 | for (final searchPath in searchPaths) { |
205 | if (globals.fs.directory(searchPath).existsSync()) { |
206 | return searchPath; |
207 | } |
208 | } |
209 | return null; |
210 | } |
211 | |
212 | Directory get _platformsDir => directory.childDirectory('platforms' ); |
213 | |
214 | Iterable<Directory> get _platforms { |
215 | Iterable<Directory> platforms = <Directory>[]; |
216 | if (_platformsDir.existsSync()) { |
217 | platforms = _platformsDir.listSync().whereType<Directory>(); |
218 | } |
219 | return platforms; |
220 | } |
221 | |
222 | /// Validate the Android SDK. This returns an empty list if there are no |
223 | /// issues; otherwise, it returns a list of issues found. |
224 | List<String> validateSdkWellFormed() { |
225 | if (adbPath == null || !globals.processManager.canRun(adbPath)) { |
226 | return <String>['Android SDK file not found: ${adbPath ?? 'adb' }.' ]; |
227 | } |
228 | |
229 | if (sdkVersions.isEmpty || latestVersion == null) { |
230 | final msg = StringBuffer('No valid Android SDK platforms found in ${_platformsDir.path}.' ); |
231 | if (_platforms.isEmpty) { |
232 | msg.write(' Directory was empty.' ); |
233 | } else { |
234 | msg.write(' Candidates were:\n' ); |
235 | msg.write(_platforms.map((Directory dir) => ' - ${dir.basename}' ).join('\n' )); |
236 | } |
237 | return <String>[msg.toString()]; |
238 | } |
239 | |
240 | if (directory.absolute.path.contains(' ' )) { |
241 | final androidSdkSpaceWarning = |
242 | 'Android SDK location currently ' |
243 | 'contains spaces, which is not supported by the Android SDK as it ' |
244 | 'causes problems with NDK tools. Try moving it from ' |
245 | ' ${directory.absolute.path} to a path without spaces.' ; |
246 | return <String>[androidSdkSpaceWarning]; |
247 | } |
248 | |
249 | return latestVersion!.validateSdkWellFormed(); |
250 | } |
251 | |
252 | String? getPlatformToolsPath(String binaryName) { |
253 | final File cmdlineToolsBinary = directory.childDirectory('cmdline-tools' ).childFile(binaryName); |
254 | if (cmdlineToolsBinary.existsSync()) { |
255 | return cmdlineToolsBinary.path; |
256 | } |
257 | final File platformToolBinary = directory |
258 | .childDirectory('platform-tools' ) |
259 | .childFile(binaryName); |
260 | if (platformToolBinary.existsSync()) { |
261 | return platformToolBinary.path; |
262 | } |
263 | return null; |
264 | } |
265 | |
266 | String? getEmulatorPath() { |
267 | final binaryName = globals.platform.isWindows ? 'emulator.exe' : 'emulator' ; |
268 | // Emulator now lives inside "emulator" but used to live inside "tools" so |
269 | // try both. |
270 | final searchFolders = <String>['emulator' , 'tools' ]; |
271 | for (final folder in searchFolders) { |
272 | final File file = directory.childDirectory(folder).childFile(binaryName); |
273 | if (file.existsSync()) { |
274 | return file.path; |
275 | } |
276 | } |
277 | return null; |
278 | } |
279 | |
280 | String? getCmdlineToolsPath(String binaryName, {bool skipOldTools = false}) { |
281 | // First look for the latest version of the command-line tools |
282 | final File cmdlineToolsLatestBinary = directory |
283 | .childDirectory('cmdline-tools' ) |
284 | .childDirectory('latest' ) |
285 | .childDirectory('bin' ) |
286 | .childFile(binaryName); |
287 | if (cmdlineToolsLatestBinary.existsSync()) { |
288 | return cmdlineToolsLatestBinary.path; |
289 | } |
290 | |
291 | // Next look for the highest version of the command-line tools |
292 | final Directory cmdlineToolsDir = directory.childDirectory('cmdline-tools' ); |
293 | if (cmdlineToolsDir.existsSync()) { |
294 | final List<Version> cmdlineTools = cmdlineToolsDir |
295 | .listSync() |
296 | .whereType<Directory>() |
297 | .map((Directory subDirectory) { |
298 | try { |
299 | return Version.parse(subDirectory.basename); |
300 | } on Exception { |
301 | return null; |
302 | } |
303 | }) |
304 | .whereType<Version>() |
305 | .toList(); |
306 | cmdlineTools.sort(); |
307 | |
308 | for (final Version cmdlineToolsVersion in cmdlineTools.reversed) { |
309 | final File cmdlineToolsBinary = directory |
310 | .childDirectory('cmdline-tools' ) |
311 | .childDirectory(cmdlineToolsVersion.toString()) |
312 | .childDirectory('bin' ) |
313 | .childFile(binaryName); |
314 | if (cmdlineToolsBinary.existsSync()) { |
315 | return cmdlineToolsBinary.path; |
316 | } |
317 | } |
318 | } |
319 | if (skipOldTools) { |
320 | return null; |
321 | } |
322 | |
323 | // Finally fallback to the old SDK tools |
324 | final File toolsBinary = directory |
325 | .childDirectory('tools' ) |
326 | .childDirectory('bin' ) |
327 | .childFile(binaryName); |
328 | if (toolsBinary.existsSync()) { |
329 | return toolsBinary.path; |
330 | } |
331 | |
332 | return null; |
333 | } |
334 | |
335 | String? getAvdManagerPath() => |
336 | getCmdlineToolsPath(globals.platform.isWindows ? 'avdmanager.bat' : 'avdmanager' ); |
337 | |
338 | /// From https://developer.android.com/ndk/guides/other_build_systems. |
339 | static const _llvmHostDirectoryName = <String, String>{ |
340 | 'macos' : 'darwin-x86_64' , |
341 | 'linux' : 'linux-x86_64' , |
342 | 'windows' : 'windows-x86_64' , |
343 | }; |
344 | |
345 | /// Locates the binary path for an NDK binary. |
346 | /// |
347 | /// The order of resolution is as follows: |
348 | /// |
349 | /// 1. If [globals.config] defines an `'android-ndk'` use that. |
350 | /// 2. If the environment variable `ANDROID_NDK_HOME` is defined, use that. |
351 | /// 3. If the environment variable `ANDROID_NDK_PATH` is defined, use that. |
352 | /// 4. If the environment variable `ANDROID_NDK_ROOT` is defined, use that. |
353 | /// 5. Look for the default install location inside the Android SDK: |
354 | /// [directory]/ndk/\<version\>/. If multiple versions exist, use the |
355 | /// newest. |
356 | String? getNdkBinaryPath(String binaryName, {Platform? platform, Config? config}) { |
357 | platform ??= globals.platform; |
358 | config ??= globals.config; |
359 | Directory? findAndroidNdkHomeDir() { |
360 | String? androidNdkHomeDir; |
361 | if (config!.containsKey('android-ndk' )) { |
362 | androidNdkHomeDir = config.getValue('android-ndk' ) as String?; |
363 | } else if (platform!.environment.containsKey(kAndroidNdkHome)) { |
364 | androidNdkHomeDir = platform.environment[kAndroidNdkHome]; |
365 | } else if (platform.environment.containsKey(kAndroidNdkPath)) { |
366 | androidNdkHomeDir = platform.environment[kAndroidNdkPath]; |
367 | } else if (platform.environment.containsKey(kAndroidNdkRoot)) { |
368 | androidNdkHomeDir = platform.environment[kAndroidNdkRoot]; |
369 | } |
370 | if (androidNdkHomeDir != null) { |
371 | return directory.fileSystem.directory(androidNdkHomeDir); |
372 | } |
373 | |
374 | // Look for the default install location of the NDK inside the Android |
375 | // SDK when installed through `sdkmanager` or Android studio. |
376 | final Directory ndk = directory.childDirectory('ndk' ); |
377 | if (!ndk.existsSync()) { |
378 | return null; |
379 | } |
380 | final List<Version> ndkVersions = |
381 | ndk |
382 | .listSync() |
383 | .map((FileSystemEntity entity) { |
384 | try { |
385 | return Version.parse(entity.basename); |
386 | } on Exception { |
387 | return null; |
388 | } |
389 | }) |
390 | .whereType<Version>() |
391 | .toList() |
392 | // Use latest NDK first. |
393 | ..sort((Version a, Version b) => -a.compareTo(b)); |
394 | if (ndkVersions.isEmpty) { |
395 | return null; |
396 | } |
397 | return ndk.childDirectory(ndkVersions.first.toString()); |
398 | } |
399 | |
400 | final Directory? androidNdkHomeDir = findAndroidNdkHomeDir(); |
401 | if (androidNdkHomeDir == null) { |
402 | return null; |
403 | } |
404 | final File executable = androidNdkHomeDir |
405 | .childDirectory('toolchains' ) |
406 | .childDirectory('llvm' ) |
407 | .childDirectory('prebuilt' ) |
408 | .childDirectory(_llvmHostDirectoryName[platform.operatingSystem]!) |
409 | .childDirectory('bin' ) |
410 | .childFile(binaryName); |
411 | if (executable.existsSync()) { |
412 | // LLVM missing in this NDK version. |
413 | return executable.path; |
414 | } |
415 | return null; |
416 | } |
417 | |
418 | String? getNdkClangPath({Platform? platform, Config? config}) { |
419 | platform ??= globals.platform; |
420 | return getNdkBinaryPath( |
421 | platform.isWindows ? 'clang.exe' : 'clang' , |
422 | platform: platform, |
423 | config: config, |
424 | ); |
425 | } |
426 | |
427 | String? getNdkArPath({Platform? platform, Config? config}) { |
428 | platform ??= globals.platform; |
429 | return getNdkBinaryPath( |
430 | platform.isWindows ? 'llvm-ar.exe' : 'llvm-ar' , |
431 | platform: platform, |
432 | config: config, |
433 | ); |
434 | } |
435 | |
436 | String? getNdkLdPath({Platform? platform, Config? config}) { |
437 | platform ??= globals.platform; |
438 | return getNdkBinaryPath( |
439 | platform.isWindows ? 'ld.lld.exe' : 'ld.lld' , |
440 | platform: platform, |
441 | config: config, |
442 | ); |
443 | } |
444 | |
445 | /// Sets up various paths used internally. |
446 | /// |
447 | /// This method should be called in a case where the tooling may have updated |
448 | /// SDK artifacts, such as after running a gradle build. |
449 | void reinitialize({FileSystem? fileSystem}) { |
450 | var buildTools = <Version>[]; // 19.1.0, 22.0.1, ... |
451 | |
452 | final Directory buildToolsDir = directory.childDirectory('build-tools' ); |
453 | if (buildToolsDir.existsSync()) { |
454 | buildTools = buildToolsDir |
455 | .listSync() |
456 | .map((FileSystemEntity entity) { |
457 | try { |
458 | return Version.parse(entity.basename); |
459 | } on Exception { |
460 | return null; |
461 | } |
462 | }) |
463 | .whereType<Version>() |
464 | .toList(); |
465 | } |
466 | |
467 | // Match up platforms with the best corresponding build-tools. |
468 | _sdkVersions = _platforms |
469 | .map<AndroidSdkVersion?>((Directory platformDir) { |
470 | final String platformName = platformDir.basename; |
471 | int platformVersion; |
472 | |
473 | try { |
474 | final Match? numberedVersion = _numberedAndroidPlatformRe.firstMatch(platformName); |
475 | if (numberedVersion != null) { |
476 | platformVersion = int.parse(numberedVersion.group(1)!); |
477 | } else { |
478 | final String buildProps = platformDir.childFile('build.prop' ).readAsStringSync(); |
479 | final Iterable<Match> versionMatches = const LineSplitter() |
480 | .convert(buildProps) |
481 | .map<RegExpMatch?>(_sdkVersionRe.firstMatch) |
482 | .whereType<Match>(); |
483 | |
484 | if (versionMatches.isEmpty) { |
485 | return null; |
486 | } |
487 | |
488 | final String? versionString = versionMatches.first.group(1); |
489 | if (versionString == null) { |
490 | return null; |
491 | } |
492 | platformVersion = int.parse(versionString); |
493 | } |
494 | } on Exception { |
495 | return null; |
496 | } |
497 | |
498 | Version? buildToolsVersion = Version.primary( |
499 | buildTools.where((Version version) { |
500 | return version.major == platformVersion; |
501 | }).toList(), |
502 | ); |
503 | |
504 | buildToolsVersion ??= Version.primary(buildTools); |
505 | |
506 | if (buildToolsVersion == null) { |
507 | return null; |
508 | } |
509 | |
510 | return AndroidSdkVersion._( |
511 | this, |
512 | sdkLevel: platformVersion, |
513 | platformName: platformName, |
514 | buildToolsVersion: buildToolsVersion, |
515 | fileSystem: fileSystem ?? globals.fs, |
516 | ); |
517 | }) |
518 | .whereType<AndroidSdkVersion>() |
519 | .toList(); |
520 | |
521 | _sdkVersions.sort(); |
522 | |
523 | _latestVersion = _sdkVersions.isEmpty ? null : _sdkVersions.last; |
524 | } |
525 | |
526 | /// Returns the filesystem path of the Android SDK manager tool. |
527 | String? get sdkManagerPath { |
528 | final executable = globals.platform.isWindows ? 'sdkmanager.bat' : 'sdkmanager' ; |
529 | return getCmdlineToolsPath(executable, skipOldTools: true); |
530 | } |
531 | |
532 | /// Returns the version of the Android SDK manager tool or null if not found. |
533 | String? get sdkManagerVersion { |
534 | if (sdkManagerPath == null || !globals.processManager.canRun(sdkManagerPath)) { |
535 | throwToolExit( |
536 | 'Android sdkmanager not found. Update to the latest Android SDK and ensure that ' |
537 | 'the cmdline-tools are installed to resolve this.' , |
538 | ); |
539 | } |
540 | final RunResult result = globals.processUtils.runSync(<String>[ |
541 | sdkManagerPath!, |
542 | '--version' , |
543 | ], environment: _java?.environment); |
544 | if (result.exitCode != 0) { |
545 | globals.printTrace( |
546 | 'sdkmanager --version failed: exitCode: ${result.exitCode} stdout: ${result.stdout} stderr: ${result.stderr}' , |
547 | ); |
548 | return null; |
549 | } |
550 | return result.stdout.trim(); |
551 | } |
552 | |
553 | @override |
554 | String toString() => 'AndroidSdk: $directory' ; |
555 | } |
556 | |
557 | class AndroidSdkVersion implements Comparable<AndroidSdkVersion> { |
558 | AndroidSdkVersion._( |
559 | this.sdk, { |
560 | required this.sdkLevel, |
561 | required this.platformName, |
562 | required this.buildToolsVersion, |
563 | required FileSystem fileSystem, |
564 | }) : _fileSystem = fileSystem; |
565 | |
566 | final AndroidSdk sdk; |
567 | final int sdkLevel; |
568 | final String platformName; |
569 | final Version buildToolsVersion; |
570 | |
571 | final FileSystem _fileSystem; |
572 | |
573 | String get buildToolsVersionName => buildToolsVersion.toString(); |
574 | |
575 | String get androidJarPath => getPlatformsPath('android.jar' ); |
576 | |
577 | /// Return the path to the android application package tool. |
578 | /// |
579 | /// This is used to dump the xml in order to launch built android applications. |
580 | /// |
581 | /// See also: |
582 | /// * [AndroidApk.fromApk], which depends on this to determine application identifiers. |
583 | String get aaptPath => getBuildToolsPath('aapt' ); |
584 | |
585 | List<String> validateSdkWellFormed() { |
586 | final String? existsAndroidJarPath = _exists(androidJarPath); |
587 | if (existsAndroidJarPath != null) { |
588 | return <String>[existsAndroidJarPath]; |
589 | } |
590 | |
591 | final String? canRunAaptPath = _canRun(aaptPath); |
592 | if (canRunAaptPath != null) { |
593 | return <String>[canRunAaptPath]; |
594 | } |
595 | |
596 | return <String>[]; |
597 | } |
598 | |
599 | String getPlatformsPath(String itemName) { |
600 | return sdk.directory |
601 | .childDirectory('platforms' ) |
602 | .childDirectory(platformName) |
603 | .childFile(itemName) |
604 | .path; |
605 | } |
606 | |
607 | String getBuildToolsPath(String binaryName) { |
608 | return sdk.directory |
609 | .childDirectory('build-tools' ) |
610 | .childDirectory(buildToolsVersionName) |
611 | .childFile(binaryName) |
612 | .path; |
613 | } |
614 | |
615 | @override |
616 | int compareTo(AndroidSdkVersion other) => sdkLevel - other.sdkLevel; |
617 | |
618 | @override |
619 | String toString() => |
620 | '[ ${sdk.directory}, SDK version $sdkLevel, build-tools $buildToolsVersionName]' ; |
621 | |
622 | String? _exists(String path) { |
623 | if (!_fileSystem.isFileSync(path)) { |
624 | return 'Android SDK file not found: $path.' ; |
625 | } |
626 | return null; |
627 | } |
628 | |
629 | String? _canRun(String path) { |
630 | if (!globals.processManager.canRun(path)) { |
631 | return 'Android SDK file not found: $path.' ; |
632 | } |
633 | return null; |
634 | } |
635 | } |
636 | |