From 8cac55a4e2d718dca83934f4f9bb6538cf0f6bd0 Mon Sep 17 00:00:00 2001 From: Ian Fischer Date: Fri, 25 Sep 2015 16:29:47 -0700 Subject: [PATCH] Add sky_tools start command and associated android support. --- packages/flutter_tools/bin/sky_tools.dart | 9 +-- packages/flutter_tools/lib/src/device.dart | 63 ++++++++++++++++++ packages/flutter_tools/lib/src/install.dart | 16 +++-- packages/flutter_tools/lib/src/start.dart | 71 +++++++++++++++++++++ packages/flutter_tools/test/start_test.dart | 35 ++++++++++ 5 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 packages/flutter_tools/lib/src/start.dart create mode 100644 packages/flutter_tools/test/start_test.dart diff --git a/packages/flutter_tools/bin/sky_tools.dart b/packages/flutter_tools/bin/sky_tools.dart index 920ea5ecbbb..3ee3ebc8882 100644 --- a/packages/flutter_tools/bin/sky_tools.dart +++ b/packages/flutter_tools/bin/sky_tools.dart @@ -14,11 +14,12 @@ import 'package:sky_tools/src/cache.dart'; import 'package:sky_tools/src/init.dart'; import 'package:sky_tools/src/install.dart'; import 'package:sky_tools/src/run_mojo.dart'; +import 'package:sky_tools/src/start.dart'; import 'package:sky_tools/src/stop.dart'; class FlutterCommandRunner extends CommandRunner { FlutterCommandRunner() - : super('flutter', 'Manage your flutter app development.') { + : super('flutter', 'Manage your Flutter app development.') { argParser.addFlag('verbose', abbr: 'v', negatable: false, @@ -77,8 +78,7 @@ class FlutterCommandRunner extends CommandRunner { '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'); + help: 'Path to your packages directory.', defaultsTo: 'packages'); } Future runCommand(ArgResults topLevelResults) async { @@ -144,11 +144,12 @@ void main(List args) { }); new FlutterCommandRunner() - ..addCommand(new StopCommand()) ..addCommand(new BuildCommand()) ..addCommand(new CacheCommand()) ..addCommand(new InitCommand()) ..addCommand(new InstallCommand()) ..addCommand(new RunMojoCommand()) + ..addCommand(new StartCommand()) + ..addCommand(new StopCommand()) ..run(args); } diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 22db7de79c5..0cb59538654 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -4,7 +4,9 @@ library sky_tools.device; +import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; @@ -52,6 +54,7 @@ abstract class _Device { class AndroidDevice extends _Device { static const String _ADB_PATH = 'adb'; + static const String _observatoryPort = '8181'; static const String _serverPort = '9888'; static const String className = 'AndroidDevice'; @@ -233,6 +236,66 @@ class AndroidDevice extends _Device { return true; } + Future 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 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) { // Turn off reverse port forwarding diff --git a/packages/flutter_tools/lib/src/install.dart b/packages/flutter_tools/lib/src/install.dart index 93943aeff7d..c9db6a22e1d 100644 --- a/packages/flutter_tools/lib/src/install.dart +++ b/packages/flutter_tools/lib/src/install.dart @@ -19,6 +19,14 @@ class InstallCommand extends Command { @override Future run() async { + if (install()) { + return 0; + } else { + return 2; + } + } + + bool install() { bool installedSomewhere = false; Map packages = @@ -28,13 +36,9 @@ class InstallCommand extends Command { } ApplicationPackage androidApp = packages[BuildPlatform.android]; if (androidApp != null && android.isConnected()) { - installedSomewhere = installedSomewhere || android.installApp(androidApp); + installedSomewhere = android.installApp(androidApp) || installedSomewhere; } - if (installedSomewhere) { - return 0; - } else { - return 2; - } + return installedSomewhere; } } diff --git a/packages/flutter_tools/lib/src/start.dart b/packages/flutter_tools/lib/src/start.dart new file mode 100644 index 00000000000..acca2589ac3 --- /dev/null +++ b/packages/flutter_tools/lib/src/start.dart @@ -0,0 +1,71 @@ +// 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 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 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; + } + } +} diff --git a/packages/flutter_tools/test/start_test.dart b/packages/flutter_tools/test/start_test.dart new file mode 100644 index 00000000000..d3cbe5eb045 --- /dev/null +++ b/packages/flutter_tools/test/start_test.dart @@ -0,0 +1,35 @@ +// 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))); + }); + }); +}