| // Copyright (c) 2024, 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:async'; | |
| import 'dart:convert'; | |
| import 'dart:io'; | |
| import 'dart:isolate'; | |
| import 'package:devtools_shared/devtools_shared.dart' show DtdInfo; | |
| import 'package:path/path.dart' as path; | |
| import 'utils.dart'; | |
| String getDTDSnapshotDir() { | |
| // This logic is originally from pkg/dartdev/lib/src/sdk.dart | |
| // | |
| // Find SDK path. | |
| (String, bool)? trySDKPath(String executablePath) { | |
| // The common case, and how cli_util.dart computes the Dart SDK directory, | |
| // [path.dirname] called twice on Platform.executable. We confirm by | |
| // asserting that the directory `./bin/snapshots/` exists in this directory: | |
| var sdkPath = path.absolute(path.dirname(path.dirname(executablePath))); | |
| var snapshotsDir = path.join(sdkPath, 'bin', 'snapshots'); | |
| var runFromBuildRoot = false; | |
| final type = FileSystemEntity.typeSync(snapshotsDir); | |
| if (type != FileSystemEntityType.directory && | |
| type != FileSystemEntityType.link) { | |
| // This is the less common case where the user is in | |
| // the checked out Dart SDK, and is executing `dart` via: | |
| // ./out/ReleaseX64/dart ... or in google3. | |
| sdkPath = path.absolute(path.dirname(executablePath)); | |
| snapshotsDir = sdkPath; | |
| runFromBuildRoot = true; | |
| } | |
| // Try to locate the DTD snapshot to determine if we're able to find | |
| // the SDK snapshots with this SDK path. This is meant to handle | |
| // non-standard SDK layouts that can involve symlinks (e.g., Brew | |
| // installations, google3 tests, etc). Check for either the product or | |
| // non-product AOT snapshot first, falling back to the JIT snapshot. | |
| const aotSnapshots = <String>[ | |
| 'dart_tooling_daemon_aot_product.dart.snapshot', | |
| 'dart_tooling_daemon_aot.dart.snapshot', | |
| ]; | |
| final hasAotSnapshot = aotSnapshots.any( | |
| (s) => File(path.join(snapshotsDir, s)).existsSync(), | |
| ); | |
| if (!hasAotSnapshot) { | |
| // We do not have an AOT snapshot and hence look for the JIT snapshot. | |
| if (!File( | |
| path.join(snapshotsDir, 'dart_tooling_daemon.dart.snapshot'), | |
| ).existsSync()) { | |
| return null; | |
| } | |
| } | |
| return (sdkPath, runFromBuildRoot); | |
| } | |
| final (sdkPath, runFromBuildRoot) = trySDKPath(Platform.resolvedExecutable) ?? | |
| trySDKPath(Platform.executable)!; | |
| final String snapshotDir; | |
| if (runFromBuildRoot) { | |
| snapshotDir = sdkPath; | |
| } else { | |
| snapshotDir = path.absolute(sdkPath, 'bin', 'snapshots'); | |
| } | |
| return snapshotDir; | |
| } | |
| Future<DtdInfo?> startDtd({ | |
| required bool machineMode, | |
| required bool printDtdUri, | |
| }) async { | |
| final snapshotDir = getDTDSnapshotDir(); | |
| // In product builds (e.g., prebuilt SDKs and google3), the AOT snapshot is | |
| // built with the product-mode AOT runtime | |
| // (`dart_tooling_daemon_aot_product.dart.snapshot`), whereas local | |
| // non-product SDK builds produce `dart_tooling_daemon_aot.dart.snapshot`. | |
| // Check the AOT snapshot matching the current runtime mode first, falling | |
| // back to the other AOT variant if only one was built, and finally falling | |
| // back to the JIT snapshot (`dart_tooling_daemon.dart.snapshot`) when running | |
| // on a JIT VM. | |
| final isProduct = const bool.fromEnvironment('dart.vm.product'); | |
| final aotSnapshots = isProduct | |
| ? <String>[ | |
| 'dart_tooling_daemon_aot_product.dart.snapshot', | |
| 'dart_tooling_daemon_aot.dart.snapshot', | |
| ] | |
| : <String>[ | |
| 'dart_tooling_daemon_aot.dart.snapshot', | |
| 'dart_tooling_daemon_aot_product.dart.snapshot', | |
| ]; | |
| final candidateSnapshots = <String>[ | |
| ...aotSnapshots, | |
| 'dart_tooling_daemon.dart.snapshot', | |
| ]; | |
| final completer = Completer<DtdInfo?>(); | |
| void completeForError([_]) { | |
| if (!completer.isCompleted) { | |
| completer.complete(null); | |
| } | |
| } | |
| final exitPort = ReceivePort()..listen(completeForError); | |
| final errorPort = ReceivePort()..listen(completeForError); | |
| final receivePort = ReceivePort() | |
| ..listen((message) { | |
| try { | |
| // [message] is a JSON encoded String from package:dtd_impl. | |
| final json = jsonDecode(message) as Map<String, Object?>; | |
| if (json | |
| case { | |
| 'tooling_daemon_details': { | |
| 'uri': String uri, | |
| 'trusted_client_secret': String secret, | |
| } | |
| }) { | |
| if (printDtdUri || machineMode) { | |
| DevToolsUtils.printOutput( | |
| 'Serving the Dart Tooling Daemon at $uri', | |
| { | |
| 'event': 'server.dtdStarted', | |
| 'params': {'uri': uri}, | |
| }, | |
| machineMode: machineMode, | |
| ); | |
| } | |
| if (!completer.isCompleted) { | |
| completer.complete(DtdInfo(Uri.parse(uri), secret: secret)); | |
| } | |
| } | |
| } catch (_) { | |
| completeForError(); | |
| } | |
| }); | |
| for (final candidate in candidateSnapshots) { | |
| final candidatePath = path.absolute(snapshotDir, candidate); | |
| if (!File(candidatePath).existsSync()) { | |
| continue; | |
| } | |
| try { | |
| await Isolate.spawnUri( | |
| Uri.file(candidatePath), | |
| ['--machine'], | |
| receivePort.sendPort, | |
| onExit: exitPort.sendPort, | |
| onError: errorPort.sendPort, | |
| ); | |
| break; | |
| } catch (_) { | |
| // Spawning this snapshot failed, try the next candidate. | |
| } | |
| } | |
| final result = await completer.future.timeout( | |
| const Duration(seconds: 5), | |
| onTimeout: () => null, | |
| ); | |
| receivePort.close(); | |
| errorPort.close(); | |
| exitPort.close(); | |
| return result; | |
| } |