mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:39:48 +00:00
[dds][dap] Fix global evaluation inside package: files
The initial global evaluation support only worked when the open script was loaded into the VM as a `file:///` URI (eg. things in a `bin/` folder, and not a `lib/` folder). This is because we short-cut resolving file paths to `package:` URIs for most VM functionality (for example setting breakpoints) because it's unnecessary (the VM supports setting breakpoints with file:/// URIs even for `lib/`). This change forces us to resolve paths to their resolved URIs (eg. `package:` where applicable) in the case where we're looking up a script for global evaluation. We could enable this lookup for all cases (to remove the additional `force` flag here), but since it's called much more from the other path (breakpoints) we should add caching (which was more than I wanted to change for this fix). Fixes https://github.com/Dart-Code/Dart-Code/issues/4932 Change-Id: I57a99ec3b7c726d9d120e6cda7d0b938fec397bb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/346400 Reviewed-by: Ben Konyi <bkonyi@google.com> Reviewed-by: Helin Shiah <helinx@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
parent
b19141a202
commit
a95b584101
|
@ -1,3 +1,6 @@
|
||||||
|
# 3.1.3-unreleased
|
||||||
|
- [DAP] Fixed "Unable to find library" errors when using global evaluation when the context file resolves to a `package:` URI.
|
||||||
|
|
||||||
# 3.1.2
|
# 3.1.2
|
||||||
- Improved error handling for serving static DevTools assets.
|
- Improved error handling for serving static DevTools assets.
|
||||||
- Updated `devtools_shared` constraint to ^6.0.3.
|
- Updated `devtools_shared` constraint to ^6.0.3.
|
||||||
|
|
|
@ -1088,22 +1088,46 @@ class ThreadInfo with FileUtils {
|
||||||
/// This is required so that when the user sets a breakpoint in an SDK source
|
/// This is required so that when the user sets a breakpoint in an SDK source
|
||||||
/// (which they may have navigated to via the Analysis Server) we generate a
|
/// (which they may have navigated to via the Analysis Server) we generate a
|
||||||
/// valid URI that the VM would create a breakpoint for.
|
/// valid URI that the VM would create a breakpoint for.
|
||||||
Future<Uri?> resolvePathToUri(String filePath) async {
|
///
|
||||||
var google3Path = _convertPathToGoogle3Uri(filePath);
|
/// Because the VM supports using `file:` URIs in many places, we usually do
|
||||||
if (google3Path != null) {
|
/// not need to convert file paths into `package:` URIs, however this will
|
||||||
var result = await _manager._adapter.vmService
|
/// be done if [forceResolveFileUris] is `true`.
|
||||||
?.lookupPackageUris(isolate.id!, [google3Path.toString()]);
|
Future<Uri?> resolvePathToUri(
|
||||||
var uriStr = result?.uris?.first;
|
String filePath, {
|
||||||
return uriStr != null ? Uri.parse(uriStr) : null;
|
bool forceResolveFileUris = false,
|
||||||
|
}) async {
|
||||||
|
final sdkUri = _manager._adapter.convertPathToOrgDartlangSdk(filePath);
|
||||||
|
if (sdkUri != null) {
|
||||||
|
return sdkUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't need to call lookupPackageUris in non-google3 because the VM can
|
final google3Uri = _convertPathToGoogle3Uri(filePath);
|
||||||
// handle incoming file:/// URIs for packages, and also the org-dartlang-sdk
|
final uri = google3Uri ?? Uri.file(filePath);
|
||||||
// URIs directly for SDK sources (we do not need to convert to 'dart:'),
|
|
||||||
// however this method is Future-returning in case this changes in future
|
// As an optimisation, we don't resolve file -> package URIs in many cases
|
||||||
// and we need to include a call to lookupPackageUris here.
|
// because the VM can set breakpoints for file: URIs anyway. However for
|
||||||
return _manager._adapter.convertPathToOrgDartlangSdk(filePath) ??
|
// G3 or if [forceResolveFileUris] is set, we will.
|
||||||
Uri.file(filePath);
|
final performResolve = google3Uri != null || forceResolveFileUris;
|
||||||
|
|
||||||
|
// TODO(dantup): Consider caching results for this like we do for
|
||||||
|
// resolveUriToPath (and then forceResolveFileUris can be removed and just
|
||||||
|
// always used).
|
||||||
|
final packageUriList = performResolve
|
||||||
|
? await _manager._adapter.vmService
|
||||||
|
?.lookupPackageUris(isolate.id!, [uri.toString()])
|
||||||
|
: null;
|
||||||
|
final packageUriString = packageUriList?.uris?.firstOrNull;
|
||||||
|
|
||||||
|
if (packageUriString != null) {
|
||||||
|
// Use package URI if we resolved something
|
||||||
|
return Uri.parse(packageUriString);
|
||||||
|
} else if (google3Uri != null) {
|
||||||
|
// If we failed to resolve and was a Google3 URI, return null
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
// Otherwise, use the original (file) URI
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Batch resolves source URIs from the VM to a file path for the package lib
|
/// Batch resolves source URIs from the VM to a file path for the package lib
|
||||||
|
@ -1359,8 +1383,10 @@ class ThreadInfo with FileUtils {
|
||||||
//
|
//
|
||||||
// We need to handle msimatched drive letters, and also file vs package
|
// We need to handle msimatched drive letters, and also file vs package
|
||||||
// URIs.
|
// URIs.
|
||||||
final scriptResolvedUri =
|
final scriptResolvedUri = await resolvePathToUri(
|
||||||
await resolvePathToUri(scriptFileUri.toFilePath());
|
scriptFileUri.toFilePath(),
|
||||||
|
forceResolveFileUris: true,
|
||||||
|
);
|
||||||
final candidateUris = {
|
final candidateUris = {
|
||||||
scriptFileUri.toString(),
|
scriptFileUri.toString(),
|
||||||
normalizeUri(scriptFileUri).toString(),
|
normalizeUri(scriptFileUri).toString(),
|
||||||
|
|
|
@ -458,7 +458,7 @@ void main(List<String> args) async {
|
||||||
'does not step into external package code with debugExternalPackageLibraries=false',
|
'does not step into external package code with debugExternalPackageLibraries=false',
|
||||||
() async {
|
() async {
|
||||||
final client = dap.client;
|
final client = dap.client;
|
||||||
final otherPackageUri = await dap.createFooPackage();
|
final (otherPackageUri, _) = await dap.createFooPackage();
|
||||||
final testFile = dap.createTestFile('''
|
final testFile = dap.createTestFile('''
|
||||||
import '$otherPackageUri';
|
import '$otherPackageUri';
|
||||||
|
|
||||||
|
@ -491,7 +491,7 @@ void main(List<String> args) async {
|
||||||
'steps into external package code with debugExternalPackageLibraries=true',
|
'steps into external package code with debugExternalPackageLibraries=true',
|
||||||
() async {
|
() async {
|
||||||
final client = dap.client;
|
final client = dap.client;
|
||||||
final otherPackageUri = await dap.createFooPackage();
|
final (otherPackageUri, _) = await dap.createFooPackage();
|
||||||
final testFile = dap.createTestFile('''
|
final testFile = dap.createTestFile('''
|
||||||
import '$otherPackageUri';
|
import '$otherPackageUri';
|
||||||
|
|
||||||
|
@ -523,7 +523,7 @@ void main(List<String> args) async {
|
||||||
'steps into other-project package code with debugExternalPackageLibraries=false',
|
'steps into other-project package code with debugExternalPackageLibraries=false',
|
||||||
() async {
|
() async {
|
||||||
final client = dap.client;
|
final client = dap.client;
|
||||||
final otherPackageUri = await dap.createFooPackage();
|
final (otherPackageUri, _) = await dap.createFooPackage();
|
||||||
final testFile = dap.createTestFile('''
|
final testFile = dap.createTestFile('''
|
||||||
import '$otherPackageUri';
|
import '$otherPackageUri';
|
||||||
|
|
||||||
|
|
|
@ -285,8 +285,10 @@ void foo() {
|
||||||
});
|
});
|
||||||
|
|
||||||
group('global evaluation', () {
|
group('global evaluation', () {
|
||||||
test('can evaluate when not paused given a script URI', () async {
|
test('can evaluate in a bin/ file when not paused given a bin/ URI',
|
||||||
|
() async {
|
||||||
final client = dap.client;
|
final client = dap.client;
|
||||||
|
await dap.createFooPackage();
|
||||||
final testFile = dap.createTestFile(globalEvaluationProgram);
|
final testFile = dap.createTestFile(globalEvaluationProgram);
|
||||||
|
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
|
@ -305,12 +307,34 @@ void foo() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('can evaluate when not paused given a lib/ URI', () async {
|
||||||
|
final client = dap.client;
|
||||||
|
final (_, libFile) = await dap.createFooPackage();
|
||||||
|
final binFile = dap.createTestFile(globalEvaluationProgram);
|
||||||
|
|
||||||
|
await Future.wait([
|
||||||
|
client.initialize(),
|
||||||
|
client.launch(binFile.path),
|
||||||
|
], eagerError: true);
|
||||||
|
|
||||||
|
// Wait for a '.' to be printed to know the script is up and running.
|
||||||
|
await dap.client.outputEvents
|
||||||
|
.firstWhere((event) => event.output.trim() == '.');
|
||||||
|
|
||||||
|
await client.expectGlobalEvalResult(
|
||||||
|
'fooGlobal',
|
||||||
|
'"Hello, foo!"',
|
||||||
|
context: Uri.file(libFile.path).toString(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('returns a suitable error with no context', () async {
|
test('returns a suitable error with no context', () async {
|
||||||
const expectedErrorMessage = 'Evaluation is only supported when the '
|
const expectedErrorMessage = 'Evaluation is only supported when the '
|
||||||
'debugger is paused unless you have a Dart file active in the '
|
'debugger is paused unless you have a Dart file active in the '
|
||||||
'editor';
|
'editor';
|
||||||
|
|
||||||
final client = dap.client;
|
final client = dap.client;
|
||||||
|
await dap.createFooPackage();
|
||||||
final testFile = dap.createTestFile(globalEvaluationProgram);
|
final testFile = dap.createTestFile(globalEvaluationProgram);
|
||||||
|
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
|
@ -343,6 +367,7 @@ void foo() {
|
||||||
|
|
||||||
test('returns a suitable error with an unknown script context', () async {
|
test('returns a suitable error with an unknown script context', () async {
|
||||||
final client = dap.client;
|
final client = dap.client;
|
||||||
|
await dap.createFooPackage();
|
||||||
final testFile = dap.createTestFile(globalEvaluationProgram);
|
final testFile = dap.createTestFile(globalEvaluationProgram);
|
||||||
|
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
|
|
|
@ -99,7 +99,7 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('runs a simple script with commas in the filename', () async {
|
test('runs a simple script with commas in the filename', () async {
|
||||||
final packageUri = await dap.createFooPackage('foo,foo.dart');
|
final (packageUri, _) = await dap.createFooPackage('foo,foo.dart');
|
||||||
final testFile = dap.createTestFile(
|
final testFile = dap.createTestFile(
|
||||||
'''
|
'''
|
||||||
import '$packageUri';
|
import '$packageUri';
|
||||||
|
@ -167,7 +167,7 @@ main() {
|
||||||
// - stack frames with package URIs (that need asynchronously resolving)
|
// - stack frames with package URIs (that need asynchronously resolving)
|
||||||
// - stack frames with dart URIs (that need asynchronously resolving)
|
// - stack frames with dart URIs (that need asynchronously resolving)
|
||||||
final fileUri = Uri.file(dap.createTestFile('').path);
|
final fileUri = Uri.file(dap.createTestFile('').path);
|
||||||
final packageUri = await dap.createFooPackage();
|
final (packageUri, _) = await dap.createFooPackage();
|
||||||
final dartUri = Uri.parse('dart:isolate-patch/isolate_patch.dart');
|
final dartUri = Uri.parse('dart:isolate-patch/isolate_patch.dart');
|
||||||
final testFile = dap.createTestFile(
|
final testFile = dap.createTestFile(
|
||||||
stderrPrintingProgram(fileUri, packageUri, dartUri),
|
stderrPrintingProgram(fileUri, packageUri, dartUri),
|
||||||
|
@ -212,7 +212,7 @@ main() {
|
||||||
// - stack frames with package URIs (that need asynchronously resolving)
|
// - stack frames with package URIs (that need asynchronously resolving)
|
||||||
// - stack frames with dart URIs (that need asynchronously resolving)
|
// - stack frames with dart URIs (that need asynchronously resolving)
|
||||||
final fileUri = Uri.file(dap.createTestFile('').path);
|
final fileUri = Uri.file(dap.createTestFile('').path);
|
||||||
final packageUri = await dap.createFooPackage();
|
final (packageUri, _) = await dap.createFooPackage();
|
||||||
final dartUri = Uri.parse('dart:isolate-patch/isolate_patch.dart');
|
final dartUri = Uri.parse('dart:isolate-patch/isolate_patch.dart');
|
||||||
final testFile = dap.createTestFile(
|
final testFile = dap.createTestFile(
|
||||||
stderrPrintingProgram(fileUri, packageUri, dartUri),
|
stderrPrintingProgram(fileUri, packageUri, dartUri),
|
||||||
|
|
|
@ -111,10 +111,15 @@ const infiniteRunningProgram = '''
|
||||||
///
|
///
|
||||||
/// A top-level String variable `myGlobal` is available with the value
|
/// A top-level String variable `myGlobal` is available with the value
|
||||||
/// `"Hello, world!"`.
|
/// `"Hello, world!"`.
|
||||||
|
///
|
||||||
|
/// Requires the 'foo' package.
|
||||||
const globalEvaluationProgram = '''
|
const globalEvaluationProgram = '''
|
||||||
|
import 'package:foo/foo.dart';
|
||||||
|
|
||||||
var myGlobal = 'Hello, world!';
|
var myGlobal = 'Hello, world!';
|
||||||
void main(List<String> args) async {
|
void main(List<String> args) async {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
foo();
|
||||||
print('.');
|
print('.');
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,11 +174,14 @@ class DapTestSession {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a simple package named `foo` that has an empty `foo` function.
|
/// Create a simple package named `foo` that has an empty `foo` function and
|
||||||
Future<Uri> createFooPackage([String? filename]) {
|
/// a top-level variable `fooGlobal`.
|
||||||
|
Future<(Uri, File)> createFooPackage([String? filename]) {
|
||||||
return createSimplePackage(
|
return createSimplePackage(
|
||||||
'foo',
|
'foo',
|
||||||
'''
|
'''
|
||||||
|
var fooGlobal = 'Hello, foo!';
|
||||||
|
|
||||||
foo() {
|
foo() {
|
||||||
// Does nothing.
|
// Does nothing.
|
||||||
}
|
}
|
||||||
|
@ -202,7 +205,7 @@ environment:
|
||||||
|
|
||||||
/// Creates a simple package script and adds the package to
|
/// Creates a simple package script and adds the package to
|
||||||
/// .dart_tool/package_config.json
|
/// .dart_tool/package_config.json
|
||||||
Future<Uri> createSimplePackage(
|
Future<(Uri, File)> createSimplePackage(
|
||||||
String name,
|
String name,
|
||||||
String content, [
|
String content, [
|
||||||
String? filename,
|
String? filename,
|
||||||
|
@ -222,7 +225,7 @@ environment:
|
||||||
final fileUri = Uri.file('${packageDir.path}/');
|
final fileUri = Uri.file('${packageDir.path}/');
|
||||||
await addPackageDependency(testAppDir, name, fileUri);
|
await addPackageDependency(testAppDir, name, fileUri);
|
||||||
|
|
||||||
return Uri.parse('package:$name/$filename');
|
return (Uri.parse('package:$name/$filename'), testFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a file in a temporary folder to be used as an application for testing.
|
/// Creates a file in a temporary folder to be used as an application for testing.
|
||||||
|
|
Loading…
Reference in a new issue