allow any android sdk version
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart
new file mode 100644
index 0000000..0271f3a
--- /dev/null
+++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -0,0 +1,214 @@
+// Copyright 2016 The Chromium 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 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:pub_semver/pub_semver.dart';
+
+import '../base/globals.dart';
+import '../base/os.dart';
+
+// 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/platforms/android-22/android.jar
+// $ANDROID_HOME/platforms/android-23/android.jar
+
+// TODO(devoncarew): We need a way to locate the Android SDK w/o using an environment variable.
+// Perhaps something like `flutter config --android-home=foo/bar`.
+
+/// Locate ADB. Prefer to use one from an Android SDK, if we can locate that.
+String getAdbPath() {
+ AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
+
+ if (sdk?.latestVersion == null) {
+ return os.which('adb')?.path;
+ } else {
+ return sdk.adbPath;
+ }
+}
+
+class AndroidSdk {
+ AndroidSdk(this.directory) {
+ _init();
+ }
+
+ final String directory;
+
+ List<AndroidSdkVersion> _sdkVersions;
+ AndroidSdkVersion _latestVersion;
+
+ static AndroidSdk locateAndroidSdk() {
+ // TODO: Use explicit configuration information from a metadata file?
+
+ if (Platform.environment.containsKey('ANDROID_HOME')) {
+ String homeDir = Platform.environment['ANDROID_HOME'];
+ if (validSdkDirectory(homeDir))
+ return new AndroidSdk(homeDir);
+ if (validSdkDirectory(path.join(homeDir, 'sdk')))
+ return new AndroidSdk(path.join(homeDir, 'sdk'));
+ }
+
+ File aaptBin = os.which('aapt'); // in build-tools/$version/aapt
+ if (aaptBin != null) {
+ String dir = aaptBin.parent.parent.parent.path;
+ if (validSdkDirectory(dir))
+ return new AndroidSdk(dir);
+ }
+
+ File adbBin = os.which('adb'); // in platform-tools/adb
+ if (adbBin != null) {
+ String dir = adbBin.parent.parent.path;
+ if (validSdkDirectory(dir))
+ return new AndroidSdk(dir);
+ }
+
+ // No dice.
+ printTrace('Unable to locate an Android SDK.');
+ return null;
+ }
+
+ static bool validSdkDirectory(String dir) {
+ return FileSystemEntity.isDirectorySync(path.join(dir, 'platform-tools'));
+ }
+
+ List<AndroidSdkVersion> get sdkVersions => _sdkVersions;
+
+ AndroidSdkVersion get latestVersion => _latestVersion;
+
+ String get adbPath => getPlatformToolsPath('adb');
+
+ bool validateSdkWellFormed({ bool complain: false }) {
+ if (!FileSystemEntity.isFileSync(adbPath)) {
+ if (complain)
+ printError('Android SDK file not found: $adbPath.');
+ return false;
+ }
+
+ if (sdkVersions.isEmpty) {
+ if (complain)
+ printError('Android SDK does not have the proper build-tools.');
+ return false;
+ }
+
+ return latestVersion.validateSdkWellFormed(complain: complain);
+ }
+
+ String getPlatformToolsPath(String binaryName) {
+ return path.join(directory, 'platform-tools', binaryName);
+ }
+
+ void _init() {
+ List<String> platforms = <String>[]; // android-22, ...
+
+ Directory platformsDir = new Directory(path.join(directory, 'platforms'));
+ if (platformsDir.existsSync()) {
+ platforms = platformsDir
+ .listSync()
+ .map((FileSystemEntity entity) => path.basename(entity.path))
+ .where((String name) => name.startsWith('android-'))
+ .toList();
+ }
+
+ List<Version> buildToolsVersions = <Version>[]; // 19.1.0, 22.0.1, ...
+
+ Directory buildToolsDir = new Directory(path.join(directory, 'build-tools'));
+ if (buildToolsDir.existsSync()) {
+ buildToolsVersions = buildToolsDir
+ .listSync()
+ .map((FileSystemEntity entity) {
+ try {
+ return new Version.parse(path.basename(entity.path));
+ } catch (error) {
+ return null;
+ }
+ })
+ .where((Version version) => version != null)
+ .toList();
+ }
+
+ // Here we match up platforms with cooresponding build-tools. If we don't
+ // have a match, we don't return anything for that platform version. So if
+ // the user only have 'android-22' and 'build-tools/19.0.0', we don't find
+ // an Android sdk.
+ _sdkVersions = platforms.map((String platform) {
+ int sdkVersion;
+
+ try {
+ sdkVersion = int.parse(platform.substring('android-'.length));
+ } catch (error) {
+ return null;
+ }
+
+ Version buildToolsVersion = Version.primary(buildToolsVersions.where((Version version) {
+ return version.major == sdkVersion;
+ }).toList());
+
+ if (buildToolsVersion == null)
+ return null;
+
+ return new AndroidSdkVersion(this, platform, buildToolsVersion.toString());
+ }).where((AndroidSdkVersion version) => version != null).toList();
+
+ _sdkVersions.sort();
+
+ _latestVersion = _sdkVersions.isEmpty ? null : _sdkVersions.last;
+ }
+
+ String toString() => 'AndroidSdk: $directory';
+}
+
+class AndroidSdkVersion implements Comparable<AndroidSdkVersion> {
+ AndroidSdkVersion(this.sdk, this.androidVersion, this.buildToolsVersion);
+
+ final AndroidSdk sdk;
+ final String androidVersion;
+ final String buildToolsVersion;
+
+ int get sdkLevel => int.parse(androidVersion.substring('android-'.length));
+
+ String get androidJarPath => getPlatformsPath('android.jar');
+
+ String get aaptPath => getBuildToolsPath('aapt');
+
+ String get dxPath => getBuildToolsPath('dx');
+
+ String get zipalignPath => getBuildToolsPath('zipalign');
+
+ bool validateSdkWellFormed({ bool complain: false }) {
+ return
+ _exists(androidJarPath, complain: complain) &&
+ _exists(aaptPath, complain: complain) &&
+ _exists(dxPath, complain: complain) &&
+ _exists(zipalignPath, complain: complain);
+ }
+
+ String getPlatformsPath(String itemName) {
+ return path.join(sdk.directory, 'platforms', androidVersion, itemName);
+ }
+
+ String getBuildToolsPath(String binaryName) {
+ return path.join(sdk.directory, 'build-tools', buildToolsVersion, binaryName);
+ }
+
+ int compareTo(AndroidSdkVersion other) {
+ return sdkLevel - other.sdkLevel;
+ }
+
+ String toString() => '[${sdk.directory}, SDK version $sdkLevel, build-tools $buildToolsVersion]';
+
+ bool _exists(String path, { bool complain: false }) {
+ if (!FileSystemEntity.isFileSync(path)) {
+ if (complain)
+ printError('Android SDK file not found: $path.');
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/packages/flutter_tools/lib/src/android/device_android.dart b/packages/flutter_tools/lib/src/android/device_android.dart
index c55e0d4..0f7c4d0 100644
--- a/packages/flutter_tools/lib/src/android/device_android.dart
+++ b/packages/flutter_tools/lib/src/android/device_android.dart
@@ -50,18 +50,6 @@
}) : super(id) {
if (connected != null)
_connected = connected;
-
- _adbPath = getAdbPath();
- _hasAdb = _checkForAdb();
-
- // Checking for [minApiName] only needs to be done if we are starting an
- // app, but it has an important side effect, which is to discard any
- // progress messages if the adb server is restarted.
- _hasValidAndroid = _checkForSupportedAndroidVersion();
-
- if (!_hasAdb || !_hasValidAndroid) {
- printError('Unable to run on Android.');
- }
}
final String productID;
@@ -69,32 +57,9 @@
final String deviceCodeName;
bool _connected;
- String _adbPath;
- String get adbPath => _adbPath;
- bool _hasAdb = false;
- bool _hasValidAndroid = false;
-
- static String getAndroidSdkPath() {
- if (Platform.environment.containsKey('ANDROID_HOME')) {
- String androidHomeDir = Platform.environment['ANDROID_HOME'];
- if (FileSystemEntity.isDirectorySync(
- path.join(androidHomeDir, 'platform-tools'))) {
- return androidHomeDir;
- } else if (FileSystemEntity.isDirectorySync(
- path.join(androidHomeDir, 'sdk', 'platform-tools'))) {
- return path.join(androidHomeDir, 'sdk');
- } else {
- printError('Android SDK not found at $androidHomeDir');
- return null;
- }
- } else {
- printError('Android SDK not found. The ANDROID_HOME variable must be set.');
- return null;
- }
- }
List<String> adbCommandForDevice(List<String> args) {
- return <String>[adbPath, '-s', id]..addAll(args);
+ return <String>[androidSdk.adbPath, '-s', id]..addAll(args);
}
bool _isValidAdbVersion(String adbVersion) {
@@ -121,24 +86,19 @@
return true;
}
- bool _checkForAdb() {
- try {
- String adbVersion = runCheckedSync(<String>[adbPath, 'version']);
- if (_isValidAdbVersion(adbVersion)) {
- return true;
- }
+ bool _checkForSupportedAdbVersion() {
+ if (androidSdk == null)
+ return false;
- String locatedAdbPath = runCheckedSync(<String>['which', 'adb']);
- printError('"$locatedAdbPath" is too old. '
- 'Please install version 1.0.32 or later.\n'
- 'Try setting ANDROID_HOME to the path to your Android SDK install. '
- 'Android builds are unavailable.');
- } catch (e) {
- printError('"adb" not found in \$PATH. '
- 'Please install the Android SDK or set ANDROID_HOME '
- 'to the path of your Android SDK install.');
- printTrace('$e');
+ try {
+ String adbVersion = runCheckedSync(<String>[androidSdk.adbPath, 'version']);
+ if (_isValidAdbVersion(adbVersion))
+ return true;
+ printError('The ADB at "${androidSdk.adbPath}" is too old; please install version 1.0.32 or later.');
+ } catch (error, trace) {
+ printError('Error running ADB: $error', trace);
}
+
return false;
}
@@ -150,34 +110,29 @@
// * daemon started successfully *
runCheckedSync(adbCommandForDevice(<String>['start-server']));
- String ready = runSync(adbCommandForDevice(<String>['shell', 'echo', 'ready']));
- if (ready.trim() != 'ready') {
- printTrace('Android device not found.');
- return false;
- }
-
// Sample output: '22'
String sdkVersion = runCheckedSync(
adbCommandForDevice(<String>['shell', 'getprop', 'ro.build.version.sdk'])
).trimRight();
- int sdkVersionParsed =
- int.parse(sdkVersion, onError: (String source) => null);
+ int sdkVersionParsed = int.parse(sdkVersion, onError: (String source) => null);
if (sdkVersionParsed == null) {
printError('Unexpected response from getprop: "$sdkVersion"');
return false;
}
+
if (sdkVersionParsed < minApiLevel) {
printError(
'The Android version ($sdkVersion) on the target device is too old. Please '
'use a $minVersionName (version $minApiLevel / $minVersionText) device or later.');
return false;
}
+
return true;
} catch (e) {
printError('Unexpected failure from adb: $e');
+ return false;
}
- return false;
}
String _getDeviceSha1Path(ApplicationPackage app) {
@@ -220,11 +175,15 @@
printTrace('Android device not connected. Not installing.');
return false;
}
+
if (!FileSystemEntity.isFileSync(app.localPath)) {
printError('"${app.localPath}" does not exist.');
return false;
}
+ if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
+ return false;
+
printStatus('Installing ${app.name} on device.');
runCheckedSync(adbCommandForDevice(<String>['install', '-r', app.localPath]));
runCheckedSync(adbCommandForDevice(<String>['shell', 'echo', '-n', _getSourceSha1(app), '>', _getDeviceSha1Path(app)]));
@@ -306,6 +265,9 @@
int debugPort: observatoryDefaultPort,
Map<String, dynamic> platformArgs
}) async {
+ if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
+ return false;
+
flx.DirectoryResult buildResult = await flx.buildInTempDir(
toolchain,
mainPath: mainPath
@@ -420,7 +382,7 @@
return null;
}
- bool isConnected() => _connected ?? _hasValidAndroid;
+ bool isConnected() => _connected ?? androidSdk != null;
void setConnected(bool value) {
_connected = value;
@@ -447,18 +409,12 @@
}
}
-/// The [mockAndroid] argument is only to facilitate testing with mocks, so that
-/// we don't have to rely on the test setup having adb available to it.
-List<AndroidDevice> getAdbDevices([AndroidDevice mockAndroid]) {
- List<AndroidDevice> devices = [];
- String adbPath = (mockAndroid != null) ? mockAndroid.adbPath : getAdbPath();
+List<AndroidDevice> getAdbDevices() {
+ if (androidSdk == null)
+ return <AndroidDevice>[];
- try {
- runCheckedSync(<String>[adbPath, 'version']);
- } catch (e) {
- printError('Unable to find adb. Is "adb" in your path?');
- return devices;
- }
+ String adbPath = androidSdk.adbPath;
+ List<AndroidDevice> devices = [];
List<String> output = runSync(<String>[adbPath, 'devices', '-l']).trim().split('\n');
@@ -525,25 +481,6 @@
return devices;
}
-String getAdbPath() {
- if (Platform.environment.containsKey('ANDROID_HOME')) {
- String androidHomeDir = Platform.environment['ANDROID_HOME'];
- String adbPath1 = path.join(androidHomeDir, 'sdk', 'platform-tools', 'adb');
- String adbPath2 = path.join(androidHomeDir, 'platform-tools', 'adb');
- if (FileSystemEntity.isFileSync(adbPath1)) {
- return adbPath1;
- } else if (FileSystemEntity.isFileSync(adbPath2)) {
- return adbPath2;
- } else {
- printTrace('"adb" not found at\n "$adbPath1" or\n "$adbPath2"\n' +
- 'using default path "$_defaultAdbPath"');
- return _defaultAdbPath;
- }
- } else {
- return _defaultAdbPath;
- }
-}
-
/// A log reader that logs from `adb logcat`. This will have the same output as
/// another copy of [_AdbLogReader], and the two instances will be equivalent.
class _AdbLogReader extends DeviceLogReader {
diff --git a/packages/flutter_tools/lib/src/base/context.dart b/packages/flutter_tools/lib/src/base/context.dart
index 8c1f54d..87b9da4 100644
--- a/packages/flutter_tools/lib/src/base/context.dart
+++ b/packages/flutter_tools/lib/src/base/context.dart
@@ -16,6 +16,14 @@
class AppContext {
Map<Type, dynamic> _instances = <Type, dynamic>{};
+ bool isSet(Type type) {
+ if (_instances.containsKey(type))
+ return true;
+
+ AppContext parent = _calcParent(Zone.current);
+ return parent != null ? parent.isSet(type) : false;
+ }
+
dynamic getVariable(Type type) {
if (_instances.containsKey(type))
return _instances[type];
diff --git a/packages/flutter_tools/lib/src/base/globals.dart b/packages/flutter_tools/lib/src/base/globals.dart
index 5c86fb2..ebe3c12 100644
--- a/packages/flutter_tools/lib/src/base/globals.dart
+++ b/packages/flutter_tools/lib/src/base/globals.dart
@@ -2,12 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import '../android/android_sdk.dart';
import '../device.dart';
import 'context.dart';
import 'logger.dart';
DeviceManager get deviceManager => context[DeviceManager];
Logger get logger => context[Logger];
+AndroidSdk get androidSdk => context[AndroidSdk];
/// Display an error level message to the user. Commands should use this if they
/// fail in some way.
diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart
index df10b82..444991c 100644
--- a/packages/flutter_tools/lib/src/base/os.dart
+++ b/packages/flutter_tools/lib/src/base/os.dart
@@ -18,12 +18,26 @@
/// Make the given file executable. This may be a no-op on some platforms.
ProcessResult makeExecutable(File file);
+
+ /// Return the path (with symlinks resolved) to the given executable, or `null`
+ /// if `which` was not able to locate the binary.
+ File which(String execName);
}
class _PosixUtils implements OperatingSystemUtils {
ProcessResult makeExecutable(File file) {
return Process.runSync('chmod', ['u+x', file.path]);
}
+
+ /// Return the path (with symlinks resolved) to the given executable, or `null`
+ /// if `which` was not able to locate the binary.
+ File which(String execName) {
+ ProcessResult result = Process.runSync('which', <String>[execName]);
+ if (result.exitCode != 0)
+ return null;
+ String path = result.stdout.trim().split('\n').first.trim();
+ return new File(new File(path).resolveSymbolicLinksSync());
+ }
}
class _WindowsUtils implements OperatingSystemUtils {
@@ -31,6 +45,10 @@
ProcessResult makeExecutable(File file) {
return new ProcessResult(0, 0, null, null);
}
+
+ File which(String execName) {
+ throw new UnimplementedError('_WindowsUtils.which');
+ }
}
Future<int> findAvailablePort() async {
diff --git a/packages/flutter_tools/lib/src/commands/apk.dart b/packages/flutter_tools/lib/src/commands/apk.dart
index 4a9899e..e6d928e 100644
--- a/packages/flutter_tools/lib/src/commands/apk.dart
+++ b/packages/flutter_tools/lib/src/commands/apk.dart
@@ -7,11 +7,12 @@
import 'package:path/path.dart' as path;
-import '../android/device_android.dart';
+import '../android/android_sdk.dart';
import '../application_package.dart';
import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/globals.dart';
+import '../base/os.dart';
import '../base/process.dart';
import '../build_configuration.dart';
import '../device.dart';
@@ -34,9 +35,6 @@
// Password for the Chromium debug keystore
const String _kDebugKeystorePassword = "chromium";
-const String _kAndroidPlatformVersion = '22';
-const String _kBuildToolsVersion = '22.0.1';
-
/// Copies files into a new directory structure.
class _AssetBuilder {
final Directory outDir;
@@ -59,30 +57,24 @@
/// Builds an APK package using Android SDK tools.
class _ApkBuilder {
- final String androidSdk;
+ final AndroidSdkVersion sdk;
File _androidJar;
File _aapt;
File _dx;
File _zipalign;
- String _jarsigner;
+ File _jarsigner;
- _ApkBuilder(this.androidSdk) {
- _androidJar = new File('$androidSdk/platforms/android-$_kAndroidPlatformVersion/android.jar');
-
- String buildTools = '$androidSdk/build-tools/$_kBuildToolsVersion';
- _aapt = new File('$buildTools/aapt');
- _dx = new File('$buildTools/dx');
- _zipalign = new File('$buildTools/zipalign');
- _jarsigner = 'jarsigner';
- }
-
- bool checkSdkPath() {
- return (_androidJar.existsSync() && _aapt.existsSync() && _dx.existsSync() && _zipalign.existsSync());
+ _ApkBuilder(this.sdk) {
+ _androidJar = new File(sdk.androidJarPath);
+ _aapt = new File(sdk.aaptPath);
+ _dx = new File(sdk.dxPath);
+ _zipalign = new File(sdk.zipalignPath);
+ _jarsigner = os.which('jarsigner');
}
void compileClassesDex(File classesDex, List<File> jars) {
- List<String> packageArgs = [_dx.path,
+ List<String> packageArgs = <String>[_dx.path,
'--dex',
'--force-jumbo',
'--output', classesDex.path
@@ -94,7 +86,7 @@
}
void package(File outputApk, File androidManifest, Directory assets, Directory artifacts, Directory resources) {
- List<String> packageArgs = [_aapt.path,
+ List<String> packageArgs = <String>[_aapt.path,
'package',
'-M', androidManifest.path,
'-A', assets.path,
@@ -109,7 +101,7 @@
}
void sign(File keystore, String keystorePassword, String keyAlias, String keyPassword, File outputApk) {
- runCheckedSync([_jarsigner,
+ runCheckedSync(<String>[_jarsigner.path,
'-keystore', keystore.path,
'-storepass', keystorePassword,
'-keypass', keyPassword,
@@ -119,12 +111,11 @@
}
void align(File unalignedApk, File outputApk) {
- runCheckedSync([_zipalign.path, '-f', '4', unalignedApk.path, outputApk.path]);
+ runCheckedSync(<String>[_zipalign.path, '-f', '4', unalignedApk.path, outputApk.path]);
}
}
class _ApkComponents {
- Directory androidSdk;
File manifest;
File icuData;
List<File> jars;
@@ -135,11 +126,12 @@
}
class ApkKeystoreInfo {
+ ApkKeystoreInfo({ this.keystore, this.password, this.keyAlias, this.keyPassword });
+
String keystore;
String password;
String keyAlias;
String keyPassword;
- ApkKeystoreInfo({ this.keystore, this.password, this.keyAlias, this.keyPassword });
}
class ApkCommand extends FlutterCommand {
@@ -183,7 +175,19 @@
@override
Future<int> runInProject() async {
+ // Validate that we can find an android sdk.
+ if (androidSdk == null) {
+ printError('No Android SDK found.');
+ return 1;
+ }
+
+ if (!androidSdk.validateSdkWellFormed(complain: true)) {
+ printError('Try re-installing or updating your Android SDK.');
+ return 1;
+ }
+
await downloadToolchain();
+
return await buildAndroid(
toolchain: toolchain,
configs: buildConfigurations,
@@ -207,10 +211,8 @@
Future<_ApkComponents> _findApkComponents(
BuildConfiguration config, String enginePath, String manifest, String resources
) async {
- String androidSdkPath;
List<String> artifactPaths;
if (enginePath != null) {
- androidSdkPath = '$enginePath/third_party/android_tools/sdk';
artifactPaths = [
'$enginePath/third_party/icu/android/icudtl.dat',
'${config.buildDir}/gen/sky/shell/shell/classes.dex.jar',
@@ -218,9 +220,6 @@
'$enginePath/build/android/ant/chromium-debug.keystore',
];
} else {
- androidSdkPath = AndroidDevice.getAndroidSdkPath();
- if (androidSdkPath == null)
- return null;
List<ArtifactType> artifactTypes = <ArtifactType>[
ArtifactType.androidIcuData,
ArtifactType.androidClassesJar,
@@ -234,7 +233,6 @@
}
_ApkComponents components = new _ApkComponents();
- components.androidSdk = new Directory(androidSdkPath);
components.manifest = new File(manifest);
components.icuData = new File(artifactPaths[0]);
components.jars = [new File(artifactPaths[1])];
@@ -242,11 +240,7 @@
components.debugKeystore = new File(artifactPaths[3]);
components.resources = new Directory(resources);
- await parseServiceConfigs(
- components.services,
- jars: components.jars,
- androidSdk: components.androidSdk.path
- );
+ await parseServiceConfigs(components.services, jars: components.jars);
if (!components.resources.existsSync()) {
// TODO(eseidel): This level should be higher when path is manually set.
@@ -254,16 +248,6 @@
components.resources = null;
}
- if (!components.androidSdk.existsSync()) {
- printError('Can not locate Android SDK: $androidSdkPath');
- return null;
- }
- if (!(new _ApkBuilder(components.androidSdk.path).checkSdkPath())) {
- printError('Can not locate expected Android SDK tools at $androidSdkPath');
- printError('You must install version $_kAndroidPlatformVersion of the SDK platform');
- printError('and version $_kBuildToolsVersion of the build tools.');
- return null;
- }
for (File f in [
components.manifest, components.icuData, components.libSkyShell, components.debugKeystore
]..addAll(components.jars)) {
@@ -281,7 +265,7 @@
) {
Directory tempDir = Directory.systemTemp.createTempSync('flutter_tools');
try {
- _ApkBuilder builder = new _ApkBuilder(components.androidSdk.path);
+ _ApkBuilder builder = new _ApkBuilder(androidSdk.latestVersion);
File classesDex = new File('${tempDir.path}/classes.dex');
builder.compileClassesDex(classesDex, components.jars);
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 0dee7cc..f95ea65 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -7,6 +7,7 @@
import 'dart:io';
import '../android/adb.dart';
+import '../android/android_sdk.dart';
import '../android/device_android.dart';
import '../base/context.dart';
import '../base/globals.dart';
diff --git a/packages/flutter_tools/lib/src/commands/refresh.dart b/packages/flutter_tools/lib/src/commands/refresh.dart
index 35ff972..d4d5fc1 100644
--- a/packages/flutter_tools/lib/src/commands/refresh.dart
+++ b/packages/flutter_tools/lib/src/commands/refresh.dart
@@ -32,7 +32,7 @@
downloadApplicationPackagesAndConnectToDevices(),
], eagerError: true);
- if (!devices.android.isConnected()) {
+ if (devices.android == null || !devices.android.isConnected()) {
printError('No device connected.');
return 1;
}
diff --git a/packages/flutter_tools/lib/src/commands/trace.dart b/packages/flutter_tools/lib/src/commands/trace.dart
index a40af6d..365d552 100644
--- a/packages/flutter_tools/lib/src/commands/trace.dart
+++ b/packages/flutter_tools/lib/src/commands/trace.dart
@@ -29,7 +29,7 @@
Future<int> runInProject() async {
await downloadApplicationPackagesAndConnectToDevices();
- if (!devices.android.isConnected()) {
+ if (devices.android == null || !devices.android.isConnected()) {
printError('No device connected, so no trace was completed.');
return 1;
}
diff --git a/packages/flutter_tools/lib/src/ios/device_ios.dart b/packages/flutter_tools/lib/src/ios/device_ios.dart
index 1e1eba8..4ab15bf 100644
--- a/packages/flutter_tools/lib/src/ios/device_ios.dart
+++ b/packages/flutter_tools/lib/src/ios/device_ios.dart
@@ -402,6 +402,8 @@
if (!device.isConnected())
return 2;
+ // TODO(devoncarew): This regex should use the CFBundleIdentifier value from
+ // the user's plist (instead of `flutter.runner.Runner`).
return await runCommandAndStreamOutput(
<String>[device.loggerPath],
prefix: '[$name] ',
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index a01c21f..9f0cbc3 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -9,7 +9,9 @@
import 'package:args/command_runner.dart';
import 'package:path/path.dart' as path;
+import '../android/android_sdk.dart';
import '../artifacts.dart';
+import '../base/context.dart';
import '../base/globals.dart';
import '../base/process.dart';
import '../build_configuration.dart';
@@ -167,6 +169,22 @@
// See if the user specified a specific device.
deviceManager.specifiedDeviceId = globalResults['device-id'];
+ // The Android SDK could already have been set by tests.
+ if (!context.isSet(AndroidSdk)) {
+ if (enginePath != null) {
+ context[AndroidSdk] = new AndroidSdk('$enginePath/third_party/android_tools/sdk');
+ } else {
+ context[AndroidSdk] = AndroidSdk.locateAndroidSdk();
+ }
+ }
+
+ if (androidSdk != null) {
+ printTrace('Using Android SDK at ${androidSdk.directory}.');
+
+ if (androidSdk.latestVersion != null)
+ printTrace('${androidSdk.latestVersion}');
+ }
+
ArtifactStore.flutterRoot = path.normalize(path.absolute(globalResults['flutter-root']));
if (globalResults.wasParsed('package-root'))
ArtifactStore.packageRoot = path.normalize(path.absolute(globalResults['package-root']));
diff --git a/packages/flutter_tools/lib/src/services.dart b/packages/flutter_tools/lib/src/services.dart
index 0225aec..f23d68a 100644
--- a/packages/flutter_tools/lib/src/services.dart
+++ b/packages/flutter_tools/lib/src/services.dart
@@ -10,6 +10,7 @@
import 'package:yaml/yaml.dart';
import 'artifacts.dart';
+import 'base/globals.dart';
const String _kFlutterManifestPath = 'flutter.yaml';
@@ -23,7 +24,7 @@
/// Loads all services specified in `flutter.yaml`. Parses each service config file,
/// storing metadata in [services] and the list of jar files in [jars].
Future parseServiceConfigs(
- List<Map<String, String>> services, { List<File> jars, String androidSdk }
+ List<Map<String, String>> services, { List<File> jars }
) async {
if (!ArtifactStore.isPackageRootValid)
return;
@@ -49,17 +50,17 @@
if (jars != null) {
for (String jar in serviceConfig['jars'])
- jars.add(new File(await getServiceFromUrl(jar, serviceRoot, service, androidSdk: androidSdk, unzip: false)));
+ jars.add(new File(await getServiceFromUrl(jar, serviceRoot, service, unzip: false)));
}
}
}
Future<String> getServiceFromUrl(
- String url, String rootDir, String serviceName, { String androidSdk, bool unzip: false }
+ String url, String rootDir, String serviceName, { bool unzip: false }
) async {
- if (url.startsWith("android-sdk:")) {
+ if (url.startsWith("android-sdk:") && androidSdk != null) {
// It's something shipped in the standard android SDK.
- return url.replaceAll('android-sdk:', '$androidSdk/');
+ return url.replaceAll('android-sdk:', '${androidSdk.directory}/');
} else if (url.startsWith("http")) {
// It's a regular file to download.
return await ArtifactStore.getThirdPartyFile(url, serviceName, unzip);