Merge pull request #1859 from devoncarew/android_sdk_version
allow any android sdk version
This commit is contained in:
commit
981a7f378c
214
packages/flutter_tools/lib/src/android/android_sdk.dart
Normal file
214
packages/flutter_tools/lib/src/android/android_sdk.dart
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -50,18 +50,6 @@ class AndroidDevice extends Device {
|
|||||||
}) : super(id) {
|
}) : super(id) {
|
||||||
if (connected != null)
|
if (connected != null)
|
||||||
_connected = connected;
|
_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;
|
final String productID;
|
||||||
@ -69,32 +57,9 @@ class AndroidDevice extends Device {
|
|||||||
final String deviceCodeName;
|
final String deviceCodeName;
|
||||||
|
|
||||||
bool _connected;
|
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) {
|
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) {
|
bool _isValidAdbVersion(String adbVersion) {
|
||||||
@ -121,24 +86,19 @@ class AndroidDevice extends Device {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _checkForAdb() {
|
bool _checkForSupportedAdbVersion() {
|
||||||
try {
|
if (androidSdk == null)
|
||||||
String adbVersion = runCheckedSync(<String>[adbPath, 'version']);
|
return false;
|
||||||
if (_isValidAdbVersion(adbVersion)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String locatedAdbPath = runCheckedSync(<String>['which', 'adb']);
|
try {
|
||||||
printError('"$locatedAdbPath" is too old. '
|
String adbVersion = runCheckedSync(<String>[androidSdk.adbPath, 'version']);
|
||||||
'Please install version 1.0.32 or later.\n'
|
if (_isValidAdbVersion(adbVersion))
|
||||||
'Try setting ANDROID_HOME to the path to your Android SDK install. '
|
return true;
|
||||||
'Android builds are unavailable.');
|
printError('The ADB at "${androidSdk.adbPath}" is too old; please install version 1.0.32 or later.');
|
||||||
} catch (e) {
|
} catch (error, trace) {
|
||||||
printError('"adb" not found in \$PATH. '
|
printError('Error running ADB: $error', trace);
|
||||||
'Please install the Android SDK or set ANDROID_HOME '
|
|
||||||
'to the path of your Android SDK install.');
|
|
||||||
printTrace('$e');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,34 +110,29 @@ class AndroidDevice extends Device {
|
|||||||
// * daemon started successfully *
|
// * daemon started successfully *
|
||||||
runCheckedSync(adbCommandForDevice(<String>['start-server']));
|
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'
|
// Sample output: '22'
|
||||||
String sdkVersion = runCheckedSync(
|
String sdkVersion = runCheckedSync(
|
||||||
adbCommandForDevice(<String>['shell', 'getprop', 'ro.build.version.sdk'])
|
adbCommandForDevice(<String>['shell', 'getprop', 'ro.build.version.sdk'])
|
||||||
).trimRight();
|
).trimRight();
|
||||||
|
|
||||||
int sdkVersionParsed =
|
int sdkVersionParsed = int.parse(sdkVersion, onError: (String source) => null);
|
||||||
int.parse(sdkVersion, onError: (String source) => null);
|
|
||||||
if (sdkVersionParsed == null) {
|
if (sdkVersionParsed == null) {
|
||||||
printError('Unexpected response from getprop: "$sdkVersion"');
|
printError('Unexpected response from getprop: "$sdkVersion"');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sdkVersionParsed < minApiLevel) {
|
if (sdkVersionParsed < minApiLevel) {
|
||||||
printError(
|
printError(
|
||||||
'The Android version ($sdkVersion) on the target device is too old. Please '
|
'The Android version ($sdkVersion) on the target device is too old. Please '
|
||||||
'use a $minVersionName (version $minApiLevel / $minVersionText) device or later.');
|
'use a $minVersionName (version $minApiLevel / $minVersionText) device or later.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
printError('Unexpected failure from adb: $e');
|
printError('Unexpected failure from adb: $e');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getDeviceSha1Path(ApplicationPackage app) {
|
String _getDeviceSha1Path(ApplicationPackage app) {
|
||||||
@ -220,11 +175,15 @@ class AndroidDevice extends Device {
|
|||||||
printTrace('Android device not connected. Not installing.');
|
printTrace('Android device not connected. Not installing.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FileSystemEntity.isFileSync(app.localPath)) {
|
if (!FileSystemEntity.isFileSync(app.localPath)) {
|
||||||
printError('"${app.localPath}" does not exist.');
|
printError('"${app.localPath}" does not exist.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
|
||||||
|
return false;
|
||||||
|
|
||||||
printStatus('Installing ${app.name} on device.');
|
printStatus('Installing ${app.name} on device.');
|
||||||
runCheckedSync(adbCommandForDevice(<String>['install', '-r', app.localPath]));
|
runCheckedSync(adbCommandForDevice(<String>['install', '-r', app.localPath]));
|
||||||
runCheckedSync(adbCommandForDevice(<String>['shell', 'echo', '-n', _getSourceSha1(app), '>', _getDeviceSha1Path(app)]));
|
runCheckedSync(adbCommandForDevice(<String>['shell', 'echo', '-n', _getSourceSha1(app), '>', _getDeviceSha1Path(app)]));
|
||||||
@ -306,6 +265,9 @@ class AndroidDevice extends Device {
|
|||||||
int debugPort: observatoryDefaultPort,
|
int debugPort: observatoryDefaultPort,
|
||||||
Map<String, dynamic> platformArgs
|
Map<String, dynamic> platformArgs
|
||||||
}) async {
|
}) async {
|
||||||
|
if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
|
||||||
|
return false;
|
||||||
|
|
||||||
flx.DirectoryResult buildResult = await flx.buildInTempDir(
|
flx.DirectoryResult buildResult = await flx.buildInTempDir(
|
||||||
toolchain,
|
toolchain,
|
||||||
mainPath: mainPath
|
mainPath: mainPath
|
||||||
@ -420,7 +382,7 @@ class AndroidDevice extends Device {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isConnected() => _connected ?? _hasValidAndroid;
|
bool isConnected() => _connected ?? androidSdk != null;
|
||||||
|
|
||||||
void setConnected(bool value) {
|
void setConnected(bool value) {
|
||||||
_connected = value;
|
_connected = value;
|
||||||
@ -447,18 +409,12 @@ class AndroidDevice extends Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [mockAndroid] argument is only to facilitate testing with mocks, so that
|
List<AndroidDevice> getAdbDevices() {
|
||||||
/// we don't have to rely on the test setup having adb available to it.
|
if (androidSdk == null)
|
||||||
List<AndroidDevice> getAdbDevices([AndroidDevice mockAndroid]) {
|
return <AndroidDevice>[];
|
||||||
List<AndroidDevice> devices = [];
|
|
||||||
String adbPath = (mockAndroid != null) ? mockAndroid.adbPath : getAdbPath();
|
|
||||||
|
|
||||||
try {
|
String adbPath = androidSdk.adbPath;
|
||||||
runCheckedSync(<String>[adbPath, 'version']);
|
List<AndroidDevice> devices = [];
|
||||||
} catch (e) {
|
|
||||||
printError('Unable to find adb. Is "adb" in your path?');
|
|
||||||
return devices;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> output = runSync(<String>[adbPath, 'devices', '-l']).trim().split('\n');
|
List<String> output = runSync(<String>[adbPath, 'devices', '-l']).trim().split('\n');
|
||||||
|
|
||||||
@ -525,25 +481,6 @@ List<AndroidDevice> getAdbDevices([AndroidDevice mockAndroid]) {
|
|||||||
return devices;
|
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
|
/// 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.
|
/// another copy of [_AdbLogReader], and the two instances will be equivalent.
|
||||||
class _AdbLogReader extends DeviceLogReader {
|
class _AdbLogReader extends DeviceLogReader {
|
||||||
|
@ -16,6 +16,14 @@ AppContext get context {
|
|||||||
class AppContext {
|
class AppContext {
|
||||||
Map<Type, dynamic> _instances = <Type, dynamic>{};
|
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) {
|
dynamic getVariable(Type type) {
|
||||||
if (_instances.containsKey(type))
|
if (_instances.containsKey(type))
|
||||||
return _instances[type];
|
return _instances[type];
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import '../android/android_sdk.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
import 'context.dart';
|
import 'context.dart';
|
||||||
import 'logger.dart';
|
import 'logger.dart';
|
||||||
|
|
||||||
DeviceManager get deviceManager => context[DeviceManager];
|
DeviceManager get deviceManager => context[DeviceManager];
|
||||||
Logger get logger => context[Logger];
|
Logger get logger => context[Logger];
|
||||||
|
AndroidSdk get androidSdk => context[AndroidSdk];
|
||||||
|
|
||||||
/// Display an error level message to the user. Commands should use this if they
|
/// Display an error level message to the user. Commands should use this if they
|
||||||
/// fail in some way.
|
/// fail in some way.
|
||||||
|
@ -18,12 +18,26 @@ abstract class OperatingSystemUtils {
|
|||||||
|
|
||||||
/// Make the given file executable. This may be a no-op on some platforms.
|
/// Make the given file executable. This may be a no-op on some platforms.
|
||||||
ProcessResult makeExecutable(File file);
|
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 {
|
class _PosixUtils implements OperatingSystemUtils {
|
||||||
ProcessResult makeExecutable(File file) {
|
ProcessResult makeExecutable(File file) {
|
||||||
return Process.runSync('chmod', ['u+x', file.path]);
|
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 {
|
class _WindowsUtils implements OperatingSystemUtils {
|
||||||
@ -31,6 +45,10 @@ class _WindowsUtils implements OperatingSystemUtils {
|
|||||||
ProcessResult makeExecutable(File file) {
|
ProcessResult makeExecutable(File file) {
|
||||||
return new ProcessResult(0, 0, null, null);
|
return new ProcessResult(0, 0, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File which(String execName) {
|
||||||
|
throw new UnimplementedError('_WindowsUtils.which');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> findAvailablePort() async {
|
Future<int> findAvailablePort() async {
|
||||||
|
@ -7,11 +7,12 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
import '../android/device_android.dart';
|
import '../android/android_sdk.dart';
|
||||||
import '../application_package.dart';
|
import '../application_package.dart';
|
||||||
import '../artifacts.dart';
|
import '../artifacts.dart';
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/globals.dart';
|
import '../base/globals.dart';
|
||||||
|
import '../base/os.dart';
|
||||||
import '../base/process.dart';
|
import '../base/process.dart';
|
||||||
import '../build_configuration.dart';
|
import '../build_configuration.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
@ -34,9 +35,6 @@ const String _kDebugKeystoreKeyAlias = "chromiumdebugkey";
|
|||||||
// Password for the Chromium debug keystore
|
// Password for the Chromium debug keystore
|
||||||
const String _kDebugKeystorePassword = "chromium";
|
const String _kDebugKeystorePassword = "chromium";
|
||||||
|
|
||||||
const String _kAndroidPlatformVersion = '22';
|
|
||||||
const String _kBuildToolsVersion = '22.0.1';
|
|
||||||
|
|
||||||
/// Copies files into a new directory structure.
|
/// Copies files into a new directory structure.
|
||||||
class _AssetBuilder {
|
class _AssetBuilder {
|
||||||
final Directory outDir;
|
final Directory outDir;
|
||||||
@ -59,30 +57,24 @@ class _AssetBuilder {
|
|||||||
|
|
||||||
/// Builds an APK package using Android SDK tools.
|
/// Builds an APK package using Android SDK tools.
|
||||||
class _ApkBuilder {
|
class _ApkBuilder {
|
||||||
final String androidSdk;
|
final AndroidSdkVersion sdk;
|
||||||
|
|
||||||
File _androidJar;
|
File _androidJar;
|
||||||
File _aapt;
|
File _aapt;
|
||||||
File _dx;
|
File _dx;
|
||||||
File _zipalign;
|
File _zipalign;
|
||||||
String _jarsigner;
|
File _jarsigner;
|
||||||
|
|
||||||
_ApkBuilder(this.androidSdk) {
|
_ApkBuilder(this.sdk) {
|
||||||
_androidJar = new File('$androidSdk/platforms/android-$_kAndroidPlatformVersion/android.jar');
|
_androidJar = new File(sdk.androidJarPath);
|
||||||
|
_aapt = new File(sdk.aaptPath);
|
||||||
String buildTools = '$androidSdk/build-tools/$_kBuildToolsVersion';
|
_dx = new File(sdk.dxPath);
|
||||||
_aapt = new File('$buildTools/aapt');
|
_zipalign = new File(sdk.zipalignPath);
|
||||||
_dx = new File('$buildTools/dx');
|
_jarsigner = os.which('jarsigner');
|
||||||
_zipalign = new File('$buildTools/zipalign');
|
|
||||||
_jarsigner = 'jarsigner';
|
|
||||||
}
|
|
||||||
|
|
||||||
bool checkSdkPath() {
|
|
||||||
return (_androidJar.existsSync() && _aapt.existsSync() && _dx.existsSync() && _zipalign.existsSync());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void compileClassesDex(File classesDex, List<File> jars) {
|
void compileClassesDex(File classesDex, List<File> jars) {
|
||||||
List<String> packageArgs = [_dx.path,
|
List<String> packageArgs = <String>[_dx.path,
|
||||||
'--dex',
|
'--dex',
|
||||||
'--force-jumbo',
|
'--force-jumbo',
|
||||||
'--output', classesDex.path
|
'--output', classesDex.path
|
||||||
@ -94,7 +86,7 @@ class _ApkBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void package(File outputApk, File androidManifest, Directory assets, Directory artifacts, Directory resources) {
|
void package(File outputApk, File androidManifest, Directory assets, Directory artifacts, Directory resources) {
|
||||||
List<String> packageArgs = [_aapt.path,
|
List<String> packageArgs = <String>[_aapt.path,
|
||||||
'package',
|
'package',
|
||||||
'-M', androidManifest.path,
|
'-M', androidManifest.path,
|
||||||
'-A', assets.path,
|
'-A', assets.path,
|
||||||
@ -109,7 +101,7 @@ class _ApkBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sign(File keystore, String keystorePassword, String keyAlias, String keyPassword, File outputApk) {
|
void sign(File keystore, String keystorePassword, String keyAlias, String keyPassword, File outputApk) {
|
||||||
runCheckedSync([_jarsigner,
|
runCheckedSync(<String>[_jarsigner.path,
|
||||||
'-keystore', keystore.path,
|
'-keystore', keystore.path,
|
||||||
'-storepass', keystorePassword,
|
'-storepass', keystorePassword,
|
||||||
'-keypass', keyPassword,
|
'-keypass', keyPassword,
|
||||||
@ -119,12 +111,11 @@ class _ApkBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void align(File unalignedApk, File outputApk) {
|
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 {
|
class _ApkComponents {
|
||||||
Directory androidSdk;
|
|
||||||
File manifest;
|
File manifest;
|
||||||
File icuData;
|
File icuData;
|
||||||
List<File> jars;
|
List<File> jars;
|
||||||
@ -135,11 +126,12 @@ class _ApkComponents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ApkKeystoreInfo {
|
class ApkKeystoreInfo {
|
||||||
|
ApkKeystoreInfo({ this.keystore, this.password, this.keyAlias, this.keyPassword });
|
||||||
|
|
||||||
String keystore;
|
String keystore;
|
||||||
String password;
|
String password;
|
||||||
String keyAlias;
|
String keyAlias;
|
||||||
String keyPassword;
|
String keyPassword;
|
||||||
ApkKeystoreInfo({ this.keystore, this.password, this.keyAlias, this.keyPassword });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApkCommand extends FlutterCommand {
|
class ApkCommand extends FlutterCommand {
|
||||||
@ -183,7 +175,19 @@ class ApkCommand extends FlutterCommand {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> runInProject() async {
|
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();
|
await downloadToolchain();
|
||||||
|
|
||||||
return await buildAndroid(
|
return await buildAndroid(
|
||||||
toolchain: toolchain,
|
toolchain: toolchain,
|
||||||
configs: buildConfigurations,
|
configs: buildConfigurations,
|
||||||
@ -207,10 +211,8 @@ class ApkCommand extends FlutterCommand {
|
|||||||
Future<_ApkComponents> _findApkComponents(
|
Future<_ApkComponents> _findApkComponents(
|
||||||
BuildConfiguration config, String enginePath, String manifest, String resources
|
BuildConfiguration config, String enginePath, String manifest, String resources
|
||||||
) async {
|
) async {
|
||||||
String androidSdkPath;
|
|
||||||
List<String> artifactPaths;
|
List<String> artifactPaths;
|
||||||
if (enginePath != null) {
|
if (enginePath != null) {
|
||||||
androidSdkPath = '$enginePath/third_party/android_tools/sdk';
|
|
||||||
artifactPaths = [
|
artifactPaths = [
|
||||||
'$enginePath/third_party/icu/android/icudtl.dat',
|
'$enginePath/third_party/icu/android/icudtl.dat',
|
||||||
'${config.buildDir}/gen/sky/shell/shell/classes.dex.jar',
|
'${config.buildDir}/gen/sky/shell/shell/classes.dex.jar',
|
||||||
@ -218,9 +220,6 @@ Future<_ApkComponents> _findApkComponents(
|
|||||||
'$enginePath/build/android/ant/chromium-debug.keystore',
|
'$enginePath/build/android/ant/chromium-debug.keystore',
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
androidSdkPath = AndroidDevice.getAndroidSdkPath();
|
|
||||||
if (androidSdkPath == null)
|
|
||||||
return null;
|
|
||||||
List<ArtifactType> artifactTypes = <ArtifactType>[
|
List<ArtifactType> artifactTypes = <ArtifactType>[
|
||||||
ArtifactType.androidIcuData,
|
ArtifactType.androidIcuData,
|
||||||
ArtifactType.androidClassesJar,
|
ArtifactType.androidClassesJar,
|
||||||
@ -234,7 +233,6 @@ Future<_ApkComponents> _findApkComponents(
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ApkComponents components = new _ApkComponents();
|
_ApkComponents components = new _ApkComponents();
|
||||||
components.androidSdk = new Directory(androidSdkPath);
|
|
||||||
components.manifest = new File(manifest);
|
components.manifest = new File(manifest);
|
||||||
components.icuData = new File(artifactPaths[0]);
|
components.icuData = new File(artifactPaths[0]);
|
||||||
components.jars = [new File(artifactPaths[1])];
|
components.jars = [new File(artifactPaths[1])];
|
||||||
@ -242,11 +240,7 @@ Future<_ApkComponents> _findApkComponents(
|
|||||||
components.debugKeystore = new File(artifactPaths[3]);
|
components.debugKeystore = new File(artifactPaths[3]);
|
||||||
components.resources = new Directory(resources);
|
components.resources = new Directory(resources);
|
||||||
|
|
||||||
await parseServiceConfigs(
|
await parseServiceConfigs(components.services, jars: components.jars);
|
||||||
components.services,
|
|
||||||
jars: components.jars,
|
|
||||||
androidSdk: components.androidSdk.path
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!components.resources.existsSync()) {
|
if (!components.resources.existsSync()) {
|
||||||
// TODO(eseidel): This level should be higher when path is manually set.
|
// TODO(eseidel): This level should be higher when path is manually set.
|
||||||
@ -254,16 +248,6 @@ Future<_ApkComponents> _findApkComponents(
|
|||||||
components.resources = null;
|
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 [
|
for (File f in [
|
||||||
components.manifest, components.icuData, components.libSkyShell, components.debugKeystore
|
components.manifest, components.icuData, components.libSkyShell, components.debugKeystore
|
||||||
]..addAll(components.jars)) {
|
]..addAll(components.jars)) {
|
||||||
@ -281,7 +265,7 @@ int _buildApk(
|
|||||||
) {
|
) {
|
||||||
Directory tempDir = Directory.systemTemp.createTempSync('flutter_tools');
|
Directory tempDir = Directory.systemTemp.createTempSync('flutter_tools');
|
||||||
try {
|
try {
|
||||||
_ApkBuilder builder = new _ApkBuilder(components.androidSdk.path);
|
_ApkBuilder builder = new _ApkBuilder(androidSdk.latestVersion);
|
||||||
|
|
||||||
File classesDex = new File('${tempDir.path}/classes.dex');
|
File classesDex = new File('${tempDir.path}/classes.dex');
|
||||||
builder.compileClassesDex(classesDex, components.jars);
|
builder.compileClassesDex(classesDex, components.jars);
|
||||||
|
@ -7,6 +7,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import '../android/adb.dart';
|
import '../android/adb.dart';
|
||||||
|
import '../android/android_sdk.dart';
|
||||||
import '../android/device_android.dart';
|
import '../android/device_android.dart';
|
||||||
import '../base/context.dart';
|
import '../base/context.dart';
|
||||||
import '../base/globals.dart';
|
import '../base/globals.dart';
|
||||||
|
@ -32,7 +32,7 @@ class RefreshCommand extends FlutterCommand {
|
|||||||
downloadApplicationPackagesAndConnectToDevices(),
|
downloadApplicationPackagesAndConnectToDevices(),
|
||||||
], eagerError: true);
|
], eagerError: true);
|
||||||
|
|
||||||
if (!devices.android.isConnected()) {
|
if (devices.android == null || !devices.android.isConnected()) {
|
||||||
printError('No device connected.');
|
printError('No device connected.');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ class TraceCommand extends FlutterCommand {
|
|||||||
Future<int> runInProject() async {
|
Future<int> runInProject() async {
|
||||||
await downloadApplicationPackagesAndConnectToDevices();
|
await downloadApplicationPackagesAndConnectToDevices();
|
||||||
|
|
||||||
if (!devices.android.isConnected()) {
|
if (devices.android == null || !devices.android.isConnected()) {
|
||||||
printError('No device connected, so no trace was completed.');
|
printError('No device connected, so no trace was completed.');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -402,6 +402,8 @@ class _IOSDeviceLogReader extends DeviceLogReader {
|
|||||||
if (!device.isConnected())
|
if (!device.isConnected())
|
||||||
return 2;
|
return 2;
|
||||||
|
|
||||||
|
// TODO(devoncarew): This regex should use the CFBundleIdentifier value from
|
||||||
|
// the user's plist (instead of `flutter.runner.Runner`).
|
||||||
return await runCommandAndStreamOutput(
|
return await runCommandAndStreamOutput(
|
||||||
<String>[device.loggerPath],
|
<String>[device.loggerPath],
|
||||||
prefix: '[$name] ',
|
prefix: '[$name] ',
|
||||||
|
@ -9,7 +9,9 @@ import 'package:args/args.dart';
|
|||||||
import 'package:args/command_runner.dart';
|
import 'package:args/command_runner.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
import '../android/android_sdk.dart';
|
||||||
import '../artifacts.dart';
|
import '../artifacts.dart';
|
||||||
|
import '../base/context.dart';
|
||||||
import '../base/globals.dart';
|
import '../base/globals.dart';
|
||||||
import '../base/process.dart';
|
import '../base/process.dart';
|
||||||
import '../build_configuration.dart';
|
import '../build_configuration.dart';
|
||||||
@ -167,6 +169,22 @@ class FlutterCommandRunner extends CommandRunner {
|
|||||||
// See if the user specified a specific device.
|
// See if the user specified a specific device.
|
||||||
deviceManager.specifiedDeviceId = globalResults['device-id'];
|
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']));
|
ArtifactStore.flutterRoot = path.normalize(path.absolute(globalResults['flutter-root']));
|
||||||
if (globalResults.wasParsed('package-root'))
|
if (globalResults.wasParsed('package-root'))
|
||||||
ArtifactStore.packageRoot = path.normalize(path.absolute(globalResults['package-root']));
|
ArtifactStore.packageRoot = path.normalize(path.absolute(globalResults['package-root']));
|
||||||
|
@ -10,6 +10,7 @@ import 'package:path/path.dart' as path;
|
|||||||
import 'package:yaml/yaml.dart';
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
import 'artifacts.dart';
|
import 'artifacts.dart';
|
||||||
|
import 'base/globals.dart';
|
||||||
|
|
||||||
const String _kFlutterManifestPath = 'flutter.yaml';
|
const String _kFlutterManifestPath = 'flutter.yaml';
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ dynamic _loadYamlFile(String path) {
|
|||||||
/// Loads all services specified in `flutter.yaml`. Parses each service config file,
|
/// Loads all services specified in `flutter.yaml`. Parses each service config file,
|
||||||
/// storing metadata in [services] and the list of jar files in [jars].
|
/// storing metadata in [services] and the list of jar files in [jars].
|
||||||
Future parseServiceConfigs(
|
Future parseServiceConfigs(
|
||||||
List<Map<String, String>> services, { List<File> jars, String androidSdk }
|
List<Map<String, String>> services, { List<File> jars }
|
||||||
) async {
|
) async {
|
||||||
if (!ArtifactStore.isPackageRootValid)
|
if (!ArtifactStore.isPackageRootValid)
|
||||||
return;
|
return;
|
||||||
@ -49,17 +50,17 @@ Future parseServiceConfigs(
|
|||||||
|
|
||||||
if (jars != null) {
|
if (jars != null) {
|
||||||
for (String jar in serviceConfig['jars'])
|
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(
|
Future<String> getServiceFromUrl(
|
||||||
String url, String rootDir, String serviceName, { String androidSdk, bool unzip: false }
|
String url, String rootDir, String serviceName, { bool unzip: false }
|
||||||
) async {
|
) async {
|
||||||
if (url.startsWith("android-sdk:")) {
|
if (url.startsWith("android-sdk:") && androidSdk != null) {
|
||||||
// It's something shipped in the standard android SDK.
|
// 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")) {
|
} else if (url.startsWith("http")) {
|
||||||
// It's a regular file to download.
|
// It's a regular file to download.
|
||||||
return await ArtifactStore.getThirdPartyFile(url, serviceName, unzip);
|
return await ArtifactStore.getThirdPartyFile(url, serviceName, unzip);
|
||||||
|
@ -15,6 +15,7 @@ dependencies:
|
|||||||
den_api: ^0.1.0
|
den_api: ^0.1.0
|
||||||
mustache4dart: ^1.0.0
|
mustache4dart: ^1.0.0
|
||||||
path: ^1.3.0
|
path: ^1.3.0
|
||||||
|
pub_semver: ^1.0.0
|
||||||
stack_trace: ^1.4.0
|
stack_trace: ^1.4.0
|
||||||
test: 0.12.6+1 # see note below
|
test: 0.12.6+1 # see note below
|
||||||
yaml: ^2.1.3
|
yaml: ^2.1.3
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import 'package:flutter_tools/src/android/device_android.dart';
|
import 'package:flutter_tools/src/android/device_android.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'src/test_context.dart';
|
import 'src/context.dart';
|
||||||
|
|
||||||
main() => defineTests();
|
main() => defineTests();
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import 'package:flutter_tools/src/commands/create.dart';
|
|||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'src/test_context.dart';
|
import 'src/context.dart';
|
||||||
|
|
||||||
main() => defineTests();
|
main() => defineTests();
|
||||||
|
|
||||||
|
@ -16,11 +16,17 @@ import 'src/mocks.dart';
|
|||||||
main() => defineTests();
|
main() => defineTests();
|
||||||
|
|
||||||
defineTests() {
|
defineTests() {
|
||||||
group('daemon', () {
|
Daemon daemon;
|
||||||
Daemon daemon;
|
AppContext appContext;
|
||||||
AppContext appContext;
|
NotifyingLogger notifyingLogger;
|
||||||
NotifyingLogger notifyingLogger;
|
|
||||||
|
|
||||||
|
void _testUsingContext(String description, dynamic testMethod()) {
|
||||||
|
test(description, () {
|
||||||
|
return appContext.runInZone(testMethod);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group('daemon', () {
|
||||||
setUp(() {
|
setUp(() {
|
||||||
appContext = new AppContext();
|
appContext = new AppContext();
|
||||||
notifyingLogger = new NotifyingLogger();
|
notifyingLogger = new NotifyingLogger();
|
||||||
@ -32,7 +38,7 @@ defineTests() {
|
|||||||
return daemon.shutdown();
|
return daemon.shutdown();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('daemon.version', () async {
|
_testUsingContext('daemon.version', () async {
|
||||||
StreamController<Map<String, dynamic>> commands = new StreamController();
|
StreamController<Map<String, dynamic>> commands = new StreamController();
|
||||||
StreamController<Map<String, dynamic>> responses = new StreamController();
|
StreamController<Map<String, dynamic>> responses = new StreamController();
|
||||||
daemon = new Daemon(
|
daemon = new Daemon(
|
||||||
@ -47,7 +53,7 @@ defineTests() {
|
|||||||
expect(response['result'] is String, true);
|
expect(response['result'] is String, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('daemon.logMessage', () {
|
_testUsingContext('daemon.logMessage', () {
|
||||||
return appContext.runInZone(() async {
|
return appContext.runInZone(() async {
|
||||||
StreamController<Map<String, dynamic>> commands = new StreamController();
|
StreamController<Map<String, dynamic>> commands = new StreamController();
|
||||||
StreamController<Map<String, dynamic>> responses = new StreamController();
|
StreamController<Map<String, dynamic>> responses = new StreamController();
|
||||||
@ -68,7 +74,7 @@ defineTests() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('daemon.shutdown', () async {
|
_testUsingContext('daemon.shutdown', () async {
|
||||||
StreamController<Map<String, dynamic>> commands = new StreamController();
|
StreamController<Map<String, dynamic>> commands = new StreamController();
|
||||||
StreamController<Map<String, dynamic>> responses = new StreamController();
|
StreamController<Map<String, dynamic>> responses = new StreamController();
|
||||||
daemon = new Daemon(
|
daemon = new Daemon(
|
||||||
@ -82,7 +88,7 @@ defineTests() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('daemon.stopAll', () async {
|
_testUsingContext('daemon.stopAll', () async {
|
||||||
DaemonCommand command = new DaemonCommand();
|
DaemonCommand command = new DaemonCommand();
|
||||||
applyMocksToCommand(command);
|
applyMocksToCommand(command);
|
||||||
|
|
||||||
@ -112,7 +118,7 @@ defineTests() {
|
|||||||
expect(response['result'], true);
|
expect(response['result'], true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('device.getDevices', () async {
|
_testUsingContext('device.getDevices', () async {
|
||||||
StreamController<Map<String, dynamic>> commands = new StreamController();
|
StreamController<Map<String, dynamic>> commands = new StreamController();
|
||||||
StreamController<Map<String, dynamic>> responses = new StreamController();
|
StreamController<Map<String, dynamic>> responses = new StreamController();
|
||||||
daemon = new Daemon(
|
daemon = new Daemon(
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import 'package:flutter_tools/src/device.dart';
|
import 'package:flutter_tools/src/device.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'src/test_context.dart';
|
import 'src/context.dart';
|
||||||
|
|
||||||
main() => defineTests();
|
main() => defineTests();
|
||||||
|
|
||||||
|
@ -2,18 +2,19 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
|
||||||
import 'package:flutter_tools/src/commands/install.dart';
|
import 'package:flutter_tools/src/commands/install.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'src/common.dart';
|
||||||
|
import 'src/context.dart';
|
||||||
import 'src/mocks.dart';
|
import 'src/mocks.dart';
|
||||||
|
|
||||||
main() => defineTests();
|
main() => defineTests();
|
||||||
|
|
||||||
defineTests() {
|
defineTests() {
|
||||||
group('install', () {
|
group('install', () {
|
||||||
test('returns 0 when Android is connected and ready for an install', () {
|
testUsingContext('returns 0 when Android is connected and ready for an install', () {
|
||||||
InstallCommand command = new InstallCommand();
|
InstallCommand command = new InstallCommand();
|
||||||
applyMocksToCommand(command);
|
applyMocksToCommand(command);
|
||||||
MockDeviceStore mockDevices = command.devices;
|
MockDeviceStore mockDevices = command.devices;
|
||||||
@ -30,13 +31,12 @@ defineTests() {
|
|||||||
when(mockDevices.iOSSimulator.isAppInstalled(any)).thenReturn(false);
|
when(mockDevices.iOSSimulator.isAppInstalled(any)).thenReturn(false);
|
||||||
when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
|
when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
|
||||||
|
|
||||||
|
return createTestCommandRunner(command).run(['install']).then((int code) {
|
||||||
CommandRunner runner = new CommandRunner('test_flutter', '')
|
expect(code, equals(0));
|
||||||
..addCommand(command);
|
});
|
||||||
return runner.run(['install']).then((int code) => expect(code, equals(0)));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns 0 when iOS is connected and ready for an install', () {
|
testUsingContext('returns 0 when iOS is connected and ready for an install', () {
|
||||||
InstallCommand command = new InstallCommand();
|
InstallCommand command = new InstallCommand();
|
||||||
applyMocksToCommand(command);
|
applyMocksToCommand(command);
|
||||||
MockDeviceStore mockDevices = command.devices;
|
MockDeviceStore mockDevices = command.devices;
|
||||||
@ -53,9 +53,9 @@ defineTests() {
|
|||||||
when(mockDevices.iOSSimulator.isAppInstalled(any)).thenReturn(false);
|
when(mockDevices.iOSSimulator.isAppInstalled(any)).thenReturn(false);
|
||||||
when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
|
when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
|
||||||
|
|
||||||
CommandRunner runner = new CommandRunner('test_flutter', '')
|
return createTestCommandRunner(command).run(['install']).then((int code) {
|
||||||
..addCommand(command);
|
expect(code, equals(0));
|
||||||
return runner.run(['install']).then((int code) => expect(code, equals(0)));
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,43 +2,34 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:io';
|
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
|
||||||
import 'package:flutter_tools/src/commands/list.dart';
|
import 'package:flutter_tools/src/commands/list.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:flutter_tools/src/device.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'src/mocks.dart';
|
import 'src/common.dart';
|
||||||
import 'src/test_context.dart';
|
import 'src/context.dart';
|
||||||
|
|
||||||
main() => defineTests();
|
main() => defineTests();
|
||||||
|
|
||||||
defineTests() {
|
defineTests() {
|
||||||
group('list', () {
|
group('list', () {
|
||||||
testUsingContext('returns 0 when called', () {
|
testUsingContext('returns 0 when called', () {
|
||||||
final String mockCommand = Platform.isWindows ? 'cmd /c echo' : 'echo';
|
|
||||||
|
|
||||||
ListCommand command = new ListCommand();
|
ListCommand command = new ListCommand();
|
||||||
applyMocksToCommand(command);
|
return createTestCommandRunner(command).run(['list']).then((int code) {
|
||||||
MockDeviceStore mockDevices = command.devices;
|
expect(code, equals(0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Avoid relying on adb being installed on the test system.
|
testUsingContext('no error when no connected devices', () {
|
||||||
// Instead, cause the test to run the echo command.
|
ListCommand command = new ListCommand();
|
||||||
when(mockDevices.android.adbPath).thenReturn(mockCommand);
|
return createTestCommandRunner(command).run(['list']).then((int code) {
|
||||||
|
expect(code, equals(0));
|
||||||
// Avoid relying on idevice* being installed on the test system.
|
expect(testLogger.statusText, contains('No connected devices'));
|
||||||
// Instead, cause the test to run the echo command.
|
});
|
||||||
when(mockDevices.iOS.informerPath).thenReturn(mockCommand);
|
}, overrides: <Type, dynamic>{
|
||||||
when(mockDevices.iOS.installerPath).thenReturn(mockCommand);
|
AndroidSdk: null,
|
||||||
when(mockDevices.iOS.listerPath).thenReturn(mockCommand);
|
DeviceManager: new DeviceManager()
|
||||||
|
|
||||||
// Avoid relying on xcrun being installed on the test system.
|
|
||||||
// Instead, cause the test to run the echo command.
|
|
||||||
when(mockDevices.iOSSimulator.xcrunPath).thenReturn(mockCommand);
|
|
||||||
|
|
||||||
CommandRunner runner = new CommandRunner('test_flutter', '')..addCommand(command);
|
|
||||||
return runner.run(['list']).then((int code) => expect(code, equals(0)));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,13 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
|
||||||
import 'package:flutter_tools/src/commands/listen.dart';
|
import 'package:flutter_tools/src/commands/listen.dart';
|
||||||
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
|
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'src/common.dart';
|
||||||
|
import 'src/context.dart';
|
||||||
import 'src/mocks.dart';
|
import 'src/mocks.dart';
|
||||||
import 'src/test_context.dart';
|
|
||||||
|
|
||||||
main() => defineTests();
|
main() => defineTests();
|
||||||
|
|
||||||
@ -24,8 +23,9 @@ defineTests() {
|
|||||||
when(mockDevices.iOS.isConnected()).thenReturn(false);
|
when(mockDevices.iOS.isConnected()).thenReturn(false);
|
||||||
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
|
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
|
||||||
|
|
||||||
CommandRunner runner = new FlutterCommandRunner()..addCommand(command);
|
return createTestCommandRunner(command).run(['listen']).then((int code) {
|
||||||
return runner.run(['listen']).then((int code) => expect(code, equals(0)));
|
expect(code, equals(0));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
|
||||||
import 'package:flutter_tools/src/commands/logs.dart';
|
import 'package:flutter_tools/src/commands/logs.dart';
|
||||||
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'src/common.dart';
|
||||||
|
import 'src/context.dart';
|
||||||
import 'src/mocks.dart';
|
import 'src/mocks.dart';
|
||||||
import 'src/test_context.dart';
|
|
||||||
|
|
||||||
main() => defineTests();
|
main() => defineTests();
|
||||||
|
|
||||||
@ -17,8 +16,7 @@ defineTests() {
|
|||||||
testUsingContext('fail with a bad device id', () {
|
testUsingContext('fail with a bad device id', () {
|
||||||
LogsCommand command = new LogsCommand();
|
LogsCommand command = new LogsCommand();
|
||||||
applyMocksToCommand(command);
|
applyMocksToCommand(command);
|
||||||
CommandRunner runner = new FlutterCommandRunner()..addCommand(command);
|
return createTestCommandRunner(command).run(<String>['-d', 'abc123', 'logs']).then((int code) {
|
||||||
return runner.run(<String>['-d', 'abc123', 'logs']).then((int code) {
|
|
||||||
expect(code, equals(1));
|
expect(code, equals(1));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
11
packages/flutter_tools/test/src/common.dart
Normal file
11
packages/flutter_tools/test/src/common.dart
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// 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 'package:args/command_runner.dart';
|
||||||
|
import 'package:flutter_tools/src/runner/flutter_command.dart';
|
||||||
|
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
|
||||||
|
|
||||||
|
CommandRunner createTestCommandRunner(FlutterCommand command) {
|
||||||
|
return new FlutterCommandRunner()..addCommand(command);
|
||||||
|
}
|
@ -9,12 +9,25 @@ import 'package:flutter_tools/src/base/logger.dart';
|
|||||||
import 'package:flutter_tools/src/device.dart';
|
import 'package:flutter_tools/src/device.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void testUsingContext(String description, dynamic testMethod(), { Timeout timeout }) {
|
/// Return the test logger. This assumes that the current Logger is a BufferLogger.
|
||||||
|
BufferLogger get testLogger => context[Logger];
|
||||||
|
|
||||||
|
void testUsingContext(String description, dynamic testMethod(), {
|
||||||
|
Timeout timeout,
|
||||||
|
Map<Type, dynamic> overrides: const <Type, dynamic>{}
|
||||||
|
}) {
|
||||||
test(description, () {
|
test(description, () {
|
||||||
AppContext testContext = new AppContext();
|
AppContext testContext = new AppContext();
|
||||||
|
|
||||||
testContext[Logger] = new BufferLogger();
|
overrides.forEach((Type type, dynamic value) {
|
||||||
testContext[DeviceManager] = new MockDeviceManager();
|
testContext[type] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!overrides.containsKey(Logger))
|
||||||
|
testContext[Logger] = new BufferLogger();
|
||||||
|
|
||||||
|
if (!overrides.containsKey(DeviceManager))
|
||||||
|
testContext[DeviceManager] = new MockDeviceManager();
|
||||||
|
|
||||||
return testContext.runInZone(testMethod);
|
return testContext.runInZone(testMethod);
|
||||||
}, timeout: timeout);
|
}, timeout: timeout);
|
@ -2,18 +2,19 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
|
||||||
import 'package:flutter_tools/src/commands/stop.dart';
|
import 'package:flutter_tools/src/commands/stop.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'src/common.dart';
|
||||||
|
import 'src/context.dart';
|
||||||
import 'src/mocks.dart';
|
import 'src/mocks.dart';
|
||||||
|
|
||||||
main() => defineTests();
|
main() => defineTests();
|
||||||
|
|
||||||
defineTests() {
|
defineTests() {
|
||||||
group('stop', () {
|
group('stop', () {
|
||||||
test('returns 0 when Android is connected and ready to be stopped', () {
|
testUsingContext('returns 0 when Android is connected and ready to be stopped', () {
|
||||||
StopCommand command = new StopCommand();
|
StopCommand command = new StopCommand();
|
||||||
applyMocksToCommand(command);
|
applyMocksToCommand(command);
|
||||||
MockDeviceStore mockDevices = command.devices;
|
MockDeviceStore mockDevices = command.devices;
|
||||||
@ -27,12 +28,12 @@ defineTests() {
|
|||||||
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
|
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
|
||||||
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
|
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
|
||||||
|
|
||||||
CommandRunner runner = new CommandRunner('test_flutter', '')
|
return createTestCommandRunner(command).run(['stop']).then((int code) {
|
||||||
..addCommand(command);
|
expect(code, equals(0));
|
||||||
return runner.run(['stop']).then((int code) => expect(code, equals(0)));
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns 0 when iOS is connected and ready to be stopped', () {
|
testUsingContext('returns 0 when iOS is connected and ready to be stopped', () {
|
||||||
StopCommand command = new StopCommand();
|
StopCommand command = new StopCommand();
|
||||||
applyMocksToCommand(command);
|
applyMocksToCommand(command);
|
||||||
MockDeviceStore mockDevices = command.devices;
|
MockDeviceStore mockDevices = command.devices;
|
||||||
@ -46,9 +47,9 @@ defineTests() {
|
|||||||
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
|
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
|
||||||
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
|
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
|
||||||
|
|
||||||
CommandRunner runner = new CommandRunner('test_flutter', '')
|
return createTestCommandRunner(command).run(['stop']).then((int code) {
|
||||||
..addCommand(command);
|
expect(code, equals(0));
|
||||||
return runner.run(['stop']).then((int code) => expect(code, equals(0)));
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
|
||||||
import 'package:flutter_tools/src/commands/trace.dart';
|
import 'package:flutter_tools/src/commands/trace.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'src/common.dart';
|
||||||
|
import 'src/context.dart';
|
||||||
import 'src/mocks.dart';
|
import 'src/mocks.dart';
|
||||||
import 'src/test_context.dart';
|
|
||||||
|
|
||||||
main() => defineTests();
|
main() => defineTests();
|
||||||
|
|
||||||
@ -21,9 +21,9 @@ defineTests() {
|
|||||||
|
|
||||||
when(mockDevices.android.isConnected()).thenReturn(false);
|
when(mockDevices.android.isConnected()).thenReturn(false);
|
||||||
|
|
||||||
CommandRunner runner = new CommandRunner('test_flutter', '')
|
return createTestCommandRunner(command).run(['trace']).then((int code) {
|
||||||
..addCommand(command);
|
expect(code, equals(1));
|
||||||
return runner.run(['trace']).then((int code) => expect(code, equals(1)));
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user