Skip to content

Commit

Permalink
Expose service manager helpers in devtools_app_shared (#7923)
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll authored Jun 12, 2024
1 parent 39812d6 commit 6ff6bff
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ class ExtensionService extends DisposableController
serviceConnection.serviceManager.connectedState.value.connected &&
serviceConnection.serviceManager.isolateManager.mainIsolate.value !=
null) {
_appRoot = await serviceConnection.connectedAppPackageRoot();
_appRoot = await serviceConnection.serviceManager
.connectedAppPackageRoot(dtdManager);
}

// TODO(kenz): gracefully handle app connections / disconnects when there
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ class DeepLinksController extends DisposableController
if (!serviceConnection.serviceManager.hasConnection) {
return null;
}
final packageUriString =
await serviceConnection.rootPackageDirectoryForMainIsolate();
final packageUriString = await serviceConnection.serviceManager
.rootPackageDirectoryForMainIsolate(dtdManager);
if (packageUriString == null) return null;
return Uri.parse(packageUriString).toFilePath();
}
Expand Down
111 changes: 0 additions & 111 deletions packages/devtools_app/lib/src/service/service_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'dart:async';

import 'package:collection/collection.dart';
import 'package:devtools_app_shared/service.dart' hide SentinelException;
import 'package:devtools_shared/devtools_shared.dart';
import 'package:logging/logging.dart';
import 'package:vm_service/vm_service.dart' hide Error;

Expand Down Expand Up @@ -175,116 +174,6 @@ class ServiceConnectionManager {
await serviceManager.isolateManager.init(isolates);
}

/// Returns the package root URI for the connected app.
///
/// This should be the directory up the tree from the debugging target that
/// contains the .dart_tool/package_config.json file.
///
/// This method contains special logic for detecting the package root for
/// test targets (i.e., a VM service connections spawned from `dart test` or
/// `flutter test`). This is because the main isolate for test targets is
/// running the test runner, and not the test library itself, so we have to do
/// some extra work to find the package root of the test target.
Future<Uri?> connectedAppPackageRoot() async {
var packageRootUriString = await rootPackageDirectoryForMainIsolate();
_log.fine(
'[connectedAppPackageRoot] root package directory for main isolate: '
'$packageRootUriString',
);

// If a Dart library URI was returned, this may be a test target (i.e. a
// VM service connection spawned from `dart test` or `flutter test`).
if (packageRootUriString?.endsWith('.dart') ?? false) {
final rootLibrary = await _mainIsolateRootLibrary();
final testTargetFileUriString =
(rootLibrary?.dependencies ?? <LibraryDependency>[])
.firstWhereOrNull((dep) => dep.prefix == 'test')
?.target
?.uri;
if (testTargetFileUriString != null) {
_log.fine(
'[connectedAppPackageRoot] detected test library from root library '
'imports: $testTargetFileUriString',
);
packageRootUriString = await packageRootFromFileUriString(
testTargetFileUriString,
dtd: dtdManager.connection.value,
);
_log.fine(
'[connectedAppPackageRoot] package root for test target: '
'$packageRootUriString',
);
}
}

return packageRootUriString == null
? null
: Uri.parse(packageRootUriString);
}

Future<Library?> _mainIsolateRootLibrary() async {
final ref = (await serviceManager.isolateManager.waitForMainIsolateState())
?.isolateNow
?.rootLib;
if (ref == null) return null;
try {
final library = await serviceManager.service!.getObject(
serviceManager.isolateManager.mainIsolate.value!.id!,
ref.id!,
);
assert(library is Library);
return library as Library;
} on SentinelException catch (_) {
// Fail gracefully if the request to get the Library object fails.
return null;
}
}

// TODO(kenz): consider caching this value for the duration of the VM service
// connection.
/// Returns a file URI String for the root library of the connected app's main
/// isolate.
///
/// If a non-null value is returned, the value will be a file URI String and
/// it will NOT have a trailing slash.
Future<String?> mainIsolateRootLibraryUriAsString() async {
final mainIsolateState =
await serviceManager.isolateManager.waitForMainIsolateState();
if (mainIsolateState == null) return null;

final rootLib = mainIsolateState.rootInfo?.library;
if (rootLib == null) return null;

final selectedIsolateRefId =
serviceManager.isolateManager.mainIsolate.value!.id!;
await serviceManager.resolvedUriManager
.fetchFileUris(selectedIsolateRefId, [rootLib]);
final fileUriString = serviceManager.resolvedUriManager.lookupFileUri(
selectedIsolateRefId,
rootLib,
);
_log.fine('rootLibraryForMainIsolate: $fileUriString');
return fileUriString;
}

