Add a detach command to detach without terminating (#21376)
* Add a detach command to detach without terminating Fixes #21154. * Bump protocol version for app.detach * Tweak to detach/quit text * Change logPrefix to named param
This commit is contained in:
parent
11ee2f71af
commit
eb9c975eb0
@ -92,6 +92,12 @@ The `callServiceExtension()` allows clients to make arbitrary calls to service p
|
||||
- `methodName`: the name of the service protocol extension to invoke; this is required.
|
||||
- `params`: an optional Map of parameters to pass to the service protocol extension.
|
||||
|
||||
#### app.detach
|
||||
|
||||
The `detach()` command takes one parameter, `appId`. It returns a `bool` to indicate success or failure in detaching from an app without stopping it.
|
||||
|
||||
- `appId`: the id of a previously started app; this is required.
|
||||
|
||||
#### app.stop
|
||||
|
||||
The `stop()` command takes one parameter, `appId`. It returns a `bool` to indicate success or failure in stopping an app.
|
||||
@ -110,7 +116,7 @@ This is sent when an observatory port is available for a started app. The `param
|
||||
|
||||
#### app.started
|
||||
|
||||
This is sent once the application launch process is complete and the app is either paused before main() (if `startPaused` is true) or main() has begun running. The `params` field will be a map containing the field `appId`.
|
||||
This is sent once the application launch process is complete and the app is either paused before main() (if `startPaused` is true) or main() has begun running. When attaching, this even will be fired once attached. The `params` field will be a map containing the field `appId`.
|
||||
|
||||
#### app.log
|
||||
|
||||
@ -122,7 +128,7 @@ This is sent when an operation starts and again when it stops. When an operation
|
||||
|
||||
#### app.stop
|
||||
|
||||
This is sent when an app is stopped. The `params` field will be a map with the field `appId`.
|
||||
This is sent when an app is stopped or detached from. The `params` field will be a map with the field `appId`.
|
||||
|
||||
### device domain
|
||||
|
||||
@ -204,6 +210,7 @@ The following subset of the app domain is available in `flutter run --machine`.
|
||||
- Commands
|
||||
- [`restart`](#apprestart)
|
||||
- [`callServiceExtension`](#appcallserviceextension)
|
||||
- [`detach`](#appdetach)
|
||||
- [`stop`](#appstop)
|
||||
- Events
|
||||
- [`start`](#appstart)
|
||||
@ -219,6 +226,7 @@ See the [source](https://github.com/flutter/flutter/blob/master/packages/flutter
|
||||
|
||||
## Changelog
|
||||
|
||||
- 0.4.2: Added `app.detach` command
|
||||
- 0.4.1: Added `flutter attach --machine`
|
||||
- 0.4.0: Added `emulator.create` command
|
||||
- 0.3.0: Added `daemon.connected` event at startup
|
||||
|
@ -28,7 +28,7 @@ import '../runner/flutter_command.dart';
|
||||
import '../tester/flutter_tester.dart';
|
||||
import '../vmservice.dart';
|
||||
|
||||
const String protocolVersion = '0.4.1';
|
||||
const String protocolVersion = '0.4.2';
|
||||
|
||||
/// A server process command. This command will start up a long-lived server.
|
||||
/// It reads JSON-RPC based commands from stdin, executes them, and returns
|
||||
@ -316,6 +316,7 @@ class AppDomain extends Domain {
|
||||
registerHandler('restart', restart);
|
||||
registerHandler('callServiceExtension', callServiceExtension);
|
||||
registerHandler('stop', stop);
|
||||
registerHandler('detach', detach);
|
||||
}
|
||||
|
||||
static final Uuid _uuidGenerator = new Uuid();
|
||||
@ -516,6 +517,23 @@ class AppDomain extends Domain {
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> detach(Map<String, dynamic> args) async {
|
||||
final String appId = _getStringArg(args, 'appId', required: true);
|
||||
|
||||
final AppInstance app = _getApp(appId);
|
||||
if (app == null)
|
||||
throw "app '$appId' not found";
|
||||
|
||||
return app.detach().timeout(const Duration(seconds: 5)).then<bool>((_) {
|
||||
return true;
|
||||
}).catchError((dynamic error) {
|
||||
_sendAppEvent(app, 'log', <String, dynamic>{ 'log': '$error', 'error': true });
|
||||
app.closeLogger();
|
||||
_apps.remove(app);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
AppInstance _getApp(String id) {
|
||||
return _apps.firstWhere((AppInstance app) => app.id == id, orElse: () => null);
|
||||
}
|
||||
@ -769,6 +787,7 @@ class AppInstance {
|
||||
}
|
||||
|
||||
Future<Null> stop() => runner.stop();
|
||||
Future<Null> detach() => runner.detach();
|
||||
|
||||
void closeLogger() {
|
||||
_logger.close();
|
||||
|
@ -66,6 +66,7 @@ class HotRunner extends ResidentRunner {
|
||||
final bool benchmarkMode;
|
||||
final File applicationBinary;
|
||||
final bool hostIsIde;
|
||||
bool _didAttach = false;
|
||||
Set<String> _dartDependencies;
|
||||
final String dillOutputPath;
|
||||
|
||||
@ -152,6 +153,7 @@ class HotRunner extends ResidentRunner {
|
||||
Completer<void> appStartedCompleter,
|
||||
String viewFilter,
|
||||
}) async {
|
||||
_didAttach = true;
|
||||
try {
|
||||
await connectToServiceProtocol(viewFilter: viewFilter,
|
||||
reloadSources: _reloadSourcesService,
|
||||
@ -751,18 +753,25 @@ class HotRunner extends ResidentRunner {
|
||||
for (Uri uri in device.observatoryUris)
|
||||
printStatus('An Observatory debugger and profiler on $dname is available at: $uri');
|
||||
}
|
||||
final String quitMessage = _didAttach
|
||||
? 'To detach, press "d"; to quit, press "q".'
|
||||
: 'To quit, press "q".';
|
||||
if (details) {
|
||||
printHelpDetails();
|
||||
printStatus('To repeat this help message, press "h". To quit, press "q".');
|
||||
printStatus('To repeat this help message, press "h". $quitMessage');
|
||||
} else {
|
||||
printStatus('For a more detailed help message, press "h". To quit, press "q".');
|
||||
printStatus('For a more detailed help message, press "h". $quitMessage');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Null> cleanupAfterSignal() async {
|
||||
await stopEchoingDeviceLog();
|
||||
await stopApp();
|
||||
if (_didAttach) {
|
||||
appFinished();
|
||||
} else {
|
||||
await stopApp();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -6,7 +6,6 @@ import 'package:file/file.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
import 'test_data/basic_project.dart';
|
||||
import 'test_driver.dart';
|
||||
|
||||
@ -18,24 +17,36 @@ void main() {
|
||||
setUp(() async {
|
||||
tempDir = fs.systemTempDirectory.createTempSync('flutter_attach_test.');
|
||||
await _project.setUpIn(tempDir);
|
||||
_flutterRun = new FlutterTestDriver(tempDir);
|
||||
_flutterAttach = new FlutterTestDriver(tempDir);
|
||||
_flutterRun = new FlutterTestDriver(tempDir, logPrefix: 'RUN');
|
||||
_flutterAttach = new FlutterTestDriver(tempDir, logPrefix: 'ATTACH');
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
// We can't call stop() on both of these because they'll both try to stop the
|
||||
// same app. Just quit the attach process and then send a stop to the original
|
||||
// process.
|
||||
await _flutterAttach.detach();
|
||||
await _flutterRun.stop();
|
||||
await _flutterAttach.quit();
|
||||
tryToDelete(tempDir);
|
||||
});
|
||||
|
||||
group('attached process', () {
|
||||
testUsingContext('can hot reload', () async {
|
||||
test('can hot reload', () async {
|
||||
await _flutterRun.run(withDebugger: true);
|
||||
await _flutterAttach.attach(_flutterRun.vmServicePort);
|
||||
await _flutterAttach.hotReload();
|
||||
});
|
||||
test('can detach, reattach, hot reload', () async {
|
||||
await _flutterRun.run(withDebugger: true);
|
||||
await _flutterAttach.attach(_flutterRun.vmServicePort);
|
||||
await _flutterAttach.detach();
|
||||
await _flutterAttach.attach(_flutterRun.vmServicePort);
|
||||
await _flutterAttach.hotReload();
|
||||
});
|
||||
test('killing process behaves the same as detach ', () async {
|
||||
await _flutterRun.run(withDebugger: true);
|
||||
await _flutterAttach.attach(_flutterRun.vmServicePort);
|
||||
await _flutterAttach.quit();
|
||||
_flutterAttach = new FlutterTestDriver(tempDir, logPrefix: 'ATTACH-2');
|
||||
await _flutterAttach.attach(_flutterRun.vmServicePort);
|
||||
await _flutterAttach.hotReload();
|
||||
});
|
||||
}, timeout: const Timeout.factor(6));
|
||||
}
|
||||
|
@ -23,9 +23,11 @@ const Duration appStartTimeout = Duration(seconds: 120);
|
||||
const Duration quitTimeout = Duration(seconds: 10);
|
||||
|
||||
class FlutterTestDriver {
|
||||
FlutterTestDriver(this._projectFolder);
|
||||
FlutterTestDriver(this._projectFolder, {String logPrefix}):
|
||||
this._logPrefix = logPrefix != null ? '$logPrefix: ' : '';
|
||||
|
||||
final Directory _projectFolder;
|
||||
final String _logPrefix;
|
||||
Process _proc;
|
||||
int _procPid;
|
||||
final StreamController<String> _stdout = new StreamController<String>.broadcast();
|
||||
@ -49,7 +51,7 @@ class FlutterTestDriver {
|
||||
msg.length > maxLength ? msg.substring(0, maxLength) + '...' : msg;
|
||||
_allMessages.add(truncatedMsg);
|
||||
if (_printJsonAndStderr) {
|
||||
print(truncatedMsg);
|
||||
print('$_logPrefix$truncatedMsg');
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
@ -162,6 +164,31 @@ class FlutterTestDriver {
|
||||
_throwErrorResponse('Hot ${fullRestart ? 'restart' : 'reload'} request failed');
|
||||
}
|
||||
|
||||
Future<int> detach() async {
|
||||
if (vmService != null) {
|
||||
_debugPrint('Closing VM service');
|
||||
await vmService.close()
|
||||
.timeout(quitTimeout,
|
||||
onTimeout: () { _debugPrint('VM Service did not quit within $quitTimeout'); });
|
||||
}
|
||||
if (_currentRunningAppId != null) {
|
||||
_debugPrint('Detaching from app');
|
||||
await Future.any<void>(<Future<void>>[
|
||||
_proc.exitCode,
|
||||
_sendRequest(
|
||||
'app.detach',
|
||||
<String, dynamic>{'appId': _currentRunningAppId}
|
||||
),
|
||||
]).timeout(
|
||||
quitTimeout,
|
||||
onTimeout: () { _debugPrint('app.detach did not return within $quitTimeout'); }
|
||||
);
|
||||
_currentRunningAppId = null;
|
||||
}
|
||||
_debugPrint('Waiting for process to end');
|
||||
return _proc.exitCode.timeout(quitTimeout, onTimeout: _killGracefully);
|
||||
}
|
||||
|
||||
Future<int> stop() async {
|
||||
if (vmService != null) {
|
||||
_debugPrint('Closing VM service');
|
||||
|
Loading…
x
Reference in New Issue
Block a user