Format frontend_server_client

Change-Id: Ibfdf6a7afedaf9e1486624a85e7d04437db13e49
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/494460
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Nicholas Shahan <nshahan@google.com>
Auto-Submit: Jonas Jensen <jonasfj@google.com>
This commit is contained in:
Jonas Finnemann Jensen
2026-04-10 09:53:29 -07:00
committed by Commit Queue
parent ef6054d688
commit 8ffc0dddd0
6 changed files with 169 additions and 143 deletions
@@ -174,8 +174,8 @@ void _print(String message) {
} }
void _prompt() => stdout.write( void _prompt() => stdout.write(
'Enter a new message to print and recompile, or type `quit` to exit:', 'Enter a new message to print and recompile, or type `quit` to exit:',
); );
final app = 'example/app/main.dart'; final app = 'example/app/main.dart';
final dartSdkJs = p.join('.dart_tool', 'out', 'dart_sdk.js'); final dartSdkJs = p.join('.dart_tool', 'out', 'dart_sdk.js');
@@ -41,16 +41,19 @@ class DartDevcFrontendServerClient implements FrontendServerClient {
final String? _mainModuleJs; final String? _mainModuleJs;
DartDevcFrontendServerClient._( DartDevcFrontendServerClient._(
this._frontendServerClient, this._entrypoint, String moduleFormat) this._frontendServerClient,
: _bootstrapJs = moduleFormat == 'amd' this._entrypoint,
? generateAmdBootstrapScript( String moduleFormat,
requireUrl: 'require.js', ) : _bootstrapJs = moduleFormat == 'amd'
mapperUrl: 'dart_stack_trace_mapper.js', ? generateAmdBootstrapScript(
entrypoint: _entrypoint) requireUrl: 'require.js',
: null, mapperUrl: 'dart_stack_trace_mapper.js',
_mainModuleJs = moduleFormat == 'amd' entrypoint: _entrypoint,
? generateAmdMainModule(entrypoint: _entrypoint) )
: null { : null,
_mainModuleJs = moduleFormat == 'amd'
? generateAmdMainModule(entrypoint: _entrypoint)
: null {
_resetAssets(); _resetAssets();
} }
@@ -86,7 +89,10 @@ class DartDevcFrontendServerClient implements FrontendServerClient {
verbose: verbose, verbose: verbose,
); );
return DartDevcFrontendServerClient._( return DartDevcFrontendServerClient._(
feServer, Uri.parse(entrypoint).path, dartdevcModuleFormat); feServer,
Uri.parse(entrypoint).path,
dartdevcModuleFormat,
);
} }
/// Returns the current bytes for the asset at [path]. /// Returns the current bytes for the asset at [path].
@@ -121,11 +127,15 @@ class DartDevcFrontendServerClient implements FrontendServerClient {
for (final entry in manifest.entries) { for (final entry in manifest.entries) {
final metadata = entry.value as Map<String, dynamic>; final metadata = entry.value as Map<String, dynamic>;
final sourceOffsets = metadata['code'] as List; final sourceOffsets = metadata['code'] as List;
_assets[entry.key] = _assets[entry.key] = sourceBytes.sublist(
sourceBytes.sublist(sourceOffsets[0] as int, sourceOffsets[1] as int); sourceOffsets[0] as int,
sourceOffsets[1] as int,
);
final sourceMapOffsets = metadata['sourcemap'] as List; final sourceMapOffsets = metadata['sourcemap'] as List;
_assets['${entry.key}.map'] = sourceMapBytes.sublist( _assets['${entry.key}.map'] = sourceMapBytes.sublist(
sourceMapOffsets[0] as int, sourceMapOffsets[1] as int); sourceMapOffsets[0] as int,
sourceMapOffsets[1] as int,
);
} }
} }
@@ -142,9 +152,9 @@ class DartDevcFrontendServerClient implements FrontendServerClient {
required String klass, required String klass,
required String libraryUri, required String libraryUri,
required List<String> typeDefinitions, required List<String> typeDefinitions,
}) => }) => throw UnsupportedError(
throw UnsupportedError( 'Use `compileExpressionToJs` for dartdevc based clients',
'Use `compileExpressionToJs` for dartdevc based clients'); );
@override @override
Future<CompileResult> compileExpressionToJs({ Future<CompileResult> compileExpressionToJs({
@@ -155,15 +165,15 @@ class DartDevcFrontendServerClient implements FrontendServerClient {
required String libraryUri, required String libraryUri,
required int line, required int line,
required String moduleName, required String moduleName,
}) => }) => _frontendServerClient.compileExpressionToJs(
_frontendServerClient.compileExpressionToJs( expression: expression,
expression: expression, column: column,
column: column, jsFrameValues: jsFrameValues,
jsFrameValues: jsFrameValues, jsModules: jsModules,
jsModules: jsModules, libraryUri: libraryUri,
libraryUri: libraryUri, line: line,
line: line, moduleName: moduleName,
moduleName: moduleName); );
@override @override
void accept() { void accept() {
@@ -201,11 +211,13 @@ class DartDevcFrontendServerClient implements FrontendServerClient {
} }
final mainModuleJs = _mainModuleJs; final mainModuleJs = _mainModuleJs;
if (mainModuleJs != null) { if (mainModuleJs != null) {
_assets['$_entrypoint.bootstrap.js'] = _assets['$_entrypoint.bootstrap.js'] = Uint8List.fromList(
Uint8List.fromList(utf8.encode(mainModuleJs)); utf8.encode(mainModuleJs),
);
} }
} }
} }
final _dartdevcPlatformKernel = final _dartdevcPlatformKernel = p
p.toUri(p.join(sdkDir, 'lib', '_internal', 'ddc_sdk.dill')).toString(); .toUri(p.join(sdkDir, 'lib', '_internal', 'ddc_sdk.dill'))
.toString();
@@ -21,10 +21,12 @@ class FrontendServerClient {
_ClientState _state; _ClientState _state;
FrontendServerClient._( FrontendServerClient._(
this._entrypoint, this._feServer, this._feServerStdoutLines, this._entrypoint,
{bool? verbose}) this._feServer,
: _verbose = verbose ?? false, this._feServerStdoutLines, {
_state = _ClientState.waitingForFirstCompile { bool? verbose,
}) : _verbose = verbose ?? false,
_state = _ClientState.waitingForFirstCompile {
_feServer.stderr.transform(utf8.decoder).listen(stderr.write); _feServer.stderr.transform(utf8.decoder).listen(stderr.write);
} }
@@ -89,49 +91,39 @@ class FrontendServerClient {
if (enabledExperiments != null) if (enabledExperiments != null)
for (final experiment in enabledExperiments) for (final experiment in enabledExperiments)
'--enable-experiment=$experiment', '--enable-experiment=$experiment',
for (final source in additionalSources) ...[ for (final source in additionalSources) ...['--source', source],
'--source', if (nativeAssets != null) ...['--native-assets', nativeAssets],
source,
],
if (nativeAssets != null) ...[
'--native-assets',
nativeAssets,
],
]; ];
late final Process feServer; late final Process feServer;
if (frontendServerPath != null) { if (frontendServerPath != null) {
feServer = await Process.start( feServer = await Process.start(Platform.resolvedExecutable, <String>[
Platform.resolvedExecutable, if (debug) '--observe',
<String>[ frontendServerPath,
if (debug) '--observe', ...commonArguments,
frontendServerPath, ]);
...commonArguments,
],
);
} else if (File(_feServerAotSnapshotPath).existsSync()) { } else if (File(_feServerAotSnapshotPath).existsSync()) {
if (debug) { if (debug) {
throw ArgumentError('The debug argument cannot be set to true when the ' throw ArgumentError(
'frontendServerPath argument is omitted.'); 'The debug argument cannot be set to true when the '
'frontendServerPath argument is omitted.',
);
} }
feServer = await Process.start( feServer = await Process.start(_dartAotRuntimePath, <String>[
_dartAotRuntimePath, _feServerAotSnapshotPath,
<String>[_feServerAotSnapshotPath, ...commonArguments], ...commonArguments,
); ]);
} else { } else {
// AOT snapshots cannot be generated on IA32, so we need this fallback // AOT snapshots cannot be generated on IA32, so we need this fallback
// branch until support for IA32 is dropped (https://dartbug.com/49969). // branch until support for IA32 is dropped (https://dartbug.com/49969).
feServer = await Process.start( feServer = await Process.start(Platform.resolvedExecutable, <String>[
Platform.resolvedExecutable, if (debug) '--observe',
<String>[ _feServerAppJitSnapshotPath,
if (debug) '--observe', ...commonArguments,
_feServerAppJitSnapshotPath, ]);
...commonArguments,
],
);
} }
final feServerStdoutLines = StreamQueue(feServer.stdout final feServerStdoutLines = StreamQueue(
.transform(utf8.decoder) feServer.stdout.transform(utf8.decoder).transform(const LineSplitter()),
.transform(const LineSplitter())); );
// The frontend_server doesn't appear to recursively create files, so we // The frontend_server doesn't appear to recursively create files, so we
// need to make sure the output dir already exists. // need to make sure the output dir already exists.
@@ -162,16 +154,20 @@ class FrontendServerClient {
break; break;
case _ClientState.waitingForAcceptOrReject: case _ClientState.waitingForAcceptOrReject:
throw StateError( throw StateError(
'Previous `CompileResult` must be accepted or rejected by ' 'Previous `CompileResult` must be accepted or rejected by '
'calling `accept` or `reject`.'); 'calling `accept` or `reject`.',
);
case _ClientState.compiling: case _ClientState.compiling:
throw StateError( throw StateError(
'App is already being compiled, you must wait for that to ' 'App is already being compiled, you must wait for that to '
'complete and `accept` or `reject` the result before compiling ' 'complete and `accept` or `reject` the result before compiling '
'again.'); 'again.',
);
case _ClientState.rejecting: case _ClientState.rejecting:
throw StateError('Still waiting for previous `reject` call to finish. ' throw StateError(
'You must await that before compiling again.'); 'Still waiting for previous `reject` call to finish. '
'You must await that before compiling again.',
);
} }
_state = _ClientState.compiling; _state = _ClientState.compiling;
@@ -180,8 +176,9 @@ class FrontendServerClient {
if (action == 'recompile') { if (action == 'recompile') {
if (invalidatedUris == null || invalidatedUris.isEmpty) { if (invalidatedUris == null || invalidatedUris.isEmpty) {
throw StateError( throw StateError(
'Subsequent compile invocations must provide a non-empty list ' 'Subsequent compile invocations must provide a non-empty list '
'of invalidated uris.'); 'of invalidated uris.',
);
} }
final boundaryKey = generateUuidV4(); final boundaryKey = generateUuidV4();
command.writeln(' $boundaryKey'); command.writeln(' $boundaryKey');
@@ -199,8 +196,8 @@ class FrontendServerClient {
final compilerOutputLines = <String>[]; final compilerOutputLines = <String>[];
var errorCount = 0; var errorCount = 0;
String? outputDillPath; String? outputDillPath;
while ( while (state != _CompileState.done &&
state != _CompileState.done && await _feServerStdoutLines.hasNext) { await _feServerStdoutLines.hasNext) {
final line = await _nextInputLine(); final line = await _nextInputLine();
switch (state) { switch (state) {
case _CompileState.started: case _CompileState.started:
@@ -230,8 +227,9 @@ class FrontendServerClient {
removedSources.add(diffUri); removedSources.add(diffUri);
} else { } else {
throw StateError( throw StateError(
'unrecognized diff line, should start with a + or - ' 'unrecognized diff line, should start with a + or - '
'but got: $line'); 'but got: $line',
);
} }
continue; continue;
case _CompileState.done: case _CompileState.done:
@@ -240,11 +238,12 @@ class FrontendServerClient {
} }
return CompileResult._( return CompileResult._(
dillOutput: outputDillPath, dillOutput: outputDillPath,
errorCount: errorCount, errorCount: errorCount,
newSources: newSources, newSources: newSources,
removedSources: removedSources, removedSources: removedSources,
compilerOutputLines: compilerOutputLines); compilerOutputLines: compilerOutputLines,
);
} finally { } finally {
_state = _ClientState.waitingForAcceptOrReject; _state = _ClientState.waitingForAcceptOrReject;
} }
@@ -258,8 +257,7 @@ class FrontendServerClient {
required String klass, required String klass,
required String libraryUri, required String libraryUri,
required List<String> typeDefinitions, required List<String> typeDefinitions,
}) => }) => throw UnimplementedError();
throw UnimplementedError();
/// TODO: Document /// TODO: Document
Future<CompileResult> compileExpressionToJs({ Future<CompileResult> compileExpressionToJs({
@@ -270,8 +268,7 @@ class FrontendServerClient {
required String libraryUri, required String libraryUri,
required int line, required int line,
required String moduleName, required String moduleName,
}) => }) => throw UnimplementedError();
throw UnimplementedError();
/// Should be invoked when results of compilation are accepted by the client. /// Should be invoked when results of compilation are accepted by the client.
/// ///
@@ -279,7 +276,8 @@ class FrontendServerClient {
void accept() { void accept() {
if (_state != _ClientState.waitingForAcceptOrReject) { if (_state != _ClientState.waitingForAcceptOrReject) {
throw StateError( throw StateError(
'Called `accept` but there was no previous compile to accept.'); 'Called `accept` but there was no previous compile to accept.',
);
} }
_sendCommand('accept'); _sendCommand('accept');
_state = _ClientState.waitingForRecompile; _state = _ClientState.waitingForRecompile;
@@ -294,7 +292,8 @@ class FrontendServerClient {
Future<void> reject() async { Future<void> reject() async {
if (_state != _ClientState.waitingForAcceptOrReject) { if (_state != _ClientState.waitingForAcceptOrReject) {
throw StateError( throw StateError(
'Called `reject` but there was no previous compile to reject.'); 'Called `reject` but there was no previous compile to reject.',
);
} }
_state = _ClientState.rejecting; _state = _ClientState.rejecting;
_sendCommand('reject'); _sendCommand('reject');
@@ -307,8 +306,9 @@ class FrontendServerClient {
case _RejectState.started: case _RejectState.started:
if (!line.startsWith('result')) { if (!line.startsWith('result')) {
throw StateError( throw StateError(
'Expected a line like `result <boundary-key>` after a `reject` ' 'Expected a line like `result <boundary-key>` after a `reject` '
'command, but got:\n$line'); 'command, but got:\n$line',
);
} }
boundaryKey = line.split(' ').last; boundaryKey = line.split(' ').last;
rejectState = _RejectState.waitingForKey; rejectState = _RejectState.waitingForKey;
@@ -332,8 +332,9 @@ class FrontendServerClient {
void reset() { void reset() {
if (_state == _ClientState.compiling) { if (_state == _ClientState.compiling) {
throw StateError( throw StateError(
'Called `reset` during an active compile, you must wait for that to ' 'Called `reset` during an active compile, you must wait for that to '
'complete first.'); 'complete first.',
);
} }
_sendCommand('reset'); _sendCommand('reset');
_state = _ClientState.waitingForRecompile; _state = _ClientState.waitingForRecompile;
@@ -377,12 +378,13 @@ class FrontendServerClient {
/// The result of a compile call. /// The result of a compile call.
class CompileResult { class CompileResult {
const CompileResult._( const CompileResult._({
{required this.dillOutput, required this.dillOutput,
required this.compilerOutputLines, required this.compilerOutputLines,
required this.errorCount, required this.errorCount,
required this.newSources, required this.newSources,
required this.removedSources}); required this.removedSources,
});
/// The produced dill output file, this will either be a full dill file, an /// The produced dill output file, this will either be a full dill file, an
/// incremental dill file, or `null` if no file was produced. /// incremental dill file, or `null` if no file was produced.
@@ -432,24 +434,23 @@ enum _ClientState {
} }
/// Frontend server interaction states for a `compile` call. /// Frontend server interaction states for a `compile` call.
enum _CompileState { enum _CompileState { started, waitingForKey, gettingSourceDiffs, done }
started,
waitingForKey,
gettingSourceDiffs,
done,
}
/// Frontend server interaction states for a `reject` call. /// Frontend server interaction states for a `reject` call.
enum _RejectState { enum _RejectState { started, waitingForKey, done }
started,
waitingForKey,
done,
}
final _dartAotRuntimePath = p.join(sdkDir, 'bin', 'dartaotruntime'); final _dartAotRuntimePath = p.join(sdkDir, 'bin', 'dartaotruntime');
final _feServerAppJitSnapshotPath = final _feServerAppJitSnapshotPath = p.join(
p.join(sdkDir, 'bin', 'snapshots', 'frontend_server.dart.snapshot'); sdkDir,
'bin',
'snapshots',
'frontend_server.dart.snapshot',
);
final _feServerAotSnapshotPath = final _feServerAotSnapshotPath = p.join(
p.join(sdkDir, 'bin', 'snapshots', 'frontend_server_aot.dart.snapshot'); sdkDir,
'bin',
'snapshots',
'frontend_server_aot.dart.snapshot',
);
@@ -18,16 +18,26 @@ void main() {
final exampleFilePath = await pathFromNearestPackageConfig( final exampleFilePath = await pathFromNearestPackageConfig(
'example/vm_client.dart', 'example/vm_client.dart',
); );
final process = await TestProcess.start( final process = await TestProcess.start(Platform.resolvedExecutable, [
Platform.resolvedExecutable, ['run', exampleFilePath]); 'run',
await expectLater(process.stdout, exampleFilePath,
emitsThrough(contains('done compiling example/app/main.dart'))); ]);
await expectLater( await expectLater(
process.stdout, emitsThrough(contains('APP -> hello/world'))); process.stdout,
await expectLater(process.stdout, emitsThrough(contains('done compiling example/app/main.dart')),
emitsThrough(contains('done recompiling example/app/main.dart'))); );
await expectLater( await expectLater(
process.stdout, emitsThrough(contains('APP -> goodbye/world'))); process.stdout,
emitsThrough(contains('APP -> hello/world')),
);
await expectLater(
process.stdout,
emitsThrough(contains('done recompiling example/app/main.dart')),
);
await expectLater(
process.stdout,
emitsThrough(contains('APP -> goodbye/world')),
);
expect(await process.exitCode, 0); expect(await process.exitCode, 0);
}); });
} }
@@ -18,15 +18,19 @@ void main() {
final exampleFilePath = await pathFromNearestPackageConfig( final exampleFilePath = await pathFromNearestPackageConfig(
'example/web_client.dart', 'example/web_client.dart',
); );
final process = await TestProcess.start( final process = await TestProcess.start(Platform.resolvedExecutable, [
Platform.resolvedExecutable, ['run', exampleFilePath]); 'run',
await expectLater(process.stdout, exampleFilePath,
emitsThrough(contains('done compiling example/app/main.dart'))); ]);
await expectLater(
process.stdout,
emitsThrough(contains('done compiling example/app/main.dart')),
);
process.stdin.writeln('new message'); process.stdin.writeln('new message');
await expectLater( await expectLater(
process.stdout, process.stdout,
emitsThrough( emitsThrough(contains('Recompile succeeded for example/app/main.dart')),
contains('Recompile succeeded for example/app/main.dart'))); );
process.stdin.writeln('quit'); process.stdin.writeln('quit');
expect(await process.exitCode, 0); expect(await process.exitCode, 0);
}); });
@@ -52,15 +52,13 @@ String get message => p.join('hello', 'world');
]), ]),
]).create(); ]).create();
packageRoot = p.join(d.sandbox, 'a'); packageRoot = p.join(d.sandbox, 'a');
await Process.run( await Process.run(Platform.resolvedExecutable, [
Platform.resolvedExecutable, 'pub',
[ 'get',
'pub', ], workingDirectory: packageRoot);
'get',
],
workingDirectory: packageRoot);
packageConfig = (await findPackageConfig(Directory(packageRoot)))!; packageConfig = (await findPackageConfig(Directory(packageRoot)))!;
packagesJsonPath = findNearestPackageConfigPath(Directory(packageRoot)) ?? packagesJsonPath =
findNearestPackageConfigPath(Directory(packageRoot)) ??
p.join(packageRoot, '.dart_tool', 'package_config.json'); p.join(packageRoot, '.dart_tool', 'package_config.json');
}); });
@@ -207,8 +205,9 @@ String get message => p.join('hello', 'world');
}); });
test('can compile and recompile a dartdevc app', () async { test('can compile and recompile a dartdevc app', () async {
final entrypoint = final entrypoint = p
p.toUri(p.join(packageRoot, 'bin', 'main.dart')).toString(); .toUri(p.join(packageRoot, 'bin', 'main.dart'))
.toString();
final dartDevcClient = client = await DartDevcFrontendServerClient.start( final dartDevcClient = client = await DartDevcFrontendServerClient.start(
entrypoint, entrypoint,
p.join(packageRoot, 'out.dill'), p.join(packageRoot, 'out.dill'),