// TODO(kenz): consider caching this value for the duration of the VM service
// connection.
/// Returns the root package directory for the main isolate.
///
/// If a non-null value is returned, the value will be a file URI String and
/// it will NOT have a trailing slash.
Future<String?> rootPackageDirectoryForMainIsolate() async {
final fileUriString = await mainIsolateRootLibraryUriAsString();
final packageUriString = fileUriString != null
? await packageRootFromFileUriString(
fileUriString,
dtd: dtdManager.connection.value,
)
: null;
_log.fine('rootPackageDirectoryForMainIsolate: $packageUriString');
return packageUriString;
}

Future<Response> get adbMemoryInfo async {
return await serviceManager.callServiceOnMainIsolate(
registrations.flutterMemoryInfo.service,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ class InspectorPreferencesController extends DisposableController
/// directories are for the current project so we make a best guess based on
/// the root library for the main isolate.
Future<String?> _inferPubRootDirectory() async {
final fileUriString =
await serviceConnection.mainIsolateRootLibraryUriAsString();
final fileUriString = await serviceConnection.serviceManager
.mainIsolateRootLibraryUriAsString();
if (fileUriString == null) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ class InspectorV2PreferencesController extends DisposableController
/// directories are for the current project so we make a best guess based on
/// the root library for the main isolate.
Future<String?> _inferPubRootDirectory() async {
final fileUriString =
await serviceConnection.mainIsolateRootLibraryUriAsString();
final fileUriString = await serviceConnection.serviceManager
.mainIsolateRootLibraryUriAsString();
if (fileUriString == null) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ void main() {
await inspectorServiceLocal.isWidgetCreationTracked(),
isTrue,
);
final rootLibrary =
await serviceConnection.mainIsolateRootLibraryUriAsString();
final rootLibrary = await serviceConnection.serviceManager
.mainIsolateRootLibraryUriAsString();
await inspectorServiceLocal.addPubRootDirectories([rootLibrary!]);
final rootDirectories =
await inspectorServiceLocal.getPubRootDirectories() ??
Expand Down
2 changes: 2 additions & 0 deletions packages/devtools_app_shared/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## 0.2.0
* Add `navigateToCode` utility method for jumping to code in IDEs.
* Add `FlutterEvent` and `DeveloperServiceEvent` constants.
* Add `connectedAppPackageRoot`, `rootPackageDirectoryForMainIsolate`, and
`mainIsolateRootLibraryUriAsString` methods to the `ServiceManager` class.

## 0.2.0-dev.0
* Add `tooltipWaitExtraLong` to `utils.dart`.
Expand Down
112 changes: 112 additions & 0 deletions packages/devtools_app_shared/lib/src/service/service_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
import 'dart:async';
import 'dart:core';

import 'package:collection/collection.dart';
import 'package:dds_service_extensions/dds_service_extensions.dart';
import 'package:devtools_shared/devtools_shared.dart';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' hide Error;

import '../utils/utils.dart';
import 'connected_app.dart';
import 'dtd_manager.dart';
import 'flutter_version.dart';
import 'isolate_manager.dart';
import 'isolate_state.dart';
Expand Down Expand Up @@ -495,6 +498,115 @@ class ServiceManager<T extends VmService> {
isolateManager.hotRestartInProgress = false;
}
}

/// Returns the package root URI for the connected app.
///
/// This should be the directory up the tree from the debugging target that
/// contains the .dart_tool/package_config.json file.
///
/// This method contains special logic for detecting the package root for
/// test targets (i.e., a VM service connections spawned from `dart test` or
/// `flutter test`). This is because the main isolate for test targets is
/// running the test runner, and not the test library itself, so we have to do
/// some extra work to find the package root of the test target.
Future<Uri?> connectedAppPackageRoot(DTDManager dtdManager) async {
var packageRootUriString =
await rootPackageDirectoryForMainIsolate(dtdManager);
_log.fine(
'[connectedAppPackageRoot] root package directory for main isolate: '
'$packageRootUriString',
);

// If a Dart library URI was returned, this may be a test target (i.e. a
// VM service connection spawned from `dart test` or `flutter test`).
if (packageRootUriString?.endsWith('.dart') ?? false) {
final rootLibrary = await _mainIsolateRootLibrary();
final testTargetFileUriString =
(rootLibrary?.dependencies ?? <LibraryDependency>[])
.firstWhereOrNull((dep) => dep.prefix == 'test')
?.target
?.uri;
if (testTargetFileUriString != null) {
_log.fine(
'[connectedAppPackageRoot] detected test library from root library '
'imports: $testTargetFileUriString',
);
packageRootUriString = await packageRootFromFileUriString(
testTargetFileUriString,
dtd: dtdManager.connection.value,
);
_log.fine(
'[connectedAppPackageRoot] package root for test target: '
'$packageRootUriString',
);
}
}

return packageRootUriString == null
? null
: Uri.parse(packageRootUriString);
}

Future<Library?> _mainIsolateRootLibrary() async {
final ref =
(await isolateManager.waitForMainIsolateState())?.isolateNow?.rootLib;
if (ref == null) return null;
try {
final library = await service!.getObject(
isolateManager.mainIsolate.value!.id!,
ref.id!,
);
assert(library is Library);
return library as Library;
} on SentinelException catch (_) {
// Fail gracefully if the request to get the Library object fails.
return null;
}
}

// TODO(kenz): consider caching this value for the duration of the VM service
// connection.
/// Returns a file URI String for the root library of the connected app's main
/// isolate.
///
/// If a non-null value is returned, the value will be a file URI String and
/// it will NOT have a trailing slash.
Future<String?> mainIsolateRootLibraryUriAsString() async {
final mainIsolateState = await isolateManager.waitForMainIsolateState();
if (mainIsolateState == null) return null;

final rootLib = mainIsolateState.rootInfo?.library;
if (rootLib == null) return null;

final selectedIsolateRefId = isolateManager.mainIsolate.value!.id!;
await resolvedUriManager.fetchFileUris(selectedIsolateRefId, [rootLib]);
final fileUriString = resolvedUriManager.lookupFileUri(
selectedIsolateRefId,
rootLib,
);
_log.fine('rootLibraryForMainIsolate: $fileUriString');
return fileUriString;
}

// TODO(kenz): consider caching this value for the duration of the VM service
// connection.
/// Returns the root package directory for the main isolate.
///
/// If a non-null value is returned, the value will be a file URI String and
/// it will NOT have a trailing slash.
Future<String?> rootPackageDirectoryForMainIsolate(
DTDManager dtdManager,
) async {
final fileUriString = await mainIsolateRootLibraryUriAsString();
final packageUriString = fileUriString != null
? await packageRootFromFileUriString(
fileUriString,
dtd: dtdManager.connection.value,
)
: null;
_log.fine('rootPackageDirectoryForMainIsolate: $packageUriString');
return packageUriString;
}
}

class VmServiceCapabilities {
Expand Down
13 changes: 6 additions & 7 deletions packages/devtools_test/lib/src/mocks/fake_service_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,6 @@ class FakeServiceConnectionManager extends Fake
}) {
return Future.value();
}

@override
Future<String?> mainIsolateRootLibraryUriAsString() {
final fakeIsolateManager =
_serviceManager.isolateManager as FakeIsolateManager;
return Future.value(fakeIsolateManager.rootLibrary);
}
}

// ignore: subtype_of_sealed_class, fake for testing.
Expand Down Expand Up @@ -304,4 +297,10 @@ class FakeServiceManager extends Fake
await initFlagManager();
return Future.value();
}

@override
Future<String?> mainIsolateRootLibraryUriAsString() {
final fakeIsolateManager = isolateManager as FakeIsolateManager;
return Future.value(fakeIsolateManager.rootLibrary);
}
}

0 comments on commit 6ff6bff

Please sign in to comment.