[ResidentFrontendServer] Add 'replaceCachedDill' endpoint
TEST=test case added to
`pkg/frontend_server/test/src/resident_frontend_server_test.dart`
Change-Id: I45a4b28c8c89714dcb93fa7f64874e158a0d69df
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/394763
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Derek Xu <derekx@google.com>
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index 5ae7891..17e97c7 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -8,6 +8,8 @@
import 'package:args/args.dart';
import 'package:front_end/src/api_prototype/compiler_options.dart'
show Verbosity;
+import 'package:frontend_server/resident_frontend_server_utils.dart'
+ show invokeReplaceCachedDill;
import 'package:path/path.dart';
import 'package:pub/pub.dart';
@@ -428,8 +430,26 @@
}
final executableFile = File(executable.executable);
- if (!await isFileKernelFile(executableFile) &&
- !await isFileAppJitSnapshot(executableFile) &&
+ if (await isFileKernelFile(executableFile)) {
+ // If the file is a kernel file, we do not need to compile it, but we do
+ // need to replace the file in the resident frontend compiler kernel
+ // cache associated with this executable, because the cached kernel file
+ // may be used to populate context for expression evaluation later.
+ await ensureCompilationServerIsRunning(residentCompilerInfoFile);
+ final succeeded = await invokeReplaceCachedDill(
+ replacementDillPath: executableFile.absolute.path,
+ serverInfoFile: residentCompilerInfoFile,
+ );
+ if (!succeeded) {
+ log.stderr(
+ 'Error: Encountered a problem accessing the Resident Frontend '
+ "Compiler's kernel file cache. Please try re-running the same "
+ 'command again. If the error persists, please file an issue at '
+ 'https://github.com/dart-lang/sdk/issues/new.',
+ );
+ return errorExitCode;
+ }
+ } else if (!await isFileAppJitSnapshot(executableFile) &&
!await isFileAotSnapshot(executableFile)) {
final compiledKernelFile = await _compileToKernelUsingResidentCompiler(
executable: executable,
diff --git a/pkg/frontend_server/lib/resident_frontend_server_utils.dart b/pkg/frontend_server/lib/resident_frontend_server_utils.dart
index a1382ae..20efd4d 100644
--- a/pkg/frontend_server/lib/resident_frontend_server_utils.dart
+++ b/pkg/frontend_server/lib/resident_frontend_server_utils.dart
@@ -2,7 +2,7 @@
// 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:convert' show jsonDecode;
+import 'dart:convert' show jsonDecode, jsonEncode;
import 'dart:io' show Directory, File, InternetAddress, Socket;
import 'package:path/path.dart' as path;
@@ -112,3 +112,22 @@
client?.destroy();
return jsonResponse;
}
+
+/// Sends a 'replaceCachedDill' request with [replacementDillPath] as the lone
+/// argument to the resident frontend compiler associated with [serverInfoFile],
+/// and returns a boolean indicating whether or not replacement succeeded.
+///
+/// Throws a [FileSystemException] if [serverInfoFile] cannot be accessed.
+Future<bool> invokeReplaceCachedDill({
+ required String replacementDillPath,
+ required File serverInfoFile,
+}) async {
+ final Map<String, dynamic> response = await sendAndReceiveResponse(
+ jsonEncode({
+ 'command': 'replaceCachedDill',
+ 'replacementDillPath': replacementDillPath,
+ }),
+ serverInfoFile,
+ );
+ return response['success'];
+}
diff --git a/pkg/frontend_server/lib/src/resident_frontend_server.dart b/pkg/frontend_server/lib/src/resident_frontend_server.dart
index c6e2c51..18b44b8b 100644
--- a/pkg/frontend_server/lib/src/resident_frontend_server.dart
+++ b/pkg/frontend_server/lib/src/resident_frontend_server.dart
@@ -18,6 +18,7 @@
import 'package:args/args.dart';
import 'package:front_end/src/api_unstable/vm.dart';
import 'package:kernel/binary/tag.dart' show expectedSdkHash;
+import 'package:kernel/kernel.dart' show Component, loadComponentFromBytes;
import 'package:path/path.dart' as path;
import '../frontend_server.dart';
@@ -89,6 +90,10 @@
updateState(_compileOptions);
}
+ void resetStateToWaitingForFirstCompile() {
+ _state = _ResidentState.waitingForFirstCompile;
+ }
+
/// The [ResidentCompiler] will use the [newOptions] for future compilation
/// requests.
void updateState(ArgResults newOptions) {
@@ -99,7 +104,7 @@
// Refresh the compiler's output for the next compile
_compilerOutput.clear();
_formattedOutput.clear();
- _state = _ResidentState.waitingForFirstCompile;
+ resetStateToWaitingForFirstCompile();
}
/// The current compiler options are outdated when any option has changed
@@ -170,7 +175,7 @@
..resetIncrementalCompiler();
_state = _ResidentState.waitingForRecompile;
} else {
- _state = _ResidentState.waitingForFirstCompile;
+ resetStateToWaitingForFirstCompile();
}
return _createResponseMap(
@@ -256,6 +261,8 @@
/// residentListenAndCompile method.
class ResidentFrontendServer {
static const String _commandString = 'command';
+ static const String _replaceCachedDillString = 'replaceCachedDill';
+ static const String _replacementDillPathString = 'replacementDillPath';
static const String _compileString = 'compile';
static const String _executableString = 'executable';
static const String _packageString = 'packages';
@@ -301,6 +308,43 @@
return residentCompiler;
}
+ static Future<String> _handleReplaceCachedDillRequest(
+ Map<String, dynamic> request,
+ ) async {
+ if (request[_replacementDillPathString] == null) {
+ return _encodeErrorMessage(
+ "'$_replaceCachedDillString' requests must include a "
+ "'$_replacementDillPathString' property.",
+ );
+ }
+
+ final File replacementDillFile =
+ new File(request[_replacementDillPathString]);
+
+ final String canonicalizedLibraryPath;
+ try {
+ final Component component =
+ loadComponentFromBytes(replacementDillFile.readAsBytesSync());
+ canonicalizedLibraryPath = path.canonicalize(
+ component.mainMethod!.enclosingLibrary.fileUri.toFilePath(),
+ );
+
+ final String cachedDillPath =
+ computeCachedDillPath(canonicalizedLibraryPath);
+ replacementDillFile.copySync(cachedDillPath);
+ } catch (e) {
+ return _encodeErrorMessage('Failed to replace cached dill');
+ }
+
+ if (compilers[canonicalizedLibraryPath] != null) {
+ compilers[canonicalizedLibraryPath]!.resetStateToWaitingForFirstCompile();
+ }
+
+ return jsonEncode({
+ "success": true,
+ });
+ }
+
static Future<String> _handleCompileRequest(
Map<String, dynamic> request,
) async {
@@ -365,6 +409,8 @@
}
switch (request[_commandString]) {
+ case _replaceCachedDillString:
+ return _handleReplaceCachedDillRequest(request);
case _compileString:
return _handleCompileRequest(request);
case _shutdownString:
diff --git a/pkg/frontend_server/test/src/resident_frontend_server_test.dart b/pkg/frontend_server/test/src/resident_frontend_server_test.dart
index bf01c6a..1b7cf14 100644
--- a/pkg/frontend_server/test/src/resident_frontend_server_test.dart
+++ b/pkg/frontend_server/test/src/resident_frontend_server_test.dart
@@ -103,7 +103,72 @@
});
});
- group('Resident Frontend Server: compile tests: ', () {
+ group("Resident Frontend Server: 'replaceCachedDill' command tests: ", () {
+ late Directory d;
+ late File executable, outputDill;
+
+ setUp(() async {
+ d = Directory.systemTemp.createTempSync();
+ executable = new File(path.join(d.path, 'src.dart'))
+ ..createSync()
+ ..writeAsStringSync('void main() {print("hello " "there");}');
+ outputDill = new File(path.join(d.path, 'src.dart.dill'));
+ });
+
+ tearDown(() async {
+ d.deleteSync(recursive: true);
+ ResidentFrontendServer.compilers.clear();
+ });
+
+ test('basic', () async {
+ final File cachedDillFile =
+ new File(computeCachedDillPath(executable.path));
+ expect(cachedDillFile.existsSync(), false);
+
+ final Map<String, dynamic> compileResult =
+ jsonDecode(await ResidentFrontendServer.handleRequest(
+ ResidentFrontendServer.createCompileJSON(
+ executable: executable.path,
+ outputDill: outputDill.path,
+ ),
+ ));
+ expect(compileResult['success'], true);
+
+ expect(cachedDillFile.existsSync(), true);
+ // Delete the kernel file associated with [executable.path] from the
+ // resident frontend compiler kernel cache.
+ cachedDillFile.deleteSync();
+
+ final Map<String, dynamic> replaceCachedDillResult = jsonDecode(
+ await ResidentFrontendServer.handleRequest(
+ jsonEncode({
+ 'command': 'replaceCachedDill',
+ 'replacementDillPath': outputDill.path,
+ }),
+ ),
+ );
+ expect(replaceCachedDillResult['success'], true);
+ // Calling 'replaceCachedDill' with [outputDill] as the replacement dill
+ // should make [outputDill] the kernel file associated with
+ // [executable.path] in the resident frontend compiler kernel cache.
+ expect(cachedDillFile.existsSync(), true);
+ cachedDillFile.deleteSync();
+ });
+
+ test("invalid 'replacementDillPath' property in request", () async {
+ final Map<String, dynamic> replaceCachedDillResult = jsonDecode(
+ await ResidentFrontendServer.handleRequest(
+ jsonEncode({
+ 'command': 'replaceCachedDill',
+ 'replacementDillPath': path.join(d.path, 'nonexistent'),
+ }),
+ ),
+ );
+ expect(replaceCachedDillResult['success'], false);
+ });
+ });
+
+ group("Resident Frontend Server: 'compile' command tests: ", () {
late Directory d;
late File executable, package, outputDill;