diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart index a7fe6fa35b..b9cb8360a2 100644 --- a/packages/flutter_tools/lib/src/base/utils.dart +++ b/packages/flutter_tools/lib/src/base/utils.dart @@ -398,6 +398,13 @@ String wrapText(String text, {int columnWidth, int hangingIndent, int indent}) { return result.join('\n'); } +void writePidFile(String pidFile) { + if (pidFile != null) { + // Write our pid to the file. + fs.file(pidFile).writeAsStringSync(io.pid.toString()); + } +} + // Used to represent a run of ANSI control sequences next to a visible // character. class _AnsiRun { diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart index 50091d8212..0f1b70a965 100644 --- a/packages/flutter_tools/lib/src/commands/attach.dart +++ b/packages/flutter_tools/lib/src/commands/attach.dart @@ -7,6 +7,7 @@ import 'dart:async'; import '../base/common.dart'; import '../base/file_system.dart'; import '../base/io.dart'; +import '../base/utils.dart'; import '../cache.dart'; import '../commands/daemon.dart'; import '../device.dart'; @@ -44,6 +45,10 @@ class AttachCommand extends FlutterCommand { ..addOption( 'debug-port', help: 'Local port where the observatory is listening.', + )..addOption('pid-file', + help: 'Specify a file to write the process id to. ' + 'You can send SIGUSR1 to trigger a hot reload ' + 'and SIGUSR2 to trigger a hot restart.', )..addOption( 'project-root', hide: !verboseHelp, @@ -90,6 +95,8 @@ class AttachCommand extends FlutterCommand { await _validateArguments(); + writePidFile(argResults['pid-file']); + final Device device = await findTargetDevice(); final int devicePort = observatoryPort; diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index b94543e128..461c4fc16f 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -6,7 +6,6 @@ import 'dart:async'; import '../base/common.dart'; import '../base/file_system.dart'; -import '../base/io.dart'; import '../base/utils.dart'; import '../build_info.dart'; import '../cache.dart'; @@ -276,6 +275,8 @@ class RunCommand extends RunCommandBase { // debug mode. final bool hotMode = shouldUseHotMode(); + writePidFile(argResults['pid-file']); + if (argResults['machine']) { if (devices.length > 1) throwToolExit('--machine does not support -d all.'); @@ -340,12 +341,6 @@ class RunCommand extends RunCommandBase { } } - final String pidFile = argResults['pid-file']; - if (pidFile != null) { - // Write our pid to the file. - fs.file(pidFile).writeAsStringSync(pid.toString()); - } - final List flutterDevices = devices.map((Device device) { return FlutterDevice( device, diff --git a/packages/flutter_tools/test/integration/flutter_attach_test.dart b/packages/flutter_tools/test/integration/flutter_attach_test.dart index 8b5cff8309..2e21ffa7f4 100644 --- a/packages/flutter_tools/test/integration/flutter_attach_test.dart +++ b/packages/flutter_tools/test/integration/flutter_attach_test.dart @@ -29,6 +29,15 @@ void main() { }); group('attached process', () { + test('writes pid-file', () async { + final File pidFile = tempDir.childFile('test.pid'); + await _flutterRun.run(withDebugger: true); + await _flutterAttach.attach( + _flutterRun.vmServicePort, + pidFile: pidFile, + ); + expect(pidFile.existsSync(), isTrue); + }); test('can hot reload', () async { await _flutterRun.run(withDebugger: true); await _flutterAttach.attach(_flutterRun.vmServicePort); diff --git a/packages/flutter_tools/test/integration/flutter_run_test.dart b/packages/flutter_tools/test/integration/flutter_run_test.dart index 2f1a577cbf..fa594cfb3b 100644 --- a/packages/flutter_tools/test/integration/flutter_run_test.dart +++ b/packages/flutter_tools/test/integration/flutter_run_test.dart @@ -9,21 +9,25 @@ import 'package:process/process.dart'; import '../src/common.dart'; import 'test_data/basic_project.dart'; +import 'test_driver.dart'; import 'test_utils.dart'; void main() { group('flutter_run', () { Directory tempDir; final BasicProject _project = BasicProject(); + FlutterTestDriver _flutter; setUp(() async { tempDir = createResolvedTempDirectorySync(); await _project.setUpIn(tempDir); + _flutter = FlutterTestDriver(tempDir); }); tearDown(() async { tryToDelete(tempDir); }); + test('reports an error if an invalid device is supplied', () async { // This test forces flutter to check for all possible devices to catch issues // like https://github.com/flutter/flutter/issues/21418 which were skipped @@ -44,5 +48,11 @@ void main() { fail("'flutter run -d invalid-device-id' did not produce the expected error"); } }); + + test('writes pid-file', () async { + final File pidFile = tempDir.childFile('test.pid'); + await _flutter.run(pidFile: pidFile); + expect(pidFile.existsSync(), isTrue); + }); }, timeout: const Timeout.factor(6)); } diff --git a/packages/flutter_tools/test/integration/test_driver.dart b/packages/flutter_tools/test/integration/test_driver.dart index a6f535e9eb..c7d89c725e 100644 --- a/packages/flutter_tools/test/integration/test_driver.dart +++ b/packages/flutter_tools/test/integration/test_driver.dart @@ -56,16 +56,25 @@ class FlutterTestDriver { return msg; } - Future run({bool withDebugger = false, bool pauseOnExceptions = false}) async { + Future run({ + bool withDebugger = false, + bool pauseOnExceptions = false, + File pidFile, + }) async { await _setupProcess([ 'run', '--machine', '-d', 'flutter-tester', - ], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions); + ], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile); } - Future attach(int port, {bool withDebugger = false, bool pauseOnExceptions = false}) async { + Future attach( + int port, { + bool withDebugger = false, + bool pauseOnExceptions = false, + File pidFile, + }) async { await _setupProcess([ 'attach', '--machine', @@ -73,20 +82,28 @@ class FlutterTestDriver { 'flutter-tester', '--debug-port', '$port', - ], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions); + ], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile); } - Future _setupProcess(List args, {bool withDebugger = false, bool pauseOnExceptions = false}) async { + Future _setupProcess( + List args, { + bool withDebugger = false, + bool pauseOnExceptions = false, + File pidFile, + }) async { final String flutterBin = fs.path.join(getFlutterRoot(), 'bin', 'flutter'); - final List flutterArgs = withDebugger - ? args.followedBy(['--start-paused']).toList() - : args; - _debugPrint('Spawning flutter $flutterArgs in ${_projectFolder.path}'); + if (withDebugger) { + args.add('--start-paused'); + } + if (pidFile != null) { + args.addAll(['--pid-file', pidFile.path]); + } + _debugPrint('Spawning flutter $args in ${_projectFolder.path}'); const ProcessManager _processManager = LocalProcessManager(); _proc = await _processManager.start( [flutterBin] - .followedBy(flutterArgs) + .followedBy(args) .toList(), workingDirectory: _projectFolder.path, environment: {'FLUTTER_TEST': 'true'});