[frontend_server] connect widget cache to frontend_server
Connect the widget cache to the frontend server to be enabled when --flutter-widget-cache is provided as a flag. See also: go/fast-single-widget-reloads Bug: https://github.com/flutter/flutter/issues/61407 Change-Id: I9d1f2c59dbb1dc56bb7e6ac7ebd05a01d1d47803 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/162943 Reviewed-by: Alexander Aprelev <aam@google.com> Commit-Queue: Jonah Williams <jonahwilliams@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
623a9bda67
commit
d0e095507c
@@ -61,7 +61,7 @@ class WidgetCache {
|
||||
/// `State` subtype.
|
||||
///
|
||||
/// Returns the class name if located, otherwise `null`.
|
||||
String checkWidgetCache(
|
||||
String checkSingleWidgetTypeModified(
|
||||
Component lastGoodComponent,
|
||||
Component partialComponent,
|
||||
ClassHierarchy classHierarchy,
|
||||
|
||||
@@ -20,6 +20,7 @@ import 'package:dev_compiler/dev_compiler.dart'
|
||||
import 'package:front_end/src/api_prototype/compiler_options.dart'
|
||||
show CompilerOptions, parseExperimentalFlags;
|
||||
import 'package:front_end/src/api_unstable/vm.dart';
|
||||
import 'package:front_end/widget_cache.dart';
|
||||
import 'package:kernel/ast.dart' show Library, Procedure, LibraryDependency;
|
||||
import 'package:kernel/binary/ast_to_binary.dart';
|
||||
import 'package:kernel/kernel.dart'
|
||||
@@ -176,7 +177,10 @@ ArgParser argParser = ArgParser(allowTrailingOptions: true)
|
||||
defaultsTo: false)
|
||||
..addOption('dartdevc-module-format',
|
||||
help: 'The module format to use on for the dartdevc compiler',
|
||||
defaultsTo: 'amd');
|
||||
defaultsTo: 'amd')
|
||||
..addFlag('flutter-widget-cache',
|
||||
help: 'Enable the widget cache to track changes to Widget subtypes',
|
||||
defaultsTo: false);
|
||||
|
||||
String usage = '''
|
||||
Usage: server [options] [input.dart]
|
||||
@@ -345,6 +349,8 @@ class FrontendCompiler implements CompilerInterface {
|
||||
IncrementalCompiler _generator;
|
||||
JavaScriptBundler _bundler;
|
||||
|
||||
WidgetCache _widgetCache;
|
||||
|
||||
String _kernelBinaryFilename;
|
||||
String _kernelBinaryFilenameIncremental;
|
||||
String _kernelBinaryFilenameFull;
|
||||
@@ -530,6 +536,9 @@ class FrontendCompiler implements CompilerInterface {
|
||||
component.uriToSource.keys);
|
||||
|
||||
incrementalSerializer = _generator.incrementalSerializer;
|
||||
if (options['flutter-widget-cache']) {
|
||||
_widgetCache = WidgetCache(component);
|
||||
}
|
||||
} else {
|
||||
if (options['link-platform']) {
|
||||
// TODO(aam): Remove linkedDependencies once platform is directly embedded
|
||||
@@ -904,6 +913,7 @@ class FrontendCompiler implements CompilerInterface {
|
||||
await writeDillFile(results, _kernelBinaryFilename,
|
||||
incrementalSerializer: _generator.incrementalSerializer);
|
||||
}
|
||||
_updateWidgetCache(deltaProgram);
|
||||
|
||||
_outputStream.writeln(boundaryKey);
|
||||
await _outputDependenciesDelta(results.compiledSources);
|
||||
@@ -1095,6 +1105,7 @@ class FrontendCompiler implements CompilerInterface {
|
||||
@override
|
||||
void acceptLastDelta() {
|
||||
_generator.accept();
|
||||
_widgetCache?.reset();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1108,11 +1119,13 @@ class FrontendCompiler implements CompilerInterface {
|
||||
@override
|
||||
void invalidate(Uri uri) {
|
||||
_generator.invalidate(uri);
|
||||
_widgetCache?.invalidate(uri);
|
||||
}
|
||||
|
||||
@override
|
||||
void resetIncrementalCompiler() {
|
||||
_generator.resetDeltaState();
|
||||
_widgetCache?.reset();
|
||||
_kernelBinaryFilename = _kernelBinaryFilenameFull;
|
||||
}
|
||||
|
||||
@@ -1122,6 +1135,29 @@ class FrontendCompiler implements CompilerInterface {
|
||||
incrementalSerialization: incrementalSerialization);
|
||||
}
|
||||
|
||||
/// If the flutter widget cache is enabled, check if a single class was modified.
|
||||
///
|
||||
/// The resulting class name is written as a String to
|
||||
/// `_kernelBinaryFilename`.widget_cache, or else the file is deleted
|
||||
/// if it exists.
|
||||
void _updateWidgetCache(Component partialComponent) {
|
||||
if (_widgetCache == null) {
|
||||
return;
|
||||
}
|
||||
final String singleModifiedClassName =
|
||||
_widgetCache.checkSingleWidgetTypeModified(
|
||||
_generator.lastKnownGoodComponent,
|
||||
partialComponent,
|
||||
_generator.getClassHierarchy(),
|
||||
);
|
||||
final File outputFile = File('$_kernelBinaryFilename.widget_cache');
|
||||
if (singleModifiedClassName != null) {
|
||||
outputFile.writeAsStringSync(singleModifiedClassName);
|
||||
} else if (outputFile.existsSync()) {
|
||||
outputFile.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
Uri _ensureFolderPath(String path) {
|
||||
String uriPath = Uri.file(path).toString();
|
||||
if (!uriPath.endsWith('/')) {
|
||||
|
||||
@@ -68,6 +68,24 @@ void main() async {
|
||||
expect(capturedArgs.single['sdk-root'], equals('sdkroot'));
|
||||
expect(capturedArgs.single['link-platform'], equals(true));
|
||||
});
|
||||
|
||||
test('compile from command line with widget cache', () async {
|
||||
final List<String> args = <String>[
|
||||
'server.dart',
|
||||
'--sdk-root',
|
||||
'sdkroot',
|
||||
'--flutter-widget-cache',
|
||||
];
|
||||
await starter(args, compiler: compiler);
|
||||
final List<dynamic> capturedArgs = verify(compiler.compile(
|
||||
argThat(equals('server.dart')),
|
||||
captureAny,
|
||||
generator: anyNamed('generator'),
|
||||
)).captured;
|
||||
expect(capturedArgs.single['sdk-root'], equals('sdkroot'));
|
||||
expect(capturedArgs.single['link-platform'], equals(true));
|
||||
expect(capturedArgs.single['flutter-widget-cache'], equals(true));
|
||||
});
|
||||
});
|
||||
|
||||
group('interactive compile with mocked compiler', () {
|
||||
@@ -230,6 +248,34 @@ void main() async {
|
||||
inputStreamController.close();
|
||||
});
|
||||
|
||||
test('recompile one file with widget cache does not fail', () async {
|
||||
// The component will not contain the flutter framework sources so
|
||||
// this should no-op.
|
||||
final StreamController<List<int>> inputStreamController =
|
||||
StreamController<List<int>>();
|
||||
final ReceivePort recompileCalled = ReceivePort();
|
||||
|
||||
when(compiler.recompileDelta(entryPoint: null))
|
||||
.thenAnswer((Invocation invocation) async {
|
||||
recompileCalled.sendPort.send(true);
|
||||
});
|
||||
Future<int> result = starter(
|
||||
<String>[...args, '--flutter-widget-cache'],
|
||||
compiler: compiler,
|
||||
input: inputStreamController.stream,
|
||||
);
|
||||
inputStreamController.add('recompile abc\nfile1.dart\nabc\n'.codeUnits);
|
||||
await recompileCalled.first;
|
||||
|
||||
verifyInOrder(<void>[
|
||||
compiler.invalidate(Uri.base.resolve('file1.dart')),
|
||||
await compiler.recompileDelta(entryPoint: null),
|
||||
]);
|
||||
inputStreamController.add('quit\n'.codeUnits);
|
||||
expect(await result, 0);
|
||||
inputStreamController.close();
|
||||
});
|
||||
|
||||
test('recompile few files with new entrypoint', () async {
|
||||
final StreamController<List<int>> inputStreamController =
|
||||
StreamController<List<int>>();
|
||||
@@ -923,6 +969,218 @@ true
|
||||
inputStreamController.close();
|
||||
});
|
||||
|
||||
test(
|
||||
'recompile request with flutter widget cache outputs change in class name',
|
||||
() async {
|
||||
var frameworkDirectory = Directory('${tempDir.path}/flutter');
|
||||
var flutterFramework =
|
||||
File('${frameworkDirectory.path}/lib/src/widgets/framework.dart')
|
||||
..createSync(recursive: true);
|
||||
flutterFramework.writeAsStringSync('''
|
||||
abstract class Widget {}
|
||||
class StatelessWidget extends Widget {}
|
||||
class StatefulWidget extends Widget {}
|
||||
class State<T extends StatefulWidget> {}
|
||||
''');
|
||||
|
||||
var file = File('${tempDir.path}/foo.dart')..createSync();
|
||||
file.writeAsStringSync("""
|
||||
import "package:flutter/src/widgets/framework.dart";
|
||||
|
||||
void main() {}
|
||||
|
||||
class FooWidget extends StatelessWidget {}
|
||||
|
||||
class FizzWidget extends StatefulWidget {}
|
||||
|
||||
class BarState extends State<FizzWidget> {}
|
||||
""");
|
||||
var config = File('${tempDir.path}/package_config.json')..createSync();
|
||||
config.writeAsStringSync('''
|
||||
{
|
||||
"configVersion": 2,
|
||||
"packages": [
|
||||
{
|
||||
"name": "flutter",
|
||||
"rootUri": "${frameworkDirectory.uri}",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.2"
|
||||
}
|
||||
]
|
||||
}
|
||||
''');
|
||||
|
||||
var dillFile = File('${tempDir.path}/app.dill');
|
||||
expect(dillFile.existsSync(), equals(false));
|
||||
final List<String> args = <String>[
|
||||
'--sdk-root=${sdkRoot.toFilePath()}',
|
||||
'--incremental',
|
||||
'--platform=${platformKernel.path}',
|
||||
'--output-dill=${dillFile.path}',
|
||||
'--flutter-widget-cache',
|
||||
'--packages=${config.path}',
|
||||
];
|
||||
|
||||
final StreamController<List<int>> inputStreamController =
|
||||
StreamController<List<int>>();
|
||||
final StreamController<List<int>> stdoutStreamController =
|
||||
StreamController<List<int>>();
|
||||
final IOSink ioSink = IOSink(stdoutStreamController.sink);
|
||||
StreamController<Result> receivedResults = StreamController<Result>();
|
||||
|
||||
final outputParser = OutputParser(receivedResults);
|
||||
stdoutStreamController.stream
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen(outputParser.listener);
|
||||
|
||||
Future<int> result =
|
||||
starter(args, input: inputStreamController.stream, output: ioSink);
|
||||
inputStreamController.add('compile ${file.path}\n'.codeUnits);
|
||||
int count = 0;
|
||||
receivedResults.stream.listen((Result compiledResult) {
|
||||
if (count == 0) {
|
||||
// First request is to 'compile', which results in full kernel file.
|
||||
expect(dillFile.existsSync(), equals(true));
|
||||
compiledResult.expectNoErrors(filename: dillFile.path);
|
||||
count += 1;
|
||||
inputStreamController.add('accept\n'.codeUnits);
|
||||
file.writeAsStringSync("""
|
||||
import "package:flutter/src/widgets/framework.dart";
|
||||
|
||||
void main() {}
|
||||
|
||||
class FooWidget extends StatelessWidget {
|
||||
// Added.
|
||||
}
|
||||
|
||||
class FizzWidget extends StatefulWidget {}
|
||||
|
||||
class BarState extends State<FizzWidget> {}
|
||||
""");
|
||||
inputStreamController.add('recompile ${file.path} abc\n'
|
||||
'${file.path}\n'
|
||||
'abc\n'
|
||||
.codeUnits);
|
||||
} else if (count == 1) {
|
||||
expect(count, 1);
|
||||
// Second request is to 'recompile', which results in incremental
|
||||
// kernel file and invalidation of StatelessWidget.
|
||||
var dillIncFile = File('${dillFile.path}.incremental.dill');
|
||||
var widgetCacheFile =
|
||||
File('${dillFile.path}.incremental.dill.widget_cache');
|
||||
compiledResult.expectNoErrors(filename: dillIncFile.path);
|
||||
expect(dillIncFile.existsSync(), equals(true));
|
||||
expect(widgetCacheFile.existsSync(), equals(true));
|
||||
expect(widgetCacheFile.readAsStringSync(), 'FooWidget');
|
||||
count += 1;
|
||||
inputStreamController.add('accept\n'.codeUnits);
|
||||
|
||||
file.writeAsStringSync("""
|
||||
import "package:flutter/src/widgets/framework.dart";
|
||||
|
||||
void main() {}
|
||||
|
||||
class FooWidget extends StatelessWidget {
|
||||
// Added.
|
||||
}
|
||||
|
||||
class FizzWidget extends StatefulWidget {
|
||||
// Added.
|
||||
}
|
||||
|
||||
class BarState extends State<FizzWidget> {}
|
||||
""");
|
||||
inputStreamController.add('recompile ${file.path} abc\n'
|
||||
'${file.path}\n'
|
||||
'abc\n'
|
||||
.codeUnits);
|
||||
} else if (count == 2) {
|
||||
// Second request is to 'recompile', which results in incremental
|
||||
// kernel file and invalidation of StatelessWidget.
|
||||
var dillIncFile = File('${dillFile.path}.incremental.dill');
|
||||
var widgetCacheFile =
|
||||
File('${dillFile.path}.incremental.dill.widget_cache');
|
||||
compiledResult.expectNoErrors(filename: dillIncFile.path);
|
||||
expect(dillIncFile.existsSync(), equals(true));
|
||||
expect(widgetCacheFile.existsSync(), equals(true));
|
||||
expect(widgetCacheFile.readAsStringSync(), 'FizzWidget');
|
||||
count += 1;
|
||||
inputStreamController.add('accept\n'.codeUnits);
|
||||
|
||||
file.writeAsStringSync("""
|
||||
import "package:flutter/src/widgets/framework.dart";
|
||||
|
||||
void main() {}
|
||||
|
||||
class FooWidget extends StatelessWidget {
|
||||
// Added.
|
||||
}
|
||||
|
||||
class FizzWidget extends StatefulWidget {
|
||||
// Added.
|
||||
}
|
||||
|
||||
class BarState extends State<FizzWidget> {
|
||||
// Added.
|
||||
}
|
||||
""");
|
||||
inputStreamController.add('recompile ${file.path} abc\n'
|
||||
'${file.path}\n'
|
||||
'abc\n'
|
||||
.codeUnits);
|
||||
} else if (count == 3) {
|
||||
// Third request is to 'recompile', which results in incremental
|
||||
// kernel file and invalidation of State class.
|
||||
var dillIncFile = File('${dillFile.path}.incremental.dill');
|
||||
var widgetCacheFile =
|
||||
File('${dillFile.path}.incremental.dill.widget_cache');
|
||||
compiledResult.expectNoErrors(filename: dillIncFile.path);
|
||||
expect(dillIncFile.existsSync(), equals(true));
|
||||
expect(widgetCacheFile.existsSync(), equals(true));
|
||||
expect(widgetCacheFile.readAsStringSync(), 'FizzWidget');
|
||||
count += 1;
|
||||
inputStreamController.add('accept\n'.codeUnits);
|
||||
|
||||
file.writeAsStringSync("""
|
||||
import "package:flutter/src/widgets/framework.dart";
|
||||
|
||||
void main() {}
|
||||
|
||||
// Added
|
||||
|
||||
class FooWidget extends StatelessWidget {
|
||||
// Added.
|
||||
}
|
||||
|
||||
class FizzWidget extends StatefulWidget {
|
||||
// Added.
|
||||
}
|
||||
|
||||
class BarState extends State<FizzWidget> {
|
||||
// Added.
|
||||
}
|
||||
""");
|
||||
inputStreamController.add('recompile ${file.path} abc\n'
|
||||
'${file.path}\n'
|
||||
'abc\n'
|
||||
.codeUnits);
|
||||
} else if (count == 4) {
|
||||
// Fourth request is to 'recompile', which results in incremental
|
||||
// kernel file and no widget cache
|
||||
var dillIncFile = File('${dillFile.path}.incremental.dill');
|
||||
var widgetCacheFile =
|
||||
File('${dillFile.path}.incremental.dill.widget_cache');
|
||||
compiledResult.expectNoErrors(filename: dillIncFile.path);
|
||||
expect(dillIncFile.existsSync(), equals(true));
|
||||
expect(widgetCacheFile.existsSync(), equals(false));
|
||||
inputStreamController.add('quit\n'.codeUnits);
|
||||
}
|
||||
});
|
||||
expect(await result, 0);
|
||||
inputStreamController.close();
|
||||
});
|
||||
|
||||
test('unsafe-package-serialization', () async {
|
||||
// Package A.
|
||||
var file = File('${tempDir.path}/pkgA/a.dart')
|
||||
|
||||
Reference in New Issue
Block a user