Skip to content

Commit c659ad6

Browse files
authored
Add support for attachRequest in DAP, running "flutter attach" (#97652)
* 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
1 parent ba01ec8 commit c659ad6

File tree

6 files changed

+569
-256
lines changed

6 files changed

+569
-256
lines changed

packages/flutter_tools/lib/src/debug_adapters/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,20 @@ Arguments common to both `launchRequest` and `attachRequest` are:
2727
- `bool? evaluateToStringInDebugViews` - whether to invoke `toString()` in expression evaluation requests (inc. hovers/watch windows) (if not supplied, defaults to `false`)
2828
- `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`)
2929
- `List<String>? additionalProjectPaths` - paths of any projects (outside of `cwd`) that are open in the users workspace
30-
- `String? cwd` - the working directory for the Dart process to be spawned in
30+
- `String? cwd` - the working directory for the Flutter process to be spawned in
31+
- `List<String>? toolArgs` - arguments for the `flutter run`, `flutter attach` or `flutter test` commands
32+
- `String? customTool` - an optional tool to run instead of `flutter` - the custom tool must be completely compatible with the tool/command it is replacing
33+
- `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`)
3134

3235
Arguments specific to `launchRequest` are:
3336

3437
- `bool? noDebug` - whether to run in debug or noDebug mode (if not supplied, defaults to debug)
3538
- `String program` - the path of the Flutter application to run
3639
- `List<String>? args` - arguments to be passed to the Flutter program
37-
- `List<String>? toolArgs` - arguments for the `flutter run` or `flutter test` commands
38-
- `String? customTool` - an optional tool to run instead of `flutter` - the custom tool must be completely compatible with the tool/command it is replacing
39-
- `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`)
4040

41-
`attachRequest` is not currently supported, but will be documented here when it is.
41+
Arguments specific to `attachRequest` are:
42+
43+
- `String? vmServiceUri` - the VM Service URI to attach to (if not supplied, Flutter will try to discover it from the device)
4244

4345
## Custom Requests
4446

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

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,51 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
8383
@override
8484
bool get terminateOnVmServiceClose => false;
8585

86+
/// Whether or not the user requested debugging be enabled.
87+
///
88+
/// debug/noDebug here refers to the DAP "debug" mode and not the Flutter
89+
/// debug mode (vs Profile/Release). It is provided by the client editor based
90+
/// on whether a user chooses to "Run" or "Debug" their app.
91+
///
92+
/// This is always enabled for attach requests, but can be disabled for launch
93+
/// requests via DAP's `noDebug` flag. If `noDebug` is not provided, will
94+
/// default to debugging.
95+
///
96+
/// When not debugging, we will not connect to the VM Service so some
97+
/// functionality (breakpoints, evaluation, etc.) will not be available.
98+
/// Functionality provided via the daemon (hot reload/restart) will still be
99+
/// available.
100+
bool get debug {
101+
final DartCommonLaunchAttachRequestArguments args = this.args;
102+
if (args is FlutterLaunchRequestArguments) {
103+
// Invert DAP's noDebug flag, treating it as false (so _do_ debug) if not
104+
// provided.
105+
return !(args.noDebug ?? false);
106+
}
107+
108+
// Otherwise (attach), always debug.
109+
return true;
110+
}
111+
86112
/// Called by [attachRequest] to request that we actually connect to the app to be debugged.
87113
@override
88114
Future<void> attachImpl() async {
89-
sendOutput('console', '\nAttach is not currently supported');
90-
handleSessionTerminate();
115+
final FlutterAttachRequestArguments args = this.args as FlutterAttachRequestArguments;
116+
117+
final String? vmServiceUri = args.vmServiceUri;
118+
final List<String> toolArgs = <String>[
119+
'attach',
120+
'--machine',
121+
if (vmServiceUri != null)
122+
...<String>['--debug-uri', vmServiceUri],
123+
];
124+
125+
await _startProcess(
126+
toolArgs: toolArgs,
127+
customTool: args.customTool,
128+
customToolReplacesArgs: args.customToolReplacesArgs,
129+
userToolArgs: args.toolArgs,
130+
);
91131
}
92132

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

174-
// "debug"/"noDebug" refers to the DAP "debug" mode and not the Flutter
175-
// debug mode (vs Profile/Release). It is possible for the user to "Run"
176-
// from VS Code (eg. not want to hit breakpoints/etc.) but still be running
177-
// a debug build.
178-
final bool debug = !(args.noDebug ?? false);
179-
final String? program = args.program;
180-
181214
final List<String> toolArgs = <String>[
182215
'run',
183216
'--machine',
184217
if (debug) '--start-paused',
185218
];
186219

