Commit dcf0b7ba authored by Devon Carew's avatar Devon Carew

allow any android sdk version

parent 5a167615
// 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) {
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 @@ class AndroidDevice extends Device {
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 @@ class AndroidDevice extends Device {
return true;
}
bool _checkForAdb() {
bool _checkForSupportedAdbVersion() {
if (androidSdk == null)
return false;
try {
String adbVersion = runCheckedSync(<String>[adbPath, 'version']);
if (_isValidAdbVersion(adbVersion)) {
String adbVersion = runCheckedSync(<String>[androidSdk.adbPath, 'version']);
if (_isValidAdbVersion(adbVersion))
return true;
}
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');
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 @@ class AndroidDevice extends Device {
// * 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 @@ class AndroidDevice extends Device {
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 @@ class AndroidDevice extends Device {
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 @@ class AndroidDevice extends Device {
return null;
}
bool isConnected() => _connected ?? _hasValidAndroid;
bool isConnected() => _connected ?? androidSdk != null;
void setConnected(bool value) {
_connected = value;
......@@ -447,18 +409,12 @@ class AndroidDevice extends Device {
}
}
/// 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 @@ List<AndroidDevice> getAdbDevices([AndroidDevice mockAndroid]) {
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 {
......
......@@ -16,6 +16,14 @@ AppContext get context {
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];
......
......@@ -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.
......
......@@ -18,12 +18,26 @@ abstract class OperatingSystemUtils {
/// 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 @@ class _WindowsUtils implements OperatingSystemUtils {
ProcessResult makeExecutable(File file) {
return new ProcessResult(0, 0, null, null);
}
File which(String execName) {
throw new UnimplementedError('_WindowsUtils.which');
}
}
Future<int> findAvailablePort() async {
......
......@@ -7,11 +7,12 @@ import 'dart:io';
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 @@ const String _kDebugKeystoreKeyAlias = "chromiumdebugkey";
// 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 @@ class _AssetBuilder {
/// 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;
_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());
File _jarsigner;
_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 @@ class _ApkBuilder {
}
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 @@ class _ApkBuilder {
}
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 @@ class _ApkBuilder {
}
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 _ApkComponents {
}
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 @@ class ApkCommand extends FlutterCommand {
@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 @@ class ApkCommand extends FlutterCommand {
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 @@ Future<_ApkComponents> _findApkComponents(
'$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 @@ Future<_ApkComponents> _findApkComponents(
}
_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 @@ Future<_ApkComponents> _findApkComponents(
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 @@ Future<_ApkComponents> _findApkComponents(
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 @@ int _buildApk(
) {
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);
......
......@@ -7,6 +7,7 @@ import 'dart:convert';
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';
......
......@@ -32,7 +32,7 @@ class RefreshCommand extends FlutterCommand {
downloadApplicationPackagesAndConnectToDevices(),
], eagerError: true);
if (!devices.android.isConnected()) {
if (devices.android == null || !devices.android.isConnected()) {
printError('No device connected.');
return 1;
}
......
......@@ -29,7 +29,7 @@ class TraceCommand extends FlutterCommand {
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;
}
......
......@@ -402,6 +402,8 @@ class _IOSDeviceLogReader extends DeviceLogReader {
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] ',
......
......@@ -9,7 +9,9 @@ import 'package:args/args.dart';
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 @@ class FlutterCommandRunner extends CommandRunner {
// 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']));
......
......@@ -10,6 +10,7 @@ import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
import 'artifacts.dart';
import 'base/globals.dart';
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,
/// 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 @@ Future parseServiceConfigs(
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);
......
......@@ -15,6 +15,7 @@ dependencies:
den_api: ^0.1.0
mustache4dart: ^1.0.0
path: ^1.3.0
pub_semver: ^1.0.0
stack_trace: ^1.4.0
test: 0.12.6+1 # see note below
yaml: ^2.1.3
......
......@@ -5,7 +5,7 @@
import 'package:flutter_tools/src/android/device_android.dart';
import 'package:test/test.dart';
import 'src/test_context.dart';
import 'src/context.dart';
main() => defineTests();
......
......@@ -11,7 +11,7 @@ import 'package:flutter_tools/src/commands/create.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'src/test_context.dart';
import 'src/context.dart';
main() => defineTests();
......
......@@ -16,11 +16,17 @@ import 'src/mocks.dart';
main() => defineTests();
defineTests() {
group('daemon', () {
Daemon daemon;
AppContext appContext;
NotifyingLogger notifyingLogger;
Daemon daemon;
AppContext appContext;
NotifyingLogger notifyingLogger;
void _testUsingContext(String description, dynamic testMethod()) {
test(description, () {
return appContext.runInZone(testMethod);
});
}
group('daemon', () {
setUp(() {
appContext = new AppContext();
notifyingLogger = new NotifyingLogger();
......@@ -32,7 +38,7 @@ defineTests() {
return daemon.shutdown();
});
test('daemon.version', () async {
_testUsingContext('daemon.version', () async {
StreamController<Map<String, dynamic>> commands = new StreamController();
StreamController<Map<String, dynamic>> responses = new StreamController();
daemon = new Daemon(
......@@ -47,7 +53,7 @@ defineTests() {
expect(response['result'] is String, true);
});
test('daemon.logMessage', () {
_testUsingContext('daemon.logMessage', () {
return appContext.runInZone(() async {
StreamController<Map<String, dynamic>> commands = 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>> responses = new StreamController();
daemon = new Daemon(
......@@ -82,7 +88,7 @@ defineTests() {
});
});
test('daemon.stopAll', () async {
_testUsingContext('daemon.stopAll', () async {
DaemonCommand command = new DaemonCommand();
applyMocksToCommand(command);
......@@ -112,7 +118,7 @@ defineTests() {
expect(response['result'], true);
});
test('device.getDevices', () async {
_testUsingContext('device.getDevices', () async {
StreamController<Map<String, dynamic>> commands = new StreamController();
StreamController<Map<String, dynamic>> responses = new StreamController();
daemon = new Daemon(
......
......@@ -5,7 +5,7 @@
import 'package:flutter_tools/src/device.dart';
import 'package:test/test.dart';
import 'src/test_context.dart';
import 'src/context.dart';
main() => defineTests();
......
......@@ -2,18 +2,19 @@
// 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/commands/install.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'src/common.dart';
import 'src/context.dart';
import 'src/mocks.dart';
main() => defineTests();
defineTests() {
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();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
......@@ -30,13 +31,12 @@ defineTests() {
when(mockDevices.iOSSimulator.isAppInstalled(any)).thenReturn(false);
when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
return runner.run(['install']).then((int code) => expect(code, equals(0)));
return createTestCommandRunner(command).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();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
......@@ -53,9 +53,9 @@ defineTests() {
when(mockDevices.iOSSimulator.isAppInstalled(any)).thenReturn(false);
when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
return runner.run(['install']).then((int code) => expect(code, equals(0)));
return createTestCommandRunner(command).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
// found in the LICENSE file.
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/android/android_sdk.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 'src/mocks.dart';
import 'src/test_context.dart';
import 'src/common.dart';
import 'src/context.dart';
main() => defineTests();
defineTests() {
group('list', () {
testUsingContext('returns 0 when called', () {
final String mockCommand = Platform.isWindows ? 'cmd /c echo' : 'echo';
ListCommand command = new ListCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
// Avoid relying on adb being installed on the test system.
// Instead, cause the test to run the echo command.
when(mockDevices.android.adbPath).thenReturn(mockCommand);
// Avoid relying on idevice* being installed on the test system.
// Instead, cause the test to run the echo command.
when(mockDevices.iOS.informerPath).thenReturn(mockCommand);
when(mockDevices.iOS.installerPath).thenReturn(mockCommand);
when(mockDevices.iOS.listerPath).thenReturn(mockCommand);
// 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);
return createTestCommandRunner(command).run(['list']).then((int code) {
expect(code, equals(0));
});
});
CommandRunner runner = new CommandRunner('test_flutter', '')..addCommand(command);
return runner.run(['list']).then((int code) => expect(code, equals(0)));
testUsingContext('no error when no connected devices', () {
ListCommand command = new ListCommand();
return createTestCommandRunner(command).run(['list']).then((int code) {
expect(code, equals(0));
expect(testLogger.statusText, contains('No connected devices'));
});
}, overrides: <Type, dynamic>{
AndroidSdk: null,
DeviceManager: new DeviceManager()
});
});
}
......@@ -2,14 +2,13 @@
// 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/commands/listen.dart';
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'src/common.dart';
import 'src/context.dart';
import 'src/mocks.dart';
import 'src/test_context.dart';
main() => defineTests();
......@@ -24,8 +23,9 @@ defineTests() {
when(mockDevices.iOS.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
CommandRunner runner = new FlutterCommandRunner()..addCommand(command);
return runner.run(['listen']).then((int code) => expect(code, equals(0)));
return createTestCommandRunner(command).run(['listen']).then((int code) {
expect(code, equals(0));
});
});
});
}
......@@ -2,13 +2,12 @@
// 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/commands/logs.dart';
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
import 'package:test/test.dart';
import 'src/common.dart';
import 'src/context.dart';
import 'src/mocks.dart';
import 'src/test_context.dart';
main() => defineTests();
......@@ -17,8 +16,7 @@ defineTests() {
testUsingContext('fail with a bad device id', () {
LogsCommand command = new LogsCommand();
applyMocksToCommand(command);
CommandRunner runner = new FlutterCommandRunner()..addCommand(command);
return runner.run(<String>['-d', 'abc123', 'logs']).then((int code) {
return createTestCommandRunner(command).run(<String>['-d', 'abc123', 'logs']).then((int code) {
expect(code, equals(1));
});
});
......
// 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: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, () {
AppContext testContext = new AppContext();
testContext[Logger] = new BufferLogger();
testContext[DeviceManager] = new MockDeviceManager();
overrides.forEach((Type type, dynamic value) {
testContext[type] = value;
});
if (!overrides.containsKey(Logger))
testContext[Logger] = new BufferLogger();
if (!overrides.containsKey(DeviceManager))
testContext[DeviceManager] = new MockDeviceManager();
return testContext.runInZone(testMethod);
}, timeout: timeout);
......
......@@ -2,18 +2,19 @@
// 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/commands/stop.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'src/common.dart';
import 'src/context.dart';
import 'src/mocks.dart';
main() => defineTests();
defineTests() {
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();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
......@@ -27,12 +28,12 @@ defineTests() {
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
return runner.run(['stop']).then((int code) => expect(code, equals(0)));
return createTestCommandRunner(command).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();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
......@@ -46,9 +47,9 @@ defineTests() {
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
return runner.run(['stop']).then((int code) => expect(code, equals(0)));
return createTestCommandRunner(command).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
// found in the LICENSE file.
import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/commands/trace.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'src/common.dart';
import 'src/context.dart';
import 'src/mocks.dart';
import 'src/test_context.dart';
main() => defineTests();
......@@ -21,9 +21,9 @@ defineTests() {
when(mockDevices.android.isConnected()).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
return runner.run(['trace']).then((int code) => expect(code, equals(1)));
return createTestCommandRunner(command).run(['trace']).then((int code) {
expect(code, equals(1));
});
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment