Add listen command and basic test, and don’t do unnecessary repeated work when listening or poking the android server.

This commit is contained in:
Ian Fischer 2015-09-29 17:10:47 -07:00
parent 2bc289dec9
commit 00bed774ce
5 changed files with 172 additions and 15 deletions

View File

@ -13,6 +13,7 @@ import 'package:sky_tools/src/build.dart';
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/listen.dart';
import 'package:sky_tools/src/logs.dart';
import 'package:sky_tools/src/run_mojo.dart';
import 'package:sky_tools/src/start.dart';
@ -149,6 +150,7 @@ void main(List<String> args) {
..addCommand(new CacheCommand())
..addCommand(new InitCommand())
..addCommand(new InstallCommand())
..addCommand(new ListenCommand())
..addCommand(new LogsCommand())
..addCommand(new RunMojoCommand())
..addCommand(new StartCommand())

View File

@ -256,19 +256,21 @@ class AndroidDevice extends _Device {
return false;
}
// Set up port forwarding for observatory.
String observatoryPortString = 'tcp:$_observatoryPort';
runCheckedSync(
[adbPath, 'forward', observatoryPortString, observatoryPortString]);
if (!poke) {
// 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);
// 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]);
// 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';

View File

@ -15,7 +15,11 @@ class InstallCommand extends Command {
final name = 'install';
final description = 'Install your Flutter app on attached devices.';
AndroidDevice android = null;
InstallCommand([this.android]);
InstallCommand([this.android]) {
if (android == null) {
android = new AndroidDevice();
}
}
@override
Future<int> run() async {
@ -31,9 +35,6 @@ class InstallCommand extends Command {
Map<BuildPlatform, ApplicationPackage> packages =
ApplicationPackageFactory.getAvailableApplicationPackages();
if (android == null) {
android = new AndroidDevice();
}
ApplicationPackage androidApp = packages[BuildPlatform.android];
if (androidApp != null && android.isConnected()) {
installedSomewhere = android.installApp(androidApp) || installedSomewhere;

View File

@ -0,0 +1,118 @@
// 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.listen;
import 'dart:async';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/device.dart';
import 'package:sky_tools/src/process.dart';
final Logger _logging = new Logger('sky_tools.listen');
class ListenCommand extends Command {
final name = 'listen';
final description = 'Listen for changes to files and reload the running app '
'on all connected devices.';
AndroidDevice android = null;
List<String> watchCommand;
/// Only run once. Used for testing.
bool singleRun;
ListenCommand({this.android, this.singleRun: false}) {
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 {
if (argResults.rest.length > 0) {
watchCommand = _initWatchCommand(argResults.rest);
} else {
watchCommand = _initWatchCommand(['.']);
}
Map<BuildPlatform, ApplicationPackage> packages =
ApplicationPackageFactory.getAvailableApplicationPackages();
ApplicationPackage androidApp = packages[BuildPlatform.android];
while (true) {
_logging.info('Updating running Sky apps...');
if (android.isConnected()) {
await android.startServer(
argResults['target'], true, argResults['checked'], androidApp);
}
if (singleRun || !watchDirectory()) {
break;
}
}
return 0;
}
List<String> _initWatchCommand(List<String> directories) {
if (Platform.isMacOS) {
try {
runCheckedSync(['which', 'fswatch']);
} catch (e) {
_logging.severe('"listen" command is only useful if you have installed '
'fswatch on Mac. Run "brew install fswatch" to install it with '
'homebrew.');
return null;
}
return ['fswatch', '-r', '-v', '-1']..addAll(directories);
} else if (Platform.isLinux) {
try {
runCheckedSync(['which', 'inotifywait']);
} catch (e) {
_logging.severe('"listen" command is only useful if you have installed '
'inotifywait on Linux. Run "apt-get install inotify-tools" or '
'equivalent to install it.');
return null;
}
return [
'inotifywait',
'-r',
'-e',
// Only listen for events that matter, to avoid triggering constantly
// from the editor watching files
'modify,close_write,move,create,delete',
]..addAll(directories);
} else {
_logging.severe('"listen" command is only available on Mac and Linux.');
}
return null;
}
bool watchDirectory() {
if (watchCommand == null) {
return false;
}
try {
runCheckedSync(watchCommand);
} catch (e) {
_logging.warning('Watching directories failed.', e);
return false;
}
return true;
}
}

View File

@ -0,0 +1,34 @@
// 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 stop_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/listen.dart';
import 'package:test/test.dart';
import 'src/common.dart';
main() => defineTests();
defineTests() {
group('listen', () {
test('returns 0 when no device is connected', () {
ApplicationPackageFactory.srcPath = './';
ApplicationPackageFactory.setBuildPath(
BuildType.prebuilt, BuildPlatform.android, './');
MockAndroidDevice android = new MockAndroidDevice();
when(android.isConnected()).thenReturn(false);
ListenCommand command =
new ListenCommand(android: android, singleRun: true);
CommandRunner runner = new CommandRunner('test_flutter', '')
..addCommand(command);
runner.run(['listen']).then((int code) => expect(code, equals(0)));
});
});
}