7a5c71ba05
(Part of https://github.com/dart-lang/sdk/issues/63288) This change migrates the frontend_server package to use the new constructor declaration syntax, described in https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations. This change was performed in an automated fashion, by (a) bumping the package's SDK constraint to `3.13.0-0`, (b) enabling the lints `unnecessary_type_name_in_constructor` and `unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint failures using `dart fix`, and then (d) reformatting the affected files. To ease code review, I've reverted unrelated formatting changes. Change-Id: Icbc924a1faae2f1c8fe0d5fa07bb9bd66a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508369 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
287 lines
9.1 KiB
Dart
287 lines
9.1 KiB
Dart
// 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:convert' show jsonDecode, jsonEncode;
|
|
import 'dart:io'
|
|
show Directory, File, FileSystemException, InternetAddress, Socket;
|
|
|
|
import 'package:path/path.dart' as path;
|
|
|
|
final class ResidentCompilerInfo {
|
|
/// The SDK hash that kernel files compiled using the Resident Frontend
|
|
/// Compiler associated with this object will be stamped with.
|
|
final String? sdkHash;
|
|
|
|
/// The address that the Resident Frontend Compiler associated with this
|
|
/// object is listening from.
|
|
final InternetAddress address;
|
|
|
|
/// The port number that the Resident Frontend Compiler associated with this
|
|
/// object is listening on.
|
|
final int port;
|
|
|
|
/// Extracts the value associated with a key from [entries], where [entries]
|
|
/// is a [String] with the format '$key1:$value1 $key2:$value2 ...'.
|
|
static String _extractValueAssociatedWithKey(String entries, String key) =>
|
|
new RegExp(
|
|
'$key:'
|
|
r'(\S+)(\s|$)',
|
|
).allMatches(entries).first[1]!;
|
|
|
|
static ResidentCompilerInfo fromFile(File file) {
|
|
final String fileContents = file.readAsStringSync();
|
|
|
|
return new ResidentCompilerInfo._(
|
|
sdkHash: fileContents.contains('sdkHash:')
|
|
? _extractValueAssociatedWithKey(fileContents, 'sdkHash')
|
|
: null,
|
|
address: new InternetAddress(
|
|
_extractValueAssociatedWithKey(fileContents, 'address'),
|
|
),
|
|
port: int.parse(_extractValueAssociatedWithKey(fileContents, 'port')),
|
|
);
|
|
}
|
|
|
|
new _({required this.sdkHash, required this.port, required this.address});
|
|
}
|
|
|
|
typedef CachedDillAndCompilerOptionsPaths = ({
|
|
String cachedDillPath,
|
|
String cachedCompilerOptionsPath,
|
|
});
|
|
|
|
/// Returns the absolute paths to the cached kernel file and the cached compiler
|
|
/// options file associated with [canonicalizedLibraryPath].
|
|
CachedDillAndCompilerOptionsPaths computeCachedDillAndCompilerOptionsPaths(
|
|
String canonicalizedLibraryPath,
|
|
) {
|
|
final String dirname = path.dirname(canonicalizedLibraryPath);
|
|
final String basename = path.basename(canonicalizedLibraryPath);
|
|
|
|
final String cachedKernelDirectoryPath = path.join(
|
|
path.join(Directory.systemTemp.path, 'dart_resident_compiler_kernel_cache'),
|
|
dirname.replaceAll(new RegExp(r':|\\|\/'), '_'),
|
|
);
|
|
|
|
try {
|
|
new Directory(cachedKernelDirectoryPath).createSync(recursive: true);
|
|
} catch (e) {
|
|
throw new Exception(
|
|
'Failed to create directory for storing cached kernel files',
|
|
);
|
|
}
|
|
|
|
final String cachedDillPath = path.join(
|
|
cachedKernelDirectoryPath,
|
|
'$basename.dill',
|
|
);
|
|
final String cachedCompilerOptionsPath = path.join(
|
|
cachedKernelDirectoryPath,
|
|
'${basename}_options.json',
|
|
);
|
|
return (
|
|
cachedDillPath: cachedDillPath,
|
|
cachedCompilerOptionsPath: cachedCompilerOptionsPath,
|
|
);
|
|
}
|
|
|
|
/// Sends a compilation [request] to the resident frontend compiler associated
|
|
/// with [serverInfoFile], and returns the compiler's JSON response.
|
|
///
|
|
/// Throws a [FileSystemException] if [serverInfoFile] cannot be accessed.
|
|
Future<Map<String, Object?>> sendAndReceiveResponse(
|
|
String request,
|
|
File serverInfoFile,
|
|
) async {
|
|
Socket? client;
|
|
Map<String, Object?> jsonResponse;
|
|
final ResidentCompilerInfo residentCompilerInfo =
|
|
ResidentCompilerInfo.fromFile(serverInfoFile);
|
|
|
|
try {
|
|
client = await Socket.connect(
|
|
residentCompilerInfo.address,
|
|
residentCompilerInfo.port,
|
|
);
|
|
client.write(request);
|
|
final String data = new String.fromCharCodes(await client.first);
|
|
jsonResponse = (jsonDecode(data) as Map<String, Object?>);
|
|
} catch (e) {
|
|
jsonResponse = <String, Object?>{
|
|
'success': false,
|
|
'errorMessage': e.toString(),
|
|
};
|
|
}
|
|
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, Object?> response = await sendAndReceiveResponse(
|
|
jsonEncode({
|
|
'command': 'replaceCachedDill',
|
|
'replacementDillPath': replacementDillPath,
|
|
}),
|
|
serverInfoFile,
|
|
);
|
|
return response['success'] == true;
|
|
}
|
|
|
|
/// The result of a successful compilation request sent to a resident
|
|
/// frontend compiler.
|
|
final class CompileResult {
|
|
/// The absolute path to the kernel file produced by the compiler.
|
|
final String outputDill;
|
|
|
|
/// The number of errors produced by the compiler.
|
|
final int errorCount;
|
|
|
|
/// The output lines produced by the compiler, if any.
|
|
final List<String> compilerOutputLines;
|
|
|
|
new({
|
|
required this.outputDill,
|
|
required this.errorCount,
|
|
this.compilerOutputLines = const [],
|
|
});
|
|
}
|
|
|
|
/// The result of a successful expression compilation request sent to a
|
|
/// resident frontend compiler.
|
|
final class CompileExpressionResult {
|
|
/// The base64 encoded kernel bytes produced by the compiler.
|
|
final String kernelBytes;
|
|
|
|
/// The number of errors produced by the compiler.
|
|
final int errorCount;
|
|
|
|
/// The output lines produced by the compiler, if any.
|
|
final List<String> compilerOutputLines;
|
|
|
|
new({
|
|
required this.kernelBytes,
|
|
required this.errorCount,
|
|
this.compilerOutputLines = const [],
|
|
});
|
|
}
|
|
|
|
/// The exception thrown when a compilation request to the resident frontend
|
|
/// compiler fails.
|
|
final class CompileException implements Exception {
|
|
/// The error message from the compiler.
|
|
final String message;
|
|
|
|
new(this.message);
|
|
|
|
@override
|
|
String toString() => 'CompileException: $message';
|
|
}
|
|
|
|
/// Sends a 'compile' request to the resident frontend compiler associated with
|
|
/// [serverInfoFile], and returns a [CompileResult] on success.
|
|
///
|
|
/// Throws a [CompileException] if compilation fails.
|
|
/// Throws a [FileSystemException] if [serverInfoFile] cannot be accessed.
|
|
Future<CompileResult> invokeCompile({
|
|
required String executable,
|
|
required String outputDill,
|
|
required File serverInfoFile,
|
|
}) async {
|
|
final Map<String, Object?> response = await sendAndReceiveResponse(
|
|
jsonEncode({
|
|
'command': 'compile',
|
|
'executable': executable,
|
|
'output-dill': outputDill,
|
|
'useCachedCompilerOptionsAsBase': true,
|
|
}),
|
|
serverInfoFile,
|
|
);
|
|
|
|
if (response['success'] != true) {
|
|
final String errorMessage = switch (response) {
|
|
{'errorMessage': final String errorMessage} => errorMessage,
|
|
{'compilerOutputLines': final List<Object?> lines} => lines.join('\n'),
|
|
_ => 'Unknown error: $response',
|
|
};
|
|
throw new CompileException(errorMessage);
|
|
}
|
|
|
|
return new CompileResult(
|
|
outputDill: response['output-dill'] as String,
|
|
errorCount: response['errorCount'] as int,
|
|
compilerOutputLines:
|
|
(response['compilerOutputLines'] as List<Object?>?)?.cast<String>() ??
|
|
const [],
|
|
);
|
|
}
|
|
|
|
/// Sends a 'compileExpression' request to the resident frontend compiler
|
|
/// associated with [serverInfoFile], and returns a [CompileExpressionResult]
|
|
/// on success.
|
|
///
|
|
/// Throws a [CompileException] if compilation fails.
|
|
/// Throws a [FileSystemException] if [serverInfoFile] cannot be accessed.
|
|
Future<CompileExpressionResult> invokeCompileExpression({
|
|
required String expression,
|
|
required List<String> definitions,
|
|
required List<String> definitionTypes,
|
|
required List<String> typeDefinitions,
|
|
required List<String> typeBounds,
|
|
required List<String> typeDefaults,
|
|
required String libraryUri,
|
|
required String? klass,
|
|
required String? method,
|
|
required int offset,
|
|
required String? scriptUri,
|
|
required bool isStatic,
|
|
String? rootLibraryUri,
|
|
required File serverInfoFile,
|
|
}) async {
|
|
final Map<String, Object?> response = await sendAndReceiveResponse(
|
|
jsonEncode({
|
|
'command': 'compileExpression',
|
|
'expression': expression,
|
|
'definitions': definitions,
|
|
'definitionTypes': definitionTypes,
|
|
'typeDefinitions': typeDefinitions,
|
|
'typeBounds': typeBounds,
|
|
'typeDefaults': typeDefaults,
|
|
'libraryUri': libraryUri,
|
|
if (klass != null) 'class': klass,
|
|
if (method != null) 'method': method,
|
|
'offset': offset,
|
|
if (scriptUri != null) 'scriptUri': scriptUri,
|
|
'isStatic': isStatic,
|
|
if (rootLibraryUri != null) 'rootLibraryUri': rootLibraryUri,
|
|
'useCachedCompilerOptionsAsBase': true,
|
|
}),
|
|
serverInfoFile,
|
|
);
|
|
|
|
if (response['success'] != true) {
|
|
final String errorMessage = switch (response) {
|
|
{'errorMessage': final String errorMessage} => errorMessage,
|
|
{'compilerOutputLines': final List<Object?> lines} => lines.join('\n'),
|
|
_ => 'Unknown error: $response',
|
|
};
|
|
throw new CompileException(errorMessage);
|
|
}
|
|
|
|
return new CompileExpressionResult(
|
|
kernelBytes: response['kernelBytes'] as String,
|
|
errorCount: response['errorCount'] as int,
|
|
compilerOutputLines:
|
|
(response['compilerOutputLines'] as List<Object?>?)?.cast<String>() ??
|
|
const [],
|
|
);
|
|
}
|