| // 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 /flutter/third_party/dart/sdk |
| final String dartRoot = fileSystem.path.join(flutterSdkRoot, 'bin', 'cache', 'pkg', 'sky_engine'); |
| orgDartlangSdkMappings[dartRoot] = Uri.parse('org-dartlang-sdk:///flutter/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 debugger. |
| } |
| |
| /// 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 handleDetach(); |
| } |
| 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); |
| } |