Unverified Commit 3b0f8335 authored by Danny Tuppeny's avatar Danny Tuppeny Committed by GitHub

[flutter_tools/dap] Add a base Flutter adapter class to avoid duplication...

[flutter_tools/dap] Add a base Flutter adapter class to avoid duplication between adapters (#114533)
parent 78dbe666
...@@ -6,69 +6,35 @@ import 'dart:async'; ...@@ -6,69 +6,35 @@ import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:dds/dap.dart' hide PidTracker; import 'package:dds/dap.dart' hide PidTracker;
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' as vm; import 'package:vm_service/vm_service.dart' as vm;
import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/platform.dart';
import '../cache.dart'; import '../cache.dart';
import '../convert.dart'; import '../convert.dart';
import 'flutter_adapter_args.dart'; import 'flutter_adapter_args.dart';
import 'mixins.dart'; import 'flutter_base_adapter.dart';
/// A DAP Debug Adapter for running and debugging Flutter applications. /// A DAP Debug Adapter for running and debugging Flutter applications.
class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments, FlutterAttachRequestArguments> class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
with PidTracker, FlutterAdapter {
FlutterDebugAdapter( FlutterDebugAdapter(
super.channel, { super.channel, {
required this.fileSystem, required super.fileSystem,
required this.platform, required super.platform,
super.ipv6, super.ipv6,
bool enableDds = true, super.enableFlutterDds = true,
super.enableAuthCodes, super.enableAuthCodes,
super.logger, super.logger,
super.onError, super.onError,
}) : _enableDds = enableDds, });
flutterSdkRoot = Cache.flutterRoot!,
// Always disable in the DAP layer as it's handled in the spawned
// 'flutter' process.
super(enableDds: false) {
configureOrgDartlangSdkMappings();
}
@override
FileSystem fileSystem;
Platform platform;
Process? _process;
@override
final String flutterSdkRoot;
/// Whether DDS should be enabled in the Flutter process.
///
/// We never enable DDS in the DAP process for Flutter, so this value is not
/// the same as what is passed to the base class, which is always provided 'false'.
final bool _enableDds;
@override
final FlutterLaunchRequestArguments Function(Map<String, Object?> obj)
parseLaunchArgs = FlutterLaunchRequestArguments.fromJson;
@override
final FlutterAttachRequestArguments Function(Map<String, Object?> obj)
parseAttachArgs = FlutterAttachRequestArguments.fromJson;
/// A completer that completes when the app.started event has been received. /// A completer that completes when the app.started event has been received.
@visibleForTesting final Completer<void> _appStartedCompleter = Completer<void>();
final Completer<void> appStartedCompleter = Completer<void>();
/// Whether or not the app.started event has been received. /// Whether or not the app.started event has been received.
bool get _receivedAppStarted => appStartedCompleter.isCompleted; bool get _receivedAppStarted => _appStartedCompleter.isCompleted;
/// The appId of the current running Flutter app. /// The appId of the current running Flutter app.
@visibleForTesting String? _appId;
String? appId;
/// The ID to use for the next request sent to the Flutter run daemon. /// The ID to use for the next request sent to the Flutter run daemon.
int _flutterRequestId = 1; int _flutterRequestId = 1;
...@@ -84,13 +50,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -84,13 +50,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
@override @override
bool get supportsRestartRequest => true; bool get supportsRestartRequest => true;
/// Whether the VM Service closing should be used as a signal to terminate the debug session.
///
/// Since we always have a process for Flutter (whether run or attach) we'll
/// always use its termination instead, so this is always false.
@override
bool get terminateOnVmServiceClose => false;
/// Whether or not the user requested debugging be enabled. /// Whether or not the user requested debugging be enabled.
/// ///
/// For debugging to be enabled, the user must have chosen "Debug" (and not /// For debugging to be enabled, the user must have chosen "Debug" (and not
...@@ -104,17 +63,8 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -104,17 +63,8 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
/// functionality (breakpoints, evaluation, etc.) will not be available. /// functionality (breakpoints, evaluation, etc.) will not be available.
/// Functionality provided via the daemon (hot reload/restart) will still be /// Functionality provided via the daemon (hot reload/restart) will still be
/// available. /// available.
bool get enableDebugger { @override
final DartCommonLaunchAttachRequestArguments args = this.args; bool get enableDebugger => super.enableDebugger && !profileMode && !releaseMode;
if (args is FlutterLaunchRequestArguments) {
// Invert DAP's noDebug flag, treating it as false (so _do_ debug) if not
// provided.
return !(args.noDebug ?? false) && !profileMode && !releaseMode;
}
// Otherwise (attach), always debug.
return true;
}
/// Whether the launch configuration arguments specify `--profile`. /// Whether the launch configuration arguments specify `--profile`.
/// ///
...@@ -152,13 +102,13 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -152,13 +102,13 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
'Flutter', 'Flutter',
message: 'Attaching…', message: 'Attaching…',
); );
unawaited(appStartedCompleter.future.then((_) => progress.end())); unawaited(_appStartedCompleter.future.then((_) => progress.end()));
final String? vmServiceUri = args.vmServiceUri; final String? vmServiceUri = args.vmServiceUri;
final List<String> toolArgs = <String>[ final List<String> toolArgs = <String>[
'attach', 'attach',
'--machine', '--machine',
if (!_enableDds) '--no-dds', if (!enableFlutterDds) '--no-dds',
if (vmServiceUri != null) if (vmServiceUri != null)
...<String>['--debug-uri', vmServiceUri], ...<String>['--debug-uri', vmServiceUri],
]; ];
...@@ -206,28 +156,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -206,28 +156,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
} }
} }
@override
Future<void> debuggerConnected(vm.VM vmInfo) async {
// Usually we'd capture the pid from the VM here and record it for
// terminating, however for Flutter apps it may be running on a remote
// device so it's not valid to terminate a process with that pid locally.
// For attach, pids should never be collected as terminateRequest() should
// not terminate the debugee.
}
/// Called by [disconnectRequest] to request that we forcefully shut down the app being run (or in the case of an attach, disconnect).
///
/// Client IDEs/editors should send a terminateRequest before a
/// disconnectRequest to allow a graceful shutdown. This method must terminate
/// quickly and therefore may leave orphaned processes.
@override
Future<void> disconnectImpl() async {
if (isAttach) {
await preventBreakingAndResume();
}
terminatePids(ProcessSignal.sigkill);
}
@override @override
Future<void> handleExtensionEvent(vm.Event event) async { Future<void> handleExtensionEvent(vm.Event event) async {
await super.handleExtensionEvent(event); await super.handleExtensionEvent(event);
...@@ -274,12 +202,12 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -274,12 +202,12 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
'Flutter', 'Flutter',
message: 'Launching…', message: 'Launching…',
); );
unawaited(appStartedCompleter.future.then((_) => progress.end())); unawaited(_appStartedCompleter.future.then((_) => progress.end()));
final List<String> toolArgs = <String>[ final List<String> toolArgs = <String>[
'run', 'run',
'--machine', '--machine',
if (!_enableDds) '--no-dds', if (!enableFlutterDds) '--no-dds',
if (enableDebugger) '--start-paused', if (enableDebugger) '--start-paused',
// Structured errors are enabled by default, but since we don't connect // Structured errors are enabled by default, but since we don't connect
// the VM Service for noDebug, we need to disable them so that error text // the VM Service for noDebug, we need to disable them so that error text
...@@ -332,27 +260,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -332,27 +260,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
); );
} }
@visibleForOverriding
Future<void> launchAsProcess({
required String executable,
required List<String> processArgs,
required Map<String, String>? env,
}) async {
logger?.call('Spawning $executable with $processArgs in ${args.cwd}');
final Process process = await Process.start(
executable,
processArgs,
workingDirectory: args.cwd,
environment: env,
);
_process = process;
pidsToTerminate.add(process.pid);
process.stdout.transform(ByteToLineTransformer()).listen(_handleStdout);
process.stderr.listen(_handleStderr);
unawaited(process.exitCode.then(_handleExitCode));
}
/// restart is called by the client when the user invokes a restart (for example with the button on the debug toolbar). /// restart is called by the client when the user invokes a restart (for example with the button on the debug toolbar).
/// ///
/// For Flutter, we handle this ourselves be sending a Hot Restart request /// For Flutter, we handle this ourselves be sending a Hot Restart request
...@@ -379,7 +286,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -379,7 +286,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
Map<String, Object?>? params, { Map<String, Object?>? params, {
bool failSilently = true, bool failSilently = true,
}) async { }) async {
final Process? process = _process; final Process? process = this.process;
if (process == null) { if (process == null) {
if (failSilently) { if (failSilently) {
...@@ -416,16 +323,16 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -416,16 +323,16 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
// Send a request to stop/detach to give Flutter chance to do some cleanup. // Send a request to stop/detach to give Flutter chance to do some cleanup.
// It's possible the Flutter process will terminate before we process the // It's possible the Flutter process will terminate before we process the
// response, so accept either a response or the process exiting. // response, so accept either a response or the process exiting.
if (appId != null) { if (_appId != null) {
final String method = isAttach ? 'app.detach' : 'app.stop'; final String method = isAttach ? 'app.detach' : 'app.stop';
await Future.any<void>(<Future<void>>[ await Future.any<void>(<Future<void>>[
sendFlutterRequest(method, <String, Object?>{'appId': appId}), sendFlutterRequest(method, <String, Object?>{'appId': _appId}),
_process?.exitCode ?? Future<void>.value(), process?.exitCode ?? Future<void>.value(),
]); ]);
} }
terminatePids(ProcessSignal.sigterm); terminatePids(ProcessSignal.sigterm);
await _process?.exitCode; await process?.exitCode;
} }
/// Connects to the VM Service if the app.started event has fired, and a VM Service URI is available. /// Connects to the VM Service if the app.started event has fired, and a VM Service URI is available.
...@@ -451,21 +358,23 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -451,21 +358,23 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
/// Handles the app.start event from Flutter. /// Handles the app.start event from Flutter.
void _handleAppStart(Map<String, Object?> params) { void _handleAppStart(Map<String, Object?> params) {
appId = params['appId'] as String?; _appId = params['appId'] as String?;
if(appId == null) { if (_appId == null) {
throw DebugAdapterException('Unexpected null `appId` in app.start event'); throw DebugAdapterException('Unexpected null `appId` in app.start event');
} }
} }
/// Handles the app.started event from Flutter. /// Handles the app.started event from Flutter.
Future<void> _handleAppStarted() async { Future<void> _handleAppStarted() async {
appStartedCompleter.complete(); _appStartedCompleter.complete();
// Send a custom event so the editor knows the app has started. // Send a custom event so the editor knows the app has started.
// //
// This may be useful when there's no VM Service (for example Profile mode) // This may be useful when there's no VM Service (for example Profile mode)
// but the editor still wants to know that startup has finished. // but the editor still wants to know that startup has finished.
await debuggerInitialized; // Ensure we're fully initialized before sending. if (enableDebugger) {
await debuggerInitialized; // Ensure we're fully initialized before sending.
}
sendEvent( sendEvent(
RawEventBody(<String, Object?>{}), RawEventBody(<String, Object?>{}),
eventType: 'flutter.appStarted', eventType: 'flutter.appStarted',
...@@ -491,13 +400,14 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -491,13 +400,14 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
final Uri vmServiceUri = Uri.parse(wsUri); final Uri vmServiceUri = Uri.parse(wsUri);
// Also wait for app.started before we connect, to ensure Flutter's // Also wait for app.started before we connect, to ensure Flutter's
// initialization is all complete. // initialization is all complete.
await appStartedCompleter.future; await _appStartedCompleter.future;
await _connectDebugger(vmServiceUri); await _connectDebugger(vmServiceUri);
} }
} }
/// Handles the Flutter process exiting, terminating the debug session if it has not already begun terminating. /// Handles the Flutter process exiting, terminating the debug session if it has not already begun terminating.
void _handleExitCode(int code) { @override
void handleExitCode(int code) {
final String codeSuffix = code == 0 ? '' : ' ($code)'; final String codeSuffix = code == 0 ? '' : ' ($code)';
logger?.call('Process exited ($code)'); logger?.call('Process exited ($code)');
handleSessionTerminate(codeSuffix); handleSessionTerminate(codeSuffix);
...@@ -542,13 +452,15 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -542,13 +452,15 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
} }
} }
void _handleStderr(List<int> data) { @override
void handleStderr(List<int> data) {
logger?.call('stderr: $data'); logger?.call('stderr: $data');
sendOutput('stderr', utf8.decode(data)); sendOutput('stderr', utf8.decode(data));
} }
/// Handles stdout from the `flutter run --machine` process, decoding the JSON and calling the appropriate handlers. /// Handles stdout from the `flutter run --machine` process, decoding the JSON and calling the appropriate handlers.
void _handleStdout(String data) { @override
void handleStdout(String data) {
// Output intended for us to parse is JSON wrapped in brackets: // Output intended for us to parse is JSON wrapped in brackets:
// [{"event":"app.foo","params":{"bar":"baz"}}] // [{"event":"app.foo","params":{"bar":"baz"}}]
// However, it's also possible a user printed things that look a little like // However, it's also possible a user printed things that look a little like
...@@ -624,7 +536,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -624,7 +536,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
try { try {
await sendFlutterRequest('app.restart', <String, Object?>{ await sendFlutterRequest('app.restart', <String, Object?>{
'appId': appId, 'appId': _appId,
'fullRestart': fullRestart, 'fullRestart': fullRestart,
'pause': enableDebugger, 'pause': enableDebugger,
'reason': reason, 'reason': reason,
...@@ -633,8 +545,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -633,8 +545,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
} on DebugAdapterException catch (error) { } on DebugAdapterException catch (error) {
final String action = fullRestart ? 'Hot Restart' : 'Hot Reload'; final String action = fullRestart ? 'Hot Restart' : 'Hot Reload';
sendOutput('console', 'Failed to $action: $error'); sendOutput('console', 'Failed to $action: $error');
} } finally {
finally {
progress.end(); progress.end();
} }
} }
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:dds/dap.dart' hide PidTracker;
import 'package:vm_service/vm_service.dart' as vm;
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/platform.dart';
import '../cache.dart';
import 'flutter_adapter_args.dart';
import 'mixins.dart';
/// A base DAP Debug Adapter for Flutter applications and tests.
abstract class FlutterBaseDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments, FlutterAttachRequestArguments>
with PidTracker {
FlutterBaseDebugAdapter(
super.channel, {
required this.fileSystem,
required this.platform,
super.ipv6,
this.enableFlutterDds = true,
super.enableAuthCodes,
super.logger,
super.onError,
}) : flutterSdkRoot = Cache.flutterRoot!,
// Always disable in the DAP layer as it's handled in the spawned
// 'flutter' process.
super(enableDds: false) {
configureOrgDartlangSdkMappings();
}
FileSystem fileSystem;
Platform platform;
Process? process;
final String flutterSdkRoot;
/// Whether DDS should be enabled in the Flutter process.
///
/// We never enable DDS in the DAP process for Flutter, so this value is not
/// the same as what is passed to the base class, which is always provided 'false'.
final bool enableFlutterDds;
@override
final FlutterLaunchRequestArguments Function(Map<String, Object?> obj)
parseLaunchArgs = FlutterLaunchRequestArguments.fromJson;
@override
final FlutterAttachRequestArguments Function(Map<String, Object?> obj)
parseAttachArgs = FlutterAttachRequestArguments.fromJson;
/// Whether the VM Service closing should be used as a signal to terminate the debug session.
///
/// Since we always have a process for Flutter (whether run or attach) we'll
/// always use its termination instead, so this is always false.
@override
bool get terminateOnVmServiceClose => false;
/// Whether or not the user requested debugging be enabled.
///
/// For debugging to be enabled, the user must have chosen "Debug" (and not
/// "Run") in the editor (which maps to the DAP `noDebug` field).
bool get enableDebugger {
final DartCommonLaunchAttachRequestArguments args = this.args;
if (args is FlutterLaunchRequestArguments) {
// Invert DAP's noDebug flag, treating it as false (so _do_ debug) if not
// provided.
return !(args.noDebug ?? false);
}
// Otherwise (attach), always debug.
return true;
}
void configureOrgDartlangSdkMappings() {
/// When a user navigates into 'dart:xxx' sources in their editor (via the
/// analysis server) they will land in flutter_sdk/bin/cache/pkg/sky_engine.
///
/// The running VM knows nothing about these paths and will resolve these
/// libraries to 'org-dartlang-sdk://' URIs. We need to map between these
/// to ensure that if a user puts a breakpoint inside sky_engine the VM can
/// apply it to the correct place and once hit, we can navigate the user
/// back to the correct file on their disk.
///
/// The mapping is handled by the base adapter but we need to override the
/// paths to match the layout used by Flutter.
///
/// In future this might become unnecessary if
/// https://github.com/dart-lang/sdk/issues/48435 is implemented. Until
/// then, providing these mappings improves the debugging experience.
// Clear original Dart SDK mappings because they're not valid here.
orgDartlangSdkMappings.clear();
// 'dart:ui' maps to /flutter/lib/ui
final String flutterRoot = fileSystem.path.join(flutterSdkRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui');
orgDartlangSdkMappings[flutterRoot] = Uri.parse('org-dartlang-sdk:///flutter/lib/ui');
// The rest of the Dart SDK maps to /third_party/dart/sdk
final String dartRoot = fileSystem.path.join(flutterSdkRoot, 'bin', 'cache', 'pkg', 'sky_engine');
orgDartlangSdkMappings[dartRoot] = Uri.parse('org-dartlang-sdk:///third_party/dart/sdk');
}
@override
Future<void> debuggerConnected(vm.VM vmInfo) async {
// Usually we'd capture the pid from the VM here and record it for
// terminating, however for Flutter apps it may be running on a remote
// device so it's not valid to terminate a process with that pid locally.
// For attach, pids should never be collected as terminateRequest() should
// not terminate the debugee.
}
/// Called by [disconnectRequest] to request that we forcefully shut down the app being run (or in the case of an attach, disconnect).
///
/// Client IDEs/editors should send a terminateRequest before a
/// disconnectRequest to allow a graceful shutdown. This method must terminate
/// quickly and therefore may leave orphaned processes.
@override
Future<void> disconnectImpl() async {
if (isAttach) {
await preventBreakingAndResume();
}
terminatePids(ProcessSignal.sigkill);
}
Future<void> launchAsProcess({
required String executable,
required List<String> processArgs,
required Map<String, String>? env,
}) async {
final Process process = await (
String executable,
List<String> processArgs, {
required Map<String, String>? env,
}) async {
logger?.call('Spawning $executable with $processArgs in ${args.cwd}');
final Process process = await Process.start(
executable,
processArgs,
workingDirectory: args.cwd,
environment: env,
);
pidsToTerminate.add(process.pid);
return process;
}(executable, processArgs, env: env);
this.process = process;
process.stdout.transform(ByteToLineTransformer()).listen(handleStdout);
process.stderr.listen(handleStderr);
unawaited(process.exitCode.then(handleExitCode));
}
void handleExitCode(int code);
void handleStderr(List<int> data);
void handleStdout(String data);
}
...@@ -6,64 +6,25 @@ import 'dart:async'; ...@@ -6,64 +6,25 @@ import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:dds/dap.dart' hide PidTracker; import 'package:dds/dap.dart' hide PidTracker;
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' as vm;
import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/platform.dart';
import '../cache.dart'; import '../cache.dart';
import '../convert.dart'; import '../convert.dart';
import 'flutter_adapter_args.dart'; import 'flutter_adapter_args.dart';
import 'mixins.dart'; import 'flutter_base_adapter.dart';
/// A DAP Debug Adapter for running and debugging Flutter tests. /// A DAP Debug Adapter for running and debugging Flutter tests.
class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments, FlutterAttachRequestArguments> class FlutterTestDebugAdapter extends FlutterBaseDebugAdapter with TestAdapter {
with PidTracker, FlutterAdapter, TestAdapter {
FlutterTestDebugAdapter( FlutterTestDebugAdapter(
super.channel, { super.channel, {
required this.fileSystem, required super.fileSystem,
required this.platform, required super.platform,
super.ipv6, super.ipv6,
bool enableDds = true, super.enableFlutterDds = true,
super.enableAuthCodes, super.enableAuthCodes,
super.logger, super.logger,
super.onError, super.onError,
}) : _enableDds = enableDds, });
flutterSdkRoot = Cache.flutterRoot!,
// Always disable in the DAP layer as it's handled in the spawned
// 'flutter' process.
super(enableDds: false) {
configureOrgDartlangSdkMappings();
}
@override
FileSystem fileSystem;
Platform platform;
Process? _process;
@override
final String flutterSdkRoot;
/// Whether DDS should be enabled in the Flutter process.
///
/// We never enable DDS in the DAP process for Flutter, so this value is not
/// the same as what is passed to the base class, which is always provided 'false'.
final bool _enableDds;
@override
final FlutterLaunchRequestArguments Function(Map<String, Object?> obj)
parseLaunchArgs = FlutterLaunchRequestArguments.fromJson;
@override
final FlutterAttachRequestArguments Function(Map<String, Object?> obj)
parseAttachArgs = FlutterAttachRequestArguments.fromJson;
/// Whether the VM Service closing should be used as a signal to terminate the debug session.
///
/// Since we do not support attaching for tests, this is always false.
@override
bool get terminateOnVmServiceClose => false;
/// Called by [attachRequest] to request that we actually connect to the app to be debugged. /// Called by [attachRequest] to request that we actually connect to the app to be debugged.
@override @override
...@@ -72,45 +33,6 @@ class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArgum ...@@ -72,45 +33,6 @@ class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArgum
handleSessionTerminate(); handleSessionTerminate();
} }
@override
Future<void> debuggerConnected(vm.VM vmInfo) async {
// Capture the PID from the VM Service so that we can terminate it when
// cleaning up. Terminating the process might not be enough as it could be
// just a shell script (e.g. pub on Windows) and may not pass the
// signal on correctly.
// See: https://github.com/Dart-Code/Dart-Code/issues/907
final int? pid = vmInfo.pid;
if (pid != null) {
pidsToTerminate.add(pid);
}
}
/// Called by [disconnectRequest] to request that we forcefully shut down the app being run (or in the case of an attach, disconnect).
///
/// Client IDEs/editors should send a terminateRequest before a
/// disconnectRequest to allow a graceful shutdown. This method must terminate
/// quickly and therefore may leave orphaned processes.
@override
Future<void> disconnectImpl() async {
terminatePids(ProcessSignal.sigkill);
}
/// Whether or not the user requested debugging be enabled.
///
/// For debugging to be enabled, the user must have chosen "Debug" (and not
/// "Run") in the editor (which maps to the DAP `noDebug` field).
bool get enableDebugger {
final DartCommonLaunchAttachRequestArguments args = this.args;
if (args is FlutterLaunchRequestArguments) {
// Invert DAP's noDebug flag, treating it as false (so _do_ debug) if not
// provided.
return !(args.noDebug ?? false);
}
// Otherwise (attach), always debug.
return true;
}
/// Called by [launchRequest] to request that we actually start the tests to be run/debugged. /// Called by [launchRequest] to request that we actually start the tests to be run/debugged.
/// ///
/// For debugging, this should start paused, connect to the VM Service, set /// For debugging, this should start paused, connect to the VM Service, set
...@@ -125,7 +47,7 @@ class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArgum ...@@ -125,7 +47,7 @@ class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArgum
final List<String> toolArgs = <String>[ final List<String> toolArgs = <String>[
'test', 'test',
'--machine', '--machine',
if (!_enableDds) '--no-dds', if (!enableFlutterDds) '--no-dds',
if (debug) '--start-paused', if (debug) '--start-paused',
]; ];
...@@ -155,36 +77,16 @@ class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArgum ...@@ -155,36 +77,16 @@ class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArgum
} }
} }
@visibleForOverriding
Future<void> launchAsProcess({
required String executable,
required List<String> processArgs,
required Map<String, String>? env,
}) async {
logger?.call('Spawning $executable with $processArgs in ${args.cwd}');
final Process process = await Process.start(
executable,
processArgs,
workingDirectory: args.cwd,
environment: env,
);
_process = process;
pidsToTerminate.add(process.pid);
process.stdout.transform(ByteToLineTransformer()).listen(_handleStdout);
process.stderr.listen(_handleStderr);
unawaited(process.exitCode.then(_handleExitCode));
}
/// Called by [terminateRequest] to request that we gracefully shut down the app being run (or in the case of an attach, disconnect). /// Called by [terminateRequest] to request that we gracefully shut down the app being run (or in the case of an attach, disconnect).
@override @override
Future<void> terminateImpl() async { Future<void> terminateImpl() async {
terminatePids(ProcessSignal.sigterm); terminatePids(ProcessSignal.sigterm);
await _process?.exitCode; await process?.exitCode;
} }
/// Handles the Flutter process exiting, terminating the debug session if it has not already begun terminating. /// Handles the Flutter process exiting, terminating the debug session if it has not already begun terminating.
void _handleExitCode(int code) { @override
void handleExitCode(int code) {
final String codeSuffix = code == 0 ? '' : ' ($code)'; final String codeSuffix = code == 0 ? '' : ' ($code)';
logger?.call('Process exited ($code)'); logger?.call('Process exited ($code)');
handleSessionTerminate(codeSuffix); handleSessionTerminate(codeSuffix);
...@@ -202,13 +104,15 @@ class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArgum ...@@ -202,13 +104,15 @@ class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArgum
return false; return false;
} }
void _handleStderr(List<int> data) { @override
void handleStderr(List<int> data) {
logger?.call('stderr: $data'); logger?.call('stderr: $data');
sendOutput('stderr', utf8.decode(data)); sendOutput('stderr', utf8.decode(data));
} }
/// Handles stdout from the `flutter test --machine` process, decoding the JSON and calling the appropriate handlers. /// Handles stdout from the `flutter test --machine` process, decoding the JSON and calling the appropriate handlers.
void _handleStdout(String data) { @override
void handleStdout(String data) {
// Output to stdout from `flutter test --machine` is either: // Output to stdout from `flutter test --machine` is either:
// 1. JSON output from flutter_tools (eg. "test.startedProcess") which is // 1. JSON output from flutter_tools (eg. "test.startedProcess") which is
// wrapped in [] brackets and has an event/params. // wrapped in [] brackets and has an event/params.
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
/// A mixin for tracking additional PIDs that can be shut down at the end of a debug session. /// A mixin for tracking additional PIDs that can be shut down at the end of a debug session.
...@@ -23,38 +22,3 @@ mixin PidTracker { ...@@ -23,38 +22,3 @@ mixin PidTracker {
pidsToTerminate.forEach(signal.send); pidsToTerminate.forEach(signal.send);
} }
} }
mixin FlutterAdapter {
Map<String, Uri> get orgDartlangSdkMappings;
String get flutterSdkRoot;
FileSystem get fileSystem;
void configureOrgDartlangSdkMappings() {
/// When a user navigates into 'dart:xxx' sources in their editor (via the
/// analysis server) they will land in flutter_sdk/bin/cache/pkg/sky_engine.
///
/// The running VM knows nothing about these paths and will resolve these
/// libraries to 'org-dartlang-sdk://' URIs. We need to map between these
/// to ensure that if a user puts a breakpoint inside sky_engine the VM can
/// apply it to the correct place and once hit, we can navigate the user
/// back to the correct file on their disk.
///
/// The mapping is handled by the base adapter but we need to override the
/// paths to match the layout used by Flutter.
///
/// In future this might become unnecessary if
/// https://github.com/dart-lang/sdk/issues/48435 is implemented. Until
/// then, providing these mappings improves the debugging experience.
// Clear original Dart SDK mappings because they're not valid here.
orgDartlangSdkMappings.clear();
// 'dart:ui' maps to /flutter/lib/ui
final String flutterRoot = fileSystem.path.join(flutterSdkRoot, 'bin', 'cache', 'pkg', 'sky_engine', 'lib', 'ui');
orgDartlangSdkMappings[flutterRoot] = Uri.parse('org-dartlang-sdk:///flutter/lib/ui');
// The rest of the Dart SDK maps to /third_party/dart/sdk
final String dartRoot = fileSystem.path.join(flutterSdkRoot, 'bin', 'cache', 'pkg', 'sky_engine');
orgDartlangSdkMappings[dartRoot] = Uri.parse('org-dartlang-sdk:///third_party/dart/sdk');
}
}
...@@ -38,7 +38,7 @@ class DapServer { ...@@ -38,7 +38,7 @@ class DapServer {
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
ipv6: ipv6, ipv6: ipv6,
enableDds: enableDds, enableFlutterDds: enableDds,
enableAuthCodes: enableAuthCodes, enableAuthCodes: enableAuthCodes,
logger: logger, logger: logger,
onError: onError, onError: onError,
...@@ -47,7 +47,7 @@ class DapServer { ...@@ -47,7 +47,7 @@ class DapServer {
channel, channel,
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
enableDds: enableDds, enableFlutterDds: enableDds,
enableAuthCodes: enableAuthCodes, enableAuthCodes: enableAuthCodes,
logger: logger, logger: logger,
onError: onError, onError: onError,
......
...@@ -7,6 +7,7 @@ import 'dart:async'; ...@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:dds/dap.dart'; import 'package:dds/dap.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/debug_adapters/flutter_adapter.dart'; import 'package:flutter_tools/src/debug_adapters/flutter_adapter.dart';
import 'package:flutter_tools/src/debug_adapters/flutter_test_adapter.dart'; import 'package:flutter_tools/src/debug_adapters/flutter_test_adapter.dart';
...@@ -59,14 +60,29 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { ...@@ -59,14 +60,29 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
this.processArgs = processArgs; this.processArgs = processArgs;
this.env = env; this.env = env;
// Pretend we launched the app and got the app.started event so that // Simulate the app starting by triggering handling of events that Flutter
// launchRequest will complete. // would usually write to stdout.
if (simulateAppStarted) { if (simulateAppStarted) {
appId = 'TEST'; simulateStdoutMessage(<String, Object?>{
appStartedCompleter.complete(); 'event': 'app.started',
});
simulateStdoutMessage(<String, Object?>{
'event': 'app.start',
'params': <String, Object?>{
'appId': 'TEST',
}
});
} }
} }
/// Simulates a message emitted by the `flutter run` process by directly
/// calling the debug adapters [handleStdout] method.
void simulateStdoutMessage(Map<String, Object?> message) {
// Messages are wrapped in a list because Flutter only processes messages
// wrapped in brackets.
handleStdout(jsonEncode(<Object?>[message]));
}
@override @override
Future<Object?> sendFlutterRequest( Future<Object?> sendFlutterRequest(
String method, String method,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment