Skip to content

Commit 3b0f833

Browse files
authored
[flutter_tools/dap] Add a base Flutter adapter class to avoid duplication between adapters (#114533)
1 parent 78dbe66 commit 3b0f833

File tree

6 files changed

+231
-276
lines changed

6 files changed

+231
-276
lines changed

packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart

Lines changed: 35 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -6,69 +6,35 @@ import 'dart:async';
66
import 'dart:math' as math;
77

88
import 'package:dds/dap.dart' hide PidTracker;
9-
import 'package:meta/meta.dart';
109
import 'package:vm_service/vm_service.dart' as vm;
1110

12-
import '../base/file_system.dart';
1311
import '../base/io.dart';
14-
import '../base/platform.dart';
1512
import '../cache.dart';
1613
import '../convert.dart';
1714
import 'flutter_adapter_args.dart';
18-
import 'mixins.dart';
15+
import 'flutter_base_adapter.dart';
1916

2017
/// A DAP Debug Adapter for running and debugging Flutter applications.
21-
class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments, FlutterAttachRequestArguments>
22-
with PidTracker, FlutterAdapter {
18+
class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
2319
FlutterDebugAdapter(
2420
super.channel, {
25-
required this.fileSystem,
26-
required this.platform,
21+
required super.fileSystem,
22+
required super.platform,
2723
super.ipv6,
28-
bool enableDds = true,
24+
super.enableFlutterDds = true,
2925
super.enableAuthCodes,
3026
super.logger,
3127
super.onError,
32-
}) : _enableDds = enableDds,
33-
flutterSdkRoot = Cache.flutterRoot!,
34-
// Always disable in the DAP layer as it's handled in the spawned
35-
// 'flutter' process.
36-
super(enableDds: false) {
37-
configureOrgDartlangSdkMappings();
38-
}
39-
40-
@override
41-
FileSystem fileSystem;
42-
Platform platform;
43-
Process? _process;
44-
45-
@override
46-
final String flutterSdkRoot;
47-
48-
/// Whether DDS should be enabled in the Flutter process.
49-
///
50-
/// We never enable DDS in the DAP process for Flutter, so this value is not
51-
/// the same as what is passed to the base class, which is always provided 'false'.
52-
final bool _enableDds;
53-
54-
@override
55-
final FlutterLaunchRequestArguments Function(Map<String, Object?> obj)
56-
parseLaunchArgs = FlutterLaunchRequestArguments.fromJson;
57-
58-
@override
59-
final FlutterAttachRequestArguments Function(Map<String, Object?> obj)
60-
parseAttachArgs = FlutterAttachRequestArguments.fromJson;
28+
});
6129

6230
/// A completer that completes when the app.started event has been received.
63-
@visibleForTesting
64-
final Completer<void> appStartedCompleter = Completer<void>();
31+
final Completer<void> _appStartedCompleter = Completer<void>();
6532

6633
/// Whether or not the app.started event has been received.
67-
bool get _receivedAppStarted => appStartedCompleter.isCompleted;
34+
bool get _receivedAppStarted => _appStartedCompleter.isCompleted;
6835

6936
/// The appId of the current running Flutter app.
70-
@visibleForTesting
71-
String? appId;
37+
String? _appId;
7238

7339
/// The ID to use for the next request sent to the Flutter run daemon.
7440
int _flutterRequestId = 1;
@@ -84,13 +50,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
8450
@override
8551
bool get supportsRestartRequest => true;
8652

87-
/// Whether the VM Service closing should be used as a signal to terminate the debug session.
88-
///
89-
/// Since we always have a process for Flutter (whether run or attach) we'll
90-
/// always use its termination instead, so this is always false.
91-
@override
92-
bool get terminateOnVmServiceClose => false;
93-
9453
/// Whether or not the user requested debugging be enabled.
9554
///
9655
/// For debugging to be enabled, the user must have chosen "Debug" (and not
@@ -104,17 +63,8 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
10463
/// functionality (breakpoints, evaluation, etc.) will not be available.
10564
/// Functionality provided via the daemon (hot reload/restart) will still be
10665
/// available.
107-
bool get enableDebugger {
108-
final DartCommonLaunchAttachRequestArguments args = this.args;
109-
if (args is FlutterLaunchRequestArguments) {
110-
// Invert DAP's noDebug flag, treating it as false (so _do_ debug) if not
111-
// provided.
112-
return !(args.noDebug ?? false) && !profileMode && !releaseMode;
113-
}
114-
115-
// Otherwise (attach), always debug.
116-
return true;
117-
}
66+
@override
67+
bool get enableDebugger => super.enableDebugger && !profileMode && !releaseMode;
11868

