Noninteractive iOS debugger session (#68145)
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index d67a70b..867b25b 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -468,12 +468,10 @@
packageName: FlutterProject.current().manifest.appName,
);
if (localUri == null) {
- iosDeployDebugger?.detach();
return LaunchResult.failed();
}
return LaunchResult.succeeded(observatoryUri: localUri);
} on ProcessException catch (e) {
- iosDeployDebugger?.detach();
_logger.printError(e.message);
return LaunchResult.failed();
} finally {
@@ -486,12 +484,7 @@
IOSApp app, {
String userIdentifier,
}) async {
- // If the debugger is not attached, killing the ios-deploy process won't stop the app.
- if (iosDeployDebugger!= null && iosDeployDebugger.debuggerAttached) {
- // Avoid null.
- return iosDeployDebugger?.exit() == true;
- }
- return false;
+ return iosDeployDebugger?.exit();
}
@override
@@ -723,8 +716,8 @@
}
void logMessage(vm_service.Event event) {
- if (_iosDeployDebugger != null && _iosDeployDebugger.debuggerAttached) {
- // Prefer the more complete logs from the attached debugger.
+ if (_iosDeployDebugger != null) {
+ // Prefer the more complete logs from the debugger.
return;
}
final String message = processVmServiceMessage(event);
@@ -739,7 +732,7 @@
]);
}
- /// Log reader will listen to [debugger.logLines] and will detach debugger on dispose.
+ /// Log reader will listen to [debugger.logLines].
set debuggerStream(IOSDeployDebugger debugger) {
// Logging is gathered from syslog on iOS 13 and earlier.
if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) {
@@ -818,7 +811,6 @@
loggingSubscription.cancel();
}
_idevicesyslogProcess?.kill();
- _iosDeployDebugger?.detach();
}
}
diff --git a/packages/flutter_tools/lib/src/ios/ios_deploy.dart b/packages/flutter_tools/lib/src/ios/ios_deploy.dart
index 84186ec..756e4ec 100644
--- a/packages/flutter_tools/lib/src/ios/ios_deploy.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_deploy.dart
@@ -118,14 +118,13 @@
/// Returns [IOSDeployDebugger] wrapping attached debugger logic.
///
/// This method does not install the app. Call [IOSDeployDebugger.launchAndAttach()]
- /// to install and attach the debugger to the specified app bundle.
+ /// to install the specified app bundle.
IOSDeployDebugger prepareDebuggerForLaunch({
@required String deviceId,
@required String bundlePath,
@required List<String> launchArguments,
@required IOSDeviceInterface interfaceType,
}) {
- // Interactive debug session to support sending the lldb detach command.
final List<String> launchCommand = <String>[
'script',
'-t',
@@ -137,6 +136,7 @@
'--bundle',
bundlePath,
'--debug',
+ '--noninteractive',
if (interfaceType != IOSDeviceInterface.network)
'--no-wifi',
if (launchArguments.isNotEmpty) ...<String>[
@@ -224,7 +224,7 @@
attached,
}
-/// Wrapper to launch app and attach the debugger with ios-deploy.
+/// Wrapper to launch app with the debugger with ios-deploy.
class IOSDeployDebugger {
IOSDeployDebugger({
@required Logger logger,
@@ -264,15 +264,16 @@
Stream<String> get logLines => _debuggerOutput.stream;
final StreamController<String> _debuggerOutput = StreamController<String>.broadcast();
- bool get debuggerAttached => _debuggerState == _IOSDeployDebuggerState.attached;
_IOSDeployDebuggerState _debuggerState;
-
// (lldb) run
// https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51
static final RegExp _lldbRun = RegExp(r'\(lldb\)\s*run');
- // (lldb) run
- // https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L51
+ // (lldb) autoexit
+ // hhttps://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L61
+ static final RegExp _lldbAutoexit = RegExp(r'\(lldb\)\s*autoexit');
+
+ // From lldb on exit.
static final RegExp _lldbProcessExit = RegExp(r'Process \d* exited with status =');
/// Launch the app on the device, and attach the debugger.
@@ -295,6 +296,7 @@
// (lldb) run
// success
+ // (lldb) autoexit
// 2020-09-15 13:42:25.185474-0700 Runner[477:181141] flutter: Observatory listening on http://127.0.0.1:57782/
if (_lldbRun.hasMatch(line)) {
_logger.printTrace(line);
@@ -324,7 +326,7 @@
}
return;
}
- if (_debuggerState != _IOSDeployDebuggerState.attached) {
+ if (_debuggerState != _IOSDeployDebuggerState.attached || _lldbAutoexit.hasMatch(line)) {
_logger.printTrace(line);
return;
}
@@ -378,21 +380,6 @@
_iosDeployProcess = null;
return success;
}
-
- void detach() {
- if (!debuggerAttached) {
- return;
- }
-
- try {
- // Detach lldb from the app process.
- _iosDeployProcess?.stdin?.writeln('process detach');
- _debuggerState = _IOSDeployDebuggerState.detached;
- } on SocketException catch (error) {
- // Best effort, try to detach, but maybe the app already exited or already detached.
- _logger.printTrace('Could not detach from debugger: $error');
- }
- }
}
// Maps stdout line stream. Must return original line.
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart
index 6ec084c..35089ac 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart
@@ -3,10 +3,8 @@
// found in the LICENSE file.
import 'dart:async';
-import 'dart:convert';
import 'package:flutter_tools/src/artifacts.dart';
-import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
@@ -50,6 +48,7 @@
'--bundle',
'/',
'--debug',
+ '--noninteractive',
'--args',
<String>[
'--enable-dart-profiling',
@@ -87,7 +86,7 @@
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ios-deploy'],
- stdout: '(lldb) run\r\nsuccess\r\nsuccess\r\nLog on attach1\r\n\r\nLog on attach2\r\n\r\n\r\n\r\nPROCESS_STOPPED\r\nLog after process exit',
+ stdout: '(lldb) run\r\nsuccess\r\n(lldb) autoexit\r\nsuccess\r\nLog on attach1\r\n\r\nLog on attach2\r\n\r\n\r\n\r\nPROCESS_STOPPED\r\nLog after process exit',
),
]);
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
@@ -111,7 +110,7 @@
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['ios-deploy'],
- stdout: '(lldb) run\r\nsuccess\r\nLog on attach\r\nProcess 100 exited with status = 0\r\nLog after process exit',
+ stdout: '(lldb) run\r\nsuccess\r\n(lldb) autoexit\r\nLog on attach\r\nProcess 100 exited with status = 0\r\nLog after process exit',
),
]);
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
@@ -226,26 +225,6 @@
expect(logger.errorText, contains('Could not attach the debugger'));
});
});
-
- testWithoutContext('detach', () async {
- final StreamController<List<int>> stdin = StreamController<List<int>>();
- final Stream<String> stdinStream = stdin.stream.transform<String>(const Utf8Decoder());
- final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
- FakeCommand(
- command: const <String>[
- 'ios-deploy',
- ],
- stdout: '(lldb) run\nsuccess',
- stdin: IOSink(stdin.sink),
- ),
- ]);
- final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
- processManager: processManager,
- );
- await iosDeployDebugger.launchAndAttach();
- iosDeployDebugger.detach();
- expect(await stdinStream.first, 'process detach');
- });
});
group('IOSDeploy.uninstallApp', () {
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart
index f866c85..ebb666b 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart
@@ -239,7 +239,6 @@
));
final MockIOSDeployDebugger iosDeployDebugger = MockIOSDeployDebugger();
- when(iosDeployDebugger.debuggerAttached).thenReturn(true);
final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[
'Message from debugger'
@@ -304,24 +303,6 @@
await streamComplete.future;
});
-
- testWithoutContext('detaches debugger', () async {
- final IOSDeviceLogReader logReader = IOSDeviceLogReader.test(
- iMobileDevice: IMobileDevice(
- artifacts: artifacts,
- processManager: processManager,
- cache: fakeCache,
- logger: logger,
- ),
- useSyslog: false,
- );
- final MockIOSDeployDebugger iosDeployDebugger = MockIOSDeployDebugger();
- when(iosDeployDebugger.logLines).thenAnswer((Invocation invocation) => const Stream<String>.empty());
- logReader.debuggerStream = iosDeployDebugger;
-
- logReader.dispose();
- verify(iosDeployDebugger.detach());
- });
});
}
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
index 2533d50..c5a2790 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart
@@ -92,6 +92,7 @@
'--bundle',
'/',
'--debug',
+ '--noninteractive',
'--no-wifi',
'--args',
'--enable-dart-profiling --enable-service-port-fallback --disable-service-auth-codes --observatory-port=60700 --enable-checked-mode --verify-entry-points'
@@ -99,7 +100,7 @@
'PATH': '/usr/bin:null',
'DYLD_LIBRARY_PATH': '/path/to/libraries',
},
-stdout: '(lldb) run\nsuccess',
+stdout: '(lldb) run\nsuccess\n(lldb) autoexit',
);
void main() {
@@ -166,7 +167,6 @@
expect(launchResult.started, true);
expect(launchResult.hasObservatory, true);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1);
- expect(await device.stopApp(iosApp), false);
}, overrides: <Type, Generator>{
Usage: () => MockUsage(),
});
@@ -216,7 +216,6 @@
expect(launchResult.started, true);
expect(launchResult.hasObservatory, true);
verify(globals.flutterUsage.sendEvent('ios-handshake', 'log-success')).called(1);
- expect(await device.stopApp(iosApp), false);
}, overrides: <Type, Generator>{
Usage: () => MockUsage(),
});
@@ -302,7 +301,6 @@
expect(launchResult.started, true);
expect(launchResult.hasObservatory, false);
- expect(await device.stopApp(iosApp), false);
expect(processManager.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
Usage: () => MockUsage(),
@@ -325,6 +323,7 @@
'--bundle',
'/',
'--debug',
+ '--noninteractive',
'--no-wifi',
// The arguments below are determined by what is passed into
// the debugging options argument to startApp.
@@ -406,67 +405,10 @@
);
expect(launchResult.started, true);
- expect(await device.stopApp(iosApp), false);
expect(processManager.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
Usage: () => MockUsage(),
});
-
- // Still uses context for analytics.
- testUsingContext(
- 'IOSDevice.startApp detaches lldb when VM service connection fails',
- () async {
- final FileSystem fileSystem = MemoryFileSystem.test();
-
- final MockIOSDeploy mockIOSDeploy = MockIOSDeploy();
- final MockIOSDeployDebugger mockIOSDeployDebugger = MockIOSDeployDebugger();
- when(mockIOSDeploy.prepareDebuggerForLaunch(
- deviceId: anyNamed('deviceId'),
- bundlePath: anyNamed('bundlePath'),
- launchArguments: anyNamed('launchArguments'),
- interfaceType: anyNamed('interfaceType')))
- .thenReturn(mockIOSDeployDebugger);
- when(mockIOSDeploy.installApp(
- deviceId: anyNamed('deviceId'),
- bundlePath: anyNamed('bundlePath'),
- launchArguments: anyNamed('launchArguments'),
- interfaceType: anyNamed('interfaceType')))
- .thenAnswer((_) async => 0);
-
- when(mockIOSDeployDebugger.launchAndAttach()).thenAnswer((_) async => true);
-
- final IOSDevice device = setUpIOSDevice(
- fileSystem: fileSystem,
- iosDeploy: mockIOSDeploy,
- vmServiceConnector: (String string, {Log log}) async {
- throw const io.SocketException(
- 'OS Error: Connection refused, errno = 61, address = localhost, port '
- '= 58943',
- );
- },
- );
- final IOSApp iosApp = PrebuiltIOSApp(
- projectBundleId: 'app',
- bundleName: 'Runner',
- bundleDir: fileSystem.currentDirectory,
- );
- device.portForwarder = const NoOpDevicePortForwarder();
- device.setLogReader(iosApp, FakeDeviceLogReader());
-
- final LaunchResult launchResult = await device.startApp(
- iosApp,
- prebuiltApplication: true,
- debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
- platformArgs: <String, dynamic>{},
- fallbackPollingDelay: Duration.zero,
- fallbackThrottleTimeout: const Duration(milliseconds: 10),
- );
-
- expect(launchResult.started, false);
- verify(mockIOSDeployDebugger.detach()).called(1);
- }, overrides: <Type, Generator>{
- Usage: () => MockUsage(),
- });
}
IOSDevice setUpIOSDevice({