Skip to content

Commit

Permalink
Add support for attachRequest in DAP, running "flutter attach" (#97652)
Browse files Browse the repository at this point in the history
* Add support for attachRequest in DAP, which runs "flutter attach"

* Update DAP docs for attachRequest

* Improve doc comments

* Fix comments

* Remove noDebug from attach + create a getter for `debug`

* Fix indent
  • Loading branch information
DanTup authored Feb 7, 2022
1 parent ba01ec8 commit c659ad6
Show file tree
Hide file tree
Showing 6 changed files with 569 additions and 256 deletions.
12 changes: 7 additions & 5 deletions packages/flutter_tools/lib/src/debug_adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,20 @@ Arguments common to both `launchRequest` and `attachRequest` are:
- `bool? evaluateToStringInDebugViews` - whether to invoke `toString()` in expression evaluation requests (inc. hovers/watch windows) (if not supplied, defaults to `false`)
- `bool? sendLogsToClient` - used to proxy all VM Service traffic back to the client in custom `dart.log` events (has performance implications, intended for troubleshooting) (if not supplied, defaults to `false`)
- `List<String>? additionalProjectPaths` - paths of any projects (outside of `cwd`) that are open in the users workspace
- `String? cwd` - the working directory for the Dart process to be spawned in
- `String? cwd` - the working directory for the Flutter process to be spawned in
- `List<String>? toolArgs` - arguments for the `flutter run`, `flutter attach` or `flutter test` commands
- `String? customTool` - an optional tool to run instead of `flutter` - the custom tool must be completely compatible with the tool/command it is replacing
- `int? customToolReplacesArgs` - the number of arguments to delete from the beginning of the argument list when invoking `customTool` - e.g. setting `customTool` to `flutter_test_wrapper` and `customToolReplacesArgs` to `1` for a test run would invoke `flutter_test_wrapper foo_test.dart` instead of `flutter test foo_test.dart` (if larger than the number of computed arguments all arguments will be removed, if not supplied will default to `0`)

Arguments specific to `launchRequest` are:

- `bool? noDebug` - whether to run in debug or noDebug mode (if not supplied, defaults to debug)
- `String program` - the path of the Flutter application to run
- `List<String>? args` - arguments to be passed to the Flutter program
- `List<String>? toolArgs` - arguments for the `flutter run` or `flutter test` commands
- `String? customTool` - an optional tool to run instead of `flutter` - the custom tool must be completely compatible with the tool/command it is replacing
- `int? customToolReplacesArgs` - the number of arguments to delete from the beginning of the argument list when invoking `customTool` - e.g. setting `customTool` to `flutter_test_wrapper` and `customToolReplacesArgs` to `1` for a test run would invoke `flutter_test_wrapper foo_test.dart` instead of `flutter test foo_test.dart` (if larger than the number of computed arguments all arguments will be removed, if not supplied will default to `0`)

`attachRequest` is not currently supported, but will be documented here when it is.
Arguments specific to `attachRequest` are:

- `String? vmServiceUri` - the VM Service URI to attach to (if not supplied, Flutter will try to discover it from the device)

## Custom Requests

Expand Down
111 changes: 79 additions & 32 deletions packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,51 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
@override
bool get terminateOnVmServiceClose => false;

/// Whether or not the user requested debugging be enabled.
///
/// debug/noDebug here refers to the DAP "debug" mode and not the Flutter
/// debug mode (vs Profile/Release). It is provided by the client editor based
/// on whether a user chooses to "Run" or "Debug" their app.
///
/// This is always enabled for attach requests, but can be disabled for launch
/// requests via DAP's `noDebug` flag. If `noDebug` is not provided, will
/// default to debugging.
///
/// When not debugging, we will not connect to the VM Service so some
/// functionality (breakpoints, evaluation, etc.) will not be available.
/// Functionality provided via the daemon (hot reload/restart) will still be
/// available.
bool get debug {
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 [attachRequest] to request that we actually connect to the app to be debugged.
@override
Future<void> attachImpl() async {
sendOutput('console', '\nAttach is not currently supported');
handleSessionTerminate();
final FlutterAttachRequestArguments args = this.args as FlutterAttachRequestArguments;

final String? vmServiceUri = args.vmServiceUri;
final List<String> toolArgs = <String>[
'attach',
'--machine',
if (vmServiceUri != null)
...<String>['--debug-uri', vmServiceUri],
];

await _startProcess(
toolArgs: toolArgs,
customTool: args.customTool,
customToolReplacesArgs: args.customToolReplacesArgs,
userToolArgs: args.toolArgs,
);
}

/// [customRequest] handles any messages that do not match standard messages in the spec.
Expand Down Expand Up @@ -171,34 +211,46 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
Future<void> launchImpl() async {
final FlutterLaunchRequestArguments args = this.args as FlutterLaunchRequestArguments;

// "debug"/"noDebug" refers to the DAP "debug" mode and not the Flutter
// debug mode (vs Profile/Release). It is possible for the user to "Run"
// from VS Code (eg. not want to hit breakpoints/etc.) but still be running
// a debug build.
final bool debug = !(args.noDebug ?? false);
final String? program = args.program;

final List<String> toolArgs = <String>[
'run',
'--machine',
if (debug) '--start-paused',
];

await _startProcess(
toolArgs: toolArgs,
customTool: args.customTool,
customToolReplacesArgs: args.customToolReplacesArgs,
targetProgram: args.program,
userToolArgs: args.toolArgs,
userArgs: args.args,
);
}

/// Starts the `flutter` process to run/attach to the required app.
Future<void> _startProcess({
required String? customTool,
required int? customToolReplacesArgs,
required List<String> toolArgs,
required List<String>? userToolArgs,
String? targetProgram,
List<String>? userArgs,
}) async {
// Handle customTool and deletion of any arguments for it.
final String executable = args.customTool ?? fileSystem.path.join(Cache.flutterRoot!, 'bin', platform.isWindows ? 'flutter.bat' : 'flutter');
final int? removeArgs = args.customToolReplacesArgs;
if (args.customTool != null && removeArgs != null) {
final String executable = customTool ?? fileSystem.path.join(Cache.flutterRoot!, 'bin', platform.isWindows ? 'flutter.bat' : 'flutter');
final int? removeArgs = customToolReplacesArgs;
if (customTool != null && removeArgs != null) {
toolArgs.removeRange(0, math.min(removeArgs, toolArgs.length));
}

final List<String> processArgs = <String>[
...toolArgs,
...?args.toolArgs,
if (program != null) ...<String>[
...?userToolArgs,
if (targetProgram != null) ...<String>[
'--target',
program,
targetProgram,
],
...?args.args,
...?userArgs,
];

// Find the package_config file for this script. This is used by the
Expand All @@ -207,12 +259,12 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
// be correctly classes as "my code", "sdk" or "external packages".
// TODO(dantup): Remove this once https://github.com/dart-lang/sdk/issues/45530
// is done as it will not be necessary.
final String? possibleRoot = program == null
final String? possibleRoot = targetProgram == null
? args.cwd
: fileSystem.path.isAbsolute(program)
? fileSystem.path.dirname(program)
: fileSystem.path.isAbsolute(targetProgram)
? fileSystem.path.dirname(targetProgram)
: fileSystem.path.dirname(
fileSystem.path.normalize(fileSystem.path.join(args.cwd ?? '', args.program)));
fileSystem.path.normalize(fileSystem.path.join(args.cwd ?? '', targetProgram)));
if (possibleRoot != null) {
final File? packageConfig = findPackageConfigFile(possibleRoot);
if (packageConfig != null) {
Expand Down Expand Up @@ -330,8 +382,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
void _handleDebugPort(Map<String, Object?> params) {
// When running in noDebug mode, Flutter may still provide us a VM Service
// URI, but we will not connect it because we don't want to do any debugging.
final FlutterLaunchRequestArguments args = this.args as FlutterLaunchRequestArguments;
final bool debug = !(args.noDebug ?? false);
if (!debug) {
return;
}
Expand Down Expand Up @@ -411,13 +461,13 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
// general output printed by the user.
final String outputCategory = _receivedAppStarted ? 'stdout' : 'console';

// Output in stdout can include both user output (eg. print) and Flutter
// daemon output. Since it's not uncommon for users to print JSON while
// debugging, we must try to detect which messages are likely Flutter
// messages as reliably as possible, as trying to process users output
// as a Flutter message may result in an unhandled error that will
// terminate the debug adater in a way that does not provide feedback
// because the standard crash violates the DAP protocol.
// Output in stdout can include both user output (eg. print) and Flutter
// daemon output. Since it's not uncommon for users to print JSON while
// debugging, we must try to detect which messages are likely Flutter
// messages as reliably as possible, as trying to process users output
// as a Flutter message may result in an unhandled error that will
// terminate the debug adater in a way that does not provide feedback
// because the standard crash violates the DAP protocol.
Object? jsonData;
try {
jsonData = jsonDecode(data);
Expand Down Expand Up @@ -459,9 +509,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
bool fullRestart, [
String? reason,
]) async {
final DartCommonLaunchAttachRequestArguments args = this.args;
final bool debug =
args is! FlutterLaunchRequestArguments || args.noDebug != true;
try {
await sendFlutterRequest('app.restart', <String, Object?>{
'appId': _appId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ import 'package:dds/dap.dart';
/// An implementation of [AttachRequestArguments] that includes all fields used by the Flutter debug adapter.
///
/// This class represents the data passed from the client editor to the debug
/// adapter in attachRequest, which is a request to start debugging an
/// adapter in attachRequest, which is a request to attach to/debug a running
/// application.
class FlutterAttachRequestArguments
extends DartCommonLaunchAttachRequestArguments
implements AttachRequestArguments {
FlutterAttachRequestArguments({
this.toolArgs,
this.customTool,
this.customToolReplacesArgs,
this.vmServiceUri,
Object? restart,
String? name,
String? cwd,
Expand All @@ -34,11 +38,49 @@ class FlutterAttachRequestArguments
sendLogsToClient: sendLogsToClient,
);

FlutterAttachRequestArguments.fromMap(Map<String, Object?> obj):
FlutterAttachRequestArguments.fromMap(Map<String, Object?> obj)
: toolArgs = (obj['toolArgs'] as List<Object?>?)?.cast<String>(),
customTool = obj['customTool'] as String?,
customToolReplacesArgs = obj['customToolReplacesArgs'] as int?,
vmServiceUri = obj['vmServiceUri'] as String?,
super.fromMap(obj);

static FlutterAttachRequestArguments fromJson(Map<String, Object?> obj) =>
FlutterAttachRequestArguments.fromMap(obj);

/// Arguments to be passed to the tool that will run [program] (for example, the VM or Flutter tool).
final List<String>? toolArgs;

/// An optional tool to run instead of "flutter".
///
/// In combination with [customToolReplacesArgs] allows invoking a custom
/// tool instead of "flutter" to launch scripts/tests. The custom tool must be
/// completely compatible with the tool/command it is replacing.
///
/// This field should be a full absolute path if the tool may not be available
/// in `PATH`.
final String? customTool;

/// The number of arguments to delete from the beginning of the argument list
/// when invoking [customTool].
///
/// For example, setting [customTool] to `flutter_test_wrapper` and
/// `customToolReplacesArgs` to `1` for a test run would invoke
/// `flutter_test_wrapper foo_test.dart` instead of `flutter test foo_test.dart`.
final int? customToolReplacesArgs;

/// The VM Service URI of the running Flutter app to connect to.
final String? vmServiceUri;

@override
Map<String, Object?> toJson() => <String, Object?>{
...super.toJson(),
if (toolArgs != null) 'toolArgs': toolArgs,
if (customTool != null) 'customTool': customTool,
if (customToolReplacesArgs != null)
'customToolReplacesArgs': customToolReplacesArgs,
if (vmServiceUri != null) 'vmServiceUri': vmServiceUri,
};
}

/// An implementation of [LaunchRequestArguments] that includes all fields used by the Flutter debug adapter.
Expand Down
Loading

0 comments on commit c659ad6

Please sign in to comment.