11969
/// Whether the launch configuration arguments specify `--profile`.
12070
///
@@ -152,13 +102,13 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
152102
'Flutter',
153103
message: 'Attaching…',
154104
);
155-
unawaited(appStartedCompleter.future.then((_) => progress.end()));
105+
unawaited(_appStartedCompleter.future.then((_) => progress.end()));
156106

157107
final String? vmServiceUri = args.vmServiceUri;
158108
final List<String> toolArgs = <String>[
159109
'attach',
160110
'--machine',
161-
if (!_enableDds) '--no-dds',
111+
if (!enableFlutterDds) '--no-dds',
162112
if (vmServiceUri != null)
163113
...<String>['--debug-uri', vmServiceUri],
164114
];
@@ -206,28 +156,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
206156
}
207157
}
208158

209-
@override
210-
Future<void> debuggerConnected(vm.VM vmInfo) async {
211-
// Usually we'd capture the pid from the VM here and record it for
212-
// terminating, however for Flutter apps it may be running on a remote
213-
// device so it's not valid to terminate a process with that pid locally.
214-
// For attach, pids should never be collected as terminateRequest() should
215-
// not terminate the debugee.
216-
}
217-
218-
/// Called by [disconnectRequest] to request that we forcefully shut down the app being run (or in the case of an attach, disconnect).
219-
///
220-
/// Client IDEs/editors should send a terminateRequest before a
221-
/// disconnectRequest to allow a graceful shutdown. This method must terminate
222-
/// quickly and therefore may leave orphaned processes.
223-
@override
224-
Future<void> disconnectImpl() async {
225-
if (isAttach) {
226-
await preventBreakingAndResume();
227-
}
228-
terminatePids(ProcessSignal.sigkill);
229-
}
230-
231159
@override
232160
Future<void> handleExtensionEvent(vm.Event event) async {
233161
await super.handleExtensionEvent(event);
@@ -274,12 +202,12 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
274202
'Flutter',
275203
message: 'Launching…',
276204
);
277-
unawaited(appStartedCompleter.future.then((_) => progress.end()));
205+
unawaited(_appStartedCompleter.future.then((_) => progress.end()));
278206

279207
final List<String> toolArgs = <String>[
280208
'run',
281209
'--machine',
282-
if (!_enableDds) '--no-dds',
210+
if (!enableFlutterDds) '--no-dds',
283211
if (enableDebugger) '--start-paused',
284212
// Structured errors are enabled by default, but since we don't connect
285213
// the VM Service for noDebug, we need to disable them so that error text
@@ -332,27 +260,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
332260
);
333261
}
334262

