
* Update project.pbxproj files to say Flutter rather than Chromium Also, the templates now have an empty organization so that we don't cause people to give their apps a Flutter copyright. * Update the copyright notice checker to require a standard notice on all files * Update copyrights on Dart files. (This was a mechanical commit.) * Fix weird license headers on Dart files that deviate from our conventions; relicense Shrine. Some were already marked "The Flutter Authors", not clear why. Their dates have been normalized. Some were missing the blank line after the license. Some were randomly different in trivial ways for no apparent reason (e.g. missing the trailing period). * Clean up the copyrights in non-Dart files. (Manual edits.) Also, make sure templates don't have copyrights. * Fix some more ORGANIZATIONNAMEs
671 lines
23 KiB
Dart
671 lines
23 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:meta/meta.dart';
|
|
|
|
import '../base/common.dart';
|
|
import '../base/context.dart';
|
|
import '../base/file_system.dart';
|
|
import '../base/os.dart';
|
|
import '../base/platform.dart';
|
|
import '../base/process.dart';
|
|
import '../base/process_manager.dart';
|
|
import '../base/version.dart';
|
|
import '../convert.dart';
|
|
import '../globals.dart';
|
|
import 'android_studio.dart' as android_studio;
|
|
|
|
AndroidSdk get androidSdk => context.get<AndroidSdk>();
|
|
|
|
const String kAndroidHome = 'ANDROID_HOME';
|
|
const String kAndroidSdkRoot = 'ANDROID_SDK_ROOT';
|
|
|
|
// Android SDK layout:
|
|
|
|
// $ANDROID_HOME/platform-tools/adb
|
|
|
|
// $ANDROID_HOME/build-tools/19.1.0/aapt, dx, zipalign
|
|
// $ANDROID_HOME/build-tools/22.0.1/aapt
|
|
// $ANDROID_HOME/build-tools/23.0.2/aapt
|
|
// $ANDROID_HOME/build-tools/24.0.0-preview/aapt
|
|
// $ANDROID_HOME/build-tools/25.0.2/apksigner
|
|
|
|
// $ANDROID_HOME/platforms/android-22/android.jar
|
|
// $ANDROID_HOME/platforms/android-23/android.jar
|
|
// $ANDROID_HOME/platforms/android-N/android.jar
|
|
|
|
final RegExp _numberedAndroidPlatformRe = RegExp(r'^android-([0-9]+)$');
|
|
final RegExp _sdkVersionRe = RegExp(r'^ro.build.version.sdk=([0-9]+)$');
|
|
|
|
/// The minimum Android SDK version we support.
|
|
const int minimumAndroidSdkVersion = 25;
|
|
|
|
/// Locate ADB. Prefer to use one from an Android SDK, if we can locate that.
|
|
/// This should be used over accessing androidSdk.adbPath directly because it
|
|
/// will work for those users who have Android Platform Tools installed but
|
|
/// not the full SDK.
|
|
String getAdbPath([ AndroidSdk existingSdk ]) {
|
|
if (existingSdk?.adbPath != null) {
|
|
return existingSdk.adbPath;
|
|
}
|
|
|
|
final AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
|
|
|
|
if (sdk?.latestVersion == null) {
|
|
return os.which('adb')?.path;
|
|
} else {
|
|
return sdk.adbPath;
|
|
}
|
|
}
|
|
|
|
/// Locate 'emulator'. Prefer to use one from an Android SDK, if we can locate that.
|
|
/// This should be used over accessing androidSdk.emulatorPath directly because it
|
|
/// will work for those users who have Android Tools installed but
|
|
/// not the full SDK.
|
|
String getEmulatorPath([ AndroidSdk existingSdk ]) {
|
|
return existingSdk?.emulatorPath ??
|
|
AndroidSdk.locateAndroidSdk()?.emulatorPath;
|
|
}
|
|
|
|
/// Locate the path for storing AVD emulator images. Returns null if none found.
|
|
String getAvdPath() {
|
|
|
|
final List<String> searchPaths = <String>[
|
|
platform.environment['ANDROID_AVD_HOME'],
|
|
if (platform.environment['HOME'] != null)
|
|
fs.path.join(platform.environment['HOME'], '.android', 'avd'),
|
|
];
|
|
|
|
|
|
if (platform.isWindows) {
|
|
final String homeDrive = platform.environment['HOMEDRIVE'];
|
|
final String homePath = platform.environment['HOMEPATH'];
|
|
|
|
if (homeDrive != null && homePath != null) {
|
|
// Can't use path.join for HOMEDRIVE/HOMEPATH
|
|
// https://github.com/dart-lang/path/issues/37
|
|
final String home = homeDrive + homePath;
|
|
searchPaths.add(fs.path.join(home, '.android', 'avd'));
|
|
}
|
|
}
|
|
|
|
return searchPaths.where((String p) => p != null).firstWhere(
|
|
(String p) => fs.directory(p).existsSync(),
|
|
orElse: () => null,
|
|
);
|
|
}
|
|
|
|
/// Locate 'avdmanager'. Prefer to use one from an Android SDK, if we can locate that.
|
|
/// This should be used over accessing androidSdk.avdManagerPath directly because it
|
|
/// will work for those users who have Android Tools installed but
|
|
/// not the full SDK.
|
|
String getAvdManagerPath([ AndroidSdk existingSdk ]) {
|
|
return existingSdk?.avdManagerPath ??
|
|
AndroidSdk.locateAndroidSdk()?.avdManagerPath;
|
|
}
|
|
|
|
class AndroidNdkSearchError {
|
|
AndroidNdkSearchError(this.reason);
|
|
|
|
/// The message explaining why NDK was not found.
|
|
final String reason;
|
|
}
|
|
|
|
class AndroidNdk {
|
|
AndroidNdk._(this.directory, this.compiler, this.compilerArgs);
|
|
|
|
/// The path to the NDK.
|
|
final String directory;
|
|
|
|
/// The path to the NDK compiler.
|
|
final String compiler;
|
|
|
|
/// The mandatory arguments to the NDK compiler.
|
|
final List<String> compilerArgs;
|
|
|
|
/// Locate NDK within the given SDK or throw [AndroidNdkSearchError].
|
|
static AndroidNdk locateNdk(String androidHomeDir) {
|
|
if (androidHomeDir == null) {
|
|
throw AndroidNdkSearchError('Can not locate NDK because no SDK is found');
|
|
}
|
|
|
|
String findBundle(String androidHomeDir) {
|
|
final String ndkDirectory = fs.path.join(androidHomeDir, 'ndk-bundle');
|
|
if (!fs.isDirectorySync(ndkDirectory)) {
|
|
throw AndroidNdkSearchError('Can not locate ndk-bundle, tried: $ndkDirectory');
|
|
}
|
|
return ndkDirectory;
|
|
}
|
|
|
|
// Returns list that contains toolchain bin folder and compiler binary name.
|
|
List<String> findToolchainAndCompiler(String ndkDirectory) {
|
|
String directory;
|
|
if (platform.isLinux) {
|
|
directory = 'linux-x86_64';
|
|
} else if (platform.isMacOS) {
|
|
directory = 'darwin-x86_64';
|
|
} else {
|
|
throw AndroidNdkSearchError('Only Linux and macOS are supported');
|
|
}
|
|
|
|
final String toolchainBin = fs.path.join(ndkDirectory,
|
|
'toolchains', 'arm-linux-androideabi-4.9', 'prebuilt', directory,
|
|
'bin');
|
|
final String ndkCompiler = fs.path.join(toolchainBin,
|
|
'arm-linux-androideabi-gcc');
|
|
if (!fs.isFileSync(ndkCompiler)) {
|
|
throw AndroidNdkSearchError('Can not locate GCC binary, tried $ndkCompiler');
|
|
}
|
|
|
|
return <String>[toolchainBin, ndkCompiler];
|
|
}
|
|
|
|
List<String> findSysroot(String ndkDirectory) {
|
|
// If entity represents directory with name android-<version> that
|
|
// contains arch-arm subdirectory then returns version, otherwise
|
|
// returns null.
|
|
int toPlatformVersion(FileSystemEntity entry) {
|
|
if (entry is! Directory) {
|
|
return null;
|
|
}
|
|
|
|
if (!fs.isDirectorySync(fs.path.join(entry.path, 'arch-arm'))) {
|
|
return null;
|
|
}
|
|
|
|
final String name = fs.path.basename(entry.path);
|
|
|
|
const String platformPrefix = 'android-';
|
|
if (!name.startsWith(platformPrefix)) {
|
|
return null;
|
|
}
|
|
|
|
return int.tryParse(name.substring(platformPrefix.length));
|
|
}
|
|
|
|
final String platformsDir = fs.path.join(ndkDirectory, 'platforms');
|
|
final List<int> versions = fs
|
|
.directory(platformsDir)
|
|
.listSync()
|
|
.map(toPlatformVersion)
|
|
.where((int version) => version != null)
|
|
.toList(growable: false);
|
|
versions.sort();
|
|
|
|
final int suitableVersion = versions
|
|
.firstWhere((int version) => version >= 9, orElse: () => null);
|
|
if (suitableVersion == null) {
|
|
throw AndroidNdkSearchError('Can not locate a suitable platform ARM sysroot (need android-9 or newer), tried to look in $platformsDir');
|
|
}
|
|
|
|
final String armPlatform = fs.path.join(ndkDirectory, 'platforms',
|
|
'android-$suitableVersion', 'arch-arm');
|
|
return <String>['--sysroot', armPlatform];
|
|
}
|
|
|
|
int findNdkMajorVersion(String ndkDirectory) {
|
|
final String propertiesFile = fs.path.join(ndkDirectory, 'source.properties');
|
|
if (!fs.isFileSync(propertiesFile)) {
|
|
throw AndroidNdkSearchError('Can not establish ndk-bundle version: $propertiesFile not found');
|
|
}
|
|
|
|
// Parse source.properties: each line has Key = Value format.
|
|
final Iterable<String> propertiesFileLines = fs.file(propertiesFile)
|
|
.readAsStringSync()
|
|
.split('\n')
|
|
.map<String>((String line) => line.trim())
|
|
.where((String line) => line.isNotEmpty);
|
|
final Map<String, String> properties = <String, String>{};
|
|
for (String line in propertiesFileLines) {
|
|
final List<String> parts = line.split(' = ');
|
|
if (parts.length == 2) {
|
|
properties[parts[0]] = parts[1];
|
|
} else {
|
|
printError('Malformed line in ndk source.properties: "$line".');
|
|
}
|
|
}
|
|
|
|
if (!properties.containsKey('Pkg.Revision')) {
|
|
throw AndroidNdkSearchError('Can not establish ndk-bundle version: $propertiesFile does not contain Pkg.Revision');
|
|
}
|
|
|
|
// Extract major version from Pkg.Revision property which looks like <ndk-version>.x.y.
|
|
return int.parse(properties['Pkg.Revision'].split('.').first);
|
|
}
|
|
|
|
final String ndkDir = findBundle(androidHomeDir);
|
|
final int ndkVersion = findNdkMajorVersion(ndkDir);
|
|
final List<String> ndkToolchainAndCompiler = findToolchainAndCompiler(ndkDir);
|
|
final String ndkToolchain = ndkToolchainAndCompiler[0];
|
|
final String ndkCompiler = ndkToolchainAndCompiler[1];
|
|
final List<String> ndkCompilerArgs = findSysroot(ndkDir);
|
|
if (ndkVersion >= 18) {
|
|
// Newer versions of NDK use clang instead of gcc, which falls back to
|
|
// system linker instead of using toolchain linker. Force clang to
|
|
// use appropriate linker by passing -fuse-ld=<path-to-ld> command line
|
|
// flag.
|
|
final String ndkLinker = fs.path.join(ndkToolchain, 'arm-linux-androideabi-ld');
|
|
if (!fs.isFileSync(ndkLinker)) {
|
|
throw AndroidNdkSearchError('Can not locate linker binary, tried $ndkLinker');
|
|
}
|
|
ndkCompilerArgs.add('-fuse-ld=$ndkLinker');
|
|
}
|
|
return AndroidNdk._(ndkDir, ndkCompiler, ndkCompilerArgs);
|
|
}
|
|
|
|
/// Returns a descriptive message explaining why NDK can not be found within
|
|
/// the given SDK.
|
|
static String explainMissingNdk(String androidHomeDir) {
|
|
try {
|
|
locateNdk(androidHomeDir);
|
|
return 'Unexpected error: found NDK on the second try';
|
|
} on AndroidNdkSearchError catch (e) {
|
|
return e.reason;
|
|
}
|
|
}
|
|
}
|
|
|
|
class AndroidSdk {
|
|
AndroidSdk(this.directory, [this.ndk]) {
|
|
reinitialize();
|
|
}
|
|
|
|
static const String _javaHomeEnvironmentVariable = 'JAVA_HOME';
|
|
static const String _javaExecutable = 'java';
|
|
|
|
/// The path to the Android SDK.
|
|
final String directory;
|
|
|
|
/// Android NDK (can be `null`).
|
|
final AndroidNdk ndk;
|
|
|
|
List<AndroidSdkVersion> _sdkVersions;
|
|
AndroidSdkVersion _latestVersion;
|
|
|
|
/// Whether the `platform-tools` directory exists in the Android SDK.
|
|
///
|
|
/// It is possible to have an Android SDK folder that is missing this with
|
|
/// the expectation that it will be downloaded later, e.g. by gradle or the
|
|
/// sdkmanager. The [licensesAvailable] property should be used to determine
|
|
/// whether the licenses are at least possibly accepted.
|
|
bool get platformToolsAvailable => fs.directory(fs.path.join(directory, 'platform-tools')).existsSync();
|
|
|
|
/// Whether the `licenses` directory exists in the Android SDK.
|
|
///
|
|
/// The existence of this folder normally indicates that the SDK licenses have
|
|
/// been accepted, e.g. via the sdkmanager, Android Studio, or by copying them
|
|
/// from another workstation such as in CI scenarios. If these files are valid
|
|
/// gradle or the sdkmanager will be able to download and use other parts of
|
|
/// the SDK on demand.
|
|
bool get licensesAvailable => fs.directory(fs.path.join(directory, 'licenses')).existsSync();
|
|
|
|
static AndroidSdk locateAndroidSdk() {
|
|
String findAndroidHomeDir() {
|
|
String androidHomeDir;
|
|
if (config.containsKey('android-sdk')) {
|
|
androidHomeDir = config.getValue('android-sdk') as String;
|
|
} else if (platform.environment.containsKey(kAndroidHome)) {
|
|
androidHomeDir = platform.environment[kAndroidHome];
|
|
} else if (platform.environment.containsKey(kAndroidSdkRoot)) {
|
|
androidHomeDir = platform.environment[kAndroidSdkRoot];
|
|
} else if (platform.isLinux) {
|
|
if (homeDirPath != null) {
|
|
androidHomeDir = fs.path.join(homeDirPath, 'Android', 'Sdk');
|
|
}
|
|
} else if (platform.isMacOS) {
|
|
if (homeDirPath != null) {
|
|
androidHomeDir = fs.path.join(homeDirPath, 'Library', 'Android', 'sdk');
|
|
}
|
|
} else if (platform.isWindows) {
|
|
if (homeDirPath != null) {
|
|
androidHomeDir = fs.path.join(homeDirPath, 'AppData', 'Local', 'Android', 'sdk');
|
|
}
|
|
}
|
|
|
|
if (androidHomeDir != null) {
|
|
if (validSdkDirectory(androidHomeDir)) {
|
|
return androidHomeDir;
|
|
}
|
|
if (validSdkDirectory(fs.path.join(androidHomeDir, 'sdk'))) {
|
|
return fs.path.join(androidHomeDir, 'sdk');
|
|
}
|
|
}
|
|
|
|
// in build-tools/$version/aapt
|
|
final List<File> aaptBins = os.whichAll('aapt');
|
|
for (File aaptBin in aaptBins) {
|
|
// Make sure we're using the aapt from the SDK.
|
|
aaptBin = fs.file(aaptBin.resolveSymbolicLinksSync());
|
|
final String dir = aaptBin.parent.parent.parent.path;
|
|
if (validSdkDirectory(dir)) {
|
|
return dir;
|
|
}
|
|
}
|
|
|
|
// in platform-tools/adb
|
|
final List<File> adbBins = os.whichAll('adb');
|
|
for (File adbBin in adbBins) {
|
|
// Make sure we're using the adb from the SDK.
|
|
adbBin = fs.file(adbBin.resolveSymbolicLinksSync());
|
|
final String dir = adbBin.parent.parent.path;
|
|
if (validSdkDirectory(dir)) {
|
|
return dir;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
final String androidHomeDir = findAndroidHomeDir();
|
|
if (androidHomeDir == null) {
|
|
// No dice.
|
|
printTrace('Unable to locate an Android SDK.');
|
|
return null;
|
|
}
|
|
|
|
// Try to find the NDK compiler. If we can't find it, it's also ok.
|
|
AndroidNdk ndk;
|
|
try {
|
|
ndk = AndroidNdk.locateNdk(androidHomeDir);
|
|
} on AndroidNdkSearchError {
|
|
// Ignore AndroidNdkSearchError's but don't ignore any other
|
|
// exceptions.
|
|
}
|
|
|
|
return AndroidSdk(androidHomeDir, ndk);
|
|
}
|
|
|
|
static bool validSdkDirectory(String dir) {
|
|
return sdkDirectoryHasLicenses(dir) || sdkDirectoryHasPlatformTools(dir);
|
|
}
|
|
|
|
static bool sdkDirectoryHasPlatformTools(String dir) {
|
|
return fs.isDirectorySync(fs.path.join(dir, 'platform-tools'));
|
|
}
|
|
|
|
static bool sdkDirectoryHasLicenses(String dir) {
|
|
return fs.isDirectorySync(fs.path.join(dir, 'licenses'));
|
|
}
|
|
|
|
List<AndroidSdkVersion> get sdkVersions => _sdkVersions;
|
|
|
|
AndroidSdkVersion get latestVersion => _latestVersion;
|
|
|
|
String get adbPath => getPlatformToolsPath(platform.isWindows ? 'adb.exe' : 'adb');
|
|
|
|
String get emulatorPath => getEmulatorPath();
|
|
|
|
String get avdManagerPath => getAvdManagerPath();
|
|
|
|
Directory get _platformsDir => fs.directory(fs.path.join(directory, 'platforms'));
|
|
|
|
Iterable<Directory> get _platforms {
|
|
Iterable<Directory> platforms = <Directory>[];
|
|
if (_platformsDir.existsSync()) {
|
|
platforms = _platformsDir
|
|
.listSync()
|
|
.whereType<Directory>();
|
|
}
|
|
return platforms;
|
|
}
|
|
|
|
/// Validate the Android SDK. This returns an empty list if there are no
|
|
/// issues; otherwise, it returns a list of issues found.
|
|
List<String> validateSdkWellFormed() {
|
|
if (adbPath == null || !processManager.canRun(adbPath)) {
|
|
return <String>['Android SDK file not found: ${adbPath ?? 'adb'}.'];
|
|
}
|
|
|
|
if (sdkVersions.isEmpty || latestVersion == null) {
|
|
final StringBuffer msg = StringBuffer('No valid Android SDK platforms found in ${_platformsDir.path}.');
|
|
if (_platforms.isEmpty) {
|
|
msg.write(' Directory was empty.');
|
|
} else {
|
|
msg.write(' Candidates were:\n');
|
|
msg.write(_platforms
|
|
.map((Directory dir) => ' - ${dir.basename}')
|
|
.join('\n'));
|
|
}
|
|
return <String>[msg.toString()];
|
|
}
|
|
|
|
return latestVersion.validateSdkWellFormed();
|
|
}
|
|
|
|
String getPlatformToolsPath(String binaryName) {
|
|
final String path = fs.path.join(directory, 'platform-tools', binaryName);
|
|
if (fs.file(path).existsSync()) {
|
|
return path;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String getEmulatorPath() {
|
|
final String binaryName = platform.isWindows ? 'emulator.exe' : 'emulator';
|
|
// Emulator now lives inside "emulator" but used to live inside "tools" so
|
|
// try both.
|
|
final List<String> searchFolders = <String>['emulator', 'tools'];
|
|
for (final String folder in searchFolders) {
|
|
final String path = fs.path.join(directory, folder, binaryName);
|
|
if (fs.file(path).existsSync()) {
|
|
return path;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String getAvdManagerPath() {
|
|
final String binaryName = platform.isWindows ? 'avdmanager.bat' : 'avdmanager';
|
|
final String path = fs.path.join(directory, 'tools', 'bin', binaryName);
|
|
if (fs.file(path).existsSync()) {
|
|
return path;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Sets up various paths used internally.
|
|
///
|
|
/// This method should be called in a case where the tooling may have updated
|
|
/// SDK artifacts, such as after running a gradle build.
|
|
void reinitialize() {
|
|
List<Version> buildTools = <Version>[]; // 19.1.0, 22.0.1, ...
|
|
|
|
final Directory buildToolsDir = fs.directory(fs.path.join(directory, 'build-tools'));
|
|
if (buildToolsDir.existsSync()) {
|
|
buildTools = buildToolsDir
|
|
.listSync()
|
|
.map((FileSystemEntity entity) {
|
|
try {
|
|
return Version.parse(entity.basename);
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
})
|
|
.where((Version version) => version != null)
|
|
.toList();
|
|
}
|
|
|
|
// Match up platforms with the best corresponding build-tools.
|
|
_sdkVersions = _platforms.map<AndroidSdkVersion>((Directory platformDir) {
|
|
final String platformName = platformDir.basename;
|
|
int platformVersion;
|
|
|
|
try {
|
|
final Match numberedVersion = _numberedAndroidPlatformRe.firstMatch(platformName);
|
|
if (numberedVersion != null) {
|
|
platformVersion = int.parse(numberedVersion.group(1));
|
|
} else {
|
|
final String buildProps = platformDir.childFile('build.prop').readAsStringSync();
|
|
final String versionString = const LineSplitter()
|
|
.convert(buildProps)
|
|
.map<Match>(_sdkVersionRe.firstMatch)
|
|
.firstWhere((Match match) => match != null)
|
|
.group(1);
|
|
platformVersion = int.parse(versionString);
|
|
}
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
|
|
Version buildToolsVersion = Version.primary(buildTools.where((Version version) {
|
|
return version.major == platformVersion;
|
|
}).toList());
|
|
|
|
buildToolsVersion ??= Version.primary(buildTools);
|
|
|
|
if (buildToolsVersion == null) {
|
|
return null;
|
|
}
|
|
|
|
return AndroidSdkVersion._(
|
|
this,
|
|
sdkLevel: platformVersion,
|
|
platformName: platformName,
|
|
buildToolsVersion: buildToolsVersion,
|
|
);
|
|
}).where((AndroidSdkVersion version) => version != null).toList();
|
|
|
|
_sdkVersions.sort();
|
|
|
|
_latestVersion = _sdkVersions.isEmpty ? null : _sdkVersions.last;
|
|
}
|
|
|
|
/// Returns the filesystem path of the Android SDK manager tool or null if not found.
|
|
String get sdkManagerPath {
|
|
return fs.path.join(directory, 'tools', 'bin', 'sdkmanager');
|
|
}
|
|
|
|
/// First try Java bundled with Android Studio, then sniff JAVA_HOME, then fallback to PATH.
|
|
static String findJavaBinary() {
|
|
if (android_studio.javaPath != null) {
|
|
return fs.path.join(android_studio.javaPath, 'bin', 'java');
|
|
}
|
|
|
|
final String javaHomeEnv = platform.environment[_javaHomeEnvironmentVariable];
|
|
if (javaHomeEnv != null) {
|
|
// Trust JAVA_HOME.
|
|
return fs.path.join(javaHomeEnv, 'bin', 'java');
|
|
}
|
|
|
|
// MacOS specific logic to avoid popping up a dialog window.
|
|
// See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac.
|
|
if (platform.isMacOS) {
|
|
try {
|
|
final String javaHomeOutput = processUtils.runSync(
|
|
<String>['/usr/libexec/java_home'],
|
|
throwOnError: true,
|
|
hideStdout: true,
|
|
).stdout.trim();
|
|
if (javaHomeOutput != null) {
|
|
final List<String> javaHomeOutputSplit = javaHomeOutput.split('\n');
|
|
if ((javaHomeOutputSplit != null) && (javaHomeOutputSplit.isNotEmpty)) {
|
|
final String javaHome = javaHomeOutputSplit[0].trim();
|
|
return fs.path.join(javaHome, 'bin', 'java');
|
|
}
|
|
}
|
|
} catch (_) { /* ignore */ }
|
|
}
|
|
|
|
// Fallback to PATH based lookup.
|
|
return os.which(_javaExecutable)?.path;
|
|
}
|
|
|
|
Map<String, String> _sdkManagerEnv;
|
|
/// Returns an environment with the Java folder added to PATH for use in calling
|
|
/// Java-based Android SDK commands such as sdkmanager and avdmanager.
|
|
Map<String, String> get sdkManagerEnv {
|
|
if (_sdkManagerEnv == null) {
|
|
// If we can locate Java, then add it to the path used to run the Android SDK manager.
|
|
_sdkManagerEnv = <String, String>{};
|
|
final String javaBinary = findJavaBinary();
|
|
if (javaBinary != null) {
|
|
_sdkManagerEnv['PATH'] =
|
|
fs.path.dirname(javaBinary) + os.pathVarSeparator + platform.environment['PATH'];
|
|
}
|
|
}
|
|
return _sdkManagerEnv;
|
|
}
|
|
|
|
/// Returns the version of the Android SDK manager tool or null if not found.
|
|
String get sdkManagerVersion {
|
|
if (!processManager.canRun(sdkManagerPath)) {
|
|
throwToolExit('Android sdkmanager not found. Update to the latest Android SDK to resolve this.');
|
|
}
|
|
final RunResult result = processUtils.runSync(
|
|
<String>[sdkManagerPath, '--version'],
|
|
environment: sdkManagerEnv,
|
|
);
|
|
if (result.exitCode != 0) {
|
|
printTrace('sdkmanager --version failed: exitCode: ${result.exitCode} stdout: ${result.stdout} stderr: ${result.stderr}');
|
|
return null;
|
|
}
|
|
return result.stdout.trim();
|
|
}
|
|
|
|
@override
|
|
String toString() => 'AndroidSdk: $directory';
|
|
}
|
|
|
|
class AndroidSdkVersion implements Comparable<AndroidSdkVersion> {
|
|
AndroidSdkVersion._(
|
|
this.sdk, {
|
|
@required this.sdkLevel,
|
|
@required this.platformName,
|
|
@required this.buildToolsVersion,
|
|
}) : assert(sdkLevel != null),
|
|
assert(platformName != null),
|
|
assert(buildToolsVersion != null);
|
|
|
|
final AndroidSdk sdk;
|
|
final int sdkLevel;
|
|
final String platformName;
|
|
final Version buildToolsVersion;
|
|
|
|
String get buildToolsVersionName => buildToolsVersion.toString();
|
|
|
|
String get androidJarPath => getPlatformsPath('android.jar');
|
|
|
|
String get aaptPath => getBuildToolsPath('aapt');
|
|
|
|
List<String> validateSdkWellFormed() {
|
|
if (_exists(androidJarPath) != null) {
|
|
return <String>[_exists(androidJarPath)];
|
|
}
|
|
|
|
if (_canRun(aaptPath) != null) {
|
|
return <String>[_canRun(aaptPath)];
|
|
}
|
|
|
|
return <String>[];
|
|
}
|
|
|
|
String getPlatformsPath(String itemName) {
|
|
return fs.path.join(sdk.directory, 'platforms', platformName, itemName);
|
|
}
|
|
|
|
String getBuildToolsPath(String binaryName) {
|
|
return fs.path.join(sdk.directory, 'build-tools', buildToolsVersionName, binaryName);
|
|
}
|
|
|
|
@override
|
|
int compareTo(AndroidSdkVersion other) => sdkLevel - other.sdkLevel;
|
|
|
|
@override
|
|
String toString() => '[${sdk.directory}, SDK version $sdkLevel, build-tools $buildToolsVersionName]';
|
|
|
|
String _exists(String path) {
|
|
if (!fs.isFileSync(path)) {
|
|
return 'Android SDK file not found: $path.';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String _canRun(String path) {
|
|
if (!processManager.canRun(path)) {
|
|
return 'Android SDK file not found: $path.';
|
|
}
|
|
return null;
|
|
}
|
|
}
|