diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart index 93e58d8a849..73a2775b60a 100644 --- a/pkg/dds/lib/src/dap/adapters/dart.dart +++ b/pkg/dds/lib/src/dap/adapters/dart.dart @@ -324,6 +324,9 @@ abstract class DartDebugAdapter shutdown()); + final vmPath = Platform.resolvedExecutable; + sdkRoot = path.dirname(path.dirname(vmPath)); + _isolateManager = IsolateManager(this); _converter = ProtocolConverter(this); } @@ -1310,6 +1316,56 @@ abstract class DartDebugAdapter _defaultDartlangSdkRootUri; + + /// Converts a URI in the form org-dartlang-sdk:///sdk/lib/collection/hash_set.dart + /// to a local file path based on the current SDK. + String? convertOrgDartlangSdkToPath(Uri uri) { + // org-dartlang-sdk URIs can be in multiple forms: + // + // - org-dartlang-sdk:///sdk/lib/collection/hash_set.dart + // - org-dartlang-sdk:///runtime/lib/convert_patch.dart + // + // We currently only handle the sdk folder, as we don't know which runtime + // is being used (this code is shared) and do not want to map to the wrong + // sources. + if (uri.pathSegments.isNotEmpty && + uri.path.startsWith(dartlangSdkRootUri.path)) { + return path.joinAll([ + sdkRoot, + ...uri.pathSegments.skip(dartlangSdkRootUri.pathSegments.length), + ]); + } + + return null; + } + + /// Converts a file path inside the current SDK root into a URI in the form + /// org-dartlang-sdk:///sdk/lib/collection/hash_set.dart. + Uri? convertPathToOrgDartlangSdk(String input) { + if (path.isWithin(sdkRoot, input)) { + final relative = path.relative(input, from: sdkRoot); + return Uri( + scheme: dartlangSdkRootUri.scheme, + host: '', + pathSegments: [ + ...dartlangSdkRootUri.pathSegments, + ...path.split(relative) + ], + ); + } + + return null; + } + /// [sourceRequest] is called by the client to request source code for a given /// source. /// diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart index df95ef13e1a..aa27b5f7ccb 100644 --- a/pkg/dds/lib/src/dap/isolate_manager.dart +++ b/pkg/dds/lib/src/dap/isolate_manager.dart @@ -4,10 +4,8 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io'; import 'package:collection/collection.dart'; -import 'package:path/path.dart' as path; import 'package:vm_service/vm_service.dart' as vm; import '../rpc_error_codes.dart'; @@ -67,9 +65,6 @@ class IsolateManager { /// first time the user resumes. bool autoResumeStartingIsolates = true; - /// The root of the Dart SDK containing the VM running the debug adapter. - late final String sdkRoot; - /// Tracks breakpoints last provided by the client so they can be sent to new /// isolates that appear after initial breakpoints were sent. final Map> _clientBreakpointsByUri = {}; @@ -115,10 +110,7 @@ class IsolateManager { /// Any leading character matched in place of the dollar is in the first capture. final _braceNotPrefixedByDollarOrBackslashPattern = RegExp(r'(^|[^\\\$]){'); - IsolateManager(this._adapter) { - final vmPath = Platform.resolvedExecutable; - sdkRoot = path.dirname(path.dirname(vmPath)); - } + IsolateManager(this._adapter); /// A list of all current active isolates. /// @@ -866,7 +858,8 @@ class ThreadInfo { // 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 // and we need to include a call to lookupPackageUris here. - return _convertPathToOrgDartlangSdk(filePath) ?? Uri.file(filePath); + return _manager._adapter.convertPathToOrgDartlangSdk(filePath) ?? + Uri.file(filePath); } /// Batch resolves source URIs from the VM to a file path for the package lib @@ -982,28 +975,6 @@ class ThreadInfo { /// that are round-tripped to the client. int storeData(Object data) => _manager.storeData(this, data); - /// Converts a URI in the form org-dartlang-sdk:///sdk/lib/collection/hash_set.dart - /// to a local file path based on the current SDK. - String? _convertOrgDartlangSdkToPath(Uri uri) { - // org-dartlang-sdk URIs can be in multiple forms: - // - // - org-dartlang-sdk:///sdk/lib/collection/hash_set.dart - // - org-dartlang-sdk:///runtime/lib/convert_patch.dart - // - // We currently only handle the sdk folder, as we don't know which runtime - // is being used (this code is shared) and do not want to map to the wrong - // sources. - if (uri.pathSegments.isNotEmpty && uri.pathSegments.first == 'sdk') { - // TODO(dantup): Do we need to worry about this content not matching - // up with what's local (eg. for Flutter the VM running the app is - // on another device to the VM running this DA). - final sdkRoot = _manager.sdkRoot; - return path.joinAll([sdkRoot, ...uri.pathSegments.skip(1)]); - } - - return null; - } - Uri? _convertPathToGoogle3Uri(String input) { const search = '/google3/'; if (input.startsWith('/google') && input.contains(search)) { @@ -1019,22 +990,6 @@ class ThreadInfo { return null; } - /// Converts a file path inside the current SDK root into a URI in the form - /// org-dartlang-sdk:///sdk/lib/collection/hash_set.dart. - Uri? _convertPathToOrgDartlangSdk(String input) { - final sdkRoot = _manager.sdkRoot; - if (path.isWithin(sdkRoot, input)) { - final relative = path.relative(input, from: sdkRoot); - return Uri( - scheme: 'org-dartlang-sdk', - host: '', - pathSegments: ['sdk', ...path.split(relative)], - ); - } - - return null; - } - /// Converts a URI to a file path. /// /// Supports file:// URIs and org-dartlang-sdk:// URIs. @@ -1043,8 +998,8 @@ class ThreadInfo { return null; } else if (input.isScheme('file')) { return input.toFilePath(); - } else if (input.isScheme('org-dartlang-sdk')) { - return _convertOrgDartlangSdkToPath(input); + } else if (input.isScheme(_manager._adapter.dartlangSdkRootUri.scheme)) { + return _manager._adapter.convertOrgDartlangSdkToPath(input); } else { return null; } diff --git a/pkg/dds/test/dap/base_adapter_test.dart b/pkg/dds/test/dap/base_adapter_test.dart new file mode 100644 index 00000000000..14608bcc8a5 --- /dev/null +++ b/pkg/dds/test/dap/base_adapter_test.dart @@ -0,0 +1,67 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. 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:io'; + +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +import 'mocks.dart'; + +main() { + group('dart base adapter', () { + final vmPath = Platform.resolvedExecutable; + final sdkRoot = path.dirname(path.dirname(vmPath)); + + final sdkCorePath = path.join(sdkRoot, 'lib', 'core.dart'); + final defaultCoreUri = Uri.parse('org-dartlang-sdk:///sdk/lib/core.dart'); + + group('default org-dartlang-sdk', () { + final adapter = MockDartCliDebugAdapter(); + + test('can SDK paths to org-dartlang-sdk:///', () async { + expect( + adapter.convertPathToOrgDartlangSdk(sdkCorePath), + defaultCoreUri, + ); + }); + + test('converts org-dartlang-sdk:/// to SDK paths', () async { + expect( + adapter.convertOrgDartlangSdkToPath(defaultCoreUri), + sdkCorePath, + ); + }); + }); + + group('custom org-dartlang-sdk', () { + final adapter = MockDartCliDebugAdapter() + ..dartlangSdkRootUriOverride = + Uri.parse('org-dartlang-sdk:///custom/sdk'); + final customCoreUri = + Uri.parse('org-dartlang-sdk:///custom/sdk/lib/core.dart'); + + test('converts SDK paths to custom org-dartlang-sdk:///', () async { + expect( + adapter.convertPathToOrgDartlangSdk(sdkCorePath), + customCoreUri, + ); + }); + + test('converts custom org-dartlang-sdk:/// to SDK paths', () async { + expect( + adapter.convertOrgDartlangSdkToPath(customCoreUri), + sdkCorePath, + ); + }); + + test('does not convert default org-dartlang-sdk:///', () async { + expect( + adapter.convertOrgDartlangSdkToPath(defaultCoreUri), + isNull, + ); + }); + }); + }); +} diff --git a/pkg/dds/test/dap/mocks.dart b/pkg/dds/test/dap/mocks.dart index 9afe07954e6..fb810a9b9f5 100644 --- a/pkg/dds/test/dap/mocks.dart +++ b/pkg/dds/test/dap/mocks.dart @@ -30,6 +30,15 @@ class MockDartCliDebugAdapter extends DartCliDebugAdapter { stdinController.sink, stdoutController.stream, channel); } + /// An override for [dartlangSdkRootUri]. + /// + /// If not set, the default base value will be used. + Uri? dartlangSdkRootUriOverride; + + @override + Uri get dartlangSdkRootUri => + dartlangSdkRootUriOverride ?? super.dartlangSdkRootUri; + MockDartCliDebugAdapter._( this.stdin, this.stdout, ByteStreamServerChannel channel) : super(channel);