mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
Remove the widget cache experiment.
Bug: https://github.com/flutter/flutter/issues/132157 Change-Id: Ie87ba74b8ed99477cc5032824286adb07f1157d6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/319871 Reviewed-by: Jens Johansen <jensj@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
parent
963068bd4e
commit
86231e2d70
|
@ -1,188 +0,0 @@
|
|||
// Copyright (c) 2020, 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 'package:kernel/class_hierarchy.dart';
|
||||
import 'package:kernel/kernel.dart';
|
||||
|
||||
/// Provides support for "single widget reloads" in Flutter, by determining if
|
||||
/// a partial component contains single change to the class body of a
|
||||
/// StatelessWidget, StatefulWidget, or State subtype.
|
||||
class WidgetCache {
|
||||
/// Create a [WidgetCache] from a [Component] containing the flutter
|
||||
/// framework.
|
||||
WidgetCache(Component fullComponent) {
|
||||
Library? frameworkLibrary;
|
||||
for (Library library in fullComponent.libraries) {
|
||||
if (library.importUri == _frameworkLibrary) {
|
||||
frameworkLibrary = library;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (frameworkLibrary == null) {
|
||||
return;
|
||||
}
|
||||
_locatedClassDeclarations(frameworkLibrary);
|
||||
_frameworkTypesLocated =
|
||||
_statefulWidget != null && _state != null && _statelessWidget != null;
|
||||
}
|
||||
|
||||
static const String _stateClassName = 'State';
|
||||
static const String _statefulWidgetClassName = 'StatefulWidget';
|
||||
static const String _statelessWidgetClassName = 'StatelessWidget';
|
||||
|
||||
Class? _statelessWidget;
|
||||
Class? _state;
|
||||
Class? _statefulWidget;
|
||||
bool _frameworkTypesLocated = false;
|
||||
|
||||
static final Uri _frameworkLibrary =
|
||||
Uri.parse('package:flutter/src/widgets/framework.dart');
|
||||
|
||||
/// Mark [uri] as invalidated.
|
||||
void invalidate(Uri uri) {
|
||||
_invalidatedLibraries.add(uri);
|
||||
}
|
||||
|
||||
/// Reset the invalidated libraries.
|
||||
void reset() {
|
||||
_invalidatedLibraries.clear();
|
||||
}
|
||||
|
||||
final List<Uri> _invalidatedLibraries = <Uri>[];
|
||||
|
||||
/// Determine if any changes to [partialComponent] were located entirely
|
||||
/// within the class body of a single `StatefulWidget`, `StatelessWidget` or
|
||||
/// `State` subtype.
|
||||
///
|
||||
/// Returns the class name if located, otherwise `null`.
|
||||
String? checkSingleWidgetTypeModified(
|
||||
Component? lastGoodComponent,
|
||||
Component partialComponent,
|
||||
ClassHierarchy? classHierarchy,
|
||||
) {
|
||||
if (!_frameworkTypesLocated ||
|
||||
lastGoodComponent == null ||
|
||||
_invalidatedLibraries.length != 1) {
|
||||
return null;
|
||||
}
|
||||
Uri importUri = _invalidatedLibraries[0];
|
||||
Library? library;
|
||||
for (Library candidateLibrary in partialComponent.libraries) {
|
||||
if (candidateLibrary.importUri == importUri) {
|
||||
library = candidateLibrary;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (library == null) {
|
||||
return null;
|
||||
}
|
||||
List<int> oldSource =
|
||||
lastGoodComponent.uriToSource[library.fileUri]!.source;
|
||||
List<int> newSource = partialComponent.uriToSource[library.fileUri]!.source;
|
||||
|
||||
int newStartIndex = 0;
|
||||
int newEndIndex = newSource.length - 1;
|
||||
int oldStartIndex = 0;
|
||||
int oldEndIndex = oldSource.length - 1;
|
||||
|
||||
while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) {
|
||||
if (newSource[newStartIndex] != oldSource[oldStartIndex]) {
|
||||
break;
|
||||
}
|
||||
newStartIndex += 1;
|
||||
oldStartIndex += 1;
|
||||
}
|
||||
while (newEndIndex > newStartIndex && oldEndIndex > oldStartIndex) {
|
||||
if (newSource[newEndIndex] != oldSource[oldEndIndex]) {
|
||||
break;
|
||||
}
|
||||
newEndIndex -= 1;
|
||||
oldEndIndex -= 1;
|
||||
}
|
||||
|
||||
Class? newClass =
|
||||
_locateContainingClass(library, newStartIndex, newEndIndex);
|
||||
if (newClass == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Library oldLibrary =
|
||||
lastGoodComponent.libraries.firstWhere((Library library) {
|
||||
return library.importUri == importUri;
|
||||
});
|
||||
|
||||
Class? oldClass =
|
||||
_locateContainingClass(oldLibrary, oldStartIndex, oldEndIndex);
|
||||
|
||||
if (oldClass == null || oldClass.name != newClass.name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update the class references to stateless, stateful, and state classes.
|
||||
for (Library library in classHierarchy!.knownLibraries) {
|
||||
if (library.importUri == _frameworkLibrary) {
|
||||
_locatedClassDeclarations(library);
|
||||
}
|
||||
}
|
||||
|
||||
if (classHierarchy.isSubclassOf(newClass, _statelessWidget!) ||
|
||||
classHierarchy.isSubclassOf(newClass, _statefulWidget!)) {
|
||||
if (classHierarchy.isExtended(newClass) ||
|
||||
classHierarchy.isUsedAsMixin(newClass)) {
|
||||
return null;
|
||||
}
|
||||
return newClass.name;
|
||||
}
|
||||
|
||||
// For changes to State classes, locate the name of the corresponding
|
||||
// StatefulWidget that is provided as a type parameter. If the bounds are
|
||||
// StatefulWidget itself, fail as that indicates the type was not
|
||||
// specified.
|
||||
Supertype? stateSuperType =
|
||||
classHierarchy.getClassAsInstanceOf(newClass, _state!);
|
||||
if (stateSuperType != null) {
|
||||
if (stateSuperType.typeArguments.length != 1) {
|
||||
return null;
|
||||
}
|
||||
DartType widgetType = stateSuperType.typeArguments[0];
|
||||
if (widgetType is InterfaceType) {
|
||||
Class statefulWidgetType = widgetType.classNode;
|
||||
if (statefulWidgetType.name == _statefulWidgetClassName) {
|
||||
return null;
|
||||
}
|
||||
if (classHierarchy.isExtended(statefulWidgetType) ||
|
||||
classHierarchy.isUsedAsMixin(statefulWidgetType)) {
|
||||
return null;
|
||||
}
|
||||
return statefulWidgetType.name;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Locate the that fully contains the edit range, or null.
|
||||
Class? _locateContainingClass(
|
||||
Library library, int startOffset, int endOffset) {
|
||||
for (Class classDeclaration in library.classes) {
|
||||
if (classDeclaration.startFileOffset <= startOffset &&
|
||||
classDeclaration.fileEndOffset >= endOffset) {
|
||||
return classDeclaration;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _locatedClassDeclarations(Library library) {
|
||||
for (Class classDeclaration in library.classes) {
|
||||
if (classDeclaration.name == _statelessWidgetClassName) {
|
||||
_statelessWidget = classDeclaration;
|
||||
} else if (classDeclaration.name == _statefulWidgetClassName) {
|
||||
_statefulWidget = classDeclaration;
|
||||
} else if (classDeclaration.name == _stateClassName) {
|
||||
_state = classDeclaration;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,4 +15,4 @@ This API stability does not cover any of the source code APIs.
|
|||
* The frontend_server kernel compilation and expression evaluation for kernel should be considered "stable".
|
||||
* The frontend_server JavaScript compilation is semi-stable, but clients should anticipate coordinated breaking changes in the future.
|
||||
* The frontend_server JavaScript expression evaluation is experimental and is expected to change significantly from Dec 2020 through the end of 2021.
|
||||
* Specific flags like the --flutter-widget-cache may be added for experimentation and should not be considered stable.
|
||||
* Specific flags may be added for experimentation and should not be considered stable.
|
||||
|
|
|
@ -26,7 +26,6 @@ import 'package:dev_compiler/dev_compiler.dart'
|
|||
import 'package:front_end/src/api_unstable/ddc.dart' as ddc
|
||||
show IncrementalCompiler;
|
||||
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'
|
||||
|
@ -211,9 +210,6 @@ ArgParser argParser = ArgParser(allowTrailingOptions: true)
|
|||
defaultsTo: 'amd')
|
||||
..addFlag('dartdevc-canary',
|
||||
help: 'Enable canary features in dartdevc compiler', defaultsTo: false)
|
||||
..addFlag('flutter-widget-cache',
|
||||
help: 'Enable the widget cache to track changes to Widget subtypes',
|
||||
defaultsTo: false)
|
||||
..addFlag('print-incremental-dependencies',
|
||||
help: 'Print list of sources added and removed from compilation',
|
||||
defaultsTo: true)
|
||||
|
@ -436,7 +432,6 @@ class FrontendCompiler implements CompilerInterface {
|
|||
/// Nullable fields
|
||||
final ProgramTransformer? transformer;
|
||||
bool? unsafePackageSerialization;
|
||||
WidgetCache? _widgetCache;
|
||||
|
||||
_onDiagnostic(DiagnosticMessage message) {
|
||||
switch (message.severity) {
|
||||
|
@ -623,9 +618,6 @@ class FrontendCompiler implements CompilerInterface {
|
|||
);
|
||||
|
||||
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
|
||||
|
@ -964,7 +956,6 @@ class FrontendCompiler implements CompilerInterface {
|
|||
await writeDillFile(results, _kernelBinaryFilename,
|
||||
incrementalSerializer: _generator.incrementalSerializer);
|
||||
}
|
||||
_updateWidgetCache(deltaProgram);
|
||||
|
||||
_outputStream.writeln(boundaryKey);
|
||||
await _outputDependenciesDelta(results.compiledSources!);
|
||||
|
@ -1188,7 +1179,6 @@ class FrontendCompiler implements CompilerInterface {
|
|||
@override
|
||||
void acceptLastDelta() {
|
||||
_generator.accept();
|
||||
_widgetCache?.reset();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1202,13 +1192,11 @@ class FrontendCompiler implements CompilerInterface {
|
|||
@override
|
||||
void invalidate(Uri uri) {
|
||||
_generator.invalidate(uri);
|
||||
_widgetCache?.invalidate(uri);
|
||||
}
|
||||
|
||||
@override
|
||||
void resetIncrementalCompiler() {
|
||||
_generator.resetDeltaState();
|
||||
_widgetCache?.reset();
|
||||
_kernelBinaryFilename = _kernelBinaryFilenameFull;
|
||||
}
|
||||
|
||||
|
@ -1219,31 +1207,6 @@ 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.
|
||||
///
|
||||
/// Should not run if a full component is requested.
|
||||
void _updateWidgetCache(Component partialComponent) {
|
||||
if (_widgetCache == null || _generator.fullComponent) {
|
||||
return;
|
||||
}
|
||||
final String? singleModifiedClassName =
|
||||
_widgetCache!.checkSingleWidgetTypeModified(
|
||||
_generator.lastKnownGoodResult?.component,
|
||||
partialComponent,
|
||||
_generator.lastKnownGoodResult?.classHierarchy,
|
||||
);
|
||||
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('/')) {
|
||||
|
|
|
@ -156,24 +156,6 @@ void main() async {
|
|||
];
|
||||
await starter(args, compiler: compiler);
|
||||
});
|
||||
|
||||
test('compile from command line with widget cache', () async {
|
||||
verify(String entryPoint, ArgResults opts) {
|
||||
expect(entryPoint, equals('server.dart'));
|
||||
expect(opts['sdk-root'], equals('sdkroot'));
|
||||
expect(opts['link-platform'], equals(true));
|
||||
expect(opts['flutter-widget-cache'], equals(true));
|
||||
}
|
||||
|
||||
final compiler = _MockedCompiler(verifyCompile: verify);
|
||||
final List<String> args = <String>[
|
||||
'server.dart',
|
||||
'--sdk-root',
|
||||
'sdkroot',
|
||||
'--flutter-widget-cache',
|
||||
];
|
||||
await starter(args, compiler: compiler);
|
||||
});
|
||||
});
|
||||
|
||||
group('interactive compile with mocked compiler', () {
|
||||
|
@ -326,39 +308,6 @@ void main() async {
|
|||
await inputStreamController.close();
|
||||
});
|
||||
|
||||
test('recompile one file with widget cache does not fail', () async {
|
||||
final recompileDeltaCalled = ReceivePort();
|
||||
bool invalidated = false;
|
||||
verifyR(String? entryPoint) {
|
||||
expect(invalidated, equals(true));
|
||||
expect(entryPoint, equals(null));
|
||||
recompileDeltaCalled.sendPort.send(true);
|
||||
}
|
||||
|
||||
verifyI(Uri uri) {
|
||||
invalidated = true;
|
||||
expect(uri.path, contains('file1.dart'));
|
||||
}
|
||||
|
||||
// The component will not contain the flutter framework sources so
|
||||
// this should no-op.
|
||||
final compiler = _MockedCompiler(
|
||||
verifyRecompileDelta: verifyR, verifyInvalidate: verifyI);
|
||||
final StreamController<List<int>> inputStreamController =
|
||||
StreamController<List<int>>();
|
||||
|
||||
Future<int> result = starter(
|
||||
<String>[...args, '--flutter-widget-cache'],
|
||||
compiler: compiler,
|
||||
input: inputStreamController.stream,
|
||||
);
|
||||
inputStreamController.add('recompile abc\nfile1.dart\nabc\n'.codeUnits);
|
||||
await recompileDeltaCalled.first;
|
||||
inputStreamController.add('quit\n'.codeUnits);
|
||||
expect(await result, 0);
|
||||
await inputStreamController.close();
|
||||
});
|
||||
|
||||
test('recompile few files with new entrypoint', () async {
|
||||
int invalidated = 0;
|
||||
final recompileDeltaCalled = ReceivePort();
|
||||
|
@ -929,193 +878,6 @@ void main() async {
|
|||
frontendServer.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/"
|
||||
}
|
||||
]
|
||||
}
|
||||
''');
|
||||
|
||||
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 frontendServer = FrontendServer();
|
||||
Future<int> result = frontendServer.open(args);
|
||||
frontendServer.compile(file.path);
|
||||
int count = 0;
|
||||
frontendServer.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;
|
||||
frontendServer.accept();
|
||||
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> {}
|
||||
""");
|
||||
frontendServer.recompile(file.uri, entryPoint: file.path);
|
||||
} 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;
|
||||
frontendServer.accept();
|
||||
|
||||
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> {}
|
||||
""");
|
||||
frontendServer.recompile(file.uri, entryPoint: file.path);
|
||||
} 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;
|
||||
frontendServer.accept();
|
||||
|
||||
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.
|
||||
}
|
||||
""");
|
||||
frontendServer.recompile(file.uri, entryPoint: file.path);
|
||||
} 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;
|
||||
frontendServer.accept();
|
||||
|
||||
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.
|
||||
}
|
||||
""");
|
||||
frontendServer.recompile(file.uri, entryPoint: file.path);
|
||||
} 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));
|
||||
frontendServer.accept();
|
||||
frontendServer.quit();
|
||||
}
|
||||
});
|
||||
expect(await result, 0);
|
||||
frontendServer.close();
|
||||
});
|
||||
|
||||
test('unsafe-package-serialization', () async {
|
||||
// Package A.
|
||||
var file = File('${tempDir.path}/pkgA/a.dart')
|
||||
|
|
Loading…
Reference in a new issue