220+
await _startProcess(
221+
toolArgs: toolArgs,
222+
customTool: args.customTool,
223+
customToolReplacesArgs: args.customToolReplacesArgs,
224+
targetProgram: args.program,
225+
userToolArgs: args.toolArgs,
226+
userArgs: args.args,
227+
);
228+
}
229+
230+
/// Starts the `flutter` process to run/attach to the required app.
231+
Future<void> _startProcess({
232+
required String? customTool,
233+
required int? customToolReplacesArgs,
234+
required List<String> toolArgs,
235+
required List<String>? userToolArgs,
236+
String? targetProgram,
237+
List<String>? userArgs,
238+
}) async {
187239
// Handle customTool and deletion of any arguments for it.
188-
final String executable = args.customTool ?? fileSystem.path.join(Cache.flutterRoot!, 'bin', platform.isWindows ? 'flutter.bat' : 'flutter');
189-
final int? removeArgs = args.customToolReplacesArgs;
190-
if (args.customTool != null && removeArgs != null) {
240+
final String executable = customTool ?? fileSystem.path.join(Cache.flutterRoot!, 'bin', platform.isWindows ? 'flutter.bat' : 'flutter');
241+
final int? removeArgs = customToolReplacesArgs;
242+
if (customTool != null && removeArgs != null) {
191243
toolArgs.removeRange(0, math.min(removeArgs, toolArgs.length));
192244
}
193245

194246
final List<String> processArgs = <String>[
195247
...toolArgs,
196-
...?args.toolArgs,
197-
if (program != null) ...<String>[
248+
...?userToolArgs,
249+
if (targetProgram != null) ...<String>[
198250
'--target',
199-
program,
251+
targetProgram,
200252
],
201-
...?args.args,
253+
...?userArgs,
202254
];
203255

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

414-
// Output in stdout can include both user output (eg. print) and Flutter
415-
// daemon output. Since it's not uncommon for users to print JSON while
416-
// debugging, we must try to detect which messages are likely Flutter
417-
// messages as reliably as possible, as trying to process users output
418-
// as a Flutter message may result in an unhandled error that will
419-
// terminate the debug adater in a way that does not provide feedback
420-
// because the standard crash violates the DAP protocol.
464+
// Output in stdout can include both user output (eg. print) and Flutter
465+
// daemon output. Since it's not uncommon for users to print JSON while
466+
// debugging, we must try to detect which messages are likely Flutter
467+
// messages as reliably as possible, as trying to process users output
468+
// as a Flutter message may result in an unhandled error that will
469+
// terminate the debug adater in a way that does not provide feedback
470+
// because the standard crash violates the DAP protocol.
421471
Object? jsonData;
422472
try {
423473
jsonData = jsonDecode(data);
@@ -459,9 +509,6 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
459509
bool fullRestart, [
460510
String? reason,
461511
]) async {
462-
final DartCommonLaunchAttachRequestArguments args = this.args;
463-
final bool debug =
464-
args is! FlutterLaunchRequestArguments || args.noDebug != true;
465512
try {
466513
await sendFlutterRequest('app.restart', <String, Object?>{
467514
'appId': _appId,

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ import 'package:dds/dap.dart';
77
/// An implementation of [AttachRequestArguments] that includes all fields used by the Flutter debug adapter.
88
///
99
/// This class represents the data passed from the client editor to the debug
10-
/// adapter in attachRequest, which is a request to start debugging an
10+
/// adapter in attachRequest, which is a request to attach to/debug a running
1111
/// application.
1212
class FlutterAttachRequestArguments
1313
extends DartCommonLaunchAttachRequestArguments
1414
implements AttachRequestArguments {
1515
FlutterAttachRequestArguments({
16+
this.toolArgs,
17+
this.customTool,
18+
this.customToolReplacesArgs,
19+
this.vmServiceUri,
1620
Object? restart,
1721
String? name,
1822
String? cwd,
@@ -34,11 +38,49 @@ class FlutterAttachRequestArguments
3438
sendLogsToClient: sendLogsToClient,
3539
);
3640

37-
FlutterAttachRequestArguments.fromMap(Map<String, Object?> obj):
41+
FlutterAttachRequestArguments.fromMap(Map<String, Object?> obj)
42+
: toolArgs = (obj['toolArgs'] as List<Object?>?)?.cast<String>(),
43+
customTool = obj['customTool'] as String?,
44+
customToolReplacesArgs = obj['customToolReplacesArgs'] as int?,
45+
vmServiceUri = obj['vmServiceUri'] as String?,
3846
super.fromMap(obj);
3947

4048
static FlutterAttachRequestArguments fromJson(Map<String, Object?> obj) =>
4149
FlutterAttachRequestArguments.fromMap(obj);
50+
51+
/// Arguments to be passed to the tool that will run [program] (for example, the VM or Flutter tool).
52+
final List<String>? toolArgs;
53+
54+
/// An optional tool to run instead of "flutter".
55+
///
56+
/// In combination with [customToolReplacesArgs] allows invoking a custom
57+
/// tool instead of "flutter" to launch scripts/tests. The custom tool must be
58+
/// completely compatible with the tool/command it is replacing.
59+
///
60+
/// This field should be a full absolute path if the tool may not be available
61+
/// in `PATH`.
62+
final String? customTool;
63+
64+
/// The number of arguments to delete from the beginning of the argument list
65+
/// when invoking [customTool].
66+
///
67+
/// For example, setting [customTool] to `flutter_test_wrapper` and
68+
/// `customToolReplacesArgs` to `1` for a test run would invoke
69+
/// `flutter_test_wrapper foo_test.dart` instead of `flutter test foo_test.dart`.
70+
final int? customToolReplacesArgs;
71+
72+
/// The VM Service URI of the running Flutter app to connect to.
73+
final String? vmServiceUri;
74+
75+
@override
76+
Map<String, Object?> toJson() => <String, Object?>{
77+
...super.toJson(),
78+
if (toolArgs != null) 'toolArgs': toolArgs,
79+
if (customTool != null) 'customTool': customTool,
80+
if (customToolReplacesArgs != null)
81+
'customToolReplacesArgs': customToolReplacesArgs,
82+
if (vmServiceUri != null) 'vmServiceUri': vmServiceUri,
83+
};
4284
}
4385

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

0 commit comments

Comments
 (0)