335-
@visibleForOverriding
336-
Future<void> launchAsProcess({
337-
required String executable,
338-
required List<String> processArgs,
339-
required Map<String, String>? env,
340-
}) async {
341-
logger?.call('Spawning $executable with $processArgs in ${args.cwd}');
342-
final Process process = await Process.start(
343-
executable,
344-
processArgs,
345-
workingDirectory: args.cwd,
346-
environment: env,
347-
);
348-
_process = process;
349-
pidsToTerminate.add(process.pid);
350-
351-
process.stdout.transform(ByteToLineTransformer()).listen(_handleStdout);
352-
process.stderr.listen(_handleStderr);
353-
unawaited(process.exitCode.then(_handleExitCode));
354-
}
355-
356263
/// restart is called by the client when the user invokes a restart (for example with the button on the debug toolbar).
357264
///
358265
/// For Flutter, we handle this ourselves be sending a Hot Restart request
@@ -379,7 +286,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
379286
Map<String, Object?>? params, {
380287
bool failSilently = true,
381288
}) async {
382-
final Process? process = _process;
289+
final Process? process = this.process;
383290

384291
if (process == null) {
385292
if (failSilently) {
@@ -416,16 +323,16 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
416323
// Send a request to stop/detach to give Flutter chance to do some cleanup.
417324
// It's possible the Flutter process will terminate before we process the
418325
// response, so accept either a response or the process exiting.
419-
if (appId != null) {
326+
if (_appId != null) {
420327
final String method = isAttach ? 'app.detach' : 'app.stop';
421328
await Future.any<void>(<Future<void>>[
422-
sendFlutterRequest(method, <String, Object?>{'appId': appId}),
423-
_process?.exitCode ?? Future<void>.value(),
329+
sendFlutterRequest(method, <String, Object?>{'appId': _appId}),
330+
process?.exitCode ?? Future<void>.value(),
424331
]);
425332
}
426333

427334
terminatePids(ProcessSignal.sigterm);
428-
await _process?.exitCode;
335+
await process?.exitCode;
429336
}
430337

431338
/// 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
451358

452359
/// Handles the app.start event from Flutter.
453360
void _handleAppStart(Map<String, Object?> params) {
454-
appId = params['appId'] as String?;
455-
if(appId == null) {
361+
_appId = params['appId'] as String?;
362+
if (_appId == null) {
456363
throw DebugAdapterException('Unexpected null `appId` in app.start event');
457364
}
458365
}
459366

460367
/// Handles the app.started event from Flutter.
461368
Future<void> _handleAppStarted() async {
462-
appStartedCompleter.complete();
369+
_appStartedCompleter.complete();
463370

464371
// Send a custom event so the editor knows the app has started.
465372
//
466373
// This may be useful when there's no VM Service (for example Profile mode)
467374
// but the editor still wants to know that startup has finished.
468-
await debuggerInitialized; // Ensure we're fully initialized before sending.
375+
if (enableDebugger) {
376+
await debuggerInitialized; // Ensure we're fully initialized before sending.
377+
}
469378
sendEvent(
470379
RawEventBody(<String, Object?>{}),
471380
eventType: 'flutter.appStarted',
@@ -491,13 +400,14 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
491400
final Uri vmServiceUri = Uri.parse(wsUri);
492401
// Also wait for app.started before we connect, to ensure Flutter's
493402
// initialization is all complete.
494-
await appStartedCompleter.future;
403+
await _appStartedCompleter.future;
495404
await _connectDebugger(vmServiceUri);
496405
}
497406
}
498407

499408
/// Handles the Flutter process exiting, terminating the debug session if it has not already begun terminating.
500-
void _handleExitCode(int code) {
409+
@override
410+
void handleExitCode(int code) {
501411
final String codeSuffix = code == 0 ? '' : ' ($code)';
502412
logger?.call('Process exited ($code)');
503413
handleSessionTerminate(codeSuffix);
@@ -542,13 +452,15 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
542452
}
543453
}
544454

545-
void _handleStderr(List<int> data) {
455+
@override
456+
void handleStderr(List<int> data) {
546457
logger?.call('stderr: $data');
547458
sendOutput('stderr', utf8.decode(data));
548459
}
549460

550461
/// Handles stdout from the `flutter run --machine` process, decoding the JSON and calling the appropriate handlers.
551-
void _handleStdout(String data) {
462+
@override
463+
void handleStdout(String data) {
552464
// Output intended for us to parse is JSON wrapped in brackets:
553465
// [{"event":"app.foo","params":{"bar":"baz"}}]
554466
// However, it's also possible a user printed things that look a little like
@@ -624,7 +536,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
624536

625537
try {
626538
await sendFlutterRequest('app.restart', <String, Object?>{
627-
'appId': appId,
539+
'appId': _appId,
628540
'fullRestart': fullRestart,
629541
'pause': enableDebugger,
630542
'reason': reason,
@@ -633,8 +545,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
633545
} on DebugAdapterException catch (error) {
634546
final String action = fullRestart ? 'Hot Restart' : 'Hot Reload';
635547
sendOutput('console', 'Failed to $action: $error');
636-
}
637-
finally {
548+
} finally {
638549
progress.end();
639550
}
640551
}

0 commit comments

Comments
 (0)