blob: 567ff4634f0532d66b58f6650815180cadf92827 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Devon Carewdcf0b7b2016-02-13 12:00:41 -08002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Todd Volkertf0e88192017-11-16 17:38:53 -08005import 'package:meta/meta.dart';
Christopher Fujinoe6e14b02020-02-06 14:03:03 -08006import 'package:platform/platform.dart';
Todd Volkertf0e88192017-11-16 17:38:53 -08007
Dan Rubele20ee042016-10-13 10:44:20 -04008import '../base/common.dart';
John McCutchan0b737ac2016-11-29 07:54:20 -08009import '../base/context.dart';
Todd Volkert8bb27032017-01-06 16:51:44 -080010import '../base/file_system.dart';
Christopher Fujinoe6e14b02020-02-06 14:03:03 -080011import '../base/os.dart';
jcollins-g614df692018-02-28 12:09:52 -080012import '../base/process.dart';
John McCutchan24eeddc2017-03-06 18:20:04 -080013import '../base/version.dart';
Jonah Williams91fd89e2019-01-25 16:16:26 -080014import '../convert.dart';
Jonah Williamsee7a37f2020-01-06 11:04:20 -080015import '../globals.dart' as globals;
Christopher Fujinoe6e14b02020-02-06 14:03:03 -080016import 'android_studio.dart';
Devon Carewdcf0b7b2016-02-13 12:00:41 -080017
Jonah Williams0acd3e62019-04-25 15:51:08 -070018AndroidSdk get androidSdk => context.get<AndroidSdk>();
John McCutchan0b737ac2016-11-29 07:54:20 -080019
Dan Rubel311d3712016-10-27 22:24:50 +010020const String kAndroidHome = 'ANDROID_HOME';
Danny Tuppenyc19142d2018-12-17 17:29:09 +000021const String kAndroidSdkRoot = 'ANDROID_SDK_ROOT';
Dan Rubel311d3712016-10-27 22:24:50 +010022
Devon Carewdcf0b7b2016-02-13 12:00:41 -080023// Android SDK layout:
Devon Carew99b70da2016-03-17 15:30:47 -070024
Devon Carewdcf0b7b2016-02-13 12:00:41 -080025// $ANDROID_HOME/platform-tools/adb
Devon Carew99b70da2016-03-17 15:30:47 -070026
Devon Carewdcf0b7b2016-02-13 12:00:41 -080027// $ANDROID_HOME/build-tools/19.1.0/aapt, dx, zipalign
28// $ANDROID_HOME/build-tools/22.0.1/aapt
29// $ANDROID_HOME/build-tools/23.0.2/aapt
Devon Carew99b70da2016-03-17 15:30:47 -070030// $ANDROID_HOME/build-tools/24.0.0-preview/aapt
Michael Goderbauer7db82412017-02-21 16:26:30 -080031// $ANDROID_HOME/build-tools/25.0.2/apksigner
Devon Carew99b70da2016-03-17 15:30:47 -070032
Devon Carewdcf0b7b2016-02-13 12:00:41 -080033// $ANDROID_HOME/platforms/android-22/android.jar
34// $ANDROID_HOME/platforms/android-23/android.jar
Devon Carew99b70da2016-03-17 15:30:47 -070035// $ANDROID_HOME/platforms/android-N/android.jar
Devon Carewdcf0b7b2016-02-13 12:00:41 -080036
Alexandre Ardhuind927c932018-09-12 08:29:29 +020037final RegExp _numberedAndroidPlatformRe = RegExp(r'^android-([0-9]+)$');
38final RegExp _sdkVersionRe = RegExp(r'^ro.build.version.sdk=([0-9]+)$');
Devon Carewdcf0b7b2016-02-13 12:00:41 -080039
John McCutchan24eeddc2017-03-06 18:20:04 -080040/// The minimum Android SDK version we support.
41const int minimumAndroidSdkVersion = 25;
42
Devon Carewdcf0b7b2016-02-13 12:00:41 -080043/// Locate ADB. Prefer to use one from an Android SDK, if we can locate that.
Todd Volkert3042da92016-05-20 10:45:35 -070044/// This should be used over accessing androidSdk.adbPath directly because it
45/// will work for those users who have Android Platform Tools installed but
46/// not the full SDK.
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +010047String getAdbPath([ AndroidSdk existingSdk ]) {
Zachary Andersone2340c62019-09-13 14:51:35 -070048 if (existingSdk?.adbPath != null) {
Devon Carewb7b06c22016-02-15 14:06:23 -080049 return existingSdk.adbPath;
Zachary Andersone2340c62019-09-13 14:51:35 -070050 }
Devon Carewb7b06c22016-02-15 14:06:23 -080051
Chris Bracken7a093162017-03-03 17:50:46 -080052 final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
Devon Carewdcf0b7b2016-02-13 12:00:41 -080053
54 if (sdk?.latestVersion == null) {
Zachary Anderson92f7e162020-01-29 17:51:31 -080055 return globals.os.which('adb')?.path;
Devon Carewdcf0b7b2016-02-13 12:00:41 -080056 } else {
Emmanuel Garciaa15a81b2019-12-17 14:10:36 -080057 return sdk?.adbPath;
Devon Carewdcf0b7b2016-02-13 12:00:41 -080058 }
59}
60
Danny Tuppeny4d7c3c72018-04-18 13:43:22 +010061/// Locate 'emulator'. Prefer to use one from an Android SDK, if we can locate that.
62/// This should be used over accessing androidSdk.emulatorPath directly because it
63/// will work for those users who have Android Tools installed but
Danny Tuppeny53840fb2018-04-18 10:48:39 +010064/// not the full SDK.
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +010065String getEmulatorPath([ AndroidSdk existingSdk ]) {
Danny Tuppenycdb01182018-06-28 08:07:40 +010066 return existingSdk?.emulatorPath ??
67 AndroidSdk.locateAndroidSdk()?.emulatorPath;
Danny Tuppeny53840fb2018-04-18 10:48:39 +010068}
69
Danny Tuppeny4d7c3c72018-04-18 13:43:22 +010070/// Locate the path for storing AVD emulator images. Returns null if none found.
71String getAvdPath() {
Greg Spencer1cc03652018-07-19 23:03:58 -070072
Danny Tuppeny4d7c3c72018-04-18 13:43:22 +010073 final List<String> searchPaths = <String>[
Jonah Williamsee7a37f2020-01-06 11:04:20 -080074 globals.platform.environment['ANDROID_AVD_HOME'],
75 if (globals.platform.environment['HOME'] != null)
76 globals.fs.path.join(globals.platform.environment['HOME'], '.android', 'avd'),
Danny Tuppeny4d7c3c72018-04-18 13:43:22 +010077 ];
Danny Tuppeny5793a3c2018-04-18 16:07:33 +010078
Danny Tuppeny5793a3c2018-04-18 16:07:33 +010079
Jonah Williamsee7a37f2020-01-06 11:04:20 -080080 if (globals.platform.isWindows) {
81 final String homeDrive = globals.platform.environment['HOMEDRIVE'];
82 final String homePath = globals.platform.environment['HOMEPATH'];
Danny Tuppeny5793a3c2018-04-18 16:07:33 +010083
84 if (homeDrive != null && homePath != null) {
85 // Can't use path.join for HOMEDRIVE/HOMEPATH
86 // https://github.com/dart-lang/path/issues/37
87 final String home = homeDrive + homePath;
Jonah Williamsee7a37f2020-01-06 11:04:20 -080088 searchPaths.add(globals.fs.path.join(home, '.android', 'avd'));
Danny Tuppeny5793a3c2018-04-18 16:07:33 +010089 }
90 }
91
Danny Tuppeny4d7c3c72018-04-18 13:43:22 +010092 return searchPaths.where((String p) => p != null).firstWhere(
Jonah Williamsee7a37f2020-01-06 11:04:20 -080093 (String p) => globals.fs.directory(p).existsSync(),
Danny Tuppeny4d7c3c72018-04-18 13:43:22 +010094 orElse: () => null,
95 );
96}
97
Danny Tuppenycdb01182018-06-28 08:07:40 +010098/// Locate 'avdmanager'. Prefer to use one from an Android SDK, if we can locate that.
99/// This should be used over accessing androidSdk.avdManagerPath directly because it
100/// will work for those users who have Android Tools installed but
101/// not the full SDK.
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +0100102String getAvdManagerPath([ AndroidSdk existingSdk ]) {
Danny Tuppenycdb01182018-06-28 08:07:40 +0100103 return existingSdk?.avdManagerPath ??
104 AndroidSdk.locateAndroidSdk()?.avdManagerPath;
105}
106
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200107class AndroidNdkSearchError {
108 AndroidNdkSearchError(this.reason);
109
110 /// The message explaining why NDK was not found.
111 final String reason;
112}
113
114class AndroidNdk {
115 AndroidNdk._(this.directory, this.compiler, this.compilerArgs);
116
117 /// The path to the NDK.
118 final String directory;
119
120 /// The path to the NDK compiler.
121 final String compiler;
122
123 /// The mandatory arguments to the NDK compiler.
124 final List<String> compilerArgs;
125
126 /// Locate NDK within the given SDK or throw [AndroidNdkSearchError].
127 static AndroidNdk locateNdk(String androidHomeDir) {
128 if (androidHomeDir == null) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200129 throw AndroidNdkSearchError('Can not locate NDK because no SDK is found');
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200130 }
131
132 String findBundle(String androidHomeDir) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800133 final String ndkDirectory = globals.fs.path.join(androidHomeDir, 'ndk-bundle');
134 if (!globals.fs.isDirectorySync(ndkDirectory)) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200135 throw AndroidNdkSearchError('Can not locate ndk-bundle, tried: $ndkDirectory');
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200136 }
137 return ndkDirectory;
138 }
139
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100140 // Returns list that contains toolchain bin folder and compiler binary name.
141 List<String> findToolchainAndCompiler(String ndkDirectory) {
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200142 String directory;
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800143 if (globals.platform.isLinux) {
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200144 directory = 'linux-x86_64';
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800145 } else if (globals.platform.isMacOS) {
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200146 directory = 'darwin-x86_64';
147 } else {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200148 throw AndroidNdkSearchError('Only Linux and macOS are supported');
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200149 }
150
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800151 final String toolchainBin = globals.fs.path.join(ndkDirectory,
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200152 'toolchains', 'arm-linux-androideabi-4.9', 'prebuilt', directory,
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100153 'bin');
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800154 final String ndkCompiler = globals.fs.path.join(toolchainBin,
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100155 'arm-linux-androideabi-gcc');
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800156 if (!globals.fs.isFileSync(ndkCompiler)) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200157 throw AndroidNdkSearchError('Can not locate GCC binary, tried $ndkCompiler');
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200158 }
159
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100160 return <String>[toolchainBin, ndkCompiler];
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200161 }
162
163 List<String> findSysroot(String ndkDirectory) {
164 // If entity represents directory with name android-<version> that
165 // contains arch-arm subdirectory then returns version, otherwise
166 // returns null.
167 int toPlatformVersion(FileSystemEntity entry) {
168 if (entry is! Directory) {
169 return null;
170 }
171
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800172 if (!globals.fs.isDirectorySync(globals.fs.path.join(entry.path, 'arch-arm'))) {
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200173 return null;
174 }
175
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800176 final String name = globals.fs.path.basename(entry.path);
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200177
178 const String platformPrefix = 'android-';
179 if (!name.startsWith(platformPrefix)) {
180 return null;
181 }
182
183 return int.tryParse(name.substring(platformPrefix.length));
184 }
185
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800186 final String platformsDir = globals.fs.path.join(ndkDirectory, 'platforms');
187 final List<int> versions = globals.fs
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200188 .directory(platformsDir)
189 .listSync()
190 .map(toPlatformVersion)
191 .where((int version) => version != null)
192 .toList(growable: false);
193 versions.sort();
194
195 final int suitableVersion = versions
196 .firstWhere((int version) => version >= 9, orElse: () => null);
197 if (suitableVersion == null) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200198 throw AndroidNdkSearchError('Can not locate a suitable platform ARM sysroot (need android-9 or newer), tried to look in $platformsDir');
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200199 }
200
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800201 final String armPlatform = globals.fs.path.join(ndkDirectory, 'platforms',
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200202 'android-$suitableVersion', 'arch-arm');
203 return <String>['--sysroot', armPlatform];
204 }
205
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100206 int findNdkMajorVersion(String ndkDirectory) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800207 final String propertiesFile = globals.fs.path.join(ndkDirectory, 'source.properties');
208 if (!globals.fs.isFileSync(propertiesFile)) {
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100209 throw AndroidNdkSearchError('Can not establish ndk-bundle version: $propertiesFile not found');
210 }
211
212 // Parse source.properties: each line has Key = Value format.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800213 final Iterable<String> propertiesFileLines = globals.fs.file(propertiesFile)
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100214 .readAsStringSync()
215 .split('\n')
216 .map<String>((String line) => line.trim())
217 .where((String line) => line.isNotEmpty);
Jonah Williams75c50da2019-03-18 10:51:24 -0700218 final Map<String, String> properties = <String, String>{};
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100219 for (final String line in propertiesFileLines) {
Jonah Williams75c50da2019-03-18 10:51:24 -0700220 final List<String> parts = line.split(' = ');
221 if (parts.length == 2) {
222 properties[parts[0]] = parts[1];
223 } else {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800224 globals.printError('Malformed line in ndk source.properties: "$line".');
Jonah Williams75c50da2019-03-18 10:51:24 -0700225 }
226 }
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100227
228 if (!properties.containsKey('Pkg.Revision')) {
229 throw AndroidNdkSearchError('Can not establish ndk-bundle version: $propertiesFile does not contain Pkg.Revision');
230 }
231
232 // Extract major version from Pkg.Revision property which looks like <ndk-version>.x.y.
233 return int.parse(properties['Pkg.Revision'].split('.').first);
234 }
235
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200236 final String ndkDir = findBundle(androidHomeDir);
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100237 final int ndkVersion = findNdkMajorVersion(ndkDir);
238 final List<String> ndkToolchainAndCompiler = findToolchainAndCompiler(ndkDir);
239 final String ndkToolchain = ndkToolchainAndCompiler[0];
240 final String ndkCompiler = ndkToolchainAndCompiler[1];
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200241 final List<String> ndkCompilerArgs = findSysroot(ndkDir);
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100242 if (ndkVersion >= 18) {
243 // Newer versions of NDK use clang instead of gcc, which falls back to
244 // system linker instead of using toolchain linker. Force clang to
245 // use appropriate linker by passing -fuse-ld=<path-to-ld> command line
246 // flag.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800247 final String ndkLinker = globals.fs.path.join(ndkToolchain, 'arm-linux-androideabi-ld');
248 if (!globals.fs.isFileSync(ndkLinker)) {
Vyacheslav Egorov8f65ee92019-01-16 21:30:37 +0100249 throw AndroidNdkSearchError('Can not locate linker binary, tried $ndkLinker');
250 }
251 ndkCompilerArgs.add('-fuse-ld=$ndkLinker');
252 }
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200253 return AndroidNdk._(ndkDir, ndkCompiler, ndkCompilerArgs);
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200254 }
255
256 /// Returns a descriptive message explaining why NDK can not be found within
257 /// the given SDK.
258 static String explainMissingNdk(String androidHomeDir) {
259 try {
260 locateNdk(androidHomeDir);
261 return 'Unexpected error: found NDK on the second try';
262 } on AndroidNdkSearchError catch (e) {
263 return e.reason;
264 }
265 }
266}
267
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800268class AndroidSdk {
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200269 AndroidSdk(this.directory, [this.ndk]) {
Dan Field15f21192019-02-23 09:56:57 -0800270 reinitialize();
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800271 }
272
Greg Spencer3c5a7a32018-05-17 23:04:41 -0700273 static const String _javaHomeEnvironmentVariable = 'JAVA_HOME';
274 static const String _javaExecutable = 'java';
jcollins-g614df692018-02-28 12:09:52 -0800275
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100276 /// The path to the Android SDK.
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800277 final String directory;
278
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200279 /// Android NDK (can be `null`).
280 final AndroidNdk ndk;
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100281
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800282 List<AndroidSdkVersion> _sdkVersions;
283 AndroidSdkVersion _latestVersion;
284
Dan Field15f21192019-02-23 09:56:57 -0800285 /// Whether the `platform-tools` directory exists in the Android SDK.
286 ///
287 /// It is possible to have an Android SDK folder that is missing this with
288 /// the expectation that it will be downloaded later, e.g. by gradle or the
289 /// sdkmanager. The [licensesAvailable] property should be used to determine
290 /// whether the licenses are at least possibly accepted.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800291 bool get platformToolsAvailable => globals.fs.directory(globals.fs.path.join(directory, 'platform-tools')).existsSync();
Dan Field15f21192019-02-23 09:56:57 -0800292
293 /// Whether the `licenses` directory exists in the Android SDK.
294 ///
295 /// The existence of this folder normally indicates that the SDK licenses have
296 /// been accepted, e.g. via the sdkmanager, Android Studio, or by copying them
297 /// from another workstation such as in CI scenarios. If these files are valid
298 /// gradle or the sdkmanager will be able to download and use other parts of
299 /// the SDK on demand.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800300 bool get licensesAvailable => globals.fs.directory(globals.fs.path.join(directory, 'licenses')).existsSync();
Dan Field15f21192019-02-23 09:56:57 -0800301
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800302 static AndroidSdk locateAndroidSdk() {
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100303 String findAndroidHomeDir() {
304 String androidHomeDir;
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800305 if (globals.config.containsKey('android-sdk')) {
306 androidHomeDir = globals.config.getValue('android-sdk') as String;
307 } else if (globals.platform.environment.containsKey(kAndroidHome)) {
308 androidHomeDir = globals.platform.environment[kAndroidHome];
309 } else if (globals.platform.environment.containsKey(kAndroidSdkRoot)) {
310 androidHomeDir = globals.platform.environment[kAndroidSdkRoot];
311 } else if (globals.platform.isLinux) {
Zachary Anderson390ed1c2020-02-03 14:33:03 -0800312 if (globals.fsUtils.homeDirPath != null) {
313 androidHomeDir = globals.fs.path.join(
314 globals.fsUtils.homeDirPath,
315 'Android',
316 'Sdk',
317 );
Zachary Andersone2340c62019-09-13 14:51:35 -0700318 }
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800319 } else if (globals.platform.isMacOS) {
Zachary Anderson390ed1c2020-02-03 14:33:03 -0800320 if (globals.fsUtils.homeDirPath != null) {
321 androidHomeDir = globals.fs.path.join(
322 globals.fsUtils.homeDirPath,
323 'Library',
324 'Android',
325 'sdk',
326 );
Zachary Andersone2340c62019-09-13 14:51:35 -0700327 }
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800328 } else if (globals.platform.isWindows) {
Zachary Anderson390ed1c2020-02-03 14:33:03 -0800329 if (globals.fsUtils.homeDirPath != null) {
330 androidHomeDir = globals.fs.path.join(
331 globals.fsUtils.homeDirPath,
332 'AppData',
333 'Local',
334 'Android',
335 'sdk',
336 );
Zachary Andersone2340c62019-09-13 14:51:35 -0700337 }
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100338 }
Devon Carewc186d0d2017-07-18 18:47:20 -0700339
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100340 if (androidHomeDir != null) {
Zachary Andersone2340c62019-09-13 14:51:35 -0700341 if (validSdkDirectory(androidHomeDir)) {
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100342 return androidHomeDir;
Zachary Andersone2340c62019-09-13 14:51:35 -0700343 }
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800344 if (validSdkDirectory(globals.fs.path.join(androidHomeDir, 'sdk'))) {
345 return globals.fs.path.join(androidHomeDir, 'sdk');
Zachary Andersone2340c62019-09-13 14:51:35 -0700346 }
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100347 }
348
349 // in build-tools/$version/aapt
Zachary Anderson92f7e162020-01-29 17:51:31 -0800350 final List<File> aaptBins = globals.os.whichAll('aapt');
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100351 for (File aaptBin in aaptBins) {
352 // Make sure we're using the aapt from the SDK.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800353 aaptBin = globals.fs.file(aaptBin.resolveSymbolicLinksSync());
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100354 final String dir = aaptBin.parent.parent.parent.path;
Zachary Andersone2340c62019-09-13 14:51:35 -0700355 if (validSdkDirectory(dir)) {
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100356 return dir;
Zachary Andersone2340c62019-09-13 14:51:35 -0700357 }
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100358 }
359
360 // in platform-tools/adb
Zachary Anderson92f7e162020-01-29 17:51:31 -0800361 final List<File> adbBins = globals.os.whichAll('adb');
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100362 for (File adbBin in adbBins) {
363 // Make sure we're using the adb from the SDK.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800364 adbBin = globals.fs.file(adbBin.resolveSymbolicLinksSync());
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100365 final String dir = adbBin.parent.parent.path;
Zachary Andersone2340c62019-09-13 14:51:35 -0700366 if (validSdkDirectory(dir)) {
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100367 return dir;
Zachary Andersone2340c62019-09-13 14:51:35 -0700368 }
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100369 }
370
371 return null;
Adam Barthbef8d082016-02-16 16:51:16 -0800372 }
373
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100374 final String androidHomeDir = findAndroidHomeDir();
375 if (androidHomeDir == null) {
376 // No dice.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800377 globals.printTrace('Unable to locate an Android SDK.');
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100378 return null;
379 }
380
Alexandre Ardhuinc02b6a82018-02-02 23:27:29 +0100381 // Try to find the NDK compiler. If we can't find it, it's also ok.
Vyacheslav Egorov4931b462018-05-14 16:36:54 +0200382 AndroidNdk ndk;
383 try {
384 ndk = AndroidNdk.locateNdk(androidHomeDir);
385 } on AndroidNdkSearchError {
386 // Ignore AndroidNdkSearchError's but don't ignore any other
387 // exceptions.
Martin Kustermann545ec9e2017-11-21 15:44:03 +0100388 }
389
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200390 return AndroidSdk(androidHomeDir, ndk);
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800391 }
392
393 static bool validSdkDirectory(String dir) {
Lau Ching Junc33d2632019-03-20 14:22:10 -0700394 return sdkDirectoryHasLicenses(dir) || sdkDirectoryHasPlatformTools(dir);
Dan Fielddf465c72019-03-07 10:45:29 -0800395 }
396
397 static bool sdkDirectoryHasPlatformTools(String dir) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800398 return globals.fs.isDirectorySync(globals.fs.path.join(dir, 'platform-tools'));
Dan Fielddf465c72019-03-07 10:45:29 -0800399 }
400
Lau Ching Junc33d2632019-03-20 14:22:10 -0700401 static bool sdkDirectoryHasLicenses(String dir) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800402 return globals.fs.isDirectorySync(globals.fs.path.join(dir, 'licenses'));
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800403 }
404
405 List<AndroidSdkVersion> get sdkVersions => _sdkVersions;
406
407 AndroidSdkVersion get latestVersion => _latestVersion;
408
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800409 String get adbPath => getPlatformToolsPath(globals.platform.isWindows ? 'adb.exe' : 'adb');
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800410
Danny Tuppenyda5a64e2018-04-18 17:04:09 +0100411 String get emulatorPath => getEmulatorPath();
Danny Tuppeny53840fb2018-04-18 10:48:39 +0100412
Danny Tuppenycdb01182018-06-28 08:07:40 +0100413 String get avdManagerPath => getAvdManagerPath();
414
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800415 Directory get _platformsDir => globals.fs.directory(globals.fs.path.join(directory, 'platforms'));
jslavitz34fa90b2018-11-06 15:13:35 -0800416
417 Iterable<Directory> get _platforms {
418 Iterable<Directory> platforms = <Directory>[];
419 if (_platformsDir.existsSync()) {
420 platforms = _platformsDir
421 .listSync()
422 .whereType<Directory>();
423 }
424 return platforms;
425 }
426
Devon Carew25f332d2016-03-23 16:59:56 -0700427 /// Validate the Android SDK. This returns an empty list if there are no
428 /// issues; otherwise, it returns a list of issues found.
Jakob Andersenf79b3332017-03-22 13:51:28 +0100429 List<String> validateSdkWellFormed() {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800430 if (adbPath == null || !globals.processManager.canRun(adbPath)) {
Dan Field15f21192019-02-23 09:56:57 -0800431 return <String>['Android SDK file not found: ${adbPath ?? 'adb'}.'];
Zachary Andersone2340c62019-09-13 14:51:35 -0700432 }
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800433
jslavitz34fa90b2018-11-06 15:13:35 -0800434 if (sdkVersions.isEmpty || latestVersion == null) {
435 final StringBuffer msg = StringBuffer('No valid Android SDK platforms found in ${_platformsDir.path}.');
436 if (_platforms.isEmpty) {
437 msg.write(' Directory was empty.');
438 } else {
439 msg.write(' Candidates were:\n');
440 msg.write(_platforms
441 .map((Directory dir) => ' - ${dir.basename}')
442 .join('\n'));
443 }
444 return <String>[msg.toString()];
445 }
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800446
Jakob Andersenf79b3332017-03-22 13:51:28 +0100447 return latestVersion.validateSdkWellFormed();
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800448 }
449
450 String getPlatformToolsPath(String binaryName) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800451 final String path = globals.fs.path.join(directory, 'platform-tools', binaryName);
452 if (globals.fs.file(path).existsSync()) {
Dan Field15f21192019-02-23 09:56:57 -0800453 return path;
Zachary Andersone2340c62019-09-13 14:51:35 -0700454 }
Dan Field15f21192019-02-23 09:56:57 -0800455 return null;
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800456 }
457
Danny Tuppenyda5a64e2018-04-18 17:04:09 +0100458 String getEmulatorPath() {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800459 final String binaryName = globals.platform.isWindows ? 'emulator.exe' : 'emulator';
Danny Tuppenyda5a64e2018-04-18 17:04:09 +0100460 // Emulator now lives inside "emulator" but used to live inside "tools" so
461 // try both.
462 final List<String> searchFolders = <String>['emulator', 'tools'];
463 for (final String folder in searchFolders) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800464 final String path = globals.fs.path.join(directory, folder, binaryName);
465 if (globals.fs.file(path).existsSync()) {
Danny Tuppenyda5a64e2018-04-18 17:04:09 +0100466 return path;
Zachary Andersone2340c62019-09-13 14:51:35 -0700467 }
Danny Tuppenyda5a64e2018-04-18 17:04:09 +0100468 }
469 return null;
Danny Tuppeny53840fb2018-04-18 10:48:39 +0100470 }
471
Danny Tuppenycdb01182018-06-28 08:07:40 +0100472 String getAvdManagerPath() {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800473 final String binaryName = globals.platform.isWindows ? 'avdmanager.bat' : 'avdmanager';
474 final String path = globals.fs.path.join(directory, 'tools', 'bin', binaryName);
475 if (globals.fs.file(path).existsSync()) {
Danny Tuppenycdb01182018-06-28 08:07:40 +0100476 return path;
Zachary Andersone2340c62019-09-13 14:51:35 -0700477 }
Danny Tuppenycdb01182018-06-28 08:07:40 +0100478 return null;
479 }
480
Dan Field15f21192019-02-23 09:56:57 -0800481 /// Sets up various paths used internally.
482 ///
483 /// This method should be called in a case where the tooling may have updated
484 /// SDK artifacts, such as after running a gradle build.
485 void reinitialize() {
Devon Carew99b70da2016-03-17 15:30:47 -0700486 List<Version> buildTools = <Version>[]; // 19.1.0, 22.0.1, ...
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800487
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800488 final Directory buildToolsDir = globals.fs.directory(globals.fs.path.join(directory, 'build-tools'));
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800489 if (buildToolsDir.existsSync()) {
Devon Carew99b70da2016-03-17 15:30:47 -0700490 buildTools = buildToolsDir
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800491 .listSync()
492 .map((FileSystemEntity entity) {
493 try {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200494 return Version.parse(entity.basename);
Zachary Andersonf1cd47e2020-02-26 21:45:02 -0800495 } on Exception {
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800496 return null;
497 }
498 })
499 .where((Version version) => version != null)
500 .toList();
501 }
502
Chris Brackend4c24812016-10-24 13:03:15 -0700503 // Match up platforms with the best corresponding build-tools.
jslavitz34fa90b2018-11-06 15:13:35 -0800504 _sdkVersions = _platforms.map<AndroidSdkVersion>((Directory platformDir) {
Todd Volkertf0e88192017-11-16 17:38:53 -0800505 final String platformName = platformDir.basename;
Devon Carew99b70da2016-03-17 15:30:47 -0700506 int platformVersion;
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800507
508 try {
Todd Volkertf0e88192017-11-16 17:38:53 -0800509 final Match numberedVersion = _numberedAndroidPlatformRe.firstMatch(platformName);
510 if (numberedVersion != null) {
511 platformVersion = int.parse(numberedVersion.group(1));
512 } else {
513 final String buildProps = platformDir.childFile('build.prop').readAsStringSync();
514 final String versionString = const LineSplitter()
515 .convert(buildProps)
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +0200516 .map<Match>(_sdkVersionRe.firstMatch)
Todd Volkertf0e88192017-11-16 17:38:53 -0800517 .firstWhere((Match match) => match != null)
518 .group(1);
519 platformVersion = int.parse(versionString);
520 }
Zachary Andersonf1cd47e2020-02-26 21:45:02 -0800521 } on Exception {
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800522 return null;
523 }
524
Devon Carew99b70da2016-03-17 15:30:47 -0700525 Version buildToolsVersion = Version.primary(buildTools.where((Version version) {
526 return version.major == platformVersion;
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800527 }).toList());
528
Devon Carew45db8652016-05-11 15:34:24 -0700529 buildToolsVersion ??= Version.primary(buildTools);
530
Zachary Andersone2340c62019-09-13 14:51:35 -0700531 if (buildToolsVersion == null) {
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800532 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700533 }
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800534
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200535 return AndroidSdkVersion._(
Devon Carew99b70da2016-03-17 15:30:47 -0700536 this,
Todd Volkertf0e88192017-11-16 17:38:53 -0800537 sdkLevel: platformVersion,
538 platformName: platformName,
539 buildToolsVersion: buildToolsVersion,
Devon Carew99b70da2016-03-17 15:30:47 -0700540 );
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800541 }).where((AndroidSdkVersion version) => version != null).toList();
542
543 _sdkVersions.sort();
544
545 _latestVersion = _sdkVersions.isEmpty ? null : _sdkVersions.last;
546 }
547
Chris Bracken96546592018-01-24 12:38:08 -0800548 /// Returns the filesystem path of the Android SDK manager tool or null if not found.
549 String get sdkManagerPath {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800550 return globals.fs.path.join(directory, 'tools', 'bin', 'sdkmanager');
Chris Bracken96546592018-01-24 12:38:08 -0800551 }
552
jcollins-g614df692018-02-28 12:09:52 -0800553 /// First try Java bundled with Android Studio, then sniff JAVA_HOME, then fallback to PATH.
Christopher Fujinoe6e14b02020-02-06 14:03:03 -0800554 static String findJavaBinary({
555 @required AndroidStudio androidStudio,
556 @required FileSystem fileSystem,
557 @required OperatingSystemUtils operatingSystemUtils,
558 @required Platform platform,
559 }) {
560 if (androidStudio?.javaPath != null) {
561 return fileSystem.path.join(androidStudio.javaPath, 'bin', 'java');
Zachary Anderson73c10e82019-09-11 18:20:42 -0700562 }
jcollins-g614df692018-02-28 12:09:52 -0800563
Christopher Fujinoe6e14b02020-02-06 14:03:03 -0800564 final String javaHomeEnv = platform.environment[_javaHomeEnvironmentVariable];
jcollins-g614df692018-02-28 12:09:52 -0800565 if (javaHomeEnv != null) {
566 // Trust JAVA_HOME.
Christopher Fujinoe6e14b02020-02-06 14:03:03 -0800567 return fileSystem.path.join(javaHomeEnv, 'bin', 'java');
jcollins-g614df692018-02-28 12:09:52 -0800568 }
569
570 // MacOS specific logic to avoid popping up a dialog window.
571 // See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac.
Christopher Fujinoe6e14b02020-02-06 14:03:03 -0800572 if (platform.isMacOS) {
jcollins-g614df692018-02-28 12:09:52 -0800573 try {
Zachary Anderson73c10e82019-09-11 18:20:42 -0700574 final String javaHomeOutput = processUtils.runSync(
575 <String>['/usr/libexec/java_home'],
576 throwOnError: true,
577 hideStdout: true,
578 ).stdout.trim();
jcollins-g614df692018-02-28 12:09:52 -0800579 if (javaHomeOutput != null) {
580 final List<String> javaHomeOutputSplit = javaHomeOutput.split('\n');
581 if ((javaHomeOutputSplit != null) && (javaHomeOutputSplit.isNotEmpty)) {
582 final String javaHome = javaHomeOutputSplit[0].trim();
Christopher Fujinoe6e14b02020-02-06 14:03:03 -0800583 return fileSystem.path.join(javaHome, 'bin', 'java');
jcollins-g614df692018-02-28 12:09:52 -0800584 }
585 }
Zachary Andersonf1cd47e2020-02-26 21:45:02 -0800586 } on Exception catch (_) { /* ignore */ }
jcollins-g614df692018-02-28 12:09:52 -0800587 }
588
589 // Fallback to PATH based lookup.
Christopher Fujinoe6e14b02020-02-06 14:03:03 -0800590 return operatingSystemUtils.which(_javaExecutable)?.path;
jcollins-g614df692018-02-28 12:09:52 -0800591 }
592
593 Map<String, String> _sdkManagerEnv;
Danny Tuppeny266a7202018-06-28 12:09:04 +0100594 /// Returns an environment with the Java folder added to PATH for use in calling
595 /// Java-based Android SDK commands such as sdkmanager and avdmanager.
jcollins-g614df692018-02-28 12:09:52 -0800596 Map<String, String> get sdkManagerEnv {
597 if (_sdkManagerEnv == null) {
598 // If we can locate Java, then add it to the path used to run the Android SDK manager.
599 _sdkManagerEnv = <String, String>{};
Christopher Fujinoe6e14b02020-02-06 14:03:03 -0800600 final String javaBinary = findJavaBinary(
601 androidStudio: globals.androidStudio,
602 fileSystem: globals.fs,
603 operatingSystemUtils: globals.os,
604 platform: globals.platform,
605 );
jcollins-g614df692018-02-28 12:09:52 -0800606 if (javaBinary != null) {
Zachary Anderson92f7e162020-01-29 17:51:31 -0800607 _sdkManagerEnv['PATH'] = globals.fs.path.dirname(javaBinary) +
608 globals.os.pathVarSeparator +
609 globals.platform.environment['PATH'];
jcollins-g614df692018-02-28 12:09:52 -0800610 }
611 }
612 return _sdkManagerEnv;
613 }
614
Chris Bracken96546592018-01-24 12:38:08 -0800615 /// Returns the version of the Android SDK manager tool or null if not found.
616 String get sdkManagerVersion {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800617 if (!globals.processManager.canRun(sdkManagerPath)) {
Chris Bracken96546592018-01-24 12:38:08 -0800618 throwToolExit('Android sdkmanager not found. Update to the latest Android SDK to resolve this.');
Zachary Andersone2340c62019-09-13 14:51:35 -0700619 }
Zachary Anderson73c10e82019-09-11 18:20:42 -0700620 final RunResult result = processUtils.runSync(
621 <String>[sdkManagerPath, '--version'],
622 environment: sdkManagerEnv,
623 );
Chris Bracken96546592018-01-24 12:38:08 -0800624 if (result.exitCode != 0) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800625 globals.printTrace('sdkmanager --version failed: exitCode: ${result.exitCode} stdout: ${result.stdout} stderr: ${result.stderr}');
jcollins-g050ee9d2018-03-01 14:25:03 -0800626 return null;
Chris Bracken96546592018-01-24 12:38:08 -0800627 }
628 return result.stdout.trim();
629 }
630
Hixie797e27e2016-03-14 13:31:43 -0700631 @override
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800632 String toString() => 'AndroidSdk: $directory';
633}
634
635class AndroidSdkVersion implements Comparable<AndroidSdkVersion> {
Alexandre Ardhuinbfa1d252019-03-23 00:02:21 +0100636 AndroidSdkVersion._(
637 this.sdk, {
Todd Volkertf0e88192017-11-16 17:38:53 -0800638 @required this.sdkLevel,
639 @required this.platformName,
640 @required this.buildToolsVersion,
641 }) : assert(sdkLevel != null),
642 assert(platformName != null),
643 assert(buildToolsVersion != null);
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800644
645 final AndroidSdk sdk;
Todd Volkertf0e88192017-11-16 17:38:53 -0800646 final int sdkLevel;
647 final String platformName;
John McCutchan24eeddc2017-03-06 18:20:04 -0800648 final Version buildToolsVersion;
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800649
Todd Volkertf0e88192017-11-16 17:38:53 -0800650 String get buildToolsVersionName => buildToolsVersion.toString();
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800651
652 String get androidJarPath => getPlatformsPath('android.jar');
653
654 String get aaptPath => getBuildToolsPath('aapt');
655
Jakob Andersenf79b3332017-03-22 13:51:28 +0100656 List<String> validateSdkWellFormed() {
Zachary Andersone2340c62019-09-13 14:51:35 -0700657 if (_exists(androidJarPath) != null) {
Devon Carew25f332d2016-03-23 16:59:56 -0700658 return <String>[_exists(androidJarPath)];
Zachary Andersone2340c62019-09-13 14:51:35 -0700659 }
Devon Carew25f332d2016-03-23 16:59:56 -0700660
Zachary Andersone2340c62019-09-13 14:51:35 -0700661 if (_canRun(aaptPath) != null) {
Michael Goderbauercff7dc52017-02-10 16:54:51 -0800662 return <String>[_canRun(aaptPath)];
Zachary Andersone2340c62019-09-13 14:51:35 -0700663 }
Devon Carew25f332d2016-03-23 16:59:56 -0700664
Devon Carew25f332d2016-03-23 16:59:56 -0700665 return <String>[];
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800666 }
667
668 String getPlatformsPath(String itemName) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800669 return globals.fs.path.join(sdk.directory, 'platforms', platformName, itemName);
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800670 }
671
Michael Goderbauercff7dc52017-02-10 16:54:51 -0800672 String getBuildToolsPath(String binaryName) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800673 return globals.fs.path.join(sdk.directory, 'build-tools', buildToolsVersionName, binaryName);
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800674 }
675
Hixie797e27e2016-03-14 13:31:43 -0700676 @override
Devon Carew99b70da2016-03-17 15:30:47 -0700677 int compareTo(AndroidSdkVersion other) => sdkLevel - other.sdkLevel;
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800678
Hixie797e27e2016-03-14 13:31:43 -0700679 @override
Devon Carew99b70da2016-03-17 15:30:47 -0700680 String toString() => '[${sdk.directory}, SDK version $sdkLevel, build-tools $buildToolsVersionName]';
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800681
Devon Carew25f332d2016-03-23 16:59:56 -0700682 String _exists(String path) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800683 if (!globals.fs.isFileSync(path)) {
Devon Carew25f332d2016-03-23 16:59:56 -0700684 return 'Android SDK file not found: $path.';
Zachary Andersone2340c62019-09-13 14:51:35 -0700685 }
Devon Carew25f332d2016-03-23 16:59:56 -0700686 return null;
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800687 }
Michael Goderbauercff7dc52017-02-10 16:54:51 -0800688
689 String _canRun(String path) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800690 if (!globals.processManager.canRun(path)) {
Michael Goderbauercff7dc52017-02-10 16:54:51 -0800691 return 'Android SDK file not found: $path.';
Zachary Andersone2340c62019-09-13 14:51:35 -0700692 }
Michael Goderbauercff7dc52017-02-10 16:54:51 -0800693 return null;
694 }
Devon Carewdcf0b7b2016-02-13 12:00:41 -0800695}