Commit bdd20661 authored by Adam Barth's avatar Adam Barth

Teach sky_tools about prebuilt artifacts

This patch makes `flutter start` work without a clone of the engine git
repository. Making this work pulled a relatively large refactor of how the
commands interact with application packages and devices. Now commands that want
to interact with application packages or devices inherit from a common base
class that holds stores of those objects as members.

In production, the commands download and connect to devices based on the build
configuration stored on the FlutterCommandRunner. In testing, these fields are
used to mock out the real application package and devices.
parent a6a3f212
...@@ -2,14 +2,9 @@ ...@@ -2,14 +2,9 @@
// 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:async';
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'src/application_package.dart'; import 'src/commands/flutter_command_runner.dart';
import 'src/artifacts.dart';
import 'src/commands/build.dart'; import 'src/commands/build.dart';
import 'src/commands/cache.dart'; import 'src/commands/cache.dart';
import 'src/commands/init.dart'; import 'src/commands/init.dart';
...@@ -22,136 +17,20 @@ import 'src/commands/start.dart'; ...@@ -22,136 +17,20 @@ import 'src/commands/start.dart';
import 'src/commands/stop.dart'; import 'src/commands/stop.dart';
import 'src/commands/trace.dart'; import 'src/commands/trace.dart';
class _FlutterCommandRunner extends CommandRunner {
_FlutterCommandRunner()
: super('flutter', 'Manage your Flutter app development.') {
argParser.addFlag('verbose',
abbr: 'v',
negatable: false,
help: 'Noisy logging, including all shell commands executed.');
argParser.addFlag('very-verbose',
negatable: false,
help: 'Very noisy logging, including the output of all '
'shell commands executed.');
argParser.addSeparator('Global build selection options:');
argParser.addFlag('debug',
negatable: false,
help:
'Set this if you are building Sky locally and want to use the debug build products. '
'When set, attempts to automaticaly determine sky-src-path if sky-src-path is '
'not set. Not normally required.');
argParser.addFlag('release',
negatable: false,
help:
'Set this if you are building Sky locally and want to use the release build products. '
'When set, attempts to automaticaly determine sky-src-path if sky-src-path is '
'not set. Note that release is not compatible with the listen command '
'on iOS devices and simulators. Not normally required.');
argParser.addOption('sky-src-path',
help:
'Path to your Sky src directory, if you are building Sky locally. '
'Ignored if neither debug nor release is set. Not normally required.');
argParser.addOption('android-debug-build-path',
help:
'Path to your Android Debug out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/android_Debug/');
argParser.addOption('android-release-build-path',
help:
'Path to your Android Release out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/android_Release/');
argParser.addOption('ios-debug-build-path',
help:
'Path to your iOS Debug out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/ios_Debug/');
argParser.addOption('ios-release-build-path',
help:
'Path to your iOS Release out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/ios_Release/');
argParser.addOption('ios-sim-debug-build-path',
help:
'Path to your iOS Simulator Debug out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/ios_sim_Debug/');
argParser.addOption('ios-sim-release-build-path',
help:
'Path to your iOS Simulator Release out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/ios_sim_Release/');
argParser.addOption('package-root',
help: 'Path to your packages directory.', defaultsTo: 'packages');
}
Future<int> runCommand(ArgResults topLevelResults) async {
if (topLevelResults['verbose']) {
Logger.root.level = Level.INFO;
}
if (topLevelResults['very-verbose']) {
Logger.root.level = Level.FINE;
}
_setupPaths(topLevelResults);
return super.runCommand(topLevelResults);
}
void _setupPaths(ArgResults results) {
ArtifactStore.packageRoot = results['package-root'];
if (results['debug'] || results['release']) {
if (results['sky-src-path'] == null) {
// TODO(iansf): Figure out how to get the default src path
assert(false);
}
ApplicationPackageFactory.srcPath = results['sky-src-path'];
} else {
assert(false);
// TODO(iansf): set paths up for commands using PREBUILT binaries
// ApplicationPackageFactory.setBuildPath(BuildType.PREBUILT,
// BuildPlatform.android, results['android-debug-build-path']);
}
if (results['debug']) {
ApplicationPackageFactory.defaultBuildType = BuildType.debug;
ApplicationPackageFactory.setBuildPath(BuildType.debug,
BuildPlatform.android, results['android-debug-build-path']);
ApplicationPackageFactory.setBuildPath(
BuildType.debug, BuildPlatform.iOS, results['ios-debug-build-path']);
ApplicationPackageFactory.setBuildPath(BuildType.debug,
BuildPlatform.iOSSimulator, results['ios-sim-debug-build-path']);
}
if (results['release']) {
ApplicationPackageFactory.defaultBuildType = BuildType.release;
ApplicationPackageFactory.setBuildPath(BuildType.release,
BuildPlatform.android, results['android-release-build-path']);
ApplicationPackageFactory.setBuildPath(BuildType.release,
BuildPlatform.iOS, results['ios-release-build-path']);
ApplicationPackageFactory.setBuildPath(BuildType.release,
BuildPlatform.iOSSimulator, results['ios-sim-release-build-path']);
}
}
}
/// Main entry point for commands. /// Main entry point for commands.
/// ///
/// This function is intended to be used from the [flutter] command line tool. /// This function is intended to be used from the [flutter] command line tool.
void main(List<String> args) { void main(List<String> args) {
Logger.root.level = Level.WARNING; Logger.root.level = Level.WARNING;
Logger.root.onRecord.listen((LogRecord rec) { Logger.root.onRecord.listen((LogRecord record) {
print('${rec.level.name}: ${rec.message}'); print('${record.level.name}: ${record.message}');
if (rec.error != null) { if (record.error != null)
print(rec.error); print(record.error);
} if (record.stackTrace != null)
if (rec.stackTrace != null) { print(record.stackTrace);
print(rec.stackTrace);
}
}); });
new _FlutterCommandRunner() new FlutterCommandRunner()
..addCommand(new BuildCommand()) ..addCommand(new BuildCommand())
..addCommand(new CacheCommand()) ..addCommand(new CacheCommand())
..addCommand(new InitCommand()) ..addCommand(new InitCommand())
......
...@@ -2,137 +2,119 @@ ...@@ -2,137 +2,119 @@
// 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.
library sky_tools.application_package; import 'dart:async';
import 'dart:io';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'artifacts.dart';
import 'build_configuration.dart';
final Logger _logging = new Logger('sky_tools.application_package'); final Logger _logging = new Logger('sky_tools.application_package');
abstract class ApplicationPackage { abstract class ApplicationPackage {
/// Path to the directory the apk or bundle lives in.
String appDir;
/// Path to the actual apk or bundle. /// Path to the actual apk or bundle.
String get appPath => path.join(appDir, appFileName); final String localPath;
/// Package ID from the Android Manifest or equivalent. /// Package ID from the Android Manifest or equivalent.
String appPackageID; final String id;
/// File name of the apk or bundle. /// File name of the apk or bundle.
String appFileName; final String name;
ApplicationPackage(this.appDir, this.appPackageID, this.appFileName); ApplicationPackage({
String localPath,
this.id
}) : localPath = localPath, name = path.basename(localPath) {
assert(localPath != null);
assert(id != null);
}
} }
class AndroidApk extends ApplicationPackage { class AndroidApk extends ApplicationPackage {
static const String _apkName = 'SkyShell.apk'; static const String _defaultName = 'SkyShell.apk';
static const String _packageID = 'org.domokit.sky.shell'; static const String _defaultId = 'org.domokit.sky.shell';
static const String _componentID = '$_packageID/$_packageID.SkyActivity'; static const String _defaultLaunchActivity = '$_defaultId/$_defaultId.SkyActivity';
/// The path to the activity that should be launched. /// The path to the activity that should be launched.
/// Defaults to 'org.domokit.sky.shell/org.domokit.sky.shell.SkyActivity' /// Defaults to 'org.domokit.sky.shell/org.domokit.sky.shell.SkyActivity'
String component; final String launchActivity;
AndroidApk(String appDir,
{String appPackageID: _packageID, AndroidApk({
String appFileName: _apkName, String localPath,
this.component: _componentID}) String id: _defaultId,
: super(path.join(appDir, 'apks'), appPackageID, appFileName); this.launchActivity: _defaultLaunchActivity
}) : super(localPath: localPath, id: id) {
assert(launchActivity != null);
}
} }
class IOSApp extends ApplicationPackage { class IOSApp extends ApplicationPackage {
static const String _appName = 'SkyShell.app'; static const String _defaultName = 'SkyShell.app';
static const String _packageID = 'com.google.SkyShell'; static const String _defaultId = 'com.google.SkyShell';
IOSApp(String appDir, IOSApp({
{String appPackageID: _packageID, String appFileName: _appName}) String localPath,
: super(appDir, appPackageID, appFileName); String id: _defaultId
}) : super(localPath: localPath, id: id);
} }
enum BuildType { prebuilt, release, debug, } class ApplicationPackageStore {
final AndroidApk android;
enum BuildPlatform { android, iOS, iOSSimulator, mac, linux, } final IOSApp iOS;
final IOSApp iOSSimulator;
class ApplicationPackageFactory {
static final Map<BuildPlatform, Map<BuildType, String>> _buildPaths = ApplicationPackageStore({ this.android, this.iOS, this.iOSSimulator });
_initBuildPaths();
ApplicationPackage getPackageForPlatform(BuildPlatform platform) {
/// Path to your Sky src directory, if you are building Sky locally. switch (platform) {
/// Required if you are requesting release or debug BuildTypes. case BuildPlatform.android:
static String _srcPath = null; return android;
static String get srcPath => _srcPath; case BuildPlatform.iOS:
static void set srcPath(String newPath) { return iOS;
_srcPath = path.normalize(newPath); case BuildPlatform.iOSSimulator:
return iOSSimulator;
case BuildPlatform.mac:
case BuildPlatform.linux:
return null;
}
} }
/// Default BuildType chosen if no BuildType is specified. static Future<ApplicationPackageStore> forConfigs(List<BuildConfiguration> configs) async {
static BuildType defaultBuildType = BuildType.prebuilt; AndroidApk android;
IOSApp iOS;
/// Default BuildPlatforms chosen if no BuildPlatforms are specified. IOSApp iOSSimulator;
static List<BuildPlatform> defaultBuildPlatforms = [
BuildPlatform.android,
BuildPlatform.iOS,
BuildPlatform.iOSSimulator,
];
static Map<BuildPlatform, ApplicationPackage> getAvailableApplicationPackages(
{BuildType requestedType, List<BuildPlatform> requestedPlatforms}) {
if (requestedType == null) {
requestedType = defaultBuildType;
}
if (requestedPlatforms == null) {
requestedPlatforms = defaultBuildPlatforms;
}
Map<BuildPlatform, ApplicationPackage> packages = {}; for (BuildConfiguration config in configs) {
for (BuildPlatform platform in requestedPlatforms) { switch (config.platform) {
String buildPath = _getBuildPath(requestedType, platform);
switch (platform) {
case BuildPlatform.android: case BuildPlatform.android:
packages[platform] = new AndroidApk(buildPath); assert(android == null);
String localPath = config.type == BuildType.prebuilt ?
await ArtifactStore.getPath(Artifact.flutterShell) :
path.join(config.buildDir, 'apks', AndroidApk._defaultName);
android = new AndroidApk(localPath: localPath);
break; break;
case BuildPlatform.iOS: case BuildPlatform.iOS:
packages[platform] = new IOSApp(buildPath); assert(iOS == null);
assert(config.type != BuildType.prebuilt);
iOS = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
break; break;
case BuildPlatform.iOSSimulator: case BuildPlatform.iOSSimulator:
packages[platform] = new IOSApp(buildPath); assert(iOSSimulator == null);
assert(config.type != BuildType.prebuilt);
iOSSimulator = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
break; break;
default:
// TODO(iansf): Add other platforms case BuildPlatform.mac:
case BuildPlatform.linux:
// TODO(abarth): Support mac and linux targets.
assert(false); assert(false);
break;
} }
} }
return packages;
}
static Map<BuildPlatform, Map<BuildType, String>> _initBuildPaths() { return new ApplicationPackageStore(android: android, iOS: iOS, iOSSimulator: iOSSimulator);
Map<BuildPlatform, Map<BuildType, String>> buildPaths = {};
for (BuildPlatform platform in BuildPlatform.values) {
buildPaths[platform] = {};
}
return buildPaths;
}
static String _getBuildPath(BuildType type, BuildPlatform platform) {
String path = _buildPaths[platform][type];
// You must set paths before getting them
assert(path != null);
return path;
}
static void setBuildPath(
BuildType type, BuildPlatform platform, String buildPath) {
// You must set srcPath before attempting to set a BuildPath for
// non prebuilt ApplicationPackages.
assert(type != BuildType.prebuilt || srcPath != null);
if (type != BuildType.prebuilt) {
buildPath = path.join(srcPath, buildPath);
}
if (!FileSystemEntity.isDirectorySync(buildPath)) {
_logging.warning('$buildPath is not a valid directory');
}
_buildPaths[platform][type] = path.normalize(buildPath);
} }
} }
...@@ -12,7 +12,11 @@ import 'package:path/path.dart' as path; ...@@ -12,7 +12,11 @@ import 'package:path/path.dart' as path;
final Logger _logging = new Logger('sky_tools.artifacts'); final Logger _logging = new Logger('sky_tools.artifacts');
enum Artifact { FlutterCompiler, SkyViewerMojo, } enum Artifact {
flutterCompiler,
flutterShell,
skyViewerMojo,
}
class ArtifactStore { class ArtifactStore {
static String packageRoot; static String packageRoot;
...@@ -66,20 +70,27 @@ class ArtifactStore { ...@@ -66,20 +70,27 @@ class ArtifactStore {
// Whether the artifact needs to be marked as executable on disk. // Whether the artifact needs to be marked as executable on disk.
static bool _needsToBeExecutable(Artifact artifact) { static bool _needsToBeExecutable(Artifact artifact) {
return artifact == Artifact.FlutterCompiler; return artifact == Artifact.flutterCompiler;
} }
static Future<String> getPath(Artifact artifact) async { static Future<String> getPath(Artifact artifact) async {
Directory cacheDir = await _engineSpecificCacheDir(); Directory cacheDir = await _engineSpecificCacheDir();
String category, name; String category;
String platform;
String name;
switch (artifact) { switch (artifact) {
case Artifact.FlutterCompiler: case Artifact.flutterCompiler:
category = 'shell'; category = 'shell';
name = 'sky_snapshot'; name = 'sky_snapshot';
break; break;
case Artifact.SkyViewerMojo: case Artifact.flutterShell:
category = 'shell';
platform = 'android-arm';
name = 'SkyShell.apk';
break;
case Artifact.skyViewerMojo:
category = 'viewer'; category = 'viewer';
name = 'sky_viewer.mojo'; name = 'sky_viewer.mojo';
break; break;
...@@ -88,9 +99,12 @@ class ArtifactStore { ...@@ -88,9 +99,12 @@ class ArtifactStore {
File cachedFile = new File(path.join(cacheDir.path, name)); File cachedFile = new File(path.join(cacheDir.path, name));
if (!await cachedFile.exists()) { if (!await cachedFile.exists()) {
_logging.info('Downloading ${name} from the cloud, one moment please...'); _logging.info('Downloading ${name} from the cloud, one moment please...');
if (!Platform.isLinux) if (platform == null) {
throw new Exception('Platform unsupported.'); if (!Platform.isLinux)
String url = googleStorageUrl(category, 'linux-x64') + name; throw new Exception('Platform unsupported.');
platform = 'linux-x64';
}
String url = googleStorageUrl(category, platform) + name;
await _downloadFile(url, cachedFile); await _downloadFile(url, cachedFile);
if (_needsToBeExecutable(artifact)) { if (_needsToBeExecutable(artifact)) {
ProcessResult result = await Process.run('chmod', ['u+x', cachedFile.path]); ProcessResult result = await Process.run('chmod', ['u+x', cachedFile.path]);
......
// Copyright 2015 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:path/path.dart' as path;
enum BuildType {
prebuilt,
release,
debug,
}
enum BuildPlatform {
android,
iOS,
iOSSimulator,
mac,
linux,
}
class BuildConfiguration {
BuildConfiguration.prebuilt({ this.platform })
: type = BuildType.prebuilt, buildDir = null;
BuildConfiguration.local({
this.type,
this.platform,
String enginePath,
String buildPath
}) : buildDir = path.normalize(path.join(enginePath, buildPath)) {
assert(type == BuildType.debug || type == BuildType.release);
}
final BuildType type;
final BuildPlatform platform;
final String buildDir;
}
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// 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.
library sky_tools.build;
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
...@@ -105,7 +103,7 @@ Future _compileSnapshot({ ...@@ -105,7 +103,7 @@ Future _compileSnapshot({
String snapshotPath String snapshotPath
}) async { }) async {
if (compilerPath == null) { if (compilerPath == null) {
compilerPath = await ArtifactStore.getPath(Artifact.FlutterCompiler); compilerPath = await ArtifactStore.getPath(Artifact.flutterCompiler);
} }
ProcessResult result = await Process.run(compilerPath, [ ProcessResult result = await Process.run(compilerPath, [
mainPath, mainPath,
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// 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.
library sky_tools.cache;
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
...@@ -14,8 +12,8 @@ import '../artifacts.dart'; ...@@ -14,8 +12,8 @@ import '../artifacts.dart';
final Logger _logging = new Logger('sky_tools.cache'); final Logger _logging = new Logger('sky_tools.cache');
class CacheCommand extends Command { class CacheCommand extends Command {
final name = 'cache'; final String name = 'cache';
final description = 'Manages sky_tools\' cache of binary artifacts.'; final String description = 'Manages sky_tools\' cache of binary artifacts.';
CacheCommand() { CacheCommand() {
addSubcommand(new _ClearCommand()); addSubcommand(new _ClearCommand());
addSubcommand(new _PopulateCommand()); addSubcommand(new _PopulateCommand());
...@@ -23,8 +21,8 @@ class CacheCommand extends Command { ...@@ -23,8 +21,8 @@ class CacheCommand extends Command {
} }
class _ClearCommand extends Command { class _ClearCommand extends Command {
final name = 'clear'; final String name = 'clear';
final description = 'Clears all artifacts from the cache.'; final String description = 'Clears all artifacts from the cache.';
@override @override
Future<int> run() async { Future<int> run() async {
...@@ -34,8 +32,8 @@ class _ClearCommand extends Command { ...@@ -34,8 +32,8 @@ class _ClearCommand extends Command {
} }
class _PopulateCommand extends Command { class _PopulateCommand extends Command {
final name = 'populate'; final String name = 'populate';
final description = 'Populates the cache with all known artifacts.'; final String description = 'Populates the cache with all known artifacts.';
@override @override
Future<int> run() async { Future<int> run() async {
......
// Copyright 2015 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:async';
import 'package:args/command_runner.dart';
import '../application_package.dart';
import '../device.dart';
import 'flutter_command_runner.dart';
abstract class FlutterCommand extends Command {
FlutterCommandRunner get runner => super.runner;
Future downloadApplicationPackages() async {
if (applicationPackages == null)
applicationPackages = await ApplicationPackageStore.forConfigs(runner.buildConfigurations);
}
void connectToDevices() {
if (devices == null)
devices = new DeviceStore.forConfigs(runner.buildConfigurations);
}
Future downloadApplicationPackagesAndConnectToDevices() async {
await downloadApplicationPackages();
connectToDevices();
}
void inheritFromParent(FlutterCommand other) {
applicationPackages = other.applicationPackages;
devices = other.devices;
}
ApplicationPackageStore applicationPackages;
DeviceStore devices;
}
// Copyright 2015 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:async';
import 'dart:io';
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
import '../artifacts.dart';
import '../build_configuration.dart';
final Logger _logging = new Logger('sky_tools.flutter_command_runner');
class FlutterCommandRunner extends CommandRunner {
FlutterCommandRunner()
: super('flutter', 'Manage your Flutter app development.') {
argParser.addFlag('verbose',
abbr: 'v',
negatable: false,
help: 'Noisy logging, including all shell commands executed.');
argParser.addFlag('very-verbose',
negatable: false,
help: 'Very noisy logging, including the output of all '
'shell commands executed.');
argParser.addSeparator('Global build selection options:');
argParser.addFlag('debug',
negatable: false,
help:
'Set this if you are building Sky locally and want to use the debug build products. '
'When set, attempts to automaticaly determine sky-src-path if sky-src-path is '
'not set. Not normally required.');
argParser.addFlag('release',
negatable: false,
help:
'Set this if you are building Sky locally and want to use the release build products. '
'When set, attempts to automaticaly determine sky-src-path if sky-src-path is '
'not set. Note that release is not compatible with the listen command '
'on iOS devices and simulators. Not normally required.');
argParser.addOption('sky-src-path',
help:
'Path to your Sky src directory, if you are building Sky locally. '
'Ignored if neither debug nor release is set. Not normally required.');
argParser.addOption('android-debug-build-path',
help:
'Path to your Android Debug out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/android_Debug/');
argParser.addOption('android-release-build-path',
help:
'Path to your Android Release out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/android_Release/');
argParser.addOption('ios-debug-build-path',
help:
'Path to your iOS Debug out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/ios_Debug/');
argParser.addOption('ios-release-build-path',
help:
'Path to your iOS Release out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/ios_Release/');
argParser.addOption('ios-sim-debug-build-path',
help:
'Path to your iOS Simulator Debug out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/ios_sim_Debug/');
argParser.addOption('ios-sim-release-build-path',
help:
'Path to your iOS Simulator Release out directory, if you are building Sky locally. '
'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/ios_sim_Release/');
argParser.addOption('package-root',
help: 'Path to your packages directory.', defaultsTo: 'packages');
}
List<BuildConfiguration> buildConfigurations;
Future<int> runCommand(ArgResults globalResults) async {
if (globalResults['verbose'])
Logger.root.level = Level.INFO;
if (globalResults['very-verbose'])
Logger.root.level = Level.FINE;
ArtifactStore.packageRoot = globalResults['package-root'];
buildConfigurations = _createBuildConfigurations(globalResults);
return super.runCommand(globalResults);
}
List<BuildConfiguration> _createBuildConfigurations(ArgResults globalResults) {
// TODO(iansf): Figure out how to get the default src path
String enginePath = globalResults['sky-src-path'];
List<BuildConfiguration> configs = <BuildConfiguration>[];
if (enginePath == null) {
configs.add(new BuildConfiguration.prebuilt(platform: BuildPlatform.android));
} else {
if (!FileSystemEntity.isDirectorySync(enginePath))
_logging.warning('$enginePath is not a valid directory');
if (globalResults['debug']) {
configs.add(new BuildConfiguration.local(
type: BuildType.debug,
platform: BuildPlatform.android,
enginePath: enginePath,
buildPath: globalResults['android-debug-build-path']
));
if (Platform.isMacOS) {
configs.add(new BuildConfiguration.local(
type: BuildType.debug,
platform: BuildPlatform.iOS,
enginePath: enginePath,
buildPath: globalResults['ios-debug-build-path']
));
configs.add(new BuildConfiguration.local(
type: BuildType.debug,
platform: BuildPlatform.iOSSimulator,
enginePath: enginePath,
buildPath: globalResults['ios-sim-debug-build-path']
));
}
}
if (globalResults['release']) {
configs.add(new BuildConfiguration.local(
type: BuildType.release,
platform: BuildPlatform.android,
enginePath: enginePath,
buildPath: globalResults['android-release-build-path']
));
if (Platform.isMacOS) {
configs.add(new BuildConfiguration.local(
type: BuildType.release,
platform: BuildPlatform.iOS,
enginePath: enginePath,
buildPath: globalResults['ios-release-build-path']
));
configs.add(new BuildConfiguration.local(
type: BuildType.release,
platform: BuildPlatform.iOSSimulator,
enginePath: enginePath,
buildPath: globalResults['ios-sim-release-build-path']
));
}
}
}
return configs;
}
}
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// 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.
library sky_tools.init;
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
...@@ -12,8 +10,8 @@ import 'package:mustache4dart/mustache4dart.dart' as mustache; ...@@ -12,8 +10,8 @@ import 'package:mustache4dart/mustache4dart.dart' as mustache;
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
class InitCommand extends Command { class InitCommand extends Command {
final name = 'init'; final String name = 'init';
final description = 'Create a new Flutter project.'; final String description = 'Create a new Flutter project.';
InitCommand() { InitCommand() {
argParser.addOption('out', abbr: 'o', help: 'The output directory.'); argParser.addOption('out', abbr: 'o', help: 'The output directory.');
......
...@@ -2,70 +2,39 @@ ...@@ -2,70 +2,39 @@
// 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.
library sky_tools.install;
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../device.dart'; import '../device.dart';
import 'flutter_command.dart';
class InstallCommand extends Command { class InstallCommand extends FlutterCommand {
final name = 'install'; final String name = 'install';
final description = 'Install your Flutter app on attached devices.'; final String description = 'Install your Flutter app on attached devices.';
AndroidDevice android; InstallCommand() {
IOSDevice ios;
IOSSimulator iosSim;
InstallCommand({this.android, this.ios, this.iosSim}) {
argParser.addFlag('boot', argParser.addFlag('boot',
help: 'Boot the iOS Simulator if it isn\'t already running.'); help: 'Boot the iOS Simulator if it isn\'t already running.');
} }
@override @override
Future<int> run() async { Future<int> run() async {
if (install(argResults['boot'])) { await downloadApplicationPackagesAndConnectToDevices();
return 0; return install(boot: argResults['boot']) ? 0 : 2;
} else {
return 2;
}
} }
bool install([bool boot = false]) { bool install({ bool boot: false }) {
if (android == null) { if (boot)
android = new AndroidDevice(); devices.iOSSimulator?.boot();
}
if (ios == null) {
ios = new IOSDevice();
}
if (iosSim == null) {
iosSim = new IOSSimulator();
}
if (boot) {
iosSim.boot();
}
bool installedSomewhere = false; bool installedSomewhere = false;
Map<BuildPlatform, ApplicationPackage> packages = for (Device device in devices.all) {
ApplicationPackageFactory.getAvailableApplicationPackages(); ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
ApplicationPackage androidApp = packages[BuildPlatform.android]; if (package == null || !device.isConnected())
ApplicationPackage iosApp = packages[BuildPlatform.iOS]; continue;
ApplicationPackage iosSimApp = packages[BuildPlatform.iOSSimulator]; if (device.installApp(package))
installedSomewhere = true;
if (androidApp != null && android.isConnected()) {
installedSomewhere = android.installApp(androidApp) || installedSomewhere;
}
if (iosApp != null && ios.isConnected()) {
installedSomewhere = ios.installApp(iosApp) || installedSomewhere;
}
if (iosSimApp != null && iosSim.isConnected()) {
installedSomewhere = iosSim.installApp(iosSimApp) || installedSomewhere;
} }
return installedSomewhere; return installedSomewhere;
......
...@@ -2,25 +2,20 @@ ...@@ -2,25 +2,20 @@
// 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.
library sky_tools.list;
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'flutter_command.dart';
import '../device.dart'; import '../device.dart';
final Logger _logging = new Logger('sky_tools.list'); final Logger _logging = new Logger('sky_tools.list');
class ListCommand extends Command { class ListCommand extends FlutterCommand {
final name = 'list'; final String name = 'list';
final description = 'List all connected devices.'; final String description = 'List all connected devices.';
AndroidDevice android;
IOSDevice ios;
IOSSimulator iosSim;
ListCommand({this.android, this.ios, this.iosSim}) { ListCommand() {
argParser.addFlag('details', argParser.addFlag('details',
abbr: 'd', abbr: 'd',
negatable: false, negatable: false,
...@@ -29,11 +24,14 @@ class ListCommand extends Command { ...@@ -29,11 +24,14 @@ class ListCommand extends Command {
@override @override
Future<int> run() async { Future<int> run() async {
connectToDevices();
bool details = argResults['details']; bool details = argResults['details'];
if (details) {
if (details)
print('Android Devices:'); print('Android Devices:');
}
for (AndroidDevice device in AndroidDevice.getAttachedDevices(android)) { for (AndroidDevice device in AndroidDevice.getAttachedDevices(devices.android)) {
if (details) { if (details) {
print('${device.id}\t' print('${device.id}\t'
'${device.modelID}\t' '${device.modelID}\t'
...@@ -44,10 +42,10 @@ class ListCommand extends Command { ...@@ -44,10 +42,10 @@ class ListCommand extends Command {
} }
} }
if (details) { if (details)
print('iOS Devices:'); print('iOS Devices:');
}
for (IOSDevice device in IOSDevice.getAttachedDevices(ios)) { for (IOSDevice device in IOSDevice.getAttachedDevices(devices.iOS)) {
if (details) { if (details) {
print('${device.id}\t${device.name}'); print('${device.id}\t${device.name}');
} else { } else {
...@@ -58,7 +56,7 @@ class ListCommand extends Command { ...@@ -58,7 +56,7 @@ class ListCommand extends Command {
if (details) { if (details) {
print('iOS Simulators:'); print('iOS Simulators:');
} }
for (IOSSimulator device in IOSSimulator.getAttachedDevices(iosSim)) { for (IOSSimulator device in IOSSimulator.getAttachedDevices(devices.iOSSimulator)) {
if (details) { if (details) {
print('${device.id}\t${device.name}'); print('${device.id}\t${device.name}');
} else { } else {
......
...@@ -2,45 +2,31 @@ ...@@ -2,45 +2,31 @@
// 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.
library sky_tools.listen;
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'flutter_command.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../device.dart'; import '../device.dart';
import '../process.dart'; import '../process.dart';
final Logger _logging = new Logger('sky_tools.listen'); final Logger _logging = new Logger('sky_tools.listen');
class ListenCommand extends Command { class ListenCommand extends FlutterCommand {
final name = 'listen'; final String name = 'listen';
final description = 'Listen for changes to files and reload the running app ' final String description = 'Listen for changes to files and reload the running app on all connected devices.';
'on all connected devices.';
AndroidDevice android;
IOSDevice ios;
IOSSimulator iosSim;
List<String> watchCommand; List<String> watchCommand;
/// Only run once. Used for testing. /// Only run once. Used for testing.
bool singleRun; bool singleRun;
ListenCommand({this.android, this.ios, this.iosSim, this.singleRun: false}) {} ListenCommand({ this.singleRun: false });
@override @override
Future<int> run() async { Future<int> run() async {
if (android == null) { await downloadApplicationPackagesAndConnectToDevices();
android = new AndroidDevice();
}
if (ios == null) {
ios = new IOSDevice();
}
if (iosSim == null) {
iosSim = new IOSSimulator();
}
if (argResults.rest.length > 0) { if (argResults.rest.length > 0) {
watchCommand = _initWatchCommand(argResults.rest); watchCommand = _initWatchCommand(argResults.rest);
...@@ -48,14 +34,8 @@ class ListenCommand extends Command { ...@@ -48,14 +34,8 @@ class ListenCommand extends Command {
watchCommand = _initWatchCommand(['.']); watchCommand = _initWatchCommand(['.']);
} }
Map<BuildPlatform, ApplicationPackage> packages =
ApplicationPackageFactory.getAvailableApplicationPackages();
ApplicationPackage androidApp = packages[BuildPlatform.android];
ApplicationPackage iosApp = packages[BuildPlatform.iOS];
ApplicationPackage iosSimApp = packages[BuildPlatform.iOSSimulator];
while (true) { while (true) {
_logging.info('Updating running Sky apps...'); _logging.info('Updating running Flutter apps...');
// TODO(iansf): refactor build command so that this doesn't have // TODO(iansf): refactor build command so that this doesn't have
// to call out like this. // to call out like this.
...@@ -76,25 +56,29 @@ class ListenCommand extends Command { ...@@ -76,25 +56,29 @@ class ListenCommand extends Command {
} catch (e) {} } catch (e) {}
runSync(command); runSync(command);
String localFLXPath = 'app.flx'; String localFlutterBundle = 'app.flx';
String remoteFLXPath = 'Documents/app.flx'; String remoteFlutterBundle = 'Documents/app.flx';
if (ios.isConnected()) { for (Device device in devices.all) {
await ios.pushFile(iosApp, localFLXPath, remoteFLXPath); ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
} if (package == null || !device.isConnected())
continue;
if (iosSim.isConnected()) { if (device is AndroidDevice) {
await iosSim.pushFile(iosSimApp, localFLXPath, remoteFLXPath); await devices.android.startServer(
} argResults['target'], true, argResults['checked'], package);
} else if (device is IOSDevice) {
if (android.isConnected()) { device.pushFile(package, localFlutterBundle, remoteFlutterBundle);
await android.startServer( } else if (device is IOSSimulator) {
argResults['target'], true, argResults['checked'], androidApp); // TODO(abarth): Move pushFile up to Device once Android supports
// pushing new bundles.
device.pushFile(package, localFlutterBundle, remoteFlutterBundle);
} else {
assert(false);
}
} }
if (singleRun || !watchDirectory()) { if (singleRun || !watchDirectory())
break; break;
}
} }
return 0; return 0;
...@@ -135,9 +119,8 @@ class ListenCommand extends Command { ...@@ -135,9 +119,8 @@ class ListenCommand extends Command {
} }
bool watchDirectory() { bool watchDirectory() {
if (watchCommand == null) { if (watchCommand == null)
return false; return false;
}
try { try {
runCheckedSync(watchCommand); runCheckedSync(watchCommand);
...@@ -145,6 +128,7 @@ class ListenCommand extends Command { ...@@ -145,6 +128,7 @@ class ListenCommand extends Command {
_logging.warning('Watching directories failed.', e); _logging.warning('Watching directories failed.', e);
return false; return false;
} }
return true; return true;
} }
} }
...@@ -2,25 +2,20 @@ ...@@ -2,25 +2,20 @@
// 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.
library sky_tools.logs;
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'flutter_command.dart';
import '../device.dart'; import '../device.dart';
final Logger _logging = new Logger('sky_tools.logs'); final Logger _logging = new Logger('sky_tools.logs');
class LogsCommand extends Command { class LogsCommand extends FlutterCommand {
final name = 'logs'; final name = 'logs';
final description = 'Show logs for running Sky apps.'; final description = 'Show logs for running Sky apps.';
AndroidDevice android;
IOSDevice ios;
IOSSimulator iosSim;
LogsCommand({this.android, this.ios, this.iosSim}) { LogsCommand() {
argParser.addFlag('clear', argParser.addFlag('clear',
negatable: false, negatable: false,
help: 'Clear log history before reading from logs (Android only).'); help: 'Clear log history before reading from logs (Android only).');
...@@ -28,42 +23,15 @@ class LogsCommand extends Command { ...@@ -28,42 +23,15 @@ class LogsCommand extends Command {
@override @override
Future<int> run() async { Future<int> run() async {
if (android == null) { connectToDevices();
android = new AndroidDevice();
}
if (ios == null) {
ios = new IOSDevice();
}
if (iosSim == null) {
iosSim = new IOSSimulator();
}
Future<int> androidLogProcess = null;
if (android.isConnected()) {
androidLogProcess = android.logs(clear: argResults['clear']);
}
Future<int> iosLogProcess = null;
if (ios.isConnected()) {
iosLogProcess = ios.logs(clear: argResults['clear']);
}
Future<int> iosSimLogProcess = null;
if (iosSim.isConnected()) {
iosSimLogProcess = iosSim.logs(clear: argResults['clear']);
}
if (androidLogProcess != null) { bool clear = argResults['clear'];
await androidLogProcess;
}
if (iosLogProcess != null) { Iterable<Future<int>> results = devices.all.map(
await iosLogProcess; (Device device) => device.logs(clear: clear));
}
if (iosSimLogProcess != null) { for (Future<int> result in results)
await iosSimLogProcess; await result;
}
return 0; return 0;
} }
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// 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.
library sky_tools.run_mojo;
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
...@@ -19,8 +17,9 @@ final Logger _logging = new Logger('sky_tools.run_mojo'); ...@@ -19,8 +17,9 @@ final Logger _logging = new Logger('sky_tools.run_mojo');
enum _MojoConfig { Debug, Release } enum _MojoConfig { Debug, Release }
class RunMojoCommand extends Command { class RunMojoCommand extends Command {
final name = 'run_mojo'; final String name = 'run_mojo';
final description = 'Run a Flutter app in mojo.'; final String description = 'Run a Flutter app in mojo.';
RunMojoCommand() { RunMojoCommand() {
argParser.addFlag('android', negatable: false, help: 'Run on an Android device'); argParser.addFlag('android', negatable: false, help: 'Run on an Android device');
argParser.addFlag('checked', negatable: false, help: 'Run Flutter in checked mode'); argParser.addFlag('checked', negatable: false, help: 'Run Flutter in checked mode');
...@@ -31,6 +30,7 @@ class RunMojoCommand extends Command { ...@@ -31,6 +30,7 @@ class RunMojoCommand extends Command {
argParser.addOption('mojo-path', help: 'Path to directory containing mojo_shell and services'); argParser.addOption('mojo-path', help: 'Path to directory containing mojo_shell and services');
} }
// TODO(abarth): Why not use path.absolute?
Future<String> _makePathAbsolute(String relativePath) async { Future<String> _makePathAbsolute(String relativePath) async {
File file = new File(relativePath); File file = new File(relativePath);
if (!await file.exists()) { if (!await file.exists()) {
...@@ -65,7 +65,7 @@ class RunMojoCommand extends Command { ...@@ -65,7 +65,7 @@ class RunMojoCommand extends Command {
} }
Future<int> _runLinux(String mojoPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) async { Future<int> _runLinux(String mojoPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) async {
String viewerPath = await _makePathAbsolute(await ArtifactStore.getPath(Artifact.SkyViewerMojo)); String viewerPath = await _makePathAbsolute(await ArtifactStore.getPath(Artifact.skyViewerMojo));
String mojoBuildType = mojoConfig == _MojoConfig.Debug ? 'Debug' : 'Release'; String mojoBuildType = mojoConfig == _MojoConfig.Debug ? 'Debug' : 'Release';
String mojoShellPath = await _makePathAbsolute(path.join(mojoPath, 'out', mojoBuildType, 'mojo_shell')); String mojoShellPath = await _makePathAbsolute(path.join(mojoPath, 'out', mojoBuildType, 'mojo_shell'));
List<String> cmd = [ List<String> cmd = [
......
...@@ -2,29 +2,24 @@ ...@@ -2,29 +2,24 @@
// 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.
library sky_tools.start;
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../application_package.dart'; import '../application_package.dart';
import '../device.dart'; import '../device.dart';
import 'flutter_command.dart';
import 'install.dart'; import 'install.dart';
import 'stop.dart'; import 'stop.dart';
final Logger _logging = new Logger('sky_tools.start'); final Logger _logging = new Logger('sky_tools.start');
class StartCommand extends Command { class StartCommand extends FlutterCommand {
final name = 'start'; final String name = 'start';
final description = 'Start your Flutter app on attached devices.'; final String description = 'Start your Flutter app on attached devices.';
AndroidDevice android;
IOSDevice ios;
IOSSimulator iosSim;
StartCommand({this.android, this.ios, this.iosSim}) { StartCommand() {
argParser.addFlag('poke', argParser.addFlag('poke',
negatable: false, negatable: false,
help: 'Restart the connection to the server (Android only).'); help: 'Restart the connection to the server (Android only).');
...@@ -42,53 +37,36 @@ class StartCommand extends Command { ...@@ -42,53 +37,36 @@ class StartCommand extends Command {
@override @override
Future<int> run() async { Future<int> run() async {
if (android == null) { await downloadApplicationPackagesAndConnectToDevices();
android = new AndroidDevice();
}
if (ios == null) {
ios = new IOSDevice();
}
if (iosSim == null) {
iosSim = new IOSSimulator();
}
bool startedSomewhere = false;
bool poke = argResults['poke']; bool poke = argResults['poke'];
if (!poke) { if (!poke) {
StopCommand stopper = new StopCommand(android: android, ios: ios); StopCommand stopper = new StopCommand();
stopper.inheritFromParent(this);
stopper.stop(); stopper.stop();
// Only install if the user did not specify a poke // Only install if the user did not specify a poke
InstallCommand installer = InstallCommand installer = new InstallCommand();
new InstallCommand(android: android, ios: ios, iosSim: iosSim); installer.inheritFromParent(this);
installer.install(argResults['boot']); installer.install(boot: argResults['boot']);
} }
Map<BuildPlatform, ApplicationPackage> packages = bool startedSomething = false;
ApplicationPackageFactory.getAvailableApplicationPackages();
ApplicationPackage androidApp = packages[BuildPlatform.android];
ApplicationPackage iosApp = packages[BuildPlatform.iOS];
ApplicationPackage iosSimApp = packages[BuildPlatform.iOSSimulator];
bool startedOnAndroid = false; for (Device device in devices.all) {
if (androidApp != null && android.isConnected()) { ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
String target = path.absolute(argResults['target']); if (package == null || !device.isConnected())
startedOnAndroid = await android.startServer( continue;
target, poke, argResults['checked'], androidApp); if (device is AndroidDevice) {
String target = path.absolute(argResults['target']);
if (await device.startServer(target, poke, argResults['checked'], package))
startedSomething = true;
} else {
if (await device.startApp(package))
startedSomething = true;
}
} }
if (iosApp != null && ios.isConnected()) { return startedSomething ? 0 : 2;
startedSomewhere = await ios.startApp(iosApp) || startedSomewhere;
}
if (iosSimApp != null && iosSim.isConnected()) {
startedSomewhere = await iosSim.startApp(iosSimApp) || startedSomewhere;
}
if (startedSomewhere || startedOnAndroid) {
return 0;
} else {
return 2;
}
} }
} }
...@@ -2,64 +2,35 @@ ...@@ -2,64 +2,35 @@
// 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.
library sky_tools.stop;
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../device.dart'; import '../device.dart';
import 'flutter_command.dart';
final Logger _logging = new Logger('sky_tools.stop'); final Logger _logging = new Logger('sky_tools.stop');
class StopCommand extends Command { class StopCommand extends FlutterCommand {
final name = 'stop'; final String name = 'stop';
final description = 'Stop your Flutter app on all attached devices.'; final String description = 'Stop your Flutter app on all attached devices.';
AndroidDevice android;
IOSDevice ios;
IOSSimulator iosSim;
StopCommand({this.android, this.ios, this.iosSim});
@override @override
Future<int> run() async { Future<int> run() async {
if (await stop()) { await downloadApplicationPackagesAndConnectToDevices();
return 0; return await stop() ? 0 : 2;
} else {
return 2;
}
} }
Future<bool> stop() async { Future<bool> stop() async {
if (android == null) {
android = new AndroidDevice();
}
if (ios == null) {
ios = new IOSDevice();
}
if (iosSim == null) {
iosSim = new IOSSimulator();
}
bool stoppedSomething = false; bool stoppedSomething = false;
Map<BuildPlatform, ApplicationPackage> packages =
ApplicationPackageFactory.getAvailableApplicationPackages();
if (android.isConnected()) {
ApplicationPackage androidApp = packages[BuildPlatform.android];
stoppedSomething = await android.stopApp(androidApp) || stoppedSomething;
}
if (ios.isConnected()) {
ApplicationPackage iosApp = packages[BuildPlatform.iOS];
stoppedSomething = await ios.stopApp(iosApp) || stoppedSomething;
}
if (iosSim.isConnected()) { for (Device device in devices.all) {
ApplicationPackage iosApp = packages[BuildPlatform.iOSSimulator]; ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
stoppedSomething = await iosSim.stopApp(iosApp) || stoppedSomething; if (package == null || !device.isConnected())
continue;
if (await device.stopApp(package))
stoppedSomething = true;
} }
return stoppedSomething; return stoppedSomething;
......
...@@ -2,28 +2,25 @@ ...@@ -2,28 +2,25 @@
// 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.
library sky_tools.trace;
import 'dart:async'; import 'dart:async';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'flutter_command.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../device.dart'; import '../device.dart';
final Logger _logging = new Logger('sky_tools.trace'); final Logger _logging = new Logger('sky_tools.trace');
class TraceCommand extends Command { class TraceCommand extends FlutterCommand {
final name = 'trace'; final String name = 'trace';
final description = 'Start and stop tracing a running Flutter app ' final String description = 'Start and stop tracing a running Flutter app '
'(Android only, requires root).\n' '(Android only, requires root).\n'
'To start a trace, wait, and then stop the trace, don\'t set any flags ' 'To start a trace, wait, and then stop the trace, don\'t set any flags '
'except (optionally) duration.\n' 'except (optionally) duration.\n'
'Otherwise, specify either start or stop to manually control the trace.'; 'Otherwise, specify either start or stop to manually control the trace.';
AndroidDevice android;
TraceCommand([this.android]) { TraceCommand() {
argParser.addFlag('start', negatable: false, help: 'Start tracing.'); argParser.addFlag('start', negatable: false, help: 'Start tracing.');
argParser.addFlag('stop', negatable: false, help: 'Stop tracing.'); argParser.addFlag('stop', negatable: false, help: 'Stop tracing.');
argParser.addOption('duration', argParser.addOption('duration',
...@@ -32,35 +29,32 @@ class TraceCommand extends Command { ...@@ -32,35 +29,32 @@ class TraceCommand extends Command {
@override @override
Future<int> run() async { Future<int> run() async {
if (android == null) { await downloadApplicationPackagesAndConnectToDevices();
android = new AndroidDevice();
}
if (!android.isConnected()) { if (!devices.android.isConnected()) {
_logging.warning('No device connected, so no trace was completed.'); _logging.warning('No device connected, so no trace was completed.');
return 1; return 1;
} }
Map<BuildPlatform, ApplicationPackage> packages =
ApplicationPackageFactory.getAvailableApplicationPackages(); ApplicationPackage androidApp = applicationPackages.android;
ApplicationPackage androidApp = packages[BuildPlatform.android];
if ((!argResults['start'] && !argResults['stop']) || if ((!argResults['start'] && !argResults['stop']) ||
(argResults['start'] && argResults['stop'])) { (argResults['start'] && argResults['stop'])) {
// Setting neither flags or both flags means do both commands and wait // Setting neither flags or both flags means do both commands and wait
// duration seconds in between. // duration seconds in between.
android.startTracing(androidApp); devices.android.startTracing(androidApp);
await new Future.delayed( await new Future.delayed(
new Duration(seconds: int.parse(argResults['duration'])), new Duration(seconds: int.parse(argResults['duration'])),
() => _stopTracing(androidApp)); () => _stopTracing(devices.android, androidApp));
} else if (argResults['stop']) { } else if (argResults['stop']) {
_stopTracing(androidApp); _stopTracing(devices.android, androidApp);
} else { } else {
android.startTracing(androidApp); devices.android.startTracing(androidApp);
} }
return 0; return 0;
} }
void _stopTracing(AndroidApk androidApp) { void _stopTracing(AndroidDevice android, AndroidApk androidApp) {
String tracePath = android.stopTracing(androidApp); String tracePath = android.stopTracing(androidApp);
if (tracePath == null) { if (tracePath == null) {
_logging.warning('No trace file saved.'); _logging.warning('No trace file saved.');
......
...@@ -12,15 +12,16 @@ import 'package:logging/logging.dart'; ...@@ -12,15 +12,16 @@ import 'package:logging/logging.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'application_package.dart'; import 'application_package.dart';
import 'build_configuration.dart';
import 'process.dart'; import 'process.dart';
final Logger _logging = new Logger('sky_tools.device'); final Logger _logging = new Logger('sky_tools.device');
abstract class _Device { abstract class Device {
final String id; final String id;
static Map<String, _Device> _deviceCache = {}; static Map<String, Device> _deviceCache = {};
factory _Device(String className, [String id = null]) { factory Device._unique(String className, [String id = null]) {
if (id == null) { if (id == null) {
if (className == AndroidDevice.className) { if (className == AndroidDevice.className) {
id = AndroidDevice.defaultDeviceID; id = AndroidDevice.defaultDeviceID;
...@@ -52,7 +53,7 @@ abstract class _Device { ...@@ -52,7 +53,7 @@ abstract class _Device {
}); });
} }
_Device._(this.id); Device._(this.id);
/// Install an app package on the current device /// Install an app package on the current device
bool installApp(ApplicationPackage app); bool installApp(ApplicationPackage app);
...@@ -63,6 +64,10 @@ abstract class _Device { ...@@ -63,6 +64,10 @@ abstract class _Device {
/// Check if the current version of the given app is already installed /// Check if the current version of the given app is already installed
bool isAppInstalled(ApplicationPackage app); bool isAppInstalled(ApplicationPackage app);
BuildPlatform get platform;
Future<int> logs({bool clear: false});
/// Start an app package on the current device /// Start an app package on the current device
Future<bool> startApp(ApplicationPackage app); Future<bool> startApp(ApplicationPackage app);
...@@ -70,7 +75,7 @@ abstract class _Device { ...@@ -70,7 +75,7 @@ abstract class _Device {
Future<bool> stopApp(ApplicationPackage app); Future<bool> stopApp(ApplicationPackage app);
} }
class IOSDevice extends _Device { class IOSDevice extends Device {
static const String className = 'IOSDevice'; static const String className = 'IOSDevice';
static final String defaultDeviceID = 'default_ios_id'; static final String defaultDeviceID = 'default_ios_id';
...@@ -105,7 +110,7 @@ class IOSDevice extends _Device { ...@@ -105,7 +110,7 @@ class IOSDevice extends _Device {
String get name => _name; String get name => _name;
factory IOSDevice({String id, String name}) { factory IOSDevice({String id, String name}) {
IOSDevice device = new _Device(className, id); IOSDevice device = new Device._unique(className, id);
device._name = name; device._name = name;
return device; return device;
} }
...@@ -174,9 +179,9 @@ class IOSDevice extends _Device { ...@@ -174,9 +179,9 @@ class IOSDevice extends _Device {
bool installApp(ApplicationPackage app) { bool installApp(ApplicationPackage app) {
try { try {
if (id == defaultDeviceID) { if (id == defaultDeviceID) {
runCheckedSync([installerPath, '-i', app.appPath]); runCheckedSync([installerPath, '-i', app.localPath]);
} else { } else {
runCheckedSync([installerPath, '-u', id, '-i', app.appPath]); runCheckedSync([installerPath, '-u', id, '-i', app.localPath]);
} }
return true; return true;
} catch (e) { } catch (e) {
...@@ -200,7 +205,7 @@ class IOSDevice extends _Device { ...@@ -200,7 +205,7 @@ class IOSDevice extends _Device {
bool isAppInstalled(ApplicationPackage app) { bool isAppInstalled(ApplicationPackage app) {
try { try {
String apps = runCheckedSync([installerPath, '-l']); String apps = runCheckedSync([installerPath, '-l']);
if (new RegExp(app.appPackageID, multiLine: true).hasMatch(apps)) { if (new RegExp(app.id, multiLine: true).hasMatch(apps)) {
return true; return true;
} }
} catch (e) { } catch (e) {
...@@ -217,7 +222,7 @@ class IOSDevice extends _Device { ...@@ -217,7 +222,7 @@ class IOSDevice extends _Device {
// idevicedebug hangs forever after launching the app, so kill it after // idevicedebug hangs forever after launching the app, so kill it after
// giving it plenty of time to send the launch command. // giving it plenty of time to send the launch command.
return runAndKill( return runAndKill(
[debuggerPath, 'run', app.appPackageID], new Duration(seconds: 3)).then( [debuggerPath, 'run', app.id], new Duration(seconds: 3)).then(
(_) { (_) {
return true; return true;
}, onError: (e) { }, onError: (e) {
...@@ -240,7 +245,7 @@ class IOSDevice extends _Device { ...@@ -240,7 +245,7 @@ class IOSDevice extends _Device {
'-t', '-t',
'1', '1',
'--bundle_id', '--bundle_id',
app.appPackageID, app.id,
'--upload', '--upload',
localFile, localFile,
'--to', '--to',
...@@ -258,6 +263,9 @@ class IOSDevice extends _Device { ...@@ -258,6 +263,9 @@ class IOSDevice extends _Device {
return false; return false;
} }
@override
BuildPlatform get platform => BuildPlatform.iOS;
/// Note that clear is not supported on iOS at this time. /// Note that clear is not supported on iOS at this time.
Future<int> logs({bool clear: false}) async { Future<int> logs({bool clear: false}) async {
if (!isConnected()) { if (!isConnected()) {
...@@ -268,7 +276,7 @@ class IOSDevice extends _Device { ...@@ -268,7 +276,7 @@ class IOSDevice extends _Device {
} }
} }
class IOSSimulator extends _Device { class IOSSimulator extends Device {
static const String className = 'IOSSimulator'; static const String className = 'IOSSimulator';
static final String defaultDeviceID = 'default_ios_sim_id'; static final String defaultDeviceID = 'default_ios_sim_id';
...@@ -288,7 +296,7 @@ class IOSSimulator extends _Device { ...@@ -288,7 +296,7 @@ class IOSSimulator extends _Device {
String get name => _name; String get name => _name;
factory IOSSimulator({String id, String name, String iOSSimulatorPath}) { factory IOSSimulator({String id, String name, String iOSSimulatorPath}) {
IOSSimulator device = new _Device(className, id); IOSSimulator device = new Device._unique(className, id);
device._name = name; device._name = name;
if (iOSSimulatorPath == null) { if (iOSSimulatorPath == null) {
iOSSimulatorPath = path.join('/Applications', 'iOS Simulator.app', iOSSimulatorPath = path.join('/Applications', 'iOS Simulator.app',
...@@ -401,9 +409,9 @@ class IOSSimulator extends _Device { ...@@ -401,9 +409,9 @@ class IOSSimulator extends _Device {
} }
try { try {
if (id == defaultDeviceID) { if (id == defaultDeviceID) {
runCheckedSync([xcrunPath, 'simctl', 'install', 'booted', app.appPath]); runCheckedSync([xcrunPath, 'simctl', 'install', 'booted', app.localPath]);
} else { } else {
runCheckedSync([xcrunPath, 'simctl', 'install', id, app.appPath]); runCheckedSync([xcrunPath, 'simctl', 'install', id, app.localPath]);
} }
return true; return true;
} catch (e) { } catch (e) {
...@@ -444,9 +452,9 @@ class IOSSimulator extends _Device { ...@@ -444,9 +452,9 @@ class IOSSimulator extends _Device {
try { try {
if (id == defaultDeviceID) { if (id == defaultDeviceID) {
runCheckedSync( runCheckedSync(
[xcrunPath, 'simctl', 'launch', 'booted', app.appPackageID]); [xcrunPath, 'simctl', 'launch', 'booted', app.id]);
} else { } else {
runCheckedSync([xcrunPath, 'simctl', 'launch', id, app.appPackageID]); runCheckedSync([xcrunPath, 'simctl', 'launch', id, app.id]);
} }
return true; return true;
} catch (e) { } catch (e) {
...@@ -471,6 +479,9 @@ class IOSSimulator extends _Device { ...@@ -471,6 +479,9 @@ class IOSSimulator extends _Device {
return false; return false;
} }
@override
BuildPlatform get platform => BuildPlatform.iOSSimulator;
Future<int> logs({bool clear: false}) async { Future<int> logs({bool clear: false}) async {
if (!isConnected()) { if (!isConnected()) {
return 2; return 2;
...@@ -487,7 +498,7 @@ class IOSSimulator extends _Device { ...@@ -487,7 +498,7 @@ class IOSSimulator extends _Device {
} }
} }
class AndroidDevice extends _Device { class AndroidDevice extends Device {
static const String _ADB_PATH = 'adb'; static const String _ADB_PATH = 'adb';
static const String _observatoryPort = '8181'; static const String _observatoryPort = '8181';
static const String _serverPort = '9888'; static const String _serverPort = '9888';
...@@ -509,7 +520,7 @@ class AndroidDevice extends _Device { ...@@ -509,7 +520,7 @@ class AndroidDevice extends _Device {
String productID: null, String productID: null,
String modelID: null, String modelID: null,
String deviceCodeName: null}) { String deviceCodeName: null}) {
AndroidDevice device = new _Device(className, id); AndroidDevice device = new Device._unique(className, id);
device.productID = productID; device.productID = productID;
device.modelID = modelID; device.modelID = modelID;
device.deviceCodeName = deviceCodeName; device.deviceCodeName = deviceCodeName;
...@@ -553,15 +564,13 @@ class AndroidDevice extends _Device { ...@@ -553,15 +564,13 @@ class AndroidDevice extends _Device {
_adbPath = _getAdbPath(); _adbPath = _getAdbPath();
_hasAdb = _checkForAdb(); _hasAdb = _checkForAdb();
if (isConnected()) { // Checking for lollipop only needs to be done if we are starting an
// Checking for lollipop only needs to be done if we are starting an // app, but it has an important side effect, which is to discard any
// app, but it has an important side effect, which is to discard any // progress messages if the adb server is restarted.
// progress messages if the adb server is restarted. _hasValidAndroid = _checkForLollipopOrLater();
_hasValidAndroid = _checkForLollipopOrLater();
if (!_hasAdb || !_hasValidAndroid) { if (!_hasAdb || !_hasValidAndroid) {
_logging.severe('Unable to run on Android.'); _logging.severe('Unable to run on Android.');
}
} }
} }
...@@ -663,7 +672,7 @@ class AndroidDevice extends _Device { ...@@ -663,7 +672,7 @@ class AndroidDevice extends _Device {
} }
String _getDeviceSha1Path(ApplicationPackage app) { String _getDeviceSha1Path(ApplicationPackage app) {
return '/sdcard/${app.appPackageID}/${app.appFileName}.sha1'; return '/sdcard/${app.id}/${app.name}.sha1';
} }
String _getDeviceApkSha1(ApplicationPackage app) { String _getDeviceApkSha1(ApplicationPackage app) {
...@@ -672,7 +681,7 @@ class AndroidDevice extends _Device { ...@@ -672,7 +681,7 @@ class AndroidDevice extends _Device {
String _getSourceSha1(ApplicationPackage app) { String _getSourceSha1(ApplicationPackage app) {
String sha1 = String sha1 =
runCheckedSync(['shasum', '-a', '1', '-p', app.appPath]).split(' ')[0]; runCheckedSync(['shasum', '-a', '1', '-p', app.localPath]).split(' ')[0];
return sha1; return sha1;
} }
...@@ -681,15 +690,15 @@ class AndroidDevice extends _Device { ...@@ -681,15 +690,15 @@ class AndroidDevice extends _Device {
if (!isConnected()) { if (!isConnected()) {
return false; return false;
} }
if (runCheckedSync([adbPath, 'shell', 'pm', 'path', app.appPackageID]) == if (runCheckedSync([adbPath, 'shell', 'pm', 'path', app.id]) ==
'') { '') {
_logging.info( _logging.info(
'TODO(iansf): move this log to the caller. ${app.appFileName} is not on the device. Installing now...'); 'TODO(iansf): move this log to the caller. ${app.name} is not on the device. Installing now...');
return false; return false;
} }
if (_getDeviceApkSha1(app) != _getSourceSha1(app)) { if (_getDeviceApkSha1(app) != _getSourceSha1(app)) {
_logging.info( _logging.info(
'TODO(iansf): move this log to the caller. ${app.appFileName} is out of date. Installing now...'); 'TODO(iansf): move this log to the caller. ${app.name} is out of date. Installing now...');
return false; return false;
} }
return true; return true;
...@@ -701,16 +710,16 @@ class AndroidDevice extends _Device { ...@@ -701,16 +710,16 @@ class AndroidDevice extends _Device {
_logging.info('Android device not connected. Not installing.'); _logging.info('Android device not connected. Not installing.');
return false; return false;
} }
if (!FileSystemEntity.isFileSync(app.appPath)) { if (!FileSystemEntity.isFileSync(app.localPath)) {
_logging.severe('"${app.appPath}" does not exist.'); _logging.severe('"${app.localPath}" does not exist.');
return false; return false;
} }
runCheckedSync([adbPath, 'install', '-r', app.appPath]); runCheckedSync([adbPath, 'install', '-r', app.localPath]);
Directory tempDir = Directory.systemTemp; Directory tempDir = Directory.systemTemp;
String sha1Path = path.join( String sha1Path = path.join(
tempDir.path, (app.appPath + '.sha1').replaceAll(path.separator, '_')); tempDir.path, (app.localPath + '.sha1').replaceAll(path.separator, '_'));
File sha1TempFile = new File(sha1Path); File sha1TempFile = new File(sha1Path);
sha1TempFile.writeAsStringSync(_getSourceSha1(app), flush: true); sha1TempFile.writeAsStringSync(_getSourceSha1(app), flush: true);
runCheckedSync([adbPath, 'push', sha1Path, _getDeviceSha1Path(app)]); runCheckedSync([adbPath, 'push', sha1Path, _getDeviceSha1Path(app)]);
...@@ -774,7 +783,7 @@ class AndroidDevice extends _Device { ...@@ -774,7 +783,7 @@ class AndroidDevice extends _Device {
if (checked) { if (checked) {
cmd.addAll(['--ez', 'enable-checked-mode', 'true']); cmd.addAll(['--ez', 'enable-checked-mode', 'true']);
} }
cmd.add(apk.component); cmd.add(apk.launchActivity);
runCheckedSync(cmd); runCheckedSync(cmd);
...@@ -792,7 +801,7 @@ class AndroidDevice extends _Device { ...@@ -792,7 +801,7 @@ class AndroidDevice extends _Device {
// Turn off reverse port forwarding // Turn off reverse port forwarding
runSync([adbPath, 'reverse', '--remove', 'tcp:$_serverPort']); runSync([adbPath, 'reverse', '--remove', 'tcp:$_serverPort']);
// Stop the app // Stop the app
runSync([adbPath, 'shell', 'am', 'force-stop', apk.appPackageID]); runSync([adbPath, 'shell', 'am', 'force-stop', apk.id]);
// Kill the server // Kill the server
if (Platform.isMacOS) { if (Platform.isMacOS) {
String pid = runSync(['lsof', '-i', ':$_serverPort', '-t']); String pid = runSync(['lsof', '-i', ':$_serverPort', '-t']);
...@@ -808,6 +817,9 @@ class AndroidDevice extends _Device { ...@@ -808,6 +817,9 @@ class AndroidDevice extends _Device {
return true; return true;
} }
@override
BuildPlatform get platform => BuildPlatform.android;
void clearLogs() { void clearLogs() {
runSync([adbPath, 'logcat', '-c']); runSync([adbPath, 'logcat', '-c']);
} }
...@@ -839,7 +851,7 @@ class AndroidDevice extends _Device { ...@@ -839,7 +851,7 @@ class AndroidDevice extends _Device {
'am', 'am',
'broadcast', 'broadcast',
'-a', '-a',
'${apk.appPackageID}.TRACING_START' '${apk.id}.TRACING_START'
]); ]);
} }
...@@ -851,7 +863,7 @@ class AndroidDevice extends _Device { ...@@ -851,7 +863,7 @@ class AndroidDevice extends _Device {
'am', 'am',
'broadcast', 'broadcast',
'-a', '-a',
'${apk.appPackageID}.TRACING_STOP' '${apk.id}.TRACING_STOP'
]); ]);
RegExp traceRegExp = new RegExp(r'Saving trace to (\S+)', multiLine: true); RegExp traceRegExp = new RegExp(r'Saving trace to (\S+)', multiLine: true);
...@@ -891,3 +903,57 @@ class AndroidDevice extends _Device { ...@@ -891,3 +903,57 @@ class AndroidDevice extends _Device {
@override @override
bool isConnected() => _hasValidAndroid; bool isConnected() => _hasValidAndroid;
} }
class DeviceStore {
final AndroidDevice android;
final IOSDevice iOS;
final IOSSimulator iOSSimulator;
List<Device> get all {
List<Device> result = <Device>[];
if (android != null)
result.add(android);
if (iOS != null)
result.add(iOS);
if (iOSSimulator != null)
result.add(iOSSimulator);
return result;
}
DeviceStore({
this.android,
this.iOS,
this.iOSSimulator
});
factory DeviceStore.forConfigs(List<BuildConfiguration> configs) {
AndroidDevice android;
IOSDevice iOS;
IOSSimulator iOSSimulator;
for (BuildConfiguration config in configs) {
switch (config.platform) {
case BuildPlatform.android:
assert(android == null);
android = new AndroidDevice();
break;
case BuildPlatform.iOS:
assert(iOS == null);
iOS = new IOSDevice();
break;
case BuildPlatform.iOSSimulator:
assert(iOSSimulator == null);
iOSSimulator = new IOSSimulator();
break;
case BuildPlatform.mac:
case BuildPlatform.linux:
// TODO(abarth): Support mac and linux targets.
assert(false);
break;
}
}
return new DeviceStore(android: android, iOS: iOS, iOSSimulator: iOSSimulator);
}
}
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// 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.
library android_device_test;
import 'package:sky_tools/src/device.dart'; import 'package:sky_tools/src/device.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
...@@ -17,9 +15,9 @@ defineTests() { ...@@ -17,9 +15,9 @@ defineTests() {
}); });
test('stores the requested id', () { test('stores the requested id', () {
String deviceID = '1234'; String deviceId = '1234';
AndroidDevice android = new AndroidDevice(id: deviceID); AndroidDevice android = new AndroidDevice(id: deviceId);
expect(android.id, equals(deviceID)); expect(android.id, equals(deviceId));
}); });
test('correctly creates only one of each requested device id', () { test('correctly creates only one of each requested device id', () {
......
...@@ -2,35 +2,31 @@ ...@@ -2,35 +2,31 @@
// 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.
library install_test;
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/install.dart'; import 'package:sky_tools/src/commands/install.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'src/common.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', () { test('returns 0 when Android is connected and ready for an install', () {
applicationPackageSetup(); InstallCommand command = new InstallCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
MockAndroidDevice android = new MockAndroidDevice(); when(mockDevices.android.isConnected()).thenReturn(true);
when(android.isConnected()).thenReturn(true); when(mockDevices.android.installApp(any)).thenReturn(true);
when(android.installApp(any)).thenReturn(true);
MockIOSDevice ios = new MockIOSDevice(); when(mockDevices.iOS.isConnected()).thenReturn(false);
when(ios.isConnected()).thenReturn(false); when(mockDevices.iOS.installApp(any)).thenReturn(false);
when(ios.installApp(any)).thenReturn(false);
MockIOSSimulator iosSim = new MockIOSSimulator(); when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(iosSim.isConnected()).thenReturn(false); when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
when(iosSim.installApp(any)).thenReturn(false);
InstallCommand command = new InstallCommand(android: android, ios: ios);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
...@@ -38,22 +34,18 @@ defineTests() { ...@@ -38,22 +34,18 @@ defineTests() {
}); });
test('returns 0 when iOS is connected and ready for an install', () { test('returns 0 when iOS is connected and ready for an install', () {
applicationPackageSetup(); InstallCommand command = new InstallCommand();
applyMocksToCommand(command);
MockAndroidDevice android = new MockAndroidDevice(); MockDeviceStore mockDevices = command.devices;
when(android.isConnected()).thenReturn(false);
when(android.installApp(any)).thenReturn(false);
MockIOSDevice ios = new MockIOSDevice(); when(mockDevices.android.isConnected()).thenReturn(false);
when(ios.isConnected()).thenReturn(true); when(mockDevices.android.installApp(any)).thenReturn(false);
when(ios.installApp(any)).thenReturn(true);
MockIOSSimulator iosSim = new MockIOSSimulator(); when(mockDevices.iOS.isConnected()).thenReturn(true);
when(iosSim.isConnected()).thenReturn(false); when(mockDevices.iOS.installApp(any)).thenReturn(true);
when(iosSim.installApp(any)).thenReturn(false);
InstallCommand command = when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
new InstallCommand(android: android, ios: ios, iosSim: iosSim); when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
......
...@@ -2,41 +2,37 @@ ...@@ -2,41 +2,37 @@
// 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.
library list_test;
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/list.dart'; import 'package:sky_tools/src/commands/list.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'src/common.dart'; import 'src/mocks.dart';
main() => defineTests(); main() => defineTests();
defineTests() { defineTests() {
group('list', () { group('list', () {
test('returns 0 when called', () { test('returns 0 when called', () {
applicationPackageSetup(); ListCommand command = new ListCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
MockAndroidDevice android = new MockAndroidDevice();
// Avoid relying on adb being installed on the test system. // Avoid relying on adb being installed on the test system.
// Instead, cause the test to run the echo command. // Instead, cause the test to run the echo command.
when(android.adbPath).thenReturn('echo'); when(mockDevices.android.adbPath).thenReturn('echo');
MockIOSDevice ios = new MockIOSDevice();
// Avoid relying on idevice* being installed on the test system. // Avoid relying on idevice* being installed on the test system.
// Instead, cause the test to run the echo command. // Instead, cause the test to run the echo command.
when(ios.informerPath).thenReturn('echo'); when(mockDevices.iOS.informerPath).thenReturn('echo');
when(ios.installerPath).thenReturn('echo'); when(mockDevices.iOS.installerPath).thenReturn('echo');
when(ios.listerPath).thenReturn('echo'); when(mockDevices.iOS.listerPath).thenReturn('echo');
MockIOSSimulator iosSim = new MockIOSSimulator();
// Avoid relying on xcrun being installed on the test system. // Avoid relying on xcrun being installed on the test system.
// Instead, cause the test to run the echo command. // Instead, cause the test to run the echo command.
when(iosSim.xcrunPath).thenReturn('echo'); when(mockDevices.iOSSimulator.xcrunPath).thenReturn('echo');
ListCommand command =
new ListCommand(android: android, ios: ios, iosSim: iosSim);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
runner.run(['list']).then((int code) => expect(code, equals(0))); runner.run(['list']).then((int code) => expect(code, equals(0)));
......
...@@ -2,30 +2,25 @@ ...@@ -2,30 +2,25 @@
// 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.
library listen_test;
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/listen.dart'; import 'package:sky_tools/src/commands/listen.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'src/common.dart'; import 'src/mocks.dart';
main() => defineTests(); main() => defineTests();
defineTests() { defineTests() {
group('listen', () { group('listen', () {
test('returns 0 when no device is connected', () { test('returns 0 when no device is connected', () {
applicationPackageSetup(); ListenCommand command = new ListenCommand(singleRun: true);
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
MockAndroidDevice android = new MockAndroidDevice(); when(mockDevices.android.isConnected()).thenReturn(false);
when(android.isConnected()).thenReturn(false); when(mockDevices.iOS.isConnected()).thenReturn(false);
MockIOSDevice ios = new MockIOSDevice(); when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(ios.isConnected()).thenReturn(false);
MockIOSSimulator iosSim = new MockIOSSimulator();
when(iosSim.isConnected()).thenReturn(false);
ListenCommand command = new ListenCommand(
android: android, ios: ios, iosSim: iosSim, singleRun: true);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
......
...@@ -2,31 +2,25 @@ ...@@ -2,31 +2,25 @@
// 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.
library logs_test;
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/logs.dart'; import 'package:sky_tools/src/commands/logs.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'src/common.dart'; import 'src/mocks.dart';
main() => defineTests(); main() => defineTests();
defineTests() { defineTests() {
group('logs', () { group('logs', () {
test('returns 0 when no device is connected', () { test('returns 0 when no device is connected', () {
applicationPackageSetup(); LogsCommand command = new LogsCommand();
applyMocksToCommand(command);
MockAndroidDevice android = new MockAndroidDevice(); MockDeviceStore mockDevices = command.devices;
when(android.isConnected()).thenReturn(false);
MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(false);
MockIOSSimulator iosSim = new MockIOSSimulator();
when(iosSim.isConnected()).thenReturn(false);
LogsCommand command = when(mockDevices.android.isConnected()).thenReturn(false);
new LogsCommand(android: android, ios: ios, iosSim: iosSim); when(mockDevices.iOS.isConnected()).thenReturn(false);
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
......
...@@ -4,29 +4,47 @@ ...@@ -4,29 +4,47 @@
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/application_package.dart'; import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/build_configuration.dart';
import 'package:sky_tools/src/commands/flutter_command.dart';
import 'package:sky_tools/src/device.dart'; import 'package:sky_tools/src/device.dart';
class MockApplicationPackageStore extends ApplicationPackageStore {
MockApplicationPackageStore() : super(
android: new AndroidApk(localPath: '/mock/path/to/android/SkyShell.apk'),
iOS: new IOSApp(localPath: '/mock/path/to/iOS/SkyShell.app'),
iOSSimulator: new IOSApp(localPath: '/mock/path/to/iOSSimulator/SkyShell.app'));
}
class MockAndroidDevice extends Mock implements AndroidDevice { class MockAndroidDevice extends Mock implements AndroidDevice {
BuildPlatform get platform => BuildPlatform.android;
@override @override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
} }
class MockIOSDevice extends Mock implements IOSDevice { class MockIOSDevice extends Mock implements IOSDevice {
BuildPlatform get platform => BuildPlatform.iOS;
@override @override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
} }
class MockIOSSimulator extends Mock implements IOSSimulator { class MockIOSSimulator extends Mock implements IOSSimulator {
BuildPlatform get platform => BuildPlatform.iOSSimulator;
@override @override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
} }
void applicationPackageSetup() { class MockDeviceStore extends DeviceStore {
ApplicationPackageFactory.srcPath = './'; MockDeviceStore() : super(
ApplicationPackageFactory.setBuildPath( android: new MockAndroidDevice(),
BuildType.prebuilt, BuildPlatform.android, './'); iOS: new MockIOSDevice(),
ApplicationPackageFactory.setBuildPath( iOSSimulator: new MockIOSSimulator());
BuildType.prebuilt, BuildPlatform.iOS, './'); }
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.iOSSimulator, './'); void applyMocksToCommand(FlutterCommand command) {
command
..applicationPackages = new MockApplicationPackageStore()
..devices = new MockDeviceStore();
} }
...@@ -2,42 +2,36 @@ ...@@ -2,42 +2,36 @@
// 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.
library start_test;
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/start.dart'; import 'package:sky_tools/src/commands/start.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'src/common.dart'; import 'src/mocks.dart';
main() => defineTests(); main() => defineTests();
defineTests() { defineTests() {
group('start', () { group('start', () {
test('returns 0 when Android is connected and ready to be started', () { test('returns 0 when Android is connected and ready to be started', () {
applicationPackageSetup(); StartCommand command = new StartCommand();
applyMocksToCommand(command);
MockAndroidDevice android = new MockAndroidDevice(); MockDeviceStore mockDevices = command.devices;
when(android.isConnected()).thenReturn(true);
when(android.installApp(any)).thenReturn(true);
when(android.startServer(any, any, any, any)).thenReturn(true);
when(android.stopApp(any)).thenReturn(true);
MockIOSDevice ios = new MockIOSDevice(); when(mockDevices.android.isConnected()).thenReturn(true);
when(ios.isConnected()).thenReturn(false); when(mockDevices.android.installApp(any)).thenReturn(true);
when(ios.installApp(any)).thenReturn(false); when(mockDevices.android.startServer(any, any, any, any)).thenReturn(true);
when(ios.startApp(any)).thenReturn(false); when(mockDevices.android.stopApp(any)).thenReturn(true);
when(ios.stopApp(any)).thenReturn(false);
MockIOSSimulator iosSim = new MockIOSSimulator(); when(mockDevices.iOS.isConnected()).thenReturn(false);
when(iosSim.isConnected()).thenReturn(false); when(mockDevices.iOS.installApp(any)).thenReturn(false);
when(iosSim.installApp(any)).thenReturn(false); when(mockDevices.iOS.startApp(any)).thenReturn(false);
when(iosSim.startApp(any)).thenReturn(false); when(mockDevices.iOS.stopApp(any)).thenReturn(false);
when(iosSim.stopApp(any)).thenReturn(false);
StartCommand command = when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
new StartCommand(android: android, ios: ios, iosSim: iosSim); when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.startApp(any)).thenReturn(false);
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
...@@ -45,28 +39,24 @@ defineTests() { ...@@ -45,28 +39,24 @@ defineTests() {
}); });
test('returns 0 when iOS is connected and ready to be started', () { test('returns 0 when iOS is connected and ready to be started', () {
applicationPackageSetup(); StartCommand command = new StartCommand();
applyMocksToCommand(command);
MockAndroidDevice android = new MockAndroidDevice(); MockDeviceStore mockDevices = command.devices;
when(android.isConnected()).thenReturn(false);
when(android.installApp(any)).thenReturn(false); when(mockDevices.android.isConnected()).thenReturn(false);
when(android.startServer(any, any, any, any)).thenReturn(false); when(mockDevices.android.installApp(any)).thenReturn(false);
when(android.stopApp(any)).thenReturn(false); when(mockDevices.android.startServer(any, any, any, any)).thenReturn(false);
when(mockDevices.android.stopApp(any)).thenReturn(false);
MockIOSDevice ios = new MockIOSDevice();
when(ios.isConnected()).thenReturn(true); when(mockDevices.iOS.isConnected()).thenReturn(true);
when(ios.installApp(any)).thenReturn(true); when(mockDevices.iOS.installApp(any)).thenReturn(true);
when(ios.startApp(any)).thenReturn(true); when(mockDevices.iOS.startApp(any)).thenReturn(true);
when(ios.stopApp(any)).thenReturn(false); when(mockDevices.iOS.stopApp(any)).thenReturn(false);
MockIOSSimulator iosSim = new MockIOSSimulator(); when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(iosSim.isConnected()).thenReturn(false); when(mockDevices.iOSSimulator.installApp(any)).thenReturn(false);
when(iosSim.installApp(any)).thenReturn(false); when(mockDevices.iOSSimulator.startApp(any)).thenReturn(false);
when(iosSim.startApp(any)).thenReturn(false); when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
when(iosSim.stopApp(any)).thenReturn(false);
StartCommand command =
new StartCommand(android: android, ios: ios, iosSim: iosSim);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
......
...@@ -9,29 +9,25 @@ import 'package:mockito/mockito.dart'; ...@@ -9,29 +9,25 @@ import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/stop.dart'; import 'package:sky_tools/src/commands/stop.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'src/common.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', () { test('returns 0 when Android is connected and ready to be stopped', () {
applicationPackageSetup(); StopCommand command = new StopCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
MockAndroidDevice android = new MockAndroidDevice(); when(mockDevices.android.isConnected()).thenReturn(true);
when(android.isConnected()).thenReturn(true); when(mockDevices.android.stopApp(any)).thenReturn(true);
when(android.stopApp(any)).thenReturn(true);
MockIOSDevice ios = new MockIOSDevice(); when(mockDevices.iOS.isConnected()).thenReturn(false);
when(ios.isConnected()).thenReturn(false); when(mockDevices.iOS.stopApp(any)).thenReturn(false);
when(ios.stopApp(any)).thenReturn(false);
MockIOSSimulator iosSim = new MockIOSSimulator(); when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
when(iosSim.isConnected()).thenReturn(false); when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
when(iosSim.stopApp(any)).thenReturn(false);
StopCommand command =
new StopCommand(android: android, ios: ios, iosSim: iosSim);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
...@@ -39,22 +35,18 @@ defineTests() { ...@@ -39,22 +35,18 @@ defineTests() {
}); });
test('returns 0 when iOS is connected and ready to be stopped', () { test('returns 0 when iOS is connected and ready to be stopped', () {
applicationPackageSetup(); StopCommand command = new StopCommand();
applyMocksToCommand(command);
MockAndroidDevice android = new MockAndroidDevice(); MockDeviceStore mockDevices = command.devices;
when(android.isConnected()).thenReturn(false);
when(android.stopApp(any)).thenReturn(false);
MockIOSDevice ios = new MockIOSDevice(); when(mockDevices.android.isConnected()).thenReturn(false);
when(ios.isConnected()).thenReturn(true); when(mockDevices.android.stopApp(any)).thenReturn(false);
when(ios.stopApp(any)).thenReturn(true);
MockIOSSimulator iosSim = new MockIOSSimulator(); when(mockDevices.iOS.isConnected()).thenReturn(true);
when(iosSim.isConnected()).thenReturn(false); when(mockDevices.iOS.stopApp(any)).thenReturn(true);
when(iosSim.stopApp(any)).thenReturn(false);
StopCommand command = when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
new StopCommand(android: android, ios: ios, iosSim: iosSim); when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
......
...@@ -9,18 +9,18 @@ import 'package:mockito/mockito.dart'; ...@@ -9,18 +9,18 @@ import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/commands/trace.dart'; import 'package:sky_tools/src/commands/trace.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'src/common.dart'; import 'src/mocks.dart';
main() => defineTests(); main() => defineTests();
defineTests() { defineTests() {
group('trace', () { group('trace', () {
test('returns 1 when no Android device is connected', () { test('returns 1 when no Android device is connected', () {
applicationPackageSetup(); TraceCommand command = new TraceCommand();
applyMocksToCommand(command);
MockDeviceStore mockDevices = command.devices;
MockAndroidDevice android = new MockAndroidDevice(); when(mockDevices.android.isConnected()).thenReturn(false);
when(android.isConnected()).thenReturn(false);
TraceCommand command = new TraceCommand(android);
CommandRunner runner = new CommandRunner('test_flutter', '') CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command); ..addCommand(command);
......
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