[flutter_tools/dap] Add a base Flutter adapter class to avoid duplication between adapters (#114533)
This commit is contained in:
parent
78dbe6661b
commit
3b0f8335ee
@ -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
|
|||||||
@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
|
|||||||
/// 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
|
|||||||
'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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
|||||||
'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
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
|||||||
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
|
|||||||
// 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
|
|||||||
|
|
||||||
/// 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
|
|||||||
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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|||||||
|
|
||||||
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
|
|||||||
} 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,160 @@
|
|||||||
|
// 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';
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
|||||||
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 @@
|
|||||||
// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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';
|
|||||||
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 {
|
|||||||
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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user