Commit 8cac55a4 authored by Ian Fischer's avatar Ian Fischer

Add sky_tools start command and associated android support.

parent 6bfd60f2
...@@ -14,11 +14,12 @@ import 'package:sky_tools/src/cache.dart'; ...@@ -14,11 +14,12 @@ import 'package:sky_tools/src/cache.dart';
import 'package:sky_tools/src/init.dart'; import 'package:sky_tools/src/init.dart';
import 'package:sky_tools/src/install.dart'; import 'package:sky_tools/src/install.dart';
import 'package:sky_tools/src/run_mojo.dart'; import 'package:sky_tools/src/run_mojo.dart';
import 'package:sky_tools/src/start.dart';
import 'package:sky_tools/src/stop.dart'; import 'package:sky_tools/src/stop.dart';
class FlutterCommandRunner extends CommandRunner { class FlutterCommandRunner extends CommandRunner {
FlutterCommandRunner() FlutterCommandRunner()
: super('flutter', 'Manage your flutter app development.') { : super('flutter', 'Manage your Flutter app development.') {
argParser.addFlag('verbose', argParser.addFlag('verbose',
abbr: 'v', abbr: 'v',
negatable: false, negatable: false,
...@@ -77,8 +78,7 @@ class FlutterCommandRunner extends CommandRunner { ...@@ -77,8 +78,7 @@ class FlutterCommandRunner extends CommandRunner {
'This path is relative to sky-src-path. Not normally required.', 'This path is relative to sky-src-path. Not normally required.',
defaultsTo: 'out/ios_sim_Release/'); defaultsTo: 'out/ios_sim_Release/');
argParser.addOption('package-root', argParser.addOption('package-root',
help: 'Path to your packages directory.', help: 'Path to your packages directory.', defaultsTo: 'packages');
defaultsTo: 'packages');
} }
Future<int> runCommand(ArgResults topLevelResults) async { Future<int> runCommand(ArgResults topLevelResults) async {
...@@ -144,11 +144,12 @@ void main(List<String> args) { ...@@ -144,11 +144,12 @@ void main(List<String> args) {
}); });
new FlutterCommandRunner() new FlutterCommandRunner()
..addCommand(new StopCommand())
..addCommand(new BuildCommand()) ..addCommand(new BuildCommand())
..addCommand(new CacheCommand()) ..addCommand(new CacheCommand())
..addCommand(new InitCommand()) ..addCommand(new InitCommand())
..addCommand(new InstallCommand()) ..addCommand(new InstallCommand())
..addCommand(new RunMojoCommand()) ..addCommand(new RunMojoCommand())
..addCommand(new StartCommand())
..addCommand(new StopCommand())
..run(args); ..run(args);
} }
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
library sky_tools.device; library sky_tools.device;
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
...@@ -52,6 +54,7 @@ abstract class _Device { ...@@ -52,6 +54,7 @@ abstract class _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 _serverPort = '9888'; static const String _serverPort = '9888';
static const String className = 'AndroidDevice'; static const String className = 'AndroidDevice';
...@@ -233,6 +236,66 @@ class AndroidDevice extends _Device { ...@@ -233,6 +236,66 @@ class AndroidDevice extends _Device {
return true; return true;
} }
Future<bool> startServer(
String target, bool poke, bool checked, AndroidApk apk) async {
String serverRoot = '';
String mainDart = '';
String missingMessage = '';
if (await FileSystemEntity.isDirectory(target)) {
serverRoot = target;
mainDart = path.join(serverRoot, 'lib', 'main.dart');
missingMessage = 'Missing lib/main.dart in project: $serverRoot';
} else {
serverRoot = Directory.current.path;
mainDart = target;
missingMessage = '$mainDart does not exist.';
}
if (!await FileSystemEntity.isFile(mainDart)) {
_logging.severe(missingMessage);
return false;
}
// Set up port forwarding for observatory.
String observatoryPortString = 'tcp:$_observatoryPort';
runCheckedSync(
[adbPath, 'forward', observatoryPortString, observatoryPortString]);
// Actually start the server.
await Process.start('pub', ['run', 'sky_tools:sky_server', _serverPort],
workingDirectory: serverRoot, mode: ProcessStartMode.DETACHED);
// Set up reverse port-forwarding so that the Android app can reach the
// server running on localhost.
String serverPortString = 'tcp:$_serverPort';
runCheckedSync([adbPath, 'reverse', serverPortString, serverPortString]);
String relativeDartMain = path.relative(mainDart, from: serverRoot);
String url = 'http://localhost:$_serverPort/$relativeDartMain';
if (poke) {
url += '?rand=${new Random().nextDouble()}';
}
// Actually launch the app on Android.
List<String> cmd = [
adbPath,
'shell',
'am',
'start',
'-a',
'android.intent.action.VIEW',
'-d',
url,
];
if (checked) {
cmd.addAll(['--ez', 'enable-checked-mode', 'true']);
}
cmd.add(apk.component);
runCheckedSync(cmd);
return true;
}
bool stop(AndroidApk apk) { bool stop(AndroidApk apk) {
// Turn off reverse port forwarding // Turn off reverse port forwarding
......
...@@ -19,6 +19,14 @@ class InstallCommand extends Command { ...@@ -19,6 +19,14 @@ class InstallCommand extends Command {
@override @override
Future<int> run() async { Future<int> run() async {
if (install()) {
return 0;
} else {
return 2;
}
}
bool install() {
bool installedSomewhere = false; bool installedSomewhere = false;
Map<BuildPlatform, ApplicationPackage> packages = Map<BuildPlatform, ApplicationPackage> packages =
...@@ -28,13 +36,9 @@ class InstallCommand extends Command { ...@@ -28,13 +36,9 @@ class InstallCommand extends Command {
} }
ApplicationPackage androidApp = packages[BuildPlatform.android]; ApplicationPackage androidApp = packages[BuildPlatform.android];
if (androidApp != null && android.isConnected()) { if (androidApp != null && android.isConnected()) {
installedSomewhere = installedSomewhere || android.installApp(androidApp); installedSomewhere = android.installApp(androidApp) || installedSomewhere;
} }
if (installedSomewhere) { return installedSomewhere;
return 0;
} else {
return 2;
}
} }
} }
// 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.
library sky_tools.start;
import 'dart:async';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/device.dart';
import 'package:sky_tools/src/install.dart';
import 'package:sky_tools/src/stop.dart';
final Logger _logging = new Logger('sky_tools.start');
class StartCommand extends Command {
final name = 'start';
final description = 'Start your Flutter app on attached devices.';
AndroidDevice android = null;
StartCommand([this.android]) {
argParser.addFlag('poke',
negatable: false,
help: 'Restart the connection to the server (Android only).');
argParser.addFlag('checked',
negatable: true,
defaultsTo: true,
help: 'Toggle Dart\'s checked mode.');
argParser.addOption('target',
defaultsTo: '.',
abbr: 't',
help: 'Target app path or filename to start.');
if (android == null) {
android = new AndroidDevice();
}
}
@override
Future<int> run() async {
bool startedSomewhere = false;
bool poke = argResults['poke'];
if (!poke) {
StopCommand stopper = new StopCommand(android);
stopper.stop();
// Only install if the user did not specify a poke
InstallCommand installer = new InstallCommand(android);
startedSomewhere = installer.install();
}
bool startedOnAndroid = false;
if (android.isConnected()) {
Map<BuildPlatform, ApplicationPackage> packages =
ApplicationPackageFactory.getAvailableApplicationPackages();
ApplicationPackage androidApp = packages[BuildPlatform.android];
String target = path.absolute(argResults['target']);
startedOnAndroid = await android.startServer(
target, poke, argResults['checked'], androidApp);
}
if (startedSomewhere || startedOnAndroid) {
return 0;
} else {
return 2;
}
}
}
// 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.
library start_test;
import 'package:args/command_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/start.dart';
import 'package:test/test.dart';
import 'src/common.dart';
main() => defineTests();
defineTests() {
group('start', () {
test('returns 0 when Android is connected and ready to be started', () {
ApplicationPackageFactory.srcPath = './';
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.android, './');
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(true);
when(android.installApp(any)).thenReturn(true);
when(android.stop(any)).thenReturn(true);
StartCommand command = new StartCommand(android);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['start']).then((int code) => expect(code, equals(0)));
});
});
}
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