mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:58:29 +00:00
[dds/dap] Normalise Windows drive letters to avoid missing breakpoints
See https://github.com/dart-lang/sdk/issues/32222. See https://github.com/Dart-Code/Dart-Code/issues/4149. Change-Id: I6f975734839ff7cad4d086d5363c0ab03390b966 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/258900 Commit-Queue: Ben Konyi <bkonyi@google.com> Reviewed-by: Ben Konyi <bkonyi@google.com>
This commit is contained in:
parent
e4ae0cf2ce
commit
1b9adcb502
|
@ -1,6 +1,7 @@
|
||||||
# 2.3.0-dev
|
# 2.3.0
|
||||||
- [DAP] Removed an unused parameter `resumeIfStarting` from `DartDebugAdapter.connectDebugger`.
|
- [DAP] Removed an unused parameter `resumeIfStarting` from `DartDebugAdapter.connectDebugger`.
|
||||||
- [DAP] Fixed some issues where removing breakpoints could fail if an isolate exited during an update or multiple client breakpoints mapped to the same VM breakpoint.
|
- [DAP] Fixed some issues where removing breakpoints could fail if an isolate exited during an update or multiple client breakpoints mapped to the same VM breakpoint.
|
||||||
|
- [DAP] Paths provided to DAP now always have Windows drive letters normalized to uppercase to avoid some issues where paths may be treated case sensitively.
|
||||||
|
|
||||||
# 2.2.6
|
# 2.2.6
|
||||||
- Fixed an issue where debug adapters would not automatically close after terminating/disconnecting from the debugee.
|
- Fixed an issue where debug adapters would not automatically close after terminating/disconnecting from the debugee.
|
||||||
|
|
|
@ -22,6 +22,7 @@ import '../protocol_converter.dart';
|
||||||
import '../protocol_generated.dart';
|
import '../protocol_generated.dart';
|
||||||
import '../protocol_stream.dart';
|
import '../protocol_stream.dart';
|
||||||
import '../utils.dart';
|
import '../utils.dart';
|
||||||
|
import 'mixins.dart';
|
||||||
|
|
||||||
/// The mime type to send with source responses to the client.
|
/// The mime type to send with source responses to the client.
|
||||||
///
|
///
|
||||||
|
@ -285,7 +286,8 @@ class DartCommonLaunchAttachRequestArguments extends RequestArguments {
|
||||||
/// (for example when the server sends a `StoppedEvent` it may cause the client
|
/// (for example when the server sends a `StoppedEvent` it may cause the client
|
||||||
/// to then send a `stackTraceRequest` or `scopesRequest` to get variables).
|
/// to then send a `stackTraceRequest` or `scopesRequest` to get variables).
|
||||||
abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
|
abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
|
||||||
TA extends AttachRequestArguments> extends BaseDebugAdapter<TL, TA> {
|
TA extends AttachRequestArguments> extends BaseDebugAdapter<TL, TA>
|
||||||
|
with FileUtils {
|
||||||
late final DartCommonLaunchAttachRequestArguments args;
|
late final DartCommonLaunchAttachRequestArguments args;
|
||||||
final _debuggerInitializedCompleter = Completer<void>();
|
final _debuggerInitializedCompleter = Completer<void>();
|
||||||
final _configurationDoneCompleter = Completer<void>();
|
final _configurationDoneCompleter = Completer<void>();
|
||||||
|
@ -706,7 +708,6 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
|
||||||
void Function(Object?) sendResponse,
|
void Function(Object?) sendResponse,
|
||||||
) async {
|
) async {
|
||||||
switch (request.command) {
|
switch (request.command) {
|
||||||
|
|
||||||
// Used by tests to validate available protocols (e.g. DDS). There may be
|
// Used by tests to validate available protocols (e.g. DDS). There may be
|
||||||
// value in making this available to clients in future, but for now it's
|
// value in making this available to clients in future, but for now it's
|
||||||
// internal.
|
// internal.
|
||||||
|
@ -1186,7 +1187,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
|
||||||
|
|
||||||
final path = args.source.path;
|
final path = args.source.path;
|
||||||
final name = args.source.name;
|
final name = args.source.name;
|
||||||
final uri = path != null ? Uri.file(path).toString() : name!;
|
final uri = path != null ? Uri.file(normalizePath(path)).toString() : name!;
|
||||||
|
|
||||||
await _isolateManager.setBreakpoints(uri, breakpoints);
|
await _isolateManager.setBreakpoints(uri, breakpoints);
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,9 @@ class DartCliDebugAdapter extends DartDebugAdapter<DartLaunchRequestArguments,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle customTool and deletion of any arguments for it.
|
// Handle customTool and deletion of any arguments for it.
|
||||||
final executable = args.customTool ?? Platform.resolvedExecutable;
|
final executable = normalizePath(
|
||||||
|
args.customTool ?? Platform.resolvedExecutable,
|
||||||
|
);
|
||||||
final removeArgs = args.customToolReplacesArgs;
|
final removeArgs = args.customToolReplacesArgs;
|
||||||
if (args.customTool != null && removeArgs != null) {
|
if (args.customTool != null && removeArgs != null) {
|
||||||
vmArgs.removeRange(0, math.min(removeArgs, vmArgs.length));
|
vmArgs.removeRange(0, math.min(removeArgs, vmArgs.length));
|
||||||
|
@ -133,7 +135,7 @@ class DartCliDebugAdapter extends DartDebugAdapter<DartLaunchRequestArguments,
|
||||||
final processArgs = [
|
final processArgs = [
|
||||||
...vmArgs,
|
...vmArgs,
|
||||||
...toolArgs,
|
...toolArgs,
|
||||||
args.program,
|
normalizePath(args.program),
|
||||||
...?args.args,
|
...?args.args,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -153,20 +155,25 @@ class DartCliDebugAdapter extends DartDebugAdapter<DartLaunchRequestArguments,
|
||||||
: null
|
: null
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
var cwd = args.cwd;
|
||||||
|
if (cwd != null) {
|
||||||
|
cwd = normalizePath(cwd);
|
||||||
|
}
|
||||||
|
|
||||||
if (terminalKind != null) {
|
if (terminalKind != null) {
|
||||||
await launchInEditorTerminal(
|
await launchInEditorTerminal(
|
||||||
debug,
|
debug,
|
||||||
terminalKind,
|
terminalKind,
|
||||||
executable,
|
executable,
|
||||||
processArgs,
|
processArgs,
|
||||||
workingDirectory: args.cwd,
|
workingDirectory: cwd,
|
||||||
env: args.env,
|
env: args.env,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await launchAsProcess(
|
await launchAsProcess(
|
||||||
executable,
|
executable,
|
||||||
processArgs,
|
processArgs,
|
||||||
workingDirectory: args.cwd,
|
workingDirectory: cwd,
|
||||||
env: args.env,
|
env: args.env,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -216,7 +223,7 @@ class DartCliDebugAdapter extends DartDebugAdapter<DartLaunchRequestArguments,
|
||||||
// we can detect with the normal watching code.
|
// we can detect with the normal watching code.
|
||||||
final requestArgs = RunInTerminalRequestArguments(
|
final requestArgs = RunInTerminalRequestArguments(
|
||||||
args: [executable, ...processArgs],
|
args: [executable, ...processArgs],
|
||||||
cwd: workingDirectory ?? path.dirname(args.program),
|
cwd: workingDirectory ?? normalizePath(path.dirname(args.program)),
|
||||||
env: env,
|
env: env,
|
||||||
kind: terminalKind,
|
kind: terminalKind,
|
||||||
title: args.name ?? 'Dart',
|
title: args.name ?? 'Dart',
|
||||||
|
|
|
@ -111,7 +111,9 @@ class DartTestDebugAdapter extends DartDebugAdapter<DartLaunchRequestArguments,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Handle customTool and deletion of any arguments for it.
|
// Handle customTool and deletion of any arguments for it.
|
||||||
final executable = args.customTool ?? Platform.resolvedExecutable;
|
final executable = normalizePath(
|
||||||
|
args.customTool ?? Platform.resolvedExecutable,
|
||||||
|
);
|
||||||
final removeArgs = args.customToolReplacesArgs;
|
final removeArgs = args.customToolReplacesArgs;
|
||||||
if (args.customTool != null && removeArgs != null) {
|
if (args.customTool != null && removeArgs != null) {
|
||||||
vmArgs.removeRange(0, math.min(removeArgs, vmArgs.length));
|
vmArgs.removeRange(0, math.min(removeArgs, vmArgs.length));
|
||||||
|
@ -120,14 +122,19 @@ class DartTestDebugAdapter extends DartDebugAdapter<DartLaunchRequestArguments,
|
||||||
final processArgs = [
|
final processArgs = [
|
||||||
...vmArgs,
|
...vmArgs,
|
||||||
...?args.toolArgs,
|
...?args.toolArgs,
|
||||||
args.program,
|
normalizePath(args.program),
|
||||||
...?args.args,
|
...?args.args,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
var cwd = args.cwd;
|
||||||
|
if (cwd != null) {
|
||||||
|
cwd = normalizePath(cwd);
|
||||||
|
}
|
||||||
|
|
||||||
await launchAsProcess(
|
await launchAsProcess(
|
||||||
executable,
|
executable,
|
||||||
processArgs,
|
processArgs,
|
||||||
workingDirectory: args.cwd,
|
workingDirectory: cwd,
|
||||||
env: args.env,
|
env: args.env,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ mixin TestAdapter {
|
||||||
/// A mixin providing some utility functions for working with vm-service-info
|
/// A mixin providing some utility functions for working with vm-service-info
|
||||||
/// files such as ensuring a temp folder exists to create them in, and waiting
|
/// files such as ensuring a temp folder exists to create them in, and waiting
|
||||||
/// for the file to become valid parsable JSON.
|
/// for the file to become valid parsable JSON.
|
||||||
mixin VmServiceInfoFileUtils {
|
mixin VmServiceInfoFileUtils on FileUtils {
|
||||||
/// Creates a temp folder for the VM to write the service-info-file into and
|
/// Creates a temp folder for the VM to write the service-info-file into and
|
||||||
/// returns the [File] to use.
|
/// returns the [File] to use.
|
||||||
File generateVmServiceInfoFile() {
|
File generateVmServiceInfoFile() {
|
||||||
|
@ -143,7 +143,8 @@ mixin VmServiceInfoFileUtils {
|
||||||
// watcher. Creating/watching a folder and writing the file into it seems
|
// watcher. Creating/watching a folder and writing the file into it seems
|
||||||
// to be reliable.
|
// to be reliable.
|
||||||
final serviceInfoFilePath = path.join(
|
final serviceInfoFilePath = path.join(
|
||||||
Directory.systemTemp.createTempSync('dart-vm-service').path,
|
normalizePath(
|
||||||
|
Directory.systemTemp.createTempSync('dart-vm-service').path),
|
||||||
'vm.json',
|
'vm.json',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -203,3 +204,24 @@ mixin VmServiceInfoFileUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mixin FileUtils {
|
||||||
|
/// Normalizes [filePath] to avoid issues with different casing of drive
|
||||||
|
/// letters on Windows.
|
||||||
|
///
|
||||||
|
/// Some clients like VS Code do their own normalization and may provide drive
|
||||||
|
/// letters case different in some requests (such as breakpoints) to drive
|
||||||
|
/// letters computed elsewhere (such in `Platform.resolvedExecutable`). When
|
||||||
|
/// these do not match, breakpoints may not be hit.
|
||||||
|
///
|
||||||
|
/// This is the case for the whole path, but drive letters are most commonly
|
||||||
|
/// mismatched due to VS Code's explicit normalization.
|
||||||
|
///
|
||||||
|
/// https://github.com/dart-lang/sdk/issues/32222
|
||||||
|
String normalizePath(String filePath) {
|
||||||
|
if (!Platform.isWindows || filePath.isEmpty || path.isRelative(filePath)) {
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
return filePath.substring(0, 1).toUpperCase() + filePath.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: dds
|
name: dds
|
||||||
version: 2.2.6
|
version: 2.3.0
|
||||||
description: >-
|
description: >-
|
||||||
A library used to spawn the Dart Developer Service, used to communicate with
|
A library used to spawn the Dart Developer Service, used to communicate with
|
||||||
a Dart VM Service instance.
|
a Dart VM Service instance.
|
||||||
|
|
|
@ -107,7 +107,8 @@ void main(List<String> args) async {
|
||||||
await client.hitBreakpoint(sdkFile, breakpointLine, entryFile: testFile);
|
await client.hitBreakpoint(sdkFile, breakpointLine, entryFile: testFile);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('stops at a line breakpoint and can be resumed', () async {
|
/// Tests hitting a simple breakpoint and resuming.
|
||||||
|
Future<void> _testHitBreakpointAndResume() async {
|
||||||
final client = dap.client;
|
final client = dap.client;
|
||||||
final testFile = dap.createTestFile(simpleBreakpointProgram);
|
final testFile = dap.createTestFile(simpleBreakpointProgram);
|
||||||
final breakpointLine = lineWith(testFile, breakpointMarker);
|
final breakpointLine = lineWith(testFile, breakpointMarker);
|
||||||
|
@ -120,8 +121,32 @@ void main(List<String> args) async {
|
||||||
client.event('terminated'),
|
client.event('terminated'),
|
||||||
client.continue_(stop.threadId!),
|
client.continue_(stop.threadId!),
|
||||||
], eagerError: true);
|
], eagerError: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('stops at a line breakpoint and can be resumed', () async {
|
||||||
|
await _testHitBreakpointAndResume();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'stops at a line breakpoint and can be resumed '
|
||||||
|
'when breakpoint requests have lowercase drive letters '
|
||||||
|
'and program/VM have uppercase drive letters', () async {
|
||||||
|
final client = dap.client;
|
||||||
|
client.forceDriveLetterCasingUpper = true;
|
||||||
|
client.forceBreakpointDriveLetterCasingLower = true;
|
||||||
|
await _testHitBreakpointAndResume();
|
||||||
|
}, skip: !Platform.isWindows);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'stops at a line breakpoint and can be resumed '
|
||||||
|
'when breakpoint requests have uppercase drive letters '
|
||||||
|
'and program/VM have lowercase drive letters', () async {
|
||||||
|
final client = dap.client;
|
||||||
|
client.forceDriveLetterCasingLower = true;
|
||||||
|
client.forceBreakpointDriveLetterCasingUpper = true;
|
||||||
|
await _testHitBreakpointAndResume();
|
||||||
|
}, skip: !Platform.isWindows);
|
||||||
|
|
||||||
test('stops at a line breakpoint and can step over (next)', () async {
|
test('stops at a line breakpoint and can step over (next)', () async {
|
||||||
final testFile = dap.createTestFile('''
|
final testFile = dap.createTestFile('''
|
||||||
void main(List<String> args) async {
|
void main(List<String> args) async {
|
||||||
|
|
|
@ -38,6 +38,20 @@ class DapTestClient {
|
||||||
|
|
||||||
late final Future<Uri?> vmServiceUri;
|
late final Future<Uri?> vmServiceUri;
|
||||||
|
|
||||||
|
/// Used to control drive letter casing on Windows for testing.
|
||||||
|
bool? forceDriveLetterCasingUpper;
|
||||||
|
|
||||||
|
/// Used to control drive letter casing on Windows for testing.
|
||||||
|
bool? forceDriveLetterCasingLower;
|
||||||
|
|
||||||
|
/// Used to control drive letter casing for breakpoint requests on Windows for
|
||||||
|
/// testing.
|
||||||
|
bool? forceBreakpointDriveLetterCasingUpper;
|
||||||
|
|
||||||
|
/// Used to control drive letter casing for breakpoint requests on Windows for
|
||||||
|
/// testing.
|
||||||
|
bool? forceBreakpointDriveLetterCasingLower;
|
||||||
|
|
||||||
DapTestClient._(
|
DapTestClient._(
|
||||||
this._channel,
|
this._channel,
|
||||||
this._logger, {
|
this._logger, {
|
||||||
|
@ -238,11 +252,12 @@ class DapTestClient {
|
||||||
return sendRequest(
|
return sendRequest(
|
||||||
DartLaunchRequestArguments(
|
DartLaunchRequestArguments(
|
||||||
noDebug: noDebug,
|
noDebug: noDebug,
|
||||||
program: program,
|
program: _normalizePath(program),
|
||||||
cwd: cwd,
|
cwd: cwd != null ? _normalizePath(cwd) : null,
|
||||||
args: args,
|
args: args,
|
||||||
toolArgs: toolArgs,
|
toolArgs: toolArgs,
|
||||||
additionalProjectPaths: additionalProjectPaths,
|
additionalProjectPaths:
|
||||||
|
additionalProjectPaths?.map(_normalizePath).toList(),
|
||||||
console: console,
|
console: console,
|
||||||
debugSdkLibraries: debugSdkLibraries,
|
debugSdkLibraries: debugSdkLibraries,
|
||||||
debugExternalPackageLibraries: debugExternalPackageLibraries,
|
debugExternalPackageLibraries: debugExternalPackageLibraries,
|
||||||
|
@ -522,7 +537,7 @@ extension DapTestClientExtension on DapTestClient {
|
||||||
Future<void> setBreakpoint(File file, int line, {String? condition}) async {
|
Future<void> setBreakpoint(File file, int line, {String? condition}) async {
|
||||||
await sendRequest(
|
await sendRequest(
|
||||||
SetBreakpointsArguments(
|
SetBreakpointsArguments(
|
||||||
source: Source(path: file.path),
|
source: Source(path: _normalizeBreakpointPath(file.path)),
|
||||||
breakpoints: [SourceBreakpoint(line: line, condition: condition)],
|
breakpoints: [SourceBreakpoint(line: line, condition: condition)],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -532,12 +547,48 @@ extension DapTestClientExtension on DapTestClient {
|
||||||
Future<void> setBreakpoints(File file, List<int> lines) async {
|
Future<void> setBreakpoints(File file, List<int> lines) async {
|
||||||
await sendRequest(
|
await sendRequest(
|
||||||
SetBreakpointsArguments(
|
SetBreakpointsArguments(
|
||||||
source: Source(path: file.path),
|
source: Source(path: _normalizeBreakpointPath(file.path)),
|
||||||
breakpoints: lines.map((line) => SourceBreakpoint(line: line)).toList(),
|
breakpoints: lines.map((line) => SourceBreakpoint(line: line)).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Normalizes a non-breakpoint path being sent to the debug adapter based on
|
||||||
|
/// the values of [forceDriveLetterCasingUpper] and
|
||||||
|
/// [forceDriveLetterCasingLower].
|
||||||
|
String _normalizePath(String path) {
|
||||||
|
return _forceDriveLetterCasing(
|
||||||
|
path,
|
||||||
|
upper: forceDriveLetterCasingUpper,
|
||||||
|
lower: forceDriveLetterCasingLower,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalizes a non-breakpoint path being sent to the debug adapter based on
|
||||||
|
/// the values of [forceBreakpointDriveLetterCasingUpper] and
|
||||||
|
/// [forceBreakpointDriveLetterCasingLower].
|
||||||
|
String _normalizeBreakpointPath(String path) {
|
||||||
|
return _forceDriveLetterCasing(
|
||||||
|
path,
|
||||||
|
upper: forceBreakpointDriveLetterCasingUpper,
|
||||||
|
lower: forceBreakpointDriveLetterCasingLower,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _forceDriveLetterCasing(String path, {bool? upper, bool? lower}) {
|
||||||
|
assert(upper != true || lower != true);
|
||||||
|
if (!Platform.isWindows || path.isEmpty) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
if (upper ?? false) {
|
||||||
|
return path.substring(0, 1).toUpperCase() + path.substring(1);
|
||||||
|
} else if (lower ?? false) {
|
||||||
|
return path.substring(0, 1).toLowerCase() + path.substring(1);
|
||||||
|
} else {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the exception pause mode to [pauseMode] and expects to pause after
|
/// Sets the exception pause mode to [pauseMode] and expects to pause after
|
||||||
/// running the script.
|
/// running the script.
|
||||||
///
|
///
|
||||||
|
@ -585,7 +636,7 @@ extension DapTestClientExtension on DapTestClient {
|
||||||
initialize(),
|
initialize(),
|
||||||
sendRequest(
|
sendRequest(
|
||||||
SetBreakpointsArguments(
|
SetBreakpointsArguments(
|
||||||
source: Source(path: file.path),
|
source: Source(path: _normalizeBreakpointPath(file.path)),
|
||||||
breakpoints: [
|
breakpoints: [
|
||||||
SourceBreakpoint(
|
SourceBreakpoint(
|
||||||
line: line,
|
line: line,
|
||||||
|
|
Loading…
Reference in